Skip to content

Commit

Permalink
nest parent from req
Browse files Browse the repository at this point in the history
  • Loading branch information
Sv7enNowitzki committed Jan 17, 2024
1 parent 1431f17 commit 9c40c65
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 18 deletions.
10 changes: 5 additions & 5 deletions lib/change-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const _getEntityIDs = function (txParams) {
* ...
* }
*/
const _formatAssociationContext = async function (changes) {
const _formatAssociationContext = async function (changes, reqData) {
for (const change of changes) {
const a = cds.model.definitions[change.serviceEntity].elements[change.attribute]
if (a?.type !== "cds.Association") continue
Expand All @@ -111,10 +111,10 @@ const _formatAssociationContext = async function (changes) {
SELECT.one.from(a.target).where({ [ID]: change.valueChangedTo })
])

const fromObjId = await getObjectId(a.target, semkeys, { curObjFromDbQuery: from || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
const fromObjId = await getObjectId(reqData, a.target, semkeys, { curObjFromDbQuery: from || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
if (fromObjId) change.valueChangedFrom = fromObjId

const toObjId = await getObjectId(a.target, semkeys, { curObjFromDbQuery: to || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
const toObjId = await getObjectId(reqData, a.target, semkeys, { curObjFromDbQuery: to || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
if (toObjId) change.valueChangedTo = toObjId

const isVLvA = a["@Common.ValueList.viaAssociation"]
Expand Down Expand Up @@ -219,7 +219,7 @@ const _getObjectIdByPath = async function (
const entityUUID = getUUIDFromPathVal(nodePathVal)
const obj = await getCurObjFromDbQuery(entityName, entityUUID)
const curObj = { curObjFromReqData, curObjFromDbQuery: obj }
return getObjectId(entityName, objIdElementNames, curObj)
return getObjectId(reqData, entityName, objIdElementNames, curObj)
}

const _formatObjectID = async function (changes, reqData) {
Expand Down Expand Up @@ -267,7 +267,7 @@ const _isCompositionContextPath = function (aPath) {

const _formatChangeLog = async function (changes, req) {
await _formatObjectID(changes, req.data)
await _formatAssociationContext(changes)
await _formatAssociationContext(changes, req.data)
await _formatCompositionContext(changes, req.data)
}

Expand Down
39 changes: 26 additions & 13 deletions lib/entity-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const getCurObjFromReqData = function (reqData, nodePathVal, pathVal) {
}


async function getObjectId (entityName, fields, curObj) {
async function getObjectId (reqData, entityName, fields, curObj) {
let all = [], { curObjFromReqData: req_data={}, curObjFromDbQuery: db_data={} } = curObj
let entity = cds.model.definitions[entityName]
if (!fields?.length) fields = entity["@changelog"]?.map?.(k => k['='] || k) || []
Expand All @@ -88,26 +88,22 @@ async function getObjectId (entityName, fields, curObj) {
if (IDval) try {
// REVISIT: This always reads all elements -> should read required ones only!
let ID = assoc.keys?.[0]?.ref[0] || 'ID'
const isDraftEnabled = !!assoc._target.drafts
const isComposition = hasComposition(assoc._target, current)
// When the app creates both parent and child nodes, the parent node does not exist in the data table, so the corresponding data needs to be obtained from the draft table.
// Peer association and composition are distinguished by the value of isComposition. Only in the case of composition, data from the draft table needs to be fetched.
if (isDraftEnabled && isComposition) {
_db_data =
(await SELECT.one
.from(`${assoc._target}.drafts`)
.where({ [ID]: IDval })) || {};
// In the editing state, when updating a parent node and deleting a child node, the corresponding node will disappear from the draft table. Therefore, it is necessary to get the data from the data table.
if (JSON.stringify(_db_data) === '{}') {
// Peer association and composition are distinguished by the value of isComposition.
if (isComposition) {
// This function can recursively retrieve the desired information from reqData without having to read it from db.
_db_data = _getCompositionObjFromReq(reqData, IDval)
// When multiple layers of child nodes are deleted at the same time, the deep layer of child nodes will lose the information of the upper nodes, so data needs to be extracted from the db.
if (!_db_data || JSON.stringify(_db_data) === '{}') {
_db_data =
(await SELECT.one
.from(assoc._target)
.where({ [ID]: IDval })) || {};
.where({ [ID]: IDval })) || {}
}
} else {
_db_data =
(await SELECT.one.from(assoc._target).where({ [ID]: IDval })) ||
{};
{}
}
} catch (e) {
LOG.error("Failed to generate object Id for an association entity.", e)
Expand Down Expand Up @@ -171,6 +167,23 @@ const hasComposition = function (parentEntity, subEntity) {
return false
}

const _getCompositionObjFromReq = function (obj, targetID) {
if (obj.ID === targetID) {
return obj;
}

for (const key in obj) {
if (typeof obj[key] === "object" && obj[key] !== null) {
const result = _getCompositionObjFromReq(obj[key], targetID);
if (result) {
return result;
}
}
}

return null;
};

module.exports = {
getCurObjFromReqData,
getCurObjFromDbQuery,
Expand Down
79 changes: 79 additions & 0 deletions tests/integration/fiori-draft-disabled.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,86 @@ describe("change log draft disabled test", () => {
const deleteChange = deleteChanges[0];
expect(deleteChange.objectID).to.equal("RootOrder title1");

// Test object id when parent and child nodes are created at the same time
cds.services.AdminService.entities.Level2Order["@changelog"] = [
{ "=": "parent.parent.title" }
];
await POST(
`/odata/v4/admin/RootOrder`,
{
ID: "a670e8e1-ee06-4cad-9cbd-a2354dc37c9d",
title: "new RootOrder title",
child: [
{
ID: "48268451-8552-42a6-a3d7-67564be97733",
title: "new Level1Order title",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-1942bd228115",
title: "new Level2Order title",
}
]
}
]
},
);

const createChangesMeanwhile = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Order",
attribute: "title",
modification: "create",
}),
);
expect(createChangesMeanwhile.length).to.equal(1);
const createChangeMeanwhile = createChangesMeanwhile[0];
expect(createChangeMeanwhile.objectID).to.equal("new RootOrder title");

// Test the object id when the parent node and child node are modified at the same time
await PATCH(`/odata/v4/admin/RootOrder(ID=a670e8e1-ee06-4cad-9cbd-a2354dc37c9d)`, {
title: "RootOrder title changed",
child: [
{
ID: "48268451-8552-42a6-a3d7-67564be97733",
child:[{
ID: "12ed5dd8-d45b-11ed-afa1-1942bd228115",
title: "Level2Order title changed"
}]
}
]
});

const updateChangesMeanwhile = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Order",
attribute: "title",
modification: "update",
}),
);
expect(updateChangesMeanwhile.length).to.equal(1);
const updateChangeMeanwhile = updateChangesMeanwhile[0];
expect(updateChangeMeanwhile.objectID).to.equal("RootOrder title changed");

// Tests the object id when the parent node update and child node deletion occur simultaneously
await PATCH(`/odata/v4/admin/RootOrder(ID=a670e8e1-ee06-4cad-9cbd-a2354dc37c9d)`, {
title: "RootOrder title del",
child: []
});

const deleteChangesMeanwhile = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Order",
attribute: "title",
modification: "delete"
}),
);

expect(deleteChangesMeanwhile.length).to.equal(1);
const deleteChangeMeanwhile = deleteChangesMeanwhile[0];
expect(deleteChangeMeanwhile.objectID).to.equal("RootOrder title del");

delete cds.services.AdminService.entities.OrderItem["@changelog"];
delete cds.services.AdminService.entities.Level2Order["@changelog"];
delete cds.services.AdminService.entities.Level3Order["@changelog"];
});

Expand Down
87 changes: 87 additions & 0 deletions tests/integration/service-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,93 @@ describe("change log integration test", () => {
expect(deleteChanges.length).to.equal(1);
const deleteChange = deleteChanges[0];
expect(deleteChange.objectID).to.equal("In Preparation");

// Test object id when parent and child nodes are created at the same time
const RootEntityData = {
ID: "01234567-89ab-cdef-0123-987654fedcba",
name: "New name for RootEntity",
lifecycleStatus_code: "IP",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-0242ac120003",
title: "New name for Level1Entity",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-0242ac124446",
title: "New name for Level2Entity"
},
],
},
],
};
await adminService.run(INSERT.into(adminService.entities.RootEntity).entries(RootEntityData));

const createEntityChanges = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Entity",
attribute: "title",
modification: "create",
}),
);
expect(createEntityChanges.length).to.equal(1);
const createEntityChange = createEntityChanges[0];
expect(createEntityChange.objectID).to.equal("In Preparation");

// Test the object id when the parent node and child node are modified at the same time
await UPDATE(adminService.entities.RootEntity)
.with({
ID: "01234567-89ab-cdef-0123-987654fedcba",
name: "RootEntity name changed",
lifecycleStatus_code: "AC",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-0242ac120003",
parent_ID: "01234567-89ab-cdef-0123-987654fedcba",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-0242ac124446",
parent_ID: "12ed5dd8-d45b-11ed-afa1-0242ac120003",
title : "Level2Entity title changed"
},
],
},
],
});
const updateEntityChanges = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Entity",
attribute: "title",
modification: "update",
}),
);
expect(updateEntityChanges.length).to.equal(1);
const updateEntityChange = updateEntityChanges[0];
expect(updateEntityChange.objectID).to.equal("Open");

// Tests the object id when the parent node update and child node deletion occur simultaneously
await UPDATE(adminService.entities.RootEntity)
.with({
ID: "01234567-89ab-cdef-0123-987654fedcba",
name: "RootEntity name del",
lifecycleStatus_code: "CL",
child: [
{
ID: "12ed5dd8-d45b-11ed-afa1-0242ac120003",
parent_ID: "01234567-89ab-cdef-0123-987654fedcba",
child: [],
},
],
});
const deleteEntityChanges = await adminService.run(
SELECT.from(ChangeView).where({
entity: "sap.capire.bookshop.Level2Entity",
attribute: "title",
modification: "delete",
}),
);
expect(deleteEntityChanges.length).to.equal(1);
const deleteEntityChange = deleteEntityChanges[0];
expect(deleteEntityChange.objectID).to.equal("Closed");
});

it("8.3 Annotate fields from chained associated entities as displayed value (ERP4SMEPREPWORKAPPPLAT-4542)", async () => {
Expand Down

0 comments on commit 9c40c65

Please sign in to comment.