import {generate} from "shortid"; import {TestableCompositeData, TestableCompositeDataWithChildren, TestableData} from "./store"; import {NestedTestableNodeData} from "./InMemoryTestableNode"; import {cloneDeep} from "lodash"; import {NestedBranchData} from "./MemoryBranchNode"; import {isNonContextTestableGroupType, TestableGroupTypeId} from "./testableGroupTypes"; type TestableIdTypes = 'testable.group' | 'testable.context' | 'testable' | 'testable.partial' export const getIdTypeForTestable = (type: TestableCompositeData['type'] | undefined | TestableData['type']): TestableIdTypes => { if (isNonContextTestableGroupType(type)) { return 'testable.group'; } else if (type === TestableGroupTypeId.CONTEXT) { return 'testable.context'; } else if (typeof type === 'undefined') { return 'testable.partial'; } else { return 'testable'; } } export const generateIdForType = (type: 'tree.node' | 'branch' | 'offers' | TestableIdTypes, id?: string): string => { // giving a fixed id is useful for when you already have a fixed if and you only need the correct prefix for the type id = id || generate() if (type === 'testable.group') { return `group-${id}`; } else if (type === 'testable.context') { return `context-${id}` } else if (type === 'testable') { return `testable-${id}`; } else if (type === 'testable.partial') { return `testable-partial-${id}`; } else if (type === 'tree.node') { return `tree-node-${id}`; } else if (type === 'branch') { return `branch-${id}`; } else if (type === 'offers') { return `offer-${id}`; } else { // a generic id return id as string } } type IdToTestable = (TestableData | Omit) | (TestableCompositeData | Omit) | (TestableCompositeDataWithChildren | Omit); export function addIdToComponent(testable: IdToTestable, override = false, isOffer: boolean = false): IdToTestable & { id: string }/*TestableData | TestableCompositeData*/ { // @ts-ignore if (!testable.id || override) { // @ts-ignore testable.id = generateIdForType(isOffer ? 'offers' : getIdTypeForTestable(testable.type)) } if ('children' in testable) { testable.children.forEach(c => addIdToComponent(c, override)) } return testable as TestableData | TestableCompositeData } type IdWithChildren = { id?: string, children?: IdWithChildren[], [otherProps: string]: any } export const removeIds = = Record>(nestedData: IdWithChildren): Omit => { const { id, ...dataWithoutId } = nestedData; if (dataWithoutId.children) { dataWithoutId.children = dataWithoutId.children.map(child => removeIds(child)) } return dataWithoutId as Omit } /** * This only works for TestableData, not supported for TestableCompositeData * * Testables can have ids or not, if they don't have ids, they will be generated * * New objects are created, the references in the returned object are not the same as the input object */ export const wrapTestablesInDefaultGroup = (testables: (TestableData | Omit)[], mode: TestableCompositeData['mode'] = 'filter', type: TestableCompositeData['type'] = TestableGroupTypeId.AND): NestedTestableNodeData => { // we need a way to figure out the mode based on the testables. For now though, let's just ask the caller return { id: generateIdForType('testable.group'), type, mode, children: [ wrapTestablesInContext(testables) ] } } export const wrapTestablesInContext = (testables: (TestableData | Omit)[], mode: TestableCompositeData['mode'] = 'filter') => ({ id: generateIdForType('testable.context'), type: TestableGroupTypeId.CONTEXT, mode, children: cloneDeep(testables.map(addIdToComponent)) as TestableData[] }) export const throwError = (message: string) => { throw new Error(message); } export const generateDefaultNestedSelectActionsBranchData = (extraData?: object): NestedBranchData => { return { id: generateIdForType('branch'), type: 'select-action', data: extraData || {}, children: [] } }