diff --git a/packages/core/src/templateApi/diffHierarchy.js b/packages/core/src/templateApi/diffHierarchy.js index aa2dec91ac..5206a3526a 100644 --- a/packages/core/src/templateApi/diffHierarchy.js +++ b/packages/core/src/templateApi/diffHierarchy.js @@ -1,4 +1,4 @@ -import { getFlattenedHierarchy, isRecord, isIndex } from "./hierarchy" +import { getFlattenedHierarchy, isRecord, isIndex, isAncestor } from "./hierarchy" import { $, none } from "../common" import { map, filter, some, find } from "lodash/fp" @@ -17,15 +17,18 @@ export const diffHierarchy = (oldHierarchy, newHierarchy) => { const oldHierarchyFlat = getFlattenedHierarchy(oldHierarchy) const newHierarchyFlat = getFlattenedHierarchy(newHierarchy) + const createdRecords = findCreatedRecords(oldHierarchyFlat, newHierarchyFlat) + const deletedRecords = findDeletedRecords(oldHierarchyFlat, newHierarchyFlat) + return [ - ...createdRecords(oldHierarchyFlat, newHierarchyFlat), - ...deletedRecords(oldHierarchyFlat, newHierarchyFlat), - ...renamedRecords(oldHierarchyFlat, newHierarchyFlat), - ...recordsWithFieldsChanged(oldHierarchyFlat, newHierarchyFlat), - ...recordsWithEstimatedRecordTypeChanged(oldHierarchyFlat, newHierarchyFlat), - ...createdIndexes(oldHierarchyFlat, newHierarchyFlat), - ...deletedIndexes(oldHierarchyFlat, newHierarchyFlat), - ...updatedIndexes(oldHierarchyFlat, newHierarchyFlat), + ...createdRecords, + ...deletedRecords, + ...findRenamedRecords(oldHierarchyFlat, newHierarchyFlat), + ...findRecordsWithFieldsChanged(oldHierarchyFlat, newHierarchyFlat), + ...findRecordsWithEstimatedRecordTypeChanged(oldHierarchyFlat, newHierarchyFlat), + ...findCreatedIndexes(oldHierarchyFlat, newHierarchyFlat, createdRecords), + ...findDeletedIndexes(oldHierarchyFlat, newHierarchyFlat, deletedRecords), + ...findUpdatedIndexes(oldHierarchyFlat, newHierarchyFlat), ] } @@ -33,33 +36,43 @@ const changeItem = (type, oldNode, newNode) => ({ type, oldNode, newNode, }) -const createdRecords = (oldHierarchyFlat, newHierarchyFlat) => - $(newHierarchyFlat, [ +const findCreatedRecords = (oldHierarchyFlat, newHierarchyFlat) => { + const allCreated = $(newHierarchyFlat, [ filter(isRecord), filter(nodeDoesNotExistIn(oldHierarchyFlat)), map(n => changeItem(HierarchyChangeTypes.recordCreated, null, n)) ]) -const deletedRecords = (oldHierarchyFlat, newHierarchyFlat) => - $(oldHierarchyFlat, [ + return $(allCreated, [ + filter(r => none(r2 => isAncestor(r.newNode)(r2.newNode))(allCreated)) + ]) +} + +const findDeletedRecords = (oldHierarchyFlat, newHierarchyFlat) => { + const allDeleted = $(oldHierarchyFlat, [ filter(isRecord), filter(nodeDoesNotExistIn(newHierarchyFlat)), map(n => changeItem(HierarchyChangeTypes.recordDeleted, n, null)) ]) -const renamedRecords = (oldHierarchyFlat, newHierarchyFlat) => + return $(allDeleted, [ + filter(r => none(r2 => isAncestor(r.oldNode)(r2.oldNode))(allDeleted)) + ]) +} + +const findRenamedRecords = (oldHierarchyFlat, newHierarchyFlat) => $(oldHierarchyFlat, [ filter(isRecord), filter(nodeExistsIn(newHierarchyFlat)), filter(nodeChanged(newHierarchyFlat, (_new,old) =>_new.collectionKey !== old.collectionKey )), map(n => changeItem( - HierarchyChangeTypes.recordDeleted, + HierarchyChangeTypes.recordRenamed, n, findNodeIn(n, newHierarchyFlat)) ) ]) -const recordsWithFieldsChanged = (oldHierarchyFlat, newHierarchyFlat) => +const findRecordsWithFieldsChanged = (oldHierarchyFlat, newHierarchyFlat) => $(oldHierarchyFlat, [ filter(isRecord), filter(nodeExistsIn(newHierarchyFlat)), @@ -71,7 +84,7 @@ const recordsWithFieldsChanged = (oldHierarchyFlat, newHierarchyFlat) => ) ]) -const recordsWithEstimatedRecordTypeChanged = (oldHierarchyFlat, newHierarchyFlat) => +const findRecordsWithEstimatedRecordTypeChanged = (oldHierarchyFlat, newHierarchyFlat) => $(oldHierarchyFlat, [ filter(isRecord), filter(nodeExistsIn(newHierarchyFlat)), @@ -83,22 +96,32 @@ const recordsWithEstimatedRecordTypeChanged = (oldHierarchyFlat, newHierarchyFla ) ]) -const createdIndexes = (oldHierarchyFlat, newHierarchyFlat) => - $(newHierarchyFlat, [ +const findCreatedIndexes = (oldHierarchyFlat, newHierarchyFlat, createdRecords) => { + const allCreated = $(newHierarchyFlat, [ filter(isIndex), filter(nodeDoesNotExistIn(oldHierarchyFlat)), map(n => changeItem(HierarchyChangeTypes.indexCreated, null, n)) ]) -const deletedIndexes = (oldHierarchyFlat, newHierarchyFlat) => - $(oldHierarchyFlat, [ + return $(allCreated, [ + filter(r => none(r2 => isAncestor(r.newNode)(r2.newNode))(createdRecords)) + ]) +} + +const findDeletedIndexes = (oldHierarchyFlat, newHierarchyFlat, deletedRecords) => { + const allDeleted = $(oldHierarchyFlat, [ filter(isIndex), filter(nodeDoesNotExistIn(newHierarchyFlat)), map(n => changeItem(HierarchyChangeTypes.indexDeleted, n, null)) ]) + return $(allDeleted, [ + filter(r => none(r2 => isAncestor(r.oldNode)(r2.oldNode))(deletedRecords)) + ]) +} -const updatedIndexes = (oldHierarchyFlat, newHierarchyFlat) => + +const findUpdatedIndexes = (oldHierarchyFlat, newHierarchyFlat) => $(oldHierarchyFlat, [ filter(isRecord), filter(nodeExistsIn(newHierarchyFlat)), @@ -114,7 +137,7 @@ const hasDifferentFields = otherFlatHierarchy => record1 => { const record2 = findNodeIn(record1, otherFlatHierarchy) - if(record1.fields.length !== record2.fields.length) return false + if(record1.fields.length !== record2.fields.length) return true for(let f1 of record1.fields) { if (none(isFieldSame(f1))(record2.fields)) return true diff --git a/packages/core/test/templateApi.diffHierarchy.spec.js b/packages/core/test/templateApi.diffHierarchy.spec.js index 1506975aca..6f9cbce4e1 100644 --- a/packages/core/test/templateApi.diffHierarchy.spec.js +++ b/packages/core/test/templateApi.diffHierarchy.spec.js @@ -1,5 +1,5 @@ import { getMemoryTemplateApi } from "./specHelpers" -import { diffHierarchy } from "../src/templateApi/diffHierarchy" +import { diffHierarchy, HierarchyChangeTypes } from "../src/templateApi/diffHierarchy" import { getFlattenedHierarchy } from "../src/templateApi/hierarchy" describe("diffHierarchy", () => { @@ -11,22 +11,205 @@ describe("diffHierarchy", () => { expect(diff).toEqual([]) }) - it("should detect record created", async () => { - + it("should detect root record created", async () => { + const oldHierarchy = (await setup()).root; + const newSetup = (await setup()); + const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false) + const diff = diffHierarchy(oldHierarchy, newSetup.root) + expect(diff).toEqual([{ + newNode: opportunity, + oldNode: null, + type: HierarchyChangeTypes.recordCreated + }]) }) -}) + it("should only detect root record, when newly created root record has children ", async () => { + const oldHierarchy = (await setup()).root; + const newSetup = (await setup()); + const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false) + newSetup.templateApi.getNewRecordTemplate(opportunity, "invoice", true) + const diff = diffHierarchy(oldHierarchy, newSetup.root) + expect(diff).toEqual([{ + newNode: opportunity, + oldNode: null, + type: HierarchyChangeTypes.recordCreated + }]) + }) + + it("should detect child record created", async () => { + const oldHierarchy = (await setup()).root; + const newSetup = (await setup()); + const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.contact, "opportunity", false) + const diff = diffHierarchy(oldHierarchy, newSetup.root) + expect(diff).toEqual([{ + newNode: opportunity, + oldNode: null, + type: HierarchyChangeTypes.recordCreated + }]) + }) + + it("should detect root record deleted", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact") + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: null, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordDeleted + }]) + }) + + it("should detect child record deleted", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.contact.children = newSetup.contact.children.filter(n => n.name !== "deal") + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: null, + oldNode: oldSetup.deal, + type: HierarchyChangeTypes.recordDeleted + }]) + }) + + it("should detect root record renamed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.contact.collectionKey = "CONTACTS" + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.contact, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordRenamed + }]) + }) + + it("should detect child record renamed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.deal.collectionKey = "CONTACTS" + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.deal, + oldNode: oldSetup.deal, + type: HierarchyChangeTypes.recordRenamed + }]) + }) + + it("should detect root record field removed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name") + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.contact, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordFieldsChanged + }]) + }) + + it("should detect child record field removed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.deal.fields = newSetup.deal.fields.filter(f => f.name !== "name") + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.deal, + oldNode: oldSetup.deal, + type: HierarchyChangeTypes.recordFieldsChanged + }]) + }) + + it("should detect record field added", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + const notesField = newSetup.templateApi.getNewField("string") + notesField.name = "notes" + newSetup.templateApi.addField(newSetup.contact, notesField) + + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.contact, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordFieldsChanged + }]) + }) + + it("should detect 1 record field added and 1 removed (total no. fields unchanged)", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + const notesField = newSetup.templateApi.getNewField("string") + notesField.name = "notes" + newSetup.templateApi.addField(newSetup.contact, notesField) + newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name") + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.contact, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordFieldsChanged + }]) + }) + + it("should detect root record estimated record count changed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.contact.estimatedRecordCount = 987 + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.contact, + oldNode: oldSetup.contact, + type: HierarchyChangeTypes.recordEstimatedRecordTypeChanged + }]) + }) + + it("should detect root record estimated record count changed", async () => { + const oldSetup = (await setup()); + const newSetup = (await setup()); + newSetup.deal.estimatedRecordCount = 987 + const diff = diffHierarchy(oldSetup.root, newSetup.root) + expect(diff).toEqual([{ + newNode: newSetup.deal, + oldNode: oldSetup.deal, + type: HierarchyChangeTypes.recordEstimatedRecordTypeChanged + }]) + }) + + it("should detect root record created", async () => { + const oldHierarchy = (await setup()).root; + const newSetup = (await setup()); + const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false) + const diff = diffHierarchy(oldHierarchy, newSetup.root) + expect(diff).toEqual([{ + newNode: opportunity, + oldNode: null, + type: HierarchyChangeTypes.recordCreated + }]) + }) + +}) const setup = async () => { const { templateApi } = await getMemoryTemplateApi() const root = templateApi.getNewRootLevel() const contact = templateApi.getNewRecordTemplate(root, "contact", true) + + const nameField = templateApi.getNewField("string") + nameField.name = "name" + const statusField = templateApi.getNewField("string") + statusField.name = "status" + + templateApi.addField(contact, nameField) + templateApi.addField(contact, statusField) + const lead = templateApi.getNewRecordTemplate(root, "lead", true) const deal = templateApi.getNewRecordTemplate(contact, "deal", true) + templateApi.addField(deal, {...nameField}) + templateApi.addField(deal, {...statusField}) + getFlattenedHierarchy(root) return { - root, contact, lead, deal, + root, contact, lead, deal, templateApi, all_contacts: root.indexes[0], all_leads: root.indexes[1], deals_for_contacts: contact.indexes[0]