Skip to content

Commit e26a0ce

Browse files
create Global switch for preserveDelete (#96)
Co-authored-by: I560824 <[email protected]>
1 parent 3bacbe2 commit e26a0ce

File tree

5 files changed

+201
-11
lines changed

5 files changed

+201
-11
lines changed

lib/change-log.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ async function track_changes (req) {
348348
let isDraftEnabled = !!target.drafts
349349
let isComposition = _isCompositionContextPath(req.context.path)
350350
let entityKey = diff.ID
351-
352-
if (cds.transaction(req).context.event === "DELETE") {
351+
352+
if (cds.transaction(req).context.event === "DELETE" && !cds.env.requires["change-tracking"]?.preserveDeletes) {
353353
if (isDraftEnabled || !isComposition) {
354354
return await DELETE.from(`sap.changelog.ChangeLog`).where({ entityKey })
355355
}

lib/entity-helper.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ async function getObjectId (reqData, entityName, fields, curObj) {
8585
req_data[foreignKey] && current.name === entityName
8686
? req_data[foreignKey]
8787
: _db_data[foreignKey]
88-
if (IDval) try {
88+
if (!IDval) {
89+
_db_data = {};
90+
} else try {
8991
// REVISIT: This always reads all elements -> should read required ones only!
9092
let ID = assoc.keys?.[0]?.ref[0] || 'ID'
9193
const isComposition = hasComposition(assoc._target, current)
@@ -94,16 +96,12 @@ async function getObjectId (reqData, entityName, fields, curObj) {
9496
// This function can recursively retrieve the desired information from reqData without having to read it from db.
9597
_db_data = _getCompositionObjFromReq(reqData, IDval)
9698
// 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.
97-
if (!_db_data || JSON.stringify(_db_data) === '{}') {
98-
_db_data =
99-
(await SELECT.one
100-
.from(assoc._target)
101-
.where({ [ID]: IDval })) || {}
99+
const entityKeys = Object.keys(reqData).filter(item => !Object.keys(assoc._target.keys).some(ele => item === ele));
100+
if (!_db_data || JSON.stringify(_db_data) === '{}' || entityKeys.length === 0) {
101+
_db_data = await getCurObjFromDbQuery(assoc._target, IDval, ID);
102102
}
103103
} else {
104-
_db_data =
105-
(await SELECT.one.from(assoc._target).where({ [ID]: IDval })) ||
106-
{}
104+
_db_data = await getCurObjFromDbQuery(assoc._target, IDval, ID);
107105
}
108106
} catch (e) {
109107
LOG.error("Failed to generate object Id for an association entity.", e)

tests/integration/fiori-draft-disabled.test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,67 @@ describe("change log draft disabled test", () => {
8989
expect(afterChanges.length).to.equal(0);
9090
});
9191

92+
it("1.4 When the global switch is on, all changelogs should be retained after the root entity is deleted, and a changelog for the deletion operation should be generated", async () => {
93+
cds.env.requires["change-tracking"].preserveDeletes = true;
94+
95+
cds.services.AdminService.entities.RootObject["@changelog"] = [
96+
{ "=": "title" }
97+
];
98+
cds.services.AdminService.entities.Level1Object["@changelog"] = [
99+
{ "=": "parent.title" }
100+
];
101+
cds.services.AdminService.entities.Level2Object["@changelog"] = [
102+
{ "=": "parent.parent.title" }
103+
];
104+
const RootObject = await POST(
105+
`/odata/v4/admin/RootObject`,
106+
{
107+
ID: "a670e8e1-ee06-4cad-9cbd-a2354dc37c9d",
108+
title: "new RootObject title",
109+
child: [
110+
{
111+
ID: "48268451-8552-42a6-a3d7-67564be97733",
112+
title: "new Level1Object title",
113+
child: [
114+
{
115+
ID: "12ed5dd8-d45b-11ed-afa1-1942bd228115",
116+
title: "new Level2Object title",
117+
}
118+
]
119+
}
120+
]
121+
},
122+
);
123+
124+
const beforeChanges = await adminService.run(SELECT.from(ChangeView));
125+
expect(beforeChanges.length > 0).to.be.true;
126+
127+
// Test when the root and child entity deletion occur simultaneously
128+
await DELETE(`/odata/v4/admin/RootObject(ID=${RootObject.data.ID})`);
129+
130+
const afterChanges = await adminService.run(SELECT.from(ChangeView));
131+
expect(afterChanges.length).to.equal(8);
132+
133+
const changelogCreated = afterChanges.filter(ele=> ele.modification === "Create");
134+
const changelogDeleted = afterChanges.filter(ele=> ele.modification === "Delete");
135+
136+
const compareAttributes = ['keys', 'attribute', 'entity', 'serviceEntity', 'parentKey', 'serviceEntityPath', 'valueDataType', 'objectID', 'parentObjectID', 'entityKey'];
137+
138+
let commonItems = changelogCreated.filter(beforeItem => {
139+
return changelogDeleted.some(afterItem => {
140+
return compareAttributes.every(attr => beforeItem[attr] === afterItem[attr])
141+
&& beforeItem['valueChangedFrom'] === afterItem['valueChangedTo']
142+
&& beforeItem['valueChangedTo'] === afterItem['valueChangedFrom'];
143+
});
144+
});
145+
146+
expect(commonItems.length > 0).to.be.true;
147+
148+
delete cds.services.AdminService.entities.RootObject["@changelog"];
149+
delete cds.services.AdminService.entities.Level1Object["@changelog"];
150+
delete cds.services.AdminService.entities.Level2Object["@changelog"];
151+
});
152+
92153
it("3.1 Composition creatition by odata request on draft disabled entity - should log changes for root entity (ERP4SMEPREPWORKAPPPLAT-670)", async () => {
93154
await POST(
94155
`/admin/Order(ID=0a41a187-a2ff-4df6-bd12-fae8996e6e31)/orderItems(ID=9a61178f-bfb3-4c17-8d17-c6b4a63e0097)/notes`,
@@ -450,6 +511,22 @@ describe("change log draft disabled test", () => {
450511
expect(createOrderChanges.length).to.equal(1);
451512
const createOrderChange = createOrderChanges[0];
452513
expect(createOrderChange.objectID).to.equal("test Order title");
514+
515+
await PATCH(`/odata/v4/admin/Order(ID=0a41a187-a2ff-4df6-bd12-fae8996e7c44)`, {
516+
title: "Order title changed"
517+
});
518+
519+
const updateOrderChanges = await adminService.run(
520+
SELECT.from(ChangeView).where({
521+
entity: "sap.capire.bookshop.Order",
522+
attribute: "title",
523+
modification: "update",
524+
}),
525+
);
526+
expect(updateOrderChanges.length).to.equal(1);
527+
const updateOrderChange = updateOrderChanges[0];
528+
expect(updateOrderChange.objectID).to.equal("Order title changed");
529+
453530
delete cds.db.entities.Order["@changelog"];
454531
});
455532

tests/integration/fiori-draft-enabled.test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,64 @@ describe("change log integration test", () => {
2424
await data.reset();
2525
});
2626

27+
28+
it("1.5 When the global switch is on, all changelogs should be retained after the root entity is deleted, and a changelog for the deletion operation should be generated", async () => {
29+
cds.env.requires["change-tracking"].preserveDeletes = true;
30+
31+
// Root and child nodes are created at the same time
32+
const createAction = POST.bind({}, `/odata/v4/admin/RootEntity`, {
33+
ID: "01234567-89ab-cdef-0123-987654fedcba",
34+
name: "New name for RootEntity",
35+
child: [
36+
{
37+
ID: "12ed5dd8-d45b-11ed-afa1-0242ac120003",
38+
title: "New name for Level1Entity",
39+
child: [
40+
{
41+
ID: "12ed5dd8-d45b-11ed-afa1-0242ac124446",
42+
title: "New name for Level2Entity",
43+
child: [
44+
{
45+
ID: "12ed5dd8-d45b-11ed-afa1-0242ac123335",
46+
title: "New name for Level3Entity",
47+
},
48+
],
49+
},
50+
],
51+
},
52+
],
53+
});
54+
await utils.apiAction(
55+
"admin",
56+
"RootEntity",
57+
"01234567-89ab-cdef-0123-987654fedcba",
58+
"AdminService",
59+
createAction,
60+
true,
61+
);
62+
const beforeChanges = await adminService.run(SELECT.from(ChangeView));
63+
expect(beforeChanges.length > 0).to.be.true;
64+
65+
await DELETE(`/admin/RootEntity(ID=01234567-89ab-cdef-0123-987654fedcba,IsActiveEntity=true)`);
66+
67+
const afterChanges = await adminService.run(SELECT.from(ChangeView));
68+
69+
const changelogCreated = afterChanges.filter(ele=> ele.modification === "Create");
70+
const changelogDeleted = afterChanges.filter(ele=> ele.modification === "Delete");
71+
72+
const compareAttributes = ['keys', 'attribute', 'entity', 'serviceEntity', 'parentKey', 'serviceEntityPath', 'valueDataType', 'objectID', 'parentObjectID', 'entityKey'];
73+
74+
let commonItems = changelogCreated.filter(beforeItem => {
75+
return changelogDeleted.some(afterItem => {
76+
return compareAttributes.every(attr => beforeItem[attr] === afterItem[attr])
77+
&& beforeItem['valueChangedFrom'] === afterItem['valueChangedTo']
78+
&& beforeItem['valueChangedTo'] === afterItem['valueChangedFrom'];
79+
});
80+
});
81+
expect(commonItems.length > 0).to.be.true;
82+
expect(afterChanges.length).to.equal(14);
83+
});
84+
2785
it("2.1 Child entity creation - should log basic data type changes (ERP4SMEPREPWORKAPPPLAT-32 ERP4SMEPREPWORKAPPPLAT-613)", async () => {
2886
const action = POST.bind(
2987
{},
@@ -814,6 +872,27 @@ describe("change log integration test", () => {
814872
const BookStoresChange = BookStoresChanges[0];
815873
expect(BookStoresChange.objectID).to.equal("new name");
816874

875+
const updateBookStoresAction = PATCH.bind({}, `/admin/BookStores(ID=9d703c23-54a8-4eff-81c1-cdce6b6587c4,IsActiveEntity=false)`, {
876+
name: "name update",
877+
});
878+
await utils.apiAction(
879+
"admin",
880+
"BookStores",
881+
"9d703c23-54a8-4eff-81c1-cdce6b6587c4",
882+
"AdminService",
883+
updateBookStoresAction,
884+
);
885+
const updateBookStoresChanges = await adminService.run(
886+
SELECT.from(ChangeView).where({
887+
entity: "sap.capire.bookshop.BookStores",
888+
attribute: "name",
889+
modification: "update",
890+
}),
891+
);
892+
expect(updateBookStoresChanges.length).to.equal(1);
893+
const updateBookStoresChange = updateBookStoresChanges[0];
894+
expect(updateBookStoresChange.objectID).to.equal("name update");
895+
817896
delete cds.services.AdminService.entities.BookStores["@changelog"];
818897

819898
cds.services.AdminService.entities.Books["@changelog"] = [

tests/integration/service-api.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ describe("change log integration test", () => {
1717
await data.reset();
1818
});
1919

20+
it("1.6 When the global switch is on, all changelogs should be retained after the root entity is deleted, and a changelog for the deletion operation should be generated", async () => {
21+
cds.env.requires["change-tracking"].preserveDeletes = true;
22+
const level3EntityData = [
23+
{
24+
ID: "12ed5dd8-d45b-11ed-afa1-0242ac654321",
25+
title: "Service api Level3 title",
26+
parent_ID: "dd1fdd7d-da2a-4600-940b-0baf2946c4ff",
27+
},
28+
];
29+
await adminService.run(INSERT.into(adminService.entities.Level3Entity).entries(level3EntityData));
30+
let beforeChanges = await SELECT.from(ChangeView);
31+
expect(beforeChanges.length > 0).to.be.true;
32+
33+
await adminService.run(DELETE.from(adminService.entities.RootEntity).where({ ID: "64625905-c234-4d0d-9bc1-283ee8940812" }));
34+
let afterChanges = await SELECT.from(ChangeView);
35+
expect(afterChanges.length).to.equal(11);
36+
});
37+
2038
it("2.5 Root entity deep creation by service API - should log changes on root entity (ERP4SMEPREPWORKAPPPLAT-32 ERP4SMEPREPWORKAPPPLAT-613)", async () => {
2139
const bookStoreData = {
2240
ID: "843b3681-8b32-4d30-82dc-937cdbc68b3a",
@@ -86,6 +104,24 @@ describe("change log integration test", () => {
86104
const createBookStoresChange = createBookStoresChanges[0];
87105
expect(createBookStoresChange.objectID).to.equal("new name");
88106

107+
await UPDATE(adminService.entities.BookStores)
108+
.where({
109+
ID: "9d703c23-54a8-4eff-81c1-cdce6b6587c4"
110+
})
111+
.with({
112+
name: "BookStores name changed"
113+
});
114+
const updateBookStoresChanges = await adminService.run(
115+
SELECT.from(ChangeView).where({
116+
entity: "sap.capire.bookshop.BookStores",
117+
attribute: "name",
118+
modification: "update",
119+
}),
120+
);
121+
expect(updateBookStoresChanges.length).to.equal(1);
122+
const updateBookStoresChange = updateBookStoresChanges[0];
123+
expect(updateBookStoresChange.objectID).to.equal("BookStores name changed");
124+
89125
cds.services.AdminService.entities.BookStores["@changelog"].pop();
90126

91127
const level3EntityData = [

0 commit comments

Comments
 (0)