@memberjunction/core 2.127.0 → 2.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/mocks/TestMetadataProvider.d.ts +4 -2
- package/dist/__tests__/mocks/TestMetadataProvider.d.ts.map +1 -1
- package/dist/__tests__/mocks/TestMetadataProvider.js +9 -3
- package/dist/__tests__/mocks/TestMetadataProvider.js.map +1 -1
- package/dist/generic/RegisterForStartup.d.ts +228 -0
- package/dist/generic/RegisterForStartup.d.ts.map +1 -0
- package/dist/generic/RegisterForStartup.js +233 -0
- package/dist/generic/RegisterForStartup.js.map +1 -0
- package/dist/generic/baseEngine.d.ts +191 -8
- package/dist/generic/baseEngine.d.ts.map +1 -1
- package/dist/generic/baseEngine.js +360 -14
- package/dist/generic/baseEngine.js.map +1 -1
- package/dist/generic/baseEngineRegistry.d.ts +247 -0
- package/dist/generic/baseEngineRegistry.d.ts.map +1 -0
- package/dist/generic/baseEngineRegistry.js +470 -0
- package/dist/generic/baseEngineRegistry.js.map +1 -0
- package/dist/generic/entityInfo.d.ts +50 -0
- package/dist/generic/entityInfo.d.ts.map +1 -1
- package/dist/generic/entityInfo.js +56 -0
- package/dist/generic/entityInfo.js.map +1 -1
- package/dist/generic/graphqlTypeNames.d.ts +90 -0
- package/dist/generic/graphqlTypeNames.d.ts.map +1 -0
- package/dist/generic/graphqlTypeNames.js +119 -0
- package/dist/generic/graphqlTypeNames.js.map +1 -0
- package/dist/generic/interfaces.d.ts +234 -3
- package/dist/generic/interfaces.d.ts.map +1 -1
- package/dist/generic/interfaces.js.map +1 -1
- package/dist/generic/localCacheManager.d.ts +388 -0
- package/dist/generic/localCacheManager.d.ts.map +1 -0
- package/dist/generic/localCacheManager.js +856 -0
- package/dist/generic/localCacheManager.js.map +1 -0
- package/dist/generic/providerBase.d.ts +227 -13
- package/dist/generic/providerBase.d.ts.map +1 -1
- package/dist/generic/providerBase.js +751 -6
- package/dist/generic/providerBase.js.map +1 -1
- package/dist/generic/queryInfo.d.ts +18 -0
- package/dist/generic/queryInfo.d.ts.map +1 -1
- package/dist/generic/queryInfo.js +18 -0
- package/dist/generic/queryInfo.js.map +1 -1
- package/dist/generic/queryInfoInterfaces.d.ts +17 -0
- package/dist/generic/queryInfoInterfaces.d.ts.map +1 -1
- package/dist/generic/runQuery.d.ts +30 -0
- package/dist/generic/runQuery.d.ts.map +1 -1
- package/dist/generic/runQuery.js +13 -0
- package/dist/generic/runQuery.js.map +1 -1
- package/dist/generic/telemetryManager.d.ts +628 -0
- package/dist/generic/telemetryManager.d.ts.map +1 -0
- package/dist/generic/telemetryManager.js +1011 -0
- package/dist/generic/telemetryManager.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/views/runView.d.ts +25 -0
- package/dist/views/runView.d.ts.map +1 -1
- package/dist/views/runView.js +4 -5
- package/dist/views/runView.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,9 +5,11 @@ exports.ProviderBase = exports.AllMetadataArrays = exports.MetadataFromSimpleObj
|
|
|
5
5
|
const baseEntity_1 = require("./baseEntity");
|
|
6
6
|
const entityInfo_1 = require("./entityInfo");
|
|
7
7
|
const interfaces_1 = require("./interfaces");
|
|
8
|
+
const localCacheManager_1 = require("./localCacheManager");
|
|
8
9
|
const applicationInfo_1 = require("../generic/applicationInfo");
|
|
9
10
|
const securityInfo_1 = require("./securityInfo");
|
|
10
11
|
const global_1 = require("@memberjunction/global");
|
|
12
|
+
const telemetryManager_1 = require("./telemetryManager");
|
|
11
13
|
const logging_1 = require("./logging");
|
|
12
14
|
const queryInfo_1 = require("./queryInfo");
|
|
13
15
|
const libraryInfo_1 = require("./libraryInfo");
|
|
@@ -98,6 +100,127 @@ class ProviderBase {
|
|
|
98
100
|
this._refresh = false;
|
|
99
101
|
this._cachedVisibleExplorerNavigationItems = null;
|
|
100
102
|
}
|
|
103
|
+
// ========================================================================
|
|
104
|
+
// PUBLIC API METHODS - Orchestrate Pre → Cache → Internal → Post flow
|
|
105
|
+
// ========================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Runs a view based on the provided parameters.
|
|
108
|
+
* This method orchestrates the full execution flow: pre-processing, cache check,
|
|
109
|
+
* internal execution, post-processing, and cache storage.
|
|
110
|
+
* @param params - The view parameters
|
|
111
|
+
* @param contextUser - Optional user context for permissions (required server-side)
|
|
112
|
+
* @returns The view results
|
|
113
|
+
*/
|
|
114
|
+
async RunView(params, contextUser) {
|
|
115
|
+
// Pre-processing: telemetry, validation, entity status check
|
|
116
|
+
const preResult = await this.PreRunView(params, contextUser);
|
|
117
|
+
// Check for cached result - end telemetry with cache hit info
|
|
118
|
+
if (preResult.cachedResult) {
|
|
119
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
120
|
+
cacheHit: true,
|
|
121
|
+
cacheStatus: preResult.cacheStatus,
|
|
122
|
+
resultCount: preResult.cachedResult.Results?.length ?? 0
|
|
123
|
+
});
|
|
124
|
+
return preResult.cachedResult;
|
|
125
|
+
}
|
|
126
|
+
// Execute the internal implementation
|
|
127
|
+
const result = await this.InternalRunView(params, contextUser);
|
|
128
|
+
// Post-processing: transformation, cache storage, telemetry end
|
|
129
|
+
await this.PostRunView(result, params, preResult, contextUser);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs multiple views based on the provided parameters.
|
|
134
|
+
* This method orchestrates the full execution flow for batch operations.
|
|
135
|
+
* @param params - Array of view parameters
|
|
136
|
+
* @param contextUser - Optional user context for permissions (required server-side)
|
|
137
|
+
* @returns Array of view results
|
|
138
|
+
*/
|
|
139
|
+
async RunViews(params, contextUser) {
|
|
140
|
+
// Pre-processing for batch
|
|
141
|
+
const preResult = await this.PreRunViews(params, contextUser);
|
|
142
|
+
// Check for smart cache check mode
|
|
143
|
+
if (preResult.useSmartCacheCheck && preResult.smartCacheCheckParams) {
|
|
144
|
+
return this.executeSmartCacheCheck(params, preResult, contextUser);
|
|
145
|
+
}
|
|
146
|
+
// Check for cached results - if all are cached, end telemetry and return early
|
|
147
|
+
if (preResult.allCached && preResult.cachedResults) {
|
|
148
|
+
const totalResults = preResult.cachedResults.reduce((sum, r) => sum + (r.Results?.length ?? 0), 0);
|
|
149
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
150
|
+
cacheHit: true,
|
|
151
|
+
allCached: true,
|
|
152
|
+
batchSize: params.length,
|
|
153
|
+
totalResultCount: totalResults
|
|
154
|
+
});
|
|
155
|
+
return preResult.cachedResults;
|
|
156
|
+
}
|
|
157
|
+
// Execute the internal implementation for non-cached items
|
|
158
|
+
const results = await this.InternalRunViews(preResult.uncachedParams || params, contextUser);
|
|
159
|
+
// Merge cached and fresh results if needed
|
|
160
|
+
const finalResults = preResult.cachedResults
|
|
161
|
+
? this.mergeCachedAndFreshResults(preResult, results)
|
|
162
|
+
: results;
|
|
163
|
+
// Post-processing for batch
|
|
164
|
+
await this.PostRunViews(finalResults, params, preResult, contextUser);
|
|
165
|
+
return finalResults;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Runs a query based on the provided parameters.
|
|
169
|
+
* This method orchestrates the full execution flow: pre-processing, cache check,
|
|
170
|
+
* internal execution, post-processing, and cache storage.
|
|
171
|
+
* @param params - The query parameters
|
|
172
|
+
* @param contextUser - Optional user context for permissions (required server-side)
|
|
173
|
+
* @returns The query results
|
|
174
|
+
*/
|
|
175
|
+
async RunQuery(params, contextUser) {
|
|
176
|
+
// Pre-processing: telemetry, cache check
|
|
177
|
+
const preResult = await this.PreRunQuery(params, contextUser);
|
|
178
|
+
// Check for cached result - end telemetry with cache hit info
|
|
179
|
+
if (preResult.cachedResult) {
|
|
180
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
181
|
+
cacheHit: true,
|
|
182
|
+
cacheStatus: preResult.cacheStatus,
|
|
183
|
+
resultCount: preResult.cachedResult.Results?.length ?? 0
|
|
184
|
+
});
|
|
185
|
+
return preResult.cachedResult;
|
|
186
|
+
}
|
|
187
|
+
// Execute the internal implementation
|
|
188
|
+
const result = await this.InternalRunQuery(params, contextUser);
|
|
189
|
+
// Post-processing: cache storage, telemetry end
|
|
190
|
+
await this.PostRunQuery(result, params, preResult, contextUser);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Runs multiple queries based on the provided parameters.
|
|
195
|
+
* This method orchestrates the full execution flow for batch query operations.
|
|
196
|
+
* @param params - Array of query parameters
|
|
197
|
+
* @param contextUser - Optional user context for permissions (required server-side)
|
|
198
|
+
* @returns Array of query results
|
|
199
|
+
*/
|
|
200
|
+
async RunQueries(params, contextUser) {
|
|
201
|
+
// Pre-processing for batch
|
|
202
|
+
const preResult = await this.PreRunQueries(params, contextUser);
|
|
203
|
+
// Check for cached results - if all are cached, end telemetry and return early
|
|
204
|
+
if (preResult.allCached && preResult.cachedResults) {
|
|
205
|
+
const totalResults = preResult.cachedResults.reduce((sum, r) => sum + (r.Results?.length ?? 0), 0);
|
|
206
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
207
|
+
cacheHit: true,
|
|
208
|
+
allCached: true,
|
|
209
|
+
batchSize: params.length,
|
|
210
|
+
totalResultCount: totalResults
|
|
211
|
+
});
|
|
212
|
+
return preResult.cachedResults;
|
|
213
|
+
}
|
|
214
|
+
// Execute the internal implementation for non-cached items
|
|
215
|
+
const results = await this.InternalRunQueries(preResult.uncachedParams || params, contextUser);
|
|
216
|
+
// Merge cached and fresh results if needed
|
|
217
|
+
const finalResults = preResult.cachedResults
|
|
218
|
+
? this.mergeQueryCachedAndFreshResults(preResult, results)
|
|
219
|
+
: results;
|
|
220
|
+
// Post-processing for batch
|
|
221
|
+
await this.PostRunQueries(finalResults, params, preResult, contextUser);
|
|
222
|
+
return finalResults;
|
|
223
|
+
}
|
|
101
224
|
/**
|
|
102
225
|
* Used to check to see if the entity in question is active or not
|
|
103
226
|
* If it is not active, it will throw an exception or log a warning depending on the status of the entity being
|
|
@@ -113,12 +236,609 @@ class ProviderBase {
|
|
|
113
236
|
}
|
|
114
237
|
entityInfo_1.EntityInfo.AssertEntityActiveStatus(entity, callerName);
|
|
115
238
|
}
|
|
239
|
+
// Type aliases for cleaner code
|
|
240
|
+
get PreRunViewResult() { return this._preRunViewResultType; }
|
|
241
|
+
get PreRunViewsResult() { return this._preRunViewsResultType; }
|
|
242
|
+
get PreRunQueryResult() { return this._preRunQueryResultType; }
|
|
243
|
+
get PreRunQueriesResult() { return this._preRunQueriesResultType; }
|
|
244
|
+
// ========================================================================
|
|
245
|
+
// PRE-PROCESSING HOOKS
|
|
246
|
+
// ========================================================================
|
|
116
247
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* @param
|
|
248
|
+
* Pre-processing hook for RunView.
|
|
249
|
+
* Handles telemetry, validation, entity status check, and cache lookup.
|
|
250
|
+
* @param params - The view parameters
|
|
251
|
+
* @param contextUser - Optional user context
|
|
252
|
+
* @returns Pre-processing result with cache status and optional cached result
|
|
253
|
+
*/
|
|
254
|
+
async PreRunView(params, contextUser) {
|
|
255
|
+
const preViewStart = performance.now();
|
|
256
|
+
// Start telemetry tracking
|
|
257
|
+
const telemetryStart = performance.now();
|
|
258
|
+
const telemetryEventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunView', 'ProviderBase.RunView', {
|
|
259
|
+
EntityName: params.EntityName,
|
|
260
|
+
ViewID: params.ViewID,
|
|
261
|
+
ViewName: params.ViewName,
|
|
262
|
+
ExtraFilter: params.ExtraFilter,
|
|
263
|
+
OrderBy: params.OrderBy,
|
|
264
|
+
ResultType: params.ResultType,
|
|
265
|
+
MaxRows: params.MaxRows,
|
|
266
|
+
StartRow: params.StartRow,
|
|
267
|
+
CacheLocal: params.CacheLocal,
|
|
268
|
+
_fromEngine: params._fromEngine
|
|
269
|
+
}, contextUser?.ID);
|
|
270
|
+
const telemetryTime = performance.now() - telemetryStart;
|
|
271
|
+
// Entity status check
|
|
272
|
+
const entityCheckStart = performance.now();
|
|
273
|
+
await this.EntityStatusCheck(params, 'PreRunView');
|
|
274
|
+
const entityCheckTime = performance.now() - entityCheckStart;
|
|
275
|
+
// Handle entity_object result type - need all fields
|
|
276
|
+
const entityLookupStart = performance.now();
|
|
277
|
+
if (params.ResultType === 'entity_object') {
|
|
278
|
+
const entity = this.Entities.find(e => e.Name.trim().toLowerCase() === params.EntityName.trim().toLowerCase());
|
|
279
|
+
if (!entity)
|
|
280
|
+
throw new Error(`Entity ${params.EntityName} not found in metadata`);
|
|
281
|
+
params.Fields = entity.Fields.map(f => f.Name);
|
|
282
|
+
}
|
|
283
|
+
const entityLookupTime = performance.now() - entityLookupStart;
|
|
284
|
+
// Check local cache if enabled
|
|
285
|
+
const cacheCheckStart = performance.now();
|
|
286
|
+
let cacheStatus = 'disabled';
|
|
287
|
+
let cachedResult;
|
|
288
|
+
let fingerprint;
|
|
289
|
+
if (params.CacheLocal && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
290
|
+
fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(params, this.InstanceConnectionString);
|
|
291
|
+
const cached = await localCacheManager_1.LocalCacheManager.Instance.GetRunViewResult(fingerprint);
|
|
292
|
+
if (cached) {
|
|
293
|
+
// Reconstruct RunViewResult from cached data
|
|
294
|
+
cachedResult = {
|
|
295
|
+
Success: true,
|
|
296
|
+
Results: cached.results,
|
|
297
|
+
RowCount: cached.results.length,
|
|
298
|
+
TotalRowCount: cached.results.length,
|
|
299
|
+
ExecutionTime: 0, // Cached, no execution time
|
|
300
|
+
ErrorMessage: '',
|
|
301
|
+
UserViewRunID: ''
|
|
302
|
+
};
|
|
303
|
+
cacheStatus = 'hit';
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
cacheStatus = 'miss';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const cacheCheckTime = performance.now() - cacheCheckStart;
|
|
310
|
+
const totalPreTime = performance.now() - preViewStart;
|
|
311
|
+
if (totalPreTime > 50) {
|
|
312
|
+
console.log(`[PERF-PRE] PreRunView ${params.EntityName}: ${totalPreTime.toFixed(1)}ms (telemetry=${telemetryTime.toFixed(1)}ms, entityCheck=${entityCheckTime.toFixed(1)}ms, entityLookup=${entityLookupTime.toFixed(1)}ms, cache=${cacheCheckTime.toFixed(1)}ms)`);
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
telemetryEventId,
|
|
316
|
+
cacheStatus,
|
|
317
|
+
cachedResult,
|
|
318
|
+
fingerprint
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Pre-processing hook for RunViews (batch).
|
|
323
|
+
* Handles telemetry, validation, and cache lookup for multiple views.
|
|
324
|
+
* @param params - Array of view parameters
|
|
325
|
+
* @param contextUser - Optional user context
|
|
326
|
+
* @returns Pre-processing result with cache status for each view
|
|
327
|
+
*/
|
|
328
|
+
async PreRunViews(params, contextUser) {
|
|
329
|
+
// Start telemetry tracking for batch operation
|
|
330
|
+
const fromEngine = params.some(p => p._fromEngine);
|
|
331
|
+
const telemetryEventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunView', 'ProviderBase.RunViews', {
|
|
332
|
+
BatchSize: params.length,
|
|
333
|
+
Entities: params.map(p => p.EntityName || p.ViewName || p.ViewID).filter(Boolean),
|
|
334
|
+
_fromEngine: fromEngine
|
|
335
|
+
}, contextUser?.ID);
|
|
336
|
+
// Check if any params have CacheLocal enabled - smart caching is always used when caching locally
|
|
337
|
+
const useSmartCacheCheck = params.some(p => p.CacheLocal);
|
|
338
|
+
// If local caching is enabled, use smart cache check flow
|
|
339
|
+
if (useSmartCacheCheck && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
340
|
+
return this.prepareSmartCacheCheckParams(params, telemetryEventId, contextUser);
|
|
341
|
+
}
|
|
342
|
+
// Traditional caching flow
|
|
343
|
+
const cacheStatusMap = new Map();
|
|
344
|
+
const uncachedParams = [];
|
|
345
|
+
const cachedResults = [];
|
|
346
|
+
let allCached = true;
|
|
347
|
+
for (let i = 0; i < params.length; i++) {
|
|
348
|
+
const param = params[i];
|
|
349
|
+
// Entity status check
|
|
350
|
+
await this.EntityStatusCheck(param, 'PreRunViews');
|
|
351
|
+
// Handle entity_object result type - need all fields
|
|
352
|
+
if (param.ResultType === 'entity_object') {
|
|
353
|
+
const entity = this.Entities.find(e => e.Name.trim().toLowerCase() === param.EntityName.trim().toLowerCase());
|
|
354
|
+
if (!entity) {
|
|
355
|
+
throw new Error(`Entity ${param.EntityName} not found in metadata`);
|
|
356
|
+
}
|
|
357
|
+
param.Fields = entity.Fields.map(f => f.Name);
|
|
358
|
+
}
|
|
359
|
+
// Check local cache if enabled
|
|
360
|
+
if (param.CacheLocal && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
361
|
+
const fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(param, this.InstanceConnectionString);
|
|
362
|
+
const cached = await localCacheManager_1.LocalCacheManager.Instance.GetRunViewResult(fingerprint);
|
|
363
|
+
if (cached) {
|
|
364
|
+
const cachedViewResult = {
|
|
365
|
+
Success: true,
|
|
366
|
+
Results: cached.results,
|
|
367
|
+
RowCount: cached.results.length,
|
|
368
|
+
TotalRowCount: cached.results.length,
|
|
369
|
+
ExecutionTime: 0,
|
|
370
|
+
ErrorMessage: '',
|
|
371
|
+
UserViewRunID: ''
|
|
372
|
+
};
|
|
373
|
+
// if needed this will transform each result into an entity object
|
|
374
|
+
await this.TransformSimpleObjectToEntityObject(param, cachedViewResult, contextUser);
|
|
375
|
+
cacheStatusMap.set(i, { status: 'hit', result: cachedViewResult });
|
|
376
|
+
cachedResults.push(cachedViewResult);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
cacheStatusMap.set(i, { status: 'miss' });
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
cacheStatusMap.set(i, { status: 'disabled' });
|
|
383
|
+
}
|
|
384
|
+
allCached = false;
|
|
385
|
+
uncachedParams.push(param);
|
|
386
|
+
cachedResults.push(null); // Placeholder for uncached
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
telemetryEventId,
|
|
390
|
+
allCached,
|
|
391
|
+
cachedResults: allCached ? cachedResults.filter(r => r !== null) : undefined,
|
|
392
|
+
uncachedParams: allCached ? undefined : uncachedParams,
|
|
393
|
+
cacheStatusMap
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Prepares smart cache check parameters for RunViews when CacheLocal is enabled.
|
|
398
|
+
* Instead of returning cached data immediately, this builds params to send to the server
|
|
399
|
+
* which will validate if the cache is current or return fresh data.
|
|
400
|
+
*/
|
|
401
|
+
async prepareSmartCacheCheckParams(params, telemetryEventId, contextUser) {
|
|
402
|
+
const smartCacheCheckParams = [];
|
|
403
|
+
for (const param of params) {
|
|
404
|
+
// Entity status check
|
|
405
|
+
await this.EntityStatusCheck(param, 'PreRunViews');
|
|
406
|
+
// Handle entity_object result type - need all fields
|
|
407
|
+
if (param.ResultType === 'entity_object') {
|
|
408
|
+
const entity = this.Entities.find(e => e.Name.trim().toLowerCase() === param.EntityName?.trim().toLowerCase());
|
|
409
|
+
if (!entity) {
|
|
410
|
+
throw new Error(`Entity ${param.EntityName} not found in metadata`);
|
|
411
|
+
}
|
|
412
|
+
param.Fields = entity.Fields.map(f => f.Name);
|
|
413
|
+
}
|
|
414
|
+
// Build the cache check param with optional cache status
|
|
415
|
+
let cacheStatus;
|
|
416
|
+
if (param.CacheLocal && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
417
|
+
const fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(param, this.InstanceConnectionString);
|
|
418
|
+
const cached = await localCacheManager_1.LocalCacheManager.Instance.GetRunViewResult(fingerprint);
|
|
419
|
+
if (cached) {
|
|
420
|
+
cacheStatus = {
|
|
421
|
+
maxUpdatedAt: cached.maxUpdatedAt,
|
|
422
|
+
rowCount: cached.rowCount
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
smartCacheCheckParams.push({
|
|
427
|
+
params: param,
|
|
428
|
+
cacheStatus
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
telemetryEventId,
|
|
433
|
+
allCached: false, // Don't return cached directly - let server validate
|
|
434
|
+
useSmartCacheCheck: true,
|
|
435
|
+
smartCacheCheckParams,
|
|
436
|
+
cacheStatusMap: new Map()
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Executes the smart cache check flow for RunViews.
|
|
441
|
+
* Calls RunViewsWithCacheCheck on the provider (if available) and processes the results,
|
|
442
|
+
* using cached data for 'current' items and fresh data for 'stale' items.
|
|
443
|
+
*
|
|
444
|
+
* Optimized to process all results in parallel using Promise.all for cache lookups,
|
|
445
|
+
* cache updates, and entity transformations.
|
|
446
|
+
*/
|
|
447
|
+
async executeSmartCacheCheck(params, preResult, contextUser) {
|
|
448
|
+
// Cast to access RunViewsWithCacheCheck method
|
|
449
|
+
const provider = this;
|
|
450
|
+
// Execute the smart cache check
|
|
451
|
+
const response = await provider.RunViewsWithCacheCheck(preResult.smartCacheCheckParams, contextUser);
|
|
452
|
+
if (!response.success) {
|
|
453
|
+
// If the smart cache check failed, log and return empty results
|
|
454
|
+
(0, logging_1.LogError)(`SmartCacheCheck failed: ${response.errorMessage}`);
|
|
455
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
456
|
+
smartCacheCheck: true,
|
|
457
|
+
success: false,
|
|
458
|
+
errorMessage: response.errorMessage
|
|
459
|
+
});
|
|
460
|
+
return params.map(() => ({
|
|
461
|
+
Success: false,
|
|
462
|
+
Results: [],
|
|
463
|
+
RowCount: 0,
|
|
464
|
+
TotalRowCount: 0,
|
|
465
|
+
ExecutionTime: 0,
|
|
466
|
+
ErrorMessage: response.errorMessage || 'SmartCacheCheck failed',
|
|
467
|
+
UserViewRunID: ''
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
// Process all results in parallel
|
|
471
|
+
const processingPromises = params.map((param, i) => this.processSingleSmartCacheResult(param, i, response.results, contextUser));
|
|
472
|
+
const processedResults = await Promise.all(processingPromises);
|
|
473
|
+
// Aggregate telemetry stats
|
|
474
|
+
let cacheHits = 0;
|
|
475
|
+
let cacheMisses = 0;
|
|
476
|
+
for (const result of processedResults) {
|
|
477
|
+
if (result.cacheHit)
|
|
478
|
+
cacheHits++;
|
|
479
|
+
if (result.cacheMiss)
|
|
480
|
+
cacheMisses++;
|
|
481
|
+
}
|
|
482
|
+
// End telemetry
|
|
483
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
484
|
+
smartCacheCheck: true,
|
|
485
|
+
success: true,
|
|
486
|
+
cacheHits,
|
|
487
|
+
cacheMisses,
|
|
488
|
+
batchSize: params.length
|
|
489
|
+
});
|
|
490
|
+
return processedResults.map(r => r.result);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Processes a single smart cache check result.
|
|
494
|
+
* Handles cache lookup for 'current' items and cache update for 'stale' items.
|
|
495
|
+
*/
|
|
496
|
+
async processSingleSmartCacheResult(param, index, serverResults, contextUser) {
|
|
497
|
+
const checkResult = serverResults.find(r => r.viewIndex === index);
|
|
498
|
+
if (!checkResult) {
|
|
499
|
+
return {
|
|
500
|
+
result: {
|
|
501
|
+
Success: false,
|
|
502
|
+
Results: [],
|
|
503
|
+
RowCount: 0,
|
|
504
|
+
TotalRowCount: 0,
|
|
505
|
+
ExecutionTime: 0,
|
|
506
|
+
ErrorMessage: 'No result returned from server',
|
|
507
|
+
UserViewRunID: ''
|
|
508
|
+
},
|
|
509
|
+
cacheHit: false,
|
|
510
|
+
cacheMiss: false
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
if (checkResult.status === 'current') {
|
|
514
|
+
// Cache is current - use cached data
|
|
515
|
+
const fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(param, this.InstanceConnectionString);
|
|
516
|
+
const cached = await localCacheManager_1.LocalCacheManager.Instance.GetRunViewResult(fingerprint);
|
|
517
|
+
if (cached) {
|
|
518
|
+
const cachedResult = {
|
|
519
|
+
Success: true,
|
|
520
|
+
Results: cached.results,
|
|
521
|
+
RowCount: cached.rowCount,
|
|
522
|
+
TotalRowCount: cached.rowCount,
|
|
523
|
+
ExecutionTime: 0,
|
|
524
|
+
ErrorMessage: '',
|
|
525
|
+
UserViewRunID: ''
|
|
526
|
+
};
|
|
527
|
+
// Transform to entity objects if needed
|
|
528
|
+
await this.TransformSimpleObjectToEntityObject(param, cachedResult, contextUser);
|
|
529
|
+
return { result: cachedResult, cacheHit: true, cacheMiss: false };
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
// Cache miss - shouldn't happen but handle gracefully
|
|
533
|
+
return {
|
|
534
|
+
result: {
|
|
535
|
+
Success: false,
|
|
536
|
+
Results: [],
|
|
537
|
+
RowCount: 0,
|
|
538
|
+
TotalRowCount: 0,
|
|
539
|
+
ExecutionTime: 0,
|
|
540
|
+
ErrorMessage: 'Cache marked current but no cached data found',
|
|
541
|
+
UserViewRunID: ''
|
|
542
|
+
},
|
|
543
|
+
cacheHit: false,
|
|
544
|
+
cacheMiss: false
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (checkResult.status === 'stale') {
|
|
549
|
+
// Cache is stale - use fresh data and update cache
|
|
550
|
+
const freshResult = {
|
|
551
|
+
Success: true,
|
|
552
|
+
Results: checkResult.results || [],
|
|
553
|
+
RowCount: checkResult.rowCount || 0,
|
|
554
|
+
TotalRowCount: checkResult.rowCount || 0,
|
|
555
|
+
ExecutionTime: 0,
|
|
556
|
+
ErrorMessage: '',
|
|
557
|
+
UserViewRunID: ''
|
|
558
|
+
};
|
|
559
|
+
// Update the local cache with fresh data (don't await - fire and forget for performance)
|
|
560
|
+
if (param.CacheLocal && checkResult.maxUpdatedAt && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
561
|
+
const fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(param, this.InstanceConnectionString);
|
|
562
|
+
// Note: We don't await here to avoid blocking the response
|
|
563
|
+
// Cache update happens in background
|
|
564
|
+
localCacheManager_1.LocalCacheManager.Instance.SetRunViewResult(fingerprint, param, checkResult.results || [], checkResult.maxUpdatedAt, checkResult.rowCount).catch(e => (0, logging_1.LogError)(`Failed to update cache: ${e}`));
|
|
565
|
+
}
|
|
566
|
+
// Transform to entity objects if needed
|
|
567
|
+
await this.TransformSimpleObjectToEntityObject(param, freshResult, contextUser);
|
|
568
|
+
return { result: freshResult, cacheHit: false, cacheMiss: true };
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// Error status
|
|
572
|
+
return {
|
|
573
|
+
result: {
|
|
574
|
+
Success: false,
|
|
575
|
+
Results: [],
|
|
576
|
+
RowCount: 0,
|
|
577
|
+
TotalRowCount: 0,
|
|
578
|
+
ExecutionTime: 0,
|
|
579
|
+
ErrorMessage: checkResult.errorMessage || 'Unknown error',
|
|
580
|
+
UserViewRunID: ''
|
|
581
|
+
},
|
|
582
|
+
cacheHit: false,
|
|
583
|
+
cacheMiss: false
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Pre-processing hook for RunQuery.
|
|
589
|
+
* Handles telemetry and cache lookup.
|
|
590
|
+
* @param params - The query parameters
|
|
591
|
+
* @param contextUser - Optional user context
|
|
592
|
+
* @returns Pre-processing result with cache status and optional cached result
|
|
593
|
+
*/
|
|
594
|
+
async PreRunQuery(params, contextUser) {
|
|
595
|
+
// Start telemetry tracking
|
|
596
|
+
const telemetryEventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunQuery', 'ProviderBase.RunQuery', {
|
|
597
|
+
QueryID: params.QueryID,
|
|
598
|
+
QueryName: params.QueryName,
|
|
599
|
+
CategoryPath: params.CategoryPath,
|
|
600
|
+
CategoryID: params.CategoryID,
|
|
601
|
+
MaxRows: params.MaxRows,
|
|
602
|
+
StartRow: params.StartRow,
|
|
603
|
+
HasParameters: params.Parameters ? Object.keys(params.Parameters).length > 0 : false
|
|
604
|
+
}, contextUser?.ID);
|
|
605
|
+
// Query caching is handled internally by the provider's query cache mechanism
|
|
606
|
+
// We just return the telemetry info here - actual cache check happens in InternalRunQuery
|
|
607
|
+
return {
|
|
608
|
+
telemetryEventId,
|
|
609
|
+
cacheStatus: 'disabled', // Query caching is handled differently
|
|
610
|
+
cachedResult: undefined,
|
|
611
|
+
fingerprint: undefined
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Pre-processing hook for RunQueries (batch).
|
|
616
|
+
* Handles telemetry for batch query operations.
|
|
617
|
+
* @param params - Array of query parameters
|
|
618
|
+
* @param contextUser - Optional user context
|
|
619
|
+
* @returns Pre-processing result
|
|
620
|
+
*/
|
|
621
|
+
async PreRunQueries(params, contextUser) {
|
|
622
|
+
// Start telemetry tracking for batch operation
|
|
623
|
+
const telemetryEventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunQuery', 'ProviderBase.RunQueries', {
|
|
624
|
+
BatchSize: params.length,
|
|
625
|
+
Queries: params.map(p => p.QueryName || p.QueryID).filter(Boolean)
|
|
626
|
+
}, contextUser?.ID);
|
|
627
|
+
// Query caching is handled internally by each query execution
|
|
628
|
+
return {
|
|
629
|
+
telemetryEventId,
|
|
630
|
+
allCached: false,
|
|
631
|
+
cachedResults: undefined,
|
|
632
|
+
uncachedParams: params,
|
|
633
|
+
cacheStatusMap: undefined
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
// ========================================================================
|
|
637
|
+
// POST-PROCESSING HOOKS
|
|
638
|
+
// ========================================================================
|
|
639
|
+
/**
|
|
640
|
+
* Post-processing hook for RunView.
|
|
641
|
+
* Handles result transformation, cache storage, and telemetry end.
|
|
642
|
+
* @param result - The view result
|
|
643
|
+
* @param params - The view parameters
|
|
644
|
+
* @param preResult - The pre-processing result
|
|
645
|
+
* @param contextUser - Optional user context
|
|
646
|
+
*/
|
|
647
|
+
async PostRunView(result, params, preResult, contextUser) {
|
|
648
|
+
// Transform the result set into BaseEntity-derived objects, if needed
|
|
649
|
+
await this.TransformSimpleObjectToEntityObject(params, result, contextUser);
|
|
650
|
+
// Store in local cache if enabled and we have a successful result
|
|
651
|
+
if (params.CacheLocal && result.Success && preResult.fingerprint && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
652
|
+
// Extract maxUpdatedAt from results if available
|
|
653
|
+
const maxUpdatedAt = this.extractMaxUpdatedAt(result.Results);
|
|
654
|
+
await localCacheManager_1.LocalCacheManager.Instance.SetRunViewResult(preResult.fingerprint, params, result.Results, maxUpdatedAt);
|
|
655
|
+
}
|
|
656
|
+
// End telemetry tracking with cache miss info
|
|
657
|
+
if (preResult.telemetryEventId) {
|
|
658
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
659
|
+
cacheHit: false,
|
|
660
|
+
cacheStatus: preResult.cacheStatus,
|
|
661
|
+
resultCount: result.Results?.length ?? 0,
|
|
662
|
+
success: result.Success
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Post-processing hook for RunViews (batch).
|
|
668
|
+
* Handles result transformation, cache storage, and telemetry end.
|
|
669
|
+
* @param results - Array of view results
|
|
670
|
+
* @param params - Array of view parameters
|
|
671
|
+
* @param preResult - The pre-processing result
|
|
672
|
+
* @param contextUser - Optional user context
|
|
673
|
+
*/
|
|
674
|
+
async PostRunViews(results, params, preResult, contextUser) {
|
|
675
|
+
// Transform results in parallel
|
|
676
|
+
const promises = [];
|
|
677
|
+
for (let i = 0; i < results.length; i++) {
|
|
678
|
+
promises.push(this.TransformSimpleObjectToEntityObject(params[i], results[i], contextUser));
|
|
679
|
+
// Store in local cache if enabled
|
|
680
|
+
if (params[i].CacheLocal && results[i].Success && localCacheManager_1.LocalCacheManager.Instance.IsInitialized) {
|
|
681
|
+
const fingerprint = localCacheManager_1.LocalCacheManager.Instance.GenerateRunViewFingerprint(params[i], this.InstanceConnectionString);
|
|
682
|
+
const maxUpdatedAt = this.extractMaxUpdatedAt(results[i].Results);
|
|
683
|
+
promises.push(localCacheManager_1.LocalCacheManager.Instance.SetRunViewResult(fingerprint, params[i], results[i].Results, maxUpdatedAt));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
await Promise.all(promises);
|
|
687
|
+
// End telemetry tracking with batch info
|
|
688
|
+
if (preResult.telemetryEventId) {
|
|
689
|
+
const totalResults = results.reduce((sum, r) => sum + (r.Results?.length ?? 0), 0);
|
|
690
|
+
const cachedCount = preResult.cacheStatusMap
|
|
691
|
+
? [...preResult.cacheStatusMap.values()].filter(s => s.status === 'hit').length
|
|
692
|
+
: 0;
|
|
693
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
694
|
+
cacheHit: false,
|
|
695
|
+
allCached: false,
|
|
696
|
+
batchSize: params.length,
|
|
697
|
+
cachedCount,
|
|
698
|
+
fetchedCount: params.length - cachedCount,
|
|
699
|
+
totalResultCount: totalResults
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Post-processing hook for RunQuery.
|
|
705
|
+
* Handles cache storage and telemetry end.
|
|
706
|
+
* @param result - The query result
|
|
707
|
+
* @param params - The query parameters
|
|
708
|
+
* @param preResult - The pre-processing result
|
|
709
|
+
* @param contextUser - Optional user context
|
|
710
|
+
*/
|
|
711
|
+
async PostRunQuery(result, params, preResult, contextUser) {
|
|
712
|
+
// Query caching is handled internally by the provider
|
|
713
|
+
// End telemetry tracking with cache miss info
|
|
714
|
+
if (preResult.telemetryEventId) {
|
|
715
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
716
|
+
cacheHit: false,
|
|
717
|
+
cacheStatus: preResult.cacheStatus,
|
|
718
|
+
resultCount: result.Results?.length ?? 0,
|
|
719
|
+
success: result.Success
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Post-processing hook for RunQueries (batch).
|
|
725
|
+
* Handles telemetry end.
|
|
726
|
+
* @param results - Array of query results
|
|
727
|
+
* @param params - Array of query parameters
|
|
728
|
+
* @param preResult - The pre-processing result
|
|
729
|
+
* @param contextUser - Optional user context
|
|
730
|
+
*/
|
|
731
|
+
async PostRunQueries(results, params, preResult, contextUser) {
|
|
732
|
+
// Query caching is handled internally by each query execution
|
|
733
|
+
// End telemetry tracking with batch info
|
|
734
|
+
if (preResult.telemetryEventId) {
|
|
735
|
+
const totalResults = results.reduce((sum, r) => sum + (r.Results?.length ?? 0), 0);
|
|
736
|
+
const cachedCount = preResult.cacheStatusMap
|
|
737
|
+
? [...preResult.cacheStatusMap.values()].filter(s => s.status === 'hit').length
|
|
738
|
+
: 0;
|
|
739
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(preResult.telemetryEventId, {
|
|
740
|
+
cacheHit: false,
|
|
741
|
+
allCached: false,
|
|
742
|
+
batchSize: params.length,
|
|
743
|
+
cachedCount,
|
|
744
|
+
fetchedCount: params.length - cachedCount,
|
|
745
|
+
totalResultCount: totalResults
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// ========================================================================
|
|
750
|
+
// CACHE HELPERS
|
|
751
|
+
// ========================================================================
|
|
752
|
+
/**
|
|
753
|
+
* Extracts the maximum __mj_UpdatedAt timestamp from a set of results.
|
|
754
|
+
* This is used for cache freshness checking.
|
|
755
|
+
* @param results - Array of result objects that may contain __mj_UpdatedAt
|
|
756
|
+
* @returns ISO string of the max timestamp, or current time if none found
|
|
757
|
+
*/
|
|
758
|
+
extractMaxUpdatedAt(results) {
|
|
759
|
+
let maxDate = null;
|
|
760
|
+
for (const item of results) {
|
|
761
|
+
if (item && typeof item === 'object') {
|
|
762
|
+
const record = item;
|
|
763
|
+
// Check for __mj_UpdatedAt field (standard MJ timestamp field)
|
|
764
|
+
const updatedAt = record['__mj_UpdatedAt'] || record['UpdatedAt'];
|
|
765
|
+
if (updatedAt) {
|
|
766
|
+
const date = updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
|
|
767
|
+
if (!isNaN(date.getTime()) && (!maxDate || date > maxDate)) {
|
|
768
|
+
maxDate = date;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return maxDate ? maxDate.toISOString() : new Date().toISOString();
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Merges cached and fresh results for RunViews, maintaining original order.
|
|
777
|
+
* @param preResult - The pre-processing result with cache info
|
|
778
|
+
* @param freshResults - The fresh results from InternalRunViews
|
|
779
|
+
* @returns Combined results in original order
|
|
780
|
+
*/
|
|
781
|
+
mergeCachedAndFreshResults(preResult, freshResults) {
|
|
782
|
+
if (!preResult.cacheStatusMap) {
|
|
783
|
+
return freshResults;
|
|
784
|
+
}
|
|
785
|
+
const merged = [];
|
|
786
|
+
let freshIndex = 0;
|
|
787
|
+
for (let i = 0; i < preResult.cacheStatusMap.size; i++) {
|
|
788
|
+
const cacheInfo = preResult.cacheStatusMap.get(i);
|
|
789
|
+
if (cacheInfo?.status === 'hit' && cacheInfo.result) {
|
|
790
|
+
merged.push(cacheInfo.result);
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
merged.push(freshResults[freshIndex++]);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return merged;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Merges cached and fresh results for RunQueries, maintaining original order.
|
|
800
|
+
* @param preResult - The pre-processing result with cache info
|
|
801
|
+
* @param freshResults - The fresh results from InternalRunQueries
|
|
802
|
+
* @returns Combined results in original order
|
|
803
|
+
*/
|
|
804
|
+
mergeQueryCachedAndFreshResults(preResult, freshResults) {
|
|
805
|
+
if (!preResult.cacheStatusMap) {
|
|
806
|
+
return freshResults;
|
|
807
|
+
}
|
|
808
|
+
const merged = [];
|
|
809
|
+
let freshIndex = 0;
|
|
810
|
+
for (let i = 0; i < preResult.cacheStatusMap.size; i++) {
|
|
811
|
+
const cacheInfo = preResult.cacheStatusMap.get(i);
|
|
812
|
+
if (cacheInfo?.status === 'hit' && cacheInfo.result) {
|
|
813
|
+
merged.push(cacheInfo.result);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
merged.push(freshResults[freshIndex++]);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return merged;
|
|
820
|
+
}
|
|
821
|
+
// ========================================================================
|
|
822
|
+
// LEGACY METHODS (kept for backward compatibility, will be removed)
|
|
823
|
+
// ========================================================================
|
|
824
|
+
/**
|
|
825
|
+
* @deprecated Use PreRunView instead. This method is kept for backward compatibility.
|
|
120
826
|
*/
|
|
121
827
|
async PreProcessRunView(params, contextUser) {
|
|
828
|
+
// Start telemetry tracking
|
|
829
|
+
const eventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunView', 'ProviderBase.RunView', {
|
|
830
|
+
EntityName: params.EntityName,
|
|
831
|
+
ViewID: params.ViewID,
|
|
832
|
+
ViewName: params.ViewName,
|
|
833
|
+
ExtraFilter: params.ExtraFilter,
|
|
834
|
+
OrderBy: params.OrderBy,
|
|
835
|
+
ResultType: params.ResultType,
|
|
836
|
+
MaxRows: params.MaxRows,
|
|
837
|
+
StartRow: params.StartRow,
|
|
838
|
+
_fromEngine: params._fromEngine
|
|
839
|
+
}, contextUser?.ID);
|
|
840
|
+
// Store on params object for retrieval in PostProcessRunView
|
|
841
|
+
params._telemetryEventId = eventId;
|
|
122
842
|
await this.EntityStatusCheck(params, 'PreProcessRunView');
|
|
123
843
|
// FIRST, if the resultType is entity_object, we need to run the view with ALL fields in the entity
|
|
124
844
|
// so that we can get the data to populate the entity object with.
|
|
@@ -139,6 +859,12 @@ class ProviderBase {
|
|
|
139
859
|
async PostProcessRunView(result, params, contextUser) {
|
|
140
860
|
// Transform the result set into BaseEntity-derived objects, if needed
|
|
141
861
|
await this.TransformSimpleObjectToEntityObject(params, result, contextUser);
|
|
862
|
+
// End telemetry tracking
|
|
863
|
+
const eventId = params._telemetryEventId;
|
|
864
|
+
if (eventId) {
|
|
865
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(eventId);
|
|
866
|
+
delete params._telemetryEventId;
|
|
867
|
+
}
|
|
142
868
|
}
|
|
143
869
|
/**
|
|
144
870
|
* Base class implementation for handling pre-processing of RunViews() each sub-class should call this
|
|
@@ -148,6 +874,17 @@ class ProviderBase {
|
|
|
148
874
|
* @returns
|
|
149
875
|
*/
|
|
150
876
|
async PreProcessRunViews(params, contextUser) {
|
|
877
|
+
// Start telemetry tracking for batch operation
|
|
878
|
+
const fromEngine = params.some(p => p._fromEngine);
|
|
879
|
+
const eventId = telemetryManager_1.TelemetryManager.Instance.StartEvent('RunView', 'ProviderBase.RunViews', {
|
|
880
|
+
BatchSize: params.length,
|
|
881
|
+
Entities: params.map(p => p.EntityName || p.ViewName || p.ViewID).filter(Boolean),
|
|
882
|
+
_fromEngine: fromEngine
|
|
883
|
+
}, contextUser?.ID);
|
|
884
|
+
// Store on first param for retrieval in PostProcessRunViews (using a special key to avoid collision)
|
|
885
|
+
if (params.length > 0) {
|
|
886
|
+
params[0]._telemetryBatchEventId = eventId;
|
|
887
|
+
}
|
|
151
888
|
if (params && params.length > 0) {
|
|
152
889
|
for (const param of params) {
|
|
153
890
|
this.EntityStatusCheck(param, 'PreProcessRunViews');
|
|
@@ -180,6 +917,12 @@ class ProviderBase {
|
|
|
180
917
|
}
|
|
181
918
|
// await the promises for all transformations
|
|
182
919
|
await Promise.all(promises);
|
|
920
|
+
// End telemetry tracking for batch operation
|
|
921
|
+
const eventId = params[0]._telemetryBatchEventId;
|
|
922
|
+
if (eventId) {
|
|
923
|
+
telemetryManager_1.TelemetryManager.Instance.EndEvent(eventId);
|
|
924
|
+
delete params[0]._telemetryBatchEventId;
|
|
925
|
+
}
|
|
183
926
|
}
|
|
184
927
|
}
|
|
185
928
|
/**
|
|
@@ -190,7 +933,7 @@ class ProviderBase {
|
|
|
190
933
|
*/
|
|
191
934
|
async TransformSimpleObjectToEntityObject(param, result, contextUser) {
|
|
192
935
|
// only if needed (e.g. ResultType==='entity_object'), transform the result set into BaseEntity-derived objects
|
|
193
|
-
if (param.ResultType === 'entity_object' && result && result.Success) {
|
|
936
|
+
if (param.ResultType === 'entity_object' && result && result.Success && result.Results?.length > 0) {
|
|
194
937
|
// we need to transform each of the items in the result set into a BaseEntity-derived object
|
|
195
938
|
// Create entities and load data in parallel for better performance
|
|
196
939
|
const entityPromises = result.Results.map(async (item) => {
|
|
@@ -376,12 +1119,15 @@ class ProviderBase {
|
|
|
376
1119
|
*/
|
|
377
1120
|
PostProcessEntityMetadata(entities, fields, fieldValues, permissions, relationships, settings) {
|
|
378
1121
|
const result = [];
|
|
1122
|
+
// Sort entities alphabetically by name to ensure deterministic ordering
|
|
1123
|
+
// This prevents non-deterministic output in CodeGen and other metadata consumers
|
|
1124
|
+
const sortedEntities = entities.sort((a, b) => a.Name.localeCompare(b.Name));
|
|
379
1125
|
if (fieldValues && fieldValues.length > 0)
|
|
380
1126
|
for (let f of fields) {
|
|
381
1127
|
// populate the field values for each field, if we have them
|
|
382
1128
|
f.EntityFieldValues = fieldValues.filter(fv => fv.EntityFieldID === f.ID);
|
|
383
1129
|
}
|
|
384
|
-
for (let e of
|
|
1130
|
+
for (let e of sortedEntities) {
|
|
385
1131
|
e.EntityFields = fields.filter(f => f.EntityID === e.ID).sort((a, b) => a.Sequence - b.Sequence);
|
|
386
1132
|
e.EntityPermissions = permissions.filter(p => p.EntityID === e.ID);
|
|
387
1133
|
e.EntityRelationships = relationships.filter(r => r.EntityID === e.ID);
|
|
@@ -936,7 +1682,6 @@ class ProviderBase {
|
|
|
936
1682
|
const temp = JSON.parse(await ls.GetItem(this.LocalStoragePrefix + _a.localStorageAllMetadataKey)); // we now have a simple object for all the metadata
|
|
937
1683
|
if (temp) {
|
|
938
1684
|
// we have local metadata
|
|
939
|
-
(0, logging_1.LogStatus)('Metadata loaded from local storage');
|
|
940
1685
|
const metadata = MetadataFromSimpleObject(temp, this); // create a new object to start this up
|
|
941
1686
|
this.UpdateLocalMetadata(metadata);
|
|
942
1687
|
}
|