Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@cap-js/change-tracking

Package Overview
Dependencies
Maintainers
2
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cap-js/change-tracking - npm Package Compare versions

Comparing version 1.0.5 to 1.0.6

12

cds-plugin.js

@@ -43,2 +43,7 @@ const cds = require('@sap/cds')

// If no key attribute is defined for the entity, the logic to add association to ChangeView should be skipped.
if(keys.length === 0) {
continue;
}
// Add association to ChangeView...

@@ -50,2 +55,4 @@ const on = [...changes.on]; keys.forEach((k, i) => { i && on.push('||'); on.push({

const query = entity.projection || entity.query?.SELECT
if(!entity['@changelog.disable_assoc'])
{
if (query) {

@@ -58,4 +65,5 @@ (query.columns ??= ['*']).push({ as: 'changes', cast: assoc })

// Add UI.Facet for Change History List
entity['@UI.Facets']?.push(facet)
if(!entity['@changelog.disable_facet'])
entity['@UI.Facets']?.push(facet)
}
// The changehistory list should be refreshed after the custom action is triggered

@@ -62,0 +70,0 @@ if (entity.actions) {

@@ -7,2 +7,18 @@ # Change Log

## Version 1.0.6 - TBD
### Fixed
- Storage of wrong ObjectID in some special scenarios
- Missing localization of managed fields
- Views without keys won't get the association and UI facet pushed anymore
### Added
- A method to disable automatic generation of the UI Facet
### Changed
- Improved documentation of the @changelog Annotation
## Version 1.0.5 - 15.01.24

@@ -9,0 +25,0 @@

12

lib/change-log.js

@@ -100,3 +100,3 @@ const cds = require("@sap/cds")

*/
const _formatAssociationContext = async function (changes) {
const _formatAssociationContext = async function (changes, reqData) {
for (const change of changes) {

@@ -115,6 +115,6 @@ const a = cds.model.definitions[change.serviceEntity].elements[change.attribute]

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

@@ -224,3 +224,3 @@

const curObj = { curObjFromReqData, curObjFromDbQuery: obj }
return getObjectId(entityName, objIdElementNames, curObj)
return getObjectId(reqData, entityName, objIdElementNames, curObj)
}

@@ -273,3 +273,3 @@

await _formatObjectID(changes, req.data)
await _formatAssociationContext(changes)
await _formatAssociationContext(changes, req.data)
await _formatCompositionContext(changes, req.data)

@@ -356,3 +356,3 @@ }

if (req.event === "DELETE") {
if (cds.transaction(req).context.event === "DELETE") {
if (isDraftEnabled || !isComposition) {

@@ -359,0 +359,0 @@ return await DELETE.from(`sap.changelog.ChangeLog`).where({ entityKey })

@@ -73,3 +73,3 @@ const cds = require("@sap/cds")

async function getObjectId (entityName, fields, curObj) {
async function getObjectId (reqData, entityName, fields, curObj) {
let all = [], { curObjFromReqData: req_data={}, curObjFromDbQuery: db_data={} } = curObj

@@ -85,9 +85,26 @@ let entity = cds.model.definitions[entityName]

let foreignKey = assoc.keys?.[0]?.$generatedFieldName
let IDval = req_data[foreignKey] || _db_data[foreignKey]
let IDval =
req_data[foreignKey] && current.name === entityName
? req_data[foreignKey]
: _db_data[foreignKey]
if (IDval) try {
// REVISIT: This always reads all elements -> should read required ones only!
let ID = assoc.keys?.[0]?.ref[0] || 'ID'
// When parent/child nodes are created simultaneously, data is taken from draft table
_db_data = await SELECT.one.from(assoc._target).where({[ID]: IDval}) ||
await SELECT.one.from(`${assoc._target}.drafts`).where({[ID]: IDval}) || {}
const isComposition = hasComposition(assoc._target, current)
// 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 })) || {}
}
} else {
_db_data =
(await SELECT.one.from(assoc._target).where({ [ID]: IDval })) ||
{}
}
} catch (e) {

@@ -101,3 +118,3 @@ LOG.error("Failed to generate object Id for an association entity.", e)

field = path.join('_')
let obj = _db_data[field] || req_data[field]
let obj = current.name === entityName && req_data[field] ? req_data[field] : _db_data[field]
if (obj) all.push(obj)

@@ -140,2 +157,35 @@ } else {

const hasComposition = function (parentEntity, subEntity) {
if (!parentEntity.compositions) {
return false
}
const compositions = Object.values(parentEntity.compositions);
for (const composition of compositions) {
if (composition.target === subEntity.name) {
return true;
}
}
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 = {

@@ -142,0 +192,0 @@ getCurObjFromReqData,

{
"name": "@cap-js/change-tracking",
"version": "1.0.5",
"version": "1.0.6",
"description": "CDS plugin providing out-of-the box support for automatic capturing, storing, and viewing of the change records of modeled entities.",

@@ -5,0 +5,0 @@ "repository": "cap-js/change-tracking",

@@ -13,8 +13,6 @@ # Change Tracking Plugin for SAP Cloud Application Programming Model (CAP)

### Table of Contents
- [Change Tracking Plugin for SAP Cloud Application Programming Model (CAP)](#change-tracking-plugin-for-sap-cloud-application-programming-model-cap)
- [Table of Contents](#table-of-contents)
- [Table of Contents](#table-of-contents)
- [Preliminaries](#preliminaries)

@@ -31,2 +29,8 @@ - [Setup](#setup)

- [Disable lazy loading](#disable-lazy-loading)
- [Disable UI Facet generation](#disable-ui-facet-generation)
- [Disable Association to Changes Generation](#disable-association-to-changes-generation)
- [Modelling Samples](#modelling-samples)
- [Specify Object ID](#specify-object-id)
- [Tracing Changes](#tracing-changes)
- [Don'ts](#donts)
- [Contributing](#contributing)

@@ -36,4 +40,2 @@ - [Code of Conduct](#code-of-conduct)

## Preliminaries

@@ -61,4 +63,2 @@

## Setup

@@ -72,4 +72,2 @@

## Annotations

@@ -100,3 +98,2 @@

### Human-readable Types and Fields

@@ -120,3 +117,2 @@

### Human-readable IDs

@@ -133,2 +129,3 @@

```
<img width="1300" alt="change-history-id" src="_assets/changes-id-wbox.png">

@@ -141,2 +138,3 @@

```
<img width="1300" alt="change-history-id-hr" src="_assets/changes-id-hr-wbox.png">

@@ -146,3 +144,2 @@

### Human-readable Values

@@ -157,3 +154,3 @@

```cds
customer @changelog;
customer @changelog;
```

@@ -166,3 +163,3 @@

```cds
customer @changelog: [customer.name];
customer @changelog: [customer.name];
```

@@ -172,3 +169,2 @@

## Test-drive locally

@@ -179,5 +175,7 @@

1. **Start the server**:
```sh
cds watch
```
```sh
cds watch
```
2. **Make a change** on your change-tracked elements. This change will automatically be persisted in the database table (`sap.changelog.ChangeLog`) and made available in a pre-defined view, namely the [Change History view](#change-history-view) for your convenience.

@@ -235,3 +233,2 @@

}]);
```

@@ -241,2 +238,315 @@

### Disable UI Facet generation
If you do not want the UI facet added to a specific UI, you can annotate the service entity with `@changelog.disable_facet`. This will disable the automatic addition of the UI faced to this specific entity, but also all views or further projections up the chain.
### Disable Association to Changes Generation
For some scenarios, e.g. when doing `UNION` and the `@changelog` annotion is still propageted, the automatic addition of the association to `changes` does not make sense. You can use `@changelog.disable_assoc`for this to be disabled on entity level.
> [!IMPORTANT]
> This will also supress the addition of the UI facet, since the change-view is not available as target entity anymore.
## Modelling Samples
This chapter describes more modelling cases for further reference, from simple to complex, including but not limited to the followings.
### Specify Object ID
Use cases for Object ID annotation
#### Use Case 1: Annotate single field/multiple fields of associated table(s) as the Object ID
Modelling in `db/schema.cds`
```cds
entity Incidents : cuid, managed {
...
customer : Association to Customers;
title : String @title: 'Title';
urgency : Association to Urgency default 'M';
status : Association to Status default 'N';
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [customer.name, urgency.code, status.criticality] {
title @changelog;
}
```
![AssociationID](_assets/AssociationID.png)
#### Use Case 2: Annotate single field/multiple fields of project customized types as the Object ID
Modelling in `db/schema.cds`
```cds
entity Incidents : cuid, managed {
...
customer : Association to Customers;
title : String @title: 'Title';
...
}
entity Customers : cuid, managed {
...
email : EMailAddress; // customized type
phone : PhoneNumber; // customized type
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [customer.email, customer.phone] {
title @changelog;
}
```
![CustomTypeID](_assets/CustomTypeID.png)
#### Use Case 3: Annotate chained associated entities from the current entity as the Object ID
Modelling in `db/schema.cds`
```cds
entity Incidents : cuid, managed {
...
customer : Association to Customers;
...
}
entity Customers : cuid, managed {
...
addresses : Association to Addresses;
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [customer.addresses.city, customer.addresses.postCode] {
title @changelog;
}
```
![ChainedAssociationID](_assets/ChainedAssociationID.png)
> Change-tracking supports annotating chained associated entities from the current entity as object ID of current entity in case the entity in consumer applications is a pure relation table. However, the usage of chained associated entities is not recommended due to performance cost.
### Tracing Changes
Use cases for tracing changes
#### Use Case 1: Trace the changes of child nodes from the current entity and display the meaningful data from child nodes (composition relation)
Modelling in `db/schema.cds`
```cds
entity Incidents : managed, cuid {
...
title : String @title: 'Title';
conversation : Composition of many Conversation;
...
}
aspect Conversation: managed, cuid {
...
message : String;
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [title] {
conversation @changelog: [conversation.message];
}
```
![CompositionChange](_assets/CompositionChange.png)
#### Use Case 2: Trace the changes of associated entities from the current entity and display the meaningful data from associated entities (association relation)
Modelling in `db/schema.cds`
```cds
entity Incidents : cuid, managed {
...
customer : Association to Customers;
title : String @title: 'Title';
...
}
entity Customers : cuid, managed {
...
email : EMailAddress;
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [title] {
customer @changelog: [customer.email];
}
```
![AssociationChange](_assets/AssociationChange.png)
#### Use Case 3: Trace the changes of fields defined by project customized types and display the meaningful data
Modelling in `db/schema.cds`
```cds
type StatusType : Association to Status;
entity Incidents : cuid, managed {
...
title : String @title: 'Title';
status : StatusType default 'N';
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [title] {
status @changelog: [status.code];
}
```
![CustomTypeChange](_assets/CustomTypeChange.png)
#### Use Case 4: Trace the changes of chained associated entities from the current entity and display the meaningful data from associated entities (association relation)
Modelling in `db/schema.cds`
```cds
entity Incidents : cuid, managed {
...
title : String @title: 'Title';
customer : Association to Customers;
...
}
entity Customers : cuid, managed {
...
addresses : Association to Addresses;
...
}
```
Add the following `@changelog` annotations in `srv/change-tracking.cds`
```cds
annotate ProcessorService.Incidents with @changelog: [title] {
customer @changelog: [customer.addresses.city, customer.addresses.streetAddress];
}
```
![ChainedAssociationChange](_assets/ChainedAssociationChange.png)
> Change-tracking supports analyzing chained associated entities from the current entity in case the entity in consumer applications is a pure relation table. However, the usage of chained associated entities is not recommended due to performance cost.
#### Use Case 5: Trace the changes of union entity and display the meaningful data
`Payable.cds`:
```cds
entity Payables : cuid {
displayId : String;
@changelog
name : String;
cryptoAmount : Decimal;
fiatAmount : Decimal;
};
```
`Payment.cds`:
```cds
entity Payments : cuid {
displayId : String; //readable ID
@changelog
name : String;
};
```
Union entity in `BusinessTransaction.cds`:
```cds
entity BusinessTransactions as(
select from payments.Payments{
key ID,
displayId,
name,
changes : Association to many ChangeView
on changes.objectID = ID AND changes.entity = 'payments.Payments'
}
)
union all
(
select from payables.Payables {
key ID,
displayId,
name,
changes : Association to many ChangeView
on changes.objectID = ID AND changes.entity = 'payables.Payables'
}
);
```
![UnionChange.png](_assets/UnionChange.png)
### Don'ts
Don'ts
#### Use Case 1: Don't trace changes for field(s) with `Association to many`
```cds
entity Customers : cuid, managed {
...
incidents : Association to many Incidents on incidents.customer = $self;
}
```
The reason is that: the relationship: `Association to many` is only for modelling purpose and there is no concrete field in database table. In the above sample, there is no column for incidents in the table Customers, but there is a navigation property of incidents in Customers OData entity metadata.
#### Use Case 2: Don't trace changes for field(s) with *Unmanaged Association*
```cds
entity AggregatedBusinessTransactionData @(cds.autoexpose) : cuid {
FootprintInventory: Association to one FootprintInventories
on FootprintInventory.month = month
and FootprintInventory.year = year
and FootprintInventory.FootprintInventoryScope.ID = FootprintInventoryScope.ID;
...
}
```
The reason is that: When deploying to relational databases, Associations are mapped to foreign keys. Yet, when mapped to non-relational databases they're just references. More details could be found in [Prefer Managed Associations](https://cap.cloud.sap/docs/guides/domain-models#managed-associations). In the above sample, there is no column for FootprintInventory in the table AggregatedBusinessTransactionData, but there is a navigation property FootprintInventoryof in OData entity metadata.
#### Use Case 3: Don't trace changes for CUD on DB entity
```cds
this.on("UpdateActivationStatus", async (req) =>
// PaymentAgreementsOutgoingDb is the DB entity
await UPDATE.entity(PaymentAgreementsOutgoingDb)
.where({ ID: paymentAgreement.ID })
.set({ ActivationStatus_code: ActivationCodes.ACTIVE });
);
```
The reason is that: Application level services are by design the only place where business logic is enforced. This by extension means, that it also is the only point where e.g. change-tracking would be enabled. The underlying method used to do change tracking is `req.diff` which is responsible to read the necessary before-image from the database, and this method is not available on DB level.
## Contributing

@@ -246,3 +556,2 @@

## Code of Conduct

@@ -252,5 +561,5 @@

## Licensing
Copyright 2023 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/change-tracking).

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc