Commit 2e07cb84 authored by Filip Šenk's avatar Filip Šenk
Browse files

Feat: add import inject pattern

parent 9505e93d
Loading
Loading
Loading
Loading
+190 −1
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import { parse, stringify } from 'yaml'
import packageJson from '../package.json'
import { db } from './indexeddb/db'
import { getConfig, getUniqueChannel } from './indexeddb/operations'
import type { Milestone } from './indexeddb/types'
import {
  InjectType,
  type ConfigTable,
@@ -13,7 +14,7 @@ import {
  type Question,
  type Questionnaire,
} from './indexeddb/types'
import { applyContentPathsDeep, defaultControl } from './utils'
import { applyContentPathsDeep, defaultControl, getNewControl } from './utils'
import { DRIVE_FOLDER_NAME, FILES_FOLDER_NAME } from './zip/utils'

const TABLE_TO_FILE: Record<string, string> = {
@@ -421,3 +422,191 @@ export const loadDbData = async (zip: JSZip) => {
  await importFiles(zip)
  await importDrive(zip)
}

export const loadInjectPattern = async (zip: JSZip) => {
  const contentMap = await loadContentFiles(zip, 'content')
  const llmMap = await loadContentFiles(zip, 'llm')

  let milestones: Milestone[] = []
  let injects: Inject[] = []
  let questionnaires: Questionnaire[] = []

  for (const [fileName, tableName] of Object.entries(FILE_TO_TABLE)) {
    const records = await loadYamlData(zip, fileName)

    if (!Array.isArray(records) || records.length === 0) {
      continue
    }

    applyContentPathsDeep(records, contentMap, llmMap, false)

    const table = db.table(tableName)
    if (!table || typeof table !== 'object') {
      throw new Error('Wrong TABLE_TO_FILE name.')
    }

    if (tableName === 'inject') {
      injects = records.map(normalizeInject)
      continue
    }
    if (tableName === 'questionnaires') {
      questionnaires = records.map(normalizeQuestionnaire)
      continue
    }
    if (tableName === 'milestones') {
      milestones = records as Milestone[]
      continue
    }
    const tableKeys = new Set(await table.toCollection().primaryKeys())

    const filtered = records.filter(record => !tableKeys.has(record.name))

    await table.bulkPut(filtered)
  }

  const milestoneNameSet = new Set(
    (await db.milestones.toArray()).map(ms => ms.name)
  )

  const changedMilestonesMap = new Map<string, string>() // OLD NAME - NEW NAME

  const changedMilestones = milestones.map(milestone => {
    if (!milestoneNameSet.has(milestone.name)) {
      milestoneNameSet.add(milestone.name)
      return milestone
    }

    let i = 0
    while (milestoneNameSet.has(`${milestone.name}_${i}`)) {
      i += 1
    }

    const oldName = milestone.name
    const newName = `${milestone.name}_${i}`
    changedMilestonesMap.set(oldName, newName)
    milestoneNameSet.add(newName)

    return { ...milestone, name: `${milestone.name}_${i}` }
  })

  const injectNamesSet = new Set(
    (await db.inject.toArray()).map(inject => inject.name)
  )

  const changedInjects = injects.map(inject => {
    let name = inject.name
    let index = 0
    while (injectNamesSet.has(name)) {
      name = `${inject.name}_${index}`
      index += 1
    }
    injectNamesSet.add(name)

    if (inject.type === InjectType.INFORMATION) {
      const control = inject.alternatives[0].control
        ? getNewControl(inject.alternatives[0].control, changedMilestonesMap)
        : undefined

      const confirmation = inject.alternatives[0].confirmation
        ? {
            ...inject.alternatives[0].confirmation,
            control: getNewControl(
              inject.alternatives[0].confirmation.control,
              changedMilestonesMap
            ),
          }
        : undefined

      return {
        ...inject,
        name,
        alternatives: [
          {
            ...inject.alternatives[0],
            control,
            confirmation,
          },
        ],
      }
    } else {
      const control = inject.alternatives[0].control
        ? getNewControl(inject.alternatives[0].control, changedMilestonesMap)
        : undefined

      return {
        ...inject,
        name,
        alternatives: [{ ...inject.alternatives[0], control }],
      }
    }
  })

  const questionnaireNamesSet = new Set(
    (await db.questionnaires.toArray()).map(questionnaire => questionnaire.name)
  )
  const changedQuestionnaires = questionnaires.map(questionnaire => {
    let name = questionnaire.name
    let index = 0
    while (questionnaireNamesSet.has(name)) {
      name = `${questionnaire.name}_${index}`
      index += 1
    }

    questionnaireNamesSet.add(name)
    const control = questionnaire.control
      ? getNewControl(questionnaire.control, changedMilestonesMap)
      : undefined
    const repeatable = questionnaire.repeatable
      ? {
          ...questionnaire.repeatable,
          on_fail: questionnaire.repeatable.on_fail
            ? getNewControl(
                questionnaire.repeatable.on_fail,
                changedMilestonesMap
              )
            : undefined,
        }
      : undefined
    const questions = questionnaire.questions.map(question => {
      switch (question.type) {
        case 'multiple-choice':
        case 'radio': {
          const controls = Array.isArray(question.controls)
            ? question.controls.map(control =>
                getNewControl(control, changedMilestonesMap)
              )
            : question.controls

          return { ...question, controls }
        }
        case 'auto-free-form': {
          const correct = question.correct
            ? getNewControl(question.correct, changedMilestonesMap)
            : undefined
          const incorrect = question.incorrect
            ? getNewControl(question.incorrect, changedMilestonesMap)
            : undefined

          return { ...question, correct, incorrect }
        }
        case 'free-form': {
          const related_milestones = question.related_milestones?.map(
            oldName => {
              const newName = changedMilestonesMap.get(oldName)
              return newName ? newName : oldName
            }
          )
          return { ...question, related_milestones }
        }
      }
    })

    return { ...questionnaire, control, questions, repeatable }
  })

  await db.milestones.bulkPut(changedMilestones)
  await db.inject.bulkPut(changedInjects)
  await db.questionnaires.bulkPut(changedQuestionnaires)
  await importFiles(zip)
  await importDrive(zip)
}
+1 −1
Original line number Diff line number Diff line
@@ -318,7 +318,7 @@ export type MultipleChoiceQuestion = {
  labels: string[]
  correct: number[]
  exact_match?: boolean
  controls: Control[] | Map<number, Control>
  controls?: Control[] | Map<number, Control>
}

export type ContentFile = {
+23 −0
Original line number Diff line number Diff line
@@ -499,3 +499,26 @@ export const uploadDefinitionZip = async () => {
    credentials: 'include',
  }).then(result => result.json())
}

export const getNewControl = (
  control: Control,
  changedMilestonesMap: Map<string, string>
) => {
  control.activate_milestone = control.activate_milestone?.map(oldName => {
    const newName = changedMilestonesMap.get(oldName)
    return newName ? newName : oldName
  })
  control.deactivate_milestone = control.deactivate_milestone?.map(oldName => {
    const newName = changedMilestonesMap.get(oldName)
    return newName ? newName : oldName
  })
  control.milestone_condition = control.milestone_condition
    ?.split(' ')
    .map(activate_ms => {
      const newName = changedMilestonesMap.get(activate_ms)
      return newName ? newName : activate_ms
    })
    .join(' ')

  return control
}