Skip to content

Commit

Permalink
Adapt to CDS 8 and optimize the logic for finding the root entity. (#105
Browse files Browse the repository at this point in the history
)

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.
  • Loading branch information
Sv7enNowitzki authored Aug 19, 2024
1 parent aaa90c6 commit 9661d6f
Show file tree
Hide file tree
Showing 13 changed files with 562 additions and 117 deletions.
148 changes: 129 additions & 19 deletions cds-plugin.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
const cds = require('@sap/cds')

const isRoot = 'change-tracking-isRootEntity'
const hasParent = 'change-tracking-parentEntity'

const isChangeTracked = (entity) => (
(entity['@changelog']
|| entity.elements && Object.values(entity.elements).some(e => e['@changelog'])) && entity.query?.SET?.op !== 'union'
)

// Add the appropriate Side Effects attribute to the custom action
const addSideEffects = (actions, flag, element) => {
if (!flag && (element === undefined || element === null)) {
return
}

for (const se of Object.values(actions)) {
const target = flag ? 'TargetProperties' : 'TargetEntities'
const sideEffectAttr = se[`@Common.SideEffects.${target}`]
Expand All @@ -23,6 +30,114 @@ const addSideEffects = (actions, flag, element) => {
}
}

function setChangeTrackingIsRootEntity(entity, csn, val = true) {
if (csn.definitions?.[entity.name]) {
csn.definitions[entity.name][isRoot] = val;
}
}

function checkAndSetRootEntity(parentEntity, entity, csn) {
if (entity[isRoot] === false) {
return entity;
}
if (parentEntity) {
return compositionRoot(parentEntity, csn);
} else {
setChangeTrackingIsRootEntity(entity, csn);
return { ...csn.definitions?.[entity.name], name: entity.name };
}
}

function processEntities(m) {
for (let name in m.definitions) {
compositionRoot({...m.definitions[name], name}, m)
}
}

function compositionRoot(entity, csn) {
if (!entity || entity.kind !== 'entity') {
return;
}
const parentEntity = compositionParent(entity, csn);
return checkAndSetRootEntity(parentEntity, entity, csn);
}

function compositionParent(entity, csn) {
if (!entity || entity.kind !== 'entity') {
return;
}
const parentAssociation = compositionParentAssociation(entity, csn);
return parentAssociation ?? null;
}

function compositionParentAssociation(entity, csn) {
if (!entity || entity.kind !== 'entity') {
return;
}
const elements = entity.elements ?? {};

// Add the change-tracking-isRootEntity attribute of the child entity
processCompositionElements(entity, csn, elements);

const hasChildFlag = entity[isRoot] !== false;
const hasParentEntity = entity[hasParent];

if (hasChildFlag || !hasParentEntity) {
// Find parent association of the entity
const parentAssociation = findParentAssociation(entity, csn, elements);
if (parentAssociation) {
const parentAssociationTarget = elements[parentAssociation]?.target;
if (hasChildFlag) setChangeTrackingIsRootEntity(entity, csn, false);
return {
...csn.definitions?.[parentAssociationTarget],
name: parentAssociationTarget
};
} else return;
}
return { ...csn.definitions?.[entity.name], name: entity.name };
}

function processCompositionElements(entity, csn, elements) {
for (const name in elements) {
const element = elements[name];
const target = element?.target;
const definition = csn.definitions?.[target];
if (
element.type !== 'cds.Composition' ||
target === entity.name ||
!definition ||
definition[isRoot] === false
) {
continue;
}
setChangeTrackingIsRootEntity({ ...definition, name: target }, csn, false);
}
}

function findParentAssociation(entity, csn, elements) {
return Object.keys(elements).find((name) => {
const element = elements[name];
const target = element?.target;
if (element.type === 'cds.Association' && target !== entity.name) {
const parentDefinition = csn.definitions?.[target] ?? {};
const parentElements = parentDefinition?.elements ?? {};
return !!Object.keys(parentElements).find((parentEntityName) => {
const parentElement = parentElements?.[parentEntityName] ?? {};
if (parentElement.type === 'cds.Composition') {
const isCompositionEntity = parentElement.target === entity.name;
// add parent information in the current entity
if (isCompositionEntity) {
csn.definitions[entity.name][hasParent] = {
associationName: name,
entityName: target
};
}
return isCompositionEntity;
}
});
}
});
}

// Unfold @changelog annotations in loaded model
cds.on('loaded', m => {
Expand All @@ -31,6 +146,9 @@ cds.on('loaded', m => {
const { 'sap.changelog.aspect': aspect } = m.definitions; if (!aspect) return // some other model
const { '@UI.Facets': [facet], elements: { changes } } = aspect
changes.on.pop() // remove ID -> filled in below

// Process entities to define the relation
processEntities(m)

for (let name in m.definitions) {
const entity = m.definitions[name]
Expand Down Expand Up @@ -63,28 +181,20 @@ cds.on('loaded', m => {
if(!entity['@changelog.disable_facet'])
entity['@UI.Facets']?.push(facet)
}
// The changehistory list should be refreshed after the custom action is triggered

if (entity.actions) {
const hasParentInfo = entity[hasParent];
const entityName = hasParentInfo?.entityName;
const parentEntity = entityName ? m.definitions[entityName] : null;

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

// When the custom action of the child entity is performed, the change history list of the parent entity is updated
if (entity.elements) {
//ToDo: Revisit Breaklook with node.js Expert
breakLoop: for (const [ele, eleValue] of Object.entries(entity.elements)) {
const parentEntity = m.definitions[eleValue.target]
if (parentEntity && parentEntity['@UI.Facets'] && eleValue.type === 'cds.Association') {
for (const value of Object.values(parentEntity.elements)) {
if (value.target === name) {
addSideEffects(entity.actions, false, ele)
break breakLoop
}
}
}
}
if (entity[isRoot] && entity['@UI.Facets']) {
// Add side effects for root entity
addSideEffects(entity.actions, true);
} else if (isParentRootAndHasFacets) {
// Add side effects for child entity
addSideEffects(entity.actions, false, hasParentInfo?.associationName);
}
}
}
Expand Down
Loading

0 comments on commit 9661d6f

Please sign in to comment.