1
1
const cds = require ( '@sap/cds' )
2
+ const DEBUG = cds . debug ( 'changelog' )
2
3
3
4
const isRoot = 'change-tracking-isRootEntity'
4
5
const hasParent = 'change-tracking-parentEntity'
5
6
6
- const isChangeTracked = ( entity ) => (
7
- ( entity [ '@changelog' ]
8
- || entity . elements && Object . values ( entity . elements ) . some ( e => e [ '@changelog' ] ) ) && entity . query ?. SET ?. op !== 'union'
9
- )
7
+ const isChangeTracked = ( entity ) => {
8
+ if ( entity . query ?. SET ?. op === 'union' ) return false // REVISIT: should that be an error or warning?
9
+ if ( entity [ '@changelog' ] ) return true
10
+ if ( Object . values ( entity . elements ) . some ( e => e [ '@changelog' ] ) ) return true
11
+ }
10
12
11
13
// Add the appropriate Side Effects attribute to the custom action
12
14
const addSideEffects = ( actions , flag , element ) => {
@@ -139,57 +141,72 @@ function findParentAssociation (entity, csn, elements) {
139
141
} )
140
142
}
141
143
144
+
145
+
146
+ /**
147
+ * Returns an expression for the key of the given entity, which we can use as the right-hand-side of an ON condition.
148
+ */
149
+ function entityKey4 ( entity ) {
150
+ const xpr = [ ]
151
+ for ( let k in entity . elements ) {
152
+ const e = entity . elements [ k ] ; if ( ! e . key ) continue
153
+ if ( xpr . length ) xpr . push ( '||' )
154
+ if ( e . type === 'cds.Association' ) xpr . push ( { ref : [ k , e . keys ?. [ 0 ] ?. ref ?. [ 0 ] ] } )
155
+ else xpr . push ( { ref :[ k ] } )
156
+ }
157
+ return xpr
158
+ }
159
+
160
+
142
161
// Unfold @changelog annotations in loaded model
143
- cds . on ( 'loaded' , m => {
162
+ function enhanceModel ( m ) {
163
+
164
+ const _enhanced = 'sap.changelog.enhanced'
165
+ if ( m . meta ?. [ _enhanced ] ) return // already enhanced
144
166
145
167
// Get definitions from Dummy entity in our models
146
168
const { 'sap.changelog.aspect' : aspect } = m . definitions ; if ( ! aspect ) return // some other model
147
169
const { '@UI.Facets' : [ facet ] , elements : { changes } } = aspect
148
- changes . on . pop ( ) // remove ID -> filled in below
170
+ if ( changes . on . length > 2 ) changes . on . pop ( ) // remove ID -> filled in below
149
171
150
- // Process entities to define the relation
151
- processEntities ( m )
172
+ processEntities ( m ) // REVISIT: why is that required ?!?
152
173
153
174
for ( let name in m . definitions ) {
154
- const entity = m . definitions [ name ]
155
- if ( isChangeTracked ( entity ) ) {
156
-
157
- // Determine entity keys
158
- const keys = [ ] , { elements : elms } = entity
159
- for ( let e in elms ) {
160
- if ( elms [ e ] . key ) {
161
- if ( elms [ e ] . type === 'cds.Association' ) {
162
- keys . push ( {
163
- e,
164
- val : elms [ e ]
165
- } )
166
- } else {
167
- keys . push ( e )
168
- }
169
- }
170
- }
171
175
172
- // If no key attribute is defined for the entity, the logic to add association to ChangeView should be skipped.
173
- if ( keys . length === 0 ) {
174
- continue
175
- }
176
+ const entity = m . definitions [ name ]
177
+ if ( entity . kind === 'entity' && ! entity [ '@cds.autoexposed' ] && isChangeTracked ( entity ) ) {
176
178
177
- // Add association to ChangeView...
178
- const on = [ ...changes . on ]
179
- keys . forEach ( ( k , i ) => {
180
- i && on . push ( "||" )
181
- on . push ( {
182
- ref : k ?. val ?. type === "cds.Association" ? [ k . e , k . val . keys ?. [ 0 ] ?. ref ?. [ 0 ] ] : [ k ]
183
- } )
184
- } )
185
- const assoc = { ...changes , on }
186
- const query = entity . projection || entity . query ?. SELECT
187
179
if ( ! entity [ '@changelog.disable_assoc' ] ) {
188
- if ( query ) {
189
- ( query . columns ??= [ '*' ] ) . push ( { as : 'changes' , cast : assoc } )
190
- } else {
191
- entity . elements . changes = assoc
192
- }
180
+
181
+ // Add association to ChangeView...
182
+ const keys = entityKey4 ( entity ) ; if ( ! keys . length ) continue // If no key attribute is defined for the entity, the logic to add association to ChangeView should be skipped.
183
+ const assoc = { ...changes , on : [ ...changes . on , ...keys ] }
184
+
185
+ // --------------------------------------------------------------------
186
+ // PARKED: Add auto-exposed projection on ChangeView to service if applicable
187
+ // const namespace = name.match(/^(.*)\.[^.]+$/)[1]
188
+ // const service = m.definitions[namespace]
189
+ // if (service) {
190
+ // const projection = {from:{ref:[assoc.target]}}
191
+ // m.definitions[assoc.target = namespace + '.' + Changes] = {
192
+ // '@cds.autoexposed':true, kind:'entity', projection
193
+ // }
194
+ // DEBUG?.(`\n
195
+ // extend service ${namespace} with {
196
+ // entity ${Changes} as projection on ${projection.from.ref[0]};
197
+ // }
198
+ // `.replace(/ {10}/g,''))
199
+ // }
200
+ // --------------------------------------------------------------------
201
+
202
+ DEBUG ?. ( `\n
203
+ extend ${ name } with {
204
+ changes : Association to many ${ assoc . target } on ${ assoc . on . map ( x => x . ref ?. join ( '.' ) || x ) . join ( ' ' ) } ;
205
+ }
206
+ ` . replace ( / { 8 } / g, '' ) )
207
+ const query = entity . projection || entity . query ?. SELECT
208
+ if ( query ) ( query . columns ??= [ '*' ] ) . push ( { as : 'changes' , cast : assoc } )
209
+ else if ( entity . elements ) entity . elements . changes = assoc
193
210
194
211
// Add UI.Facet for Change History List
195
212
if ( ! entity [ '@changelog.disable_facet' ] )
@@ -200,9 +217,7 @@ cds.on('loaded', m => {
200
217
const hasParentInfo = entity [ hasParent ]
201
218
const entityName = hasParentInfo ?. entityName
202
219
const parentEntity = entityName ? m . definitions [ entityName ] : null
203
-
204
220
const isParentRootAndHasFacets = parentEntity ?. [ isRoot ] && parentEntity ?. [ '@UI.Facets' ]
205
-
206
221
if ( entity [ isRoot ] && entity [ '@UI.Facets' ] ) {
207
222
// Add side effects for root entity
208
223
addSideEffects ( entity . actions , true )
@@ -213,10 +228,11 @@ cds.on('loaded', m => {
213
228
}
214
229
}
215
230
}
216
- } )
231
+ ( m . meta ??= { } ) [ _enhanced ] = true
232
+ }
217
233
218
234
// Add generic change tracking handlers
219
- cds . on ( 'served' , ( ) => {
235
+ function addGenericHandlers ( ) {
220
236
const { track_changes, _afterReadChangeView } = require ( "./lib/change-log" )
221
237
for ( const srv of cds . services ) {
222
238
if ( srv instanceof cds . ApplicationService ) {
@@ -234,4 +250,11 @@ cds.on('served', () => {
234
250
}
235
251
}
236
252
}
237
- } )
253
+ }
254
+
255
+
256
+ // Register plugin hooks
257
+ cds . on ( 'compile.for.runtime' , csn => { DEBUG ?. ( 'on' , 'compile.for.runtime' ) ; enhanceModel ( csn ) } )
258
+ cds . on ( 'compile.to.edmx' , csn => { DEBUG ?. ( 'on' , 'compile.to.edmx' ) ; enhanceModel ( csn ) } )
259
+ cds . on ( 'compile.to.dbx' , csn => { DEBUG ?. ( 'on' , 'compile.to.dbx' ) ; enhanceModel ( csn ) } )
260
+ cds . on ( 'served' , addGenericHandlers )
0 commit comments