Skip to content

Commit 9661d6f

Browse files
Adapt to CDS 8 and optimize the logic for finding the root entity. (#105)
Fix: 1. The `serviceEntity` is not captured in the `ChangeLog` table in some cases. 2. When modeling an `inline entity`, the keys attribute in the `Changes` table recorded the unexpected association and parent ID. 3. When reqData is undefined, special handling is required. 4. When running test cases in CDS 8, the request failed with a status code of 404. 5. In CDS 8, for auto-exposed Compositions, all direct CRUD requests are rejected in non-draft cases. When executing test cases, an error with the message `ENTITY_IS_AUTOEXPOSED` occurs because the ChangeView falls under the aforementioned circumstances. 6. In CDS 8, CAP does not support queries for draft-enabled entities on the application service. When running test cases, the related test cases failed and contained the error message: `SqliteError: NOT NULL constraint failed: AdminService_Books_drafts.DraftAdministrativeData_DraftUUID`. 7. In CDS 8, the `cds.transaction` will be deprecated and needs to be replaced with `req.event`. 8. `req._params` and `req.context` are not official APIs and need to be replaced with official properties to ensure code stability. Optimize: 1. Implement a method to analyze entities, determine their structure, add a flag to indicate whether it is a root entity, and when the entity is a child entity, record information about its parent entity.
1 parent aaa90c6 commit 9661d6f

13 files changed

+562
-117
lines changed

cds-plugin.js

Lines changed: 129 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
const cds = require('@sap/cds')
22

3+
const isRoot = 'change-tracking-isRootEntity'
4+
const hasParent = 'change-tracking-parentEntity'
5+
36
const isChangeTracked = (entity) => (
47
(entity['@changelog']
58
|| entity.elements && Object.values(entity.elements).some(e => e['@changelog'])) && entity.query?.SET?.op !== 'union'
69
)
710

811
// Add the appropriate Side Effects attribute to the custom action
912
const addSideEffects = (actions, flag, element) => {
13+
if (!flag && (element === undefined || element === null)) {
14+
return
15+
}
16+
1017
for (const se of Object.values(actions)) {
1118
const target = flag ? 'TargetProperties' : 'TargetEntities'
1219
const sideEffectAttr = se[`@Common.SideEffects.${target}`]
@@ -23,6 +30,114 @@ const addSideEffects = (actions, flag, element) => {
2330
}
2431
}
2532

33+
function setChangeTrackingIsRootEntity(entity, csn, val = true) {
34+
if (csn.definitions?.[entity.name]) {
35+
csn.definitions[entity.name][isRoot] = val;
36+
}
37+
}
38+
39+
function checkAndSetRootEntity(parentEntity, entity, csn) {
40+
if (entity[isRoot] === false) {
41+
return entity;
42+
}
43+
if (parentEntity) {
44+
return compositionRoot(parentEntity, csn);
45+
} else {
46+
setChangeTrackingIsRootEntity(entity, csn);
47+
return { ...csn.definitions?.[entity.name], name: entity.name };
48+
}
49+
}
50+
51+
function processEntities(m) {
52+
for (let name in m.definitions) {
53+
compositionRoot({...m.definitions[name], name}, m)
54+
}
55+
}
56+
57+
function compositionRoot(entity, csn) {
58+
if (!entity || entity.kind !== 'entity') {
59+
return;
60+
}
61+
const parentEntity = compositionParent(entity, csn);
62+
return checkAndSetRootEntity(parentEntity, entity, csn);
63+
}
64+
65+
function compositionParent(entity, csn) {
66+
if (!entity || entity.kind !== 'entity') {
67+
return;
68+
}
69+
const parentAssociation = compositionParentAssociation(entity, csn);
70+
return parentAssociation ?? null;
71+
}
72+
73+
function compositionParentAssociation(entity, csn) {
74+
if (!entity || entity.kind !== 'entity') {
75+
return;
76+
}
77+
const elements = entity.elements ?? {};
78+
79+
// Add the change-tracking-isRootEntity attribute of the child entity
80+
processCompositionElements(entity, csn, elements);
81+
82+
const hasChildFlag = entity[isRoot] !== false;
83+
const hasParentEntity = entity[hasParent];
84+
85+
if (hasChildFlag || !hasParentEntity) {
86+
// Find parent association of the entity
87+
const parentAssociation = findParentAssociation(entity, csn, elements);
88+
if (parentAssociation) {
89+
const parentAssociationTarget = elements[parentAssociation]?.target;
90+
if (hasChildFlag) setChangeTrackingIsRootEntity(entity, csn, false);
91+
return {
92+
...csn.definitions?.[parentAssociationTarget],
93+
name: parentAssociationTarget
94+
};
95+
} else return;
96+
}
97+
return { ...csn.definitions?.[entity.name], name: entity.name };
98+
}
99+
100+
function processCompositionElements(entity, csn, elements) {
101+
for (const name in elements) {
102+
const element = elements[name];
103+
const target = element?.target;
104+
const definition = csn.definitions?.[target];
105+
if (
106+
element.type !== 'cds.Composition' ||
107+
target === entity.name ||
108+
!definition ||
109+
definition[isRoot] === false
110+
) {
111+
continue;
112+
}
113+
setChangeTrackingIsRootEntity({ ...definition, name: target }, csn, false);
114+
}
115+
}
116+
117+
function findParentAssociation(entity, csn, elements) {
118+
return Object.keys(elements).find((name) => {
119+
const element = elements[name];
120+
const target = element?.target;
121+
if (element.type === 'cds.Association' && target !== entity.name) {
122+
const parentDefinition = csn.definitions?.[target] ?? {};
123+
const parentElements = parentDefinition?.elements ?? {};
124+
return !!Object.keys(parentElements).find((parentEntityName) => {
125+
const parentElement = parentElements?.[parentEntityName] ?? {};
126+
if (parentElement.type === 'cds.Composition') {
127+
const isCompositionEntity = parentElement.target === entity.name;
128+
// add parent information in the current entity
129+
if (isCompositionEntity) {
130+
csn.definitions[entity.name][hasParent] = {
131+
associationName: name,
132+
entityName: target
133+
};
134+
}
135+
return isCompositionEntity;
136+
}
137+
});
138+
}
139+
});
140+
}
26141

27142
// Unfold @changelog annotations in loaded model
28143
cds.on('loaded', m => {
@@ -31,6 +146,9 @@ cds.on('loaded', m => {
31146
const { 'sap.changelog.aspect': aspect } = m.definitions; if (!aspect) return // some other model
32147
const { '@UI.Facets': [facet], elements: { changes } } = aspect
33148
changes.on.pop() // remove ID -> filled in below
149+
150+
// Process entities to define the relation
151+
processEntities(m)
34152

35153
for (let name in m.definitions) {
36154
const entity = m.definitions[name]
@@ -63,28 +181,20 @@ cds.on('loaded', m => {
63181
if(!entity['@changelog.disable_facet'])
64182
entity['@UI.Facets']?.push(facet)
65183
}
66-
// The changehistory list should be refreshed after the custom action is triggered
184+
67185
if (entity.actions) {
186+
const hasParentInfo = entity[hasParent];
187+
const entityName = hasParentInfo?.entityName;
188+
const parentEntity = entityName ? m.definitions[entityName] : null;
68189

69-
// Update the changehistory list on the current entity when the custom action of the entity is triggered
70-
if (entity['@UI.Facets']) {
71-
addSideEffects(entity.actions, true)
72-
}
190+
const isParentRootAndHasFacets = parentEntity?.[isRoot] && parentEntity?.['@UI.Facets'];
73191

74-
// When the custom action of the child entity is performed, the change history list of the parent entity is updated
75-
if (entity.elements) {
76-
//ToDo: Revisit Breaklook with node.js Expert
77-
breakLoop: for (const [ele, eleValue] of Object.entries(entity.elements)) {
78-
const parentEntity = m.definitions[eleValue.target]
79-
if (parentEntity && parentEntity['@UI.Facets'] && eleValue.type === 'cds.Association') {
80-
for (const value of Object.values(parentEntity.elements)) {
81-
if (value.target === name) {
82-
addSideEffects(entity.actions, false, ele)
83-
break breakLoop
84-
}
85-
}
86-
}
87-
}
192+
if (entity[isRoot] && entity['@UI.Facets']) {
193+
// Add side effects for root entity
194+
addSideEffects(entity.actions, true);
195+
} else if (isParentRootAndHasFacets) {
196+
// Add side effects for child entity
197+
addSideEffects(entity.actions, false, hasParentInfo?.associationName);
88198
}
89199
}
90200
}

0 commit comments

Comments
 (0)