retold-data-service 2.0.21 → 2.0.23
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/.quackage-comprehension-loader.json +19 -0
- package/bin/retold-data-service-clone.js +4 -1
- package/generate-bookstore-comprehension.js +645 -0
- package/package.json +7 -7
- package/source/Retold-Data-Service.js +30 -2
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Load.js +345 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Schema.js +97 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Session.js +221 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-WebUI.js +57 -0
- package/source/services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js +536 -0
- package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader-Configuration.json +9 -0
- package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +86 -0
- package/source/services/comprehension-loader/pict-app/Pict-ComprehensionLoader-Bundle.js +6 -0
- package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +760 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +360 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +472 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +119 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +269 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +330 -0
- package/source/services/comprehension-loader/web/comprehension-loader.js +6794 -0
- package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -0
- package/source/services/comprehension-loader/web/comprehension-loader.min.js +2 -0
- package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -0
- package/source/services/comprehension-loader/web/index.html +17 -0
- package/source/services/data-cloner/DataCloner-Command-Schema.js +407 -15
- package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +59 -1
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +1 -0
- package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +125 -5
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +18 -8
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +104 -1
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +1 -1
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +12 -0
- package/source/services/data-cloner/web/data-cloner.js +201 -139
- package/source/services/data-cloner/web/data-cloner.js.map +1 -1
- package/source/services/data-cloner/web/data-cloner.min.js +1 -1
- package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
- package/test/RetoldDataService_tests.js +225 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Data Service - Comprehension Loader Service
|
|
3
|
+
*
|
|
4
|
+
* Fable service that pushes comprehension data to a remote retold-based server.
|
|
5
|
+
* Provides REST endpoints for session management, schema discovery,
|
|
6
|
+
* comprehension ingestion, and entity-by-entity load orchestration.
|
|
7
|
+
*
|
|
8
|
+
* Two route groups:
|
|
9
|
+
* connectRoutes() - JSON API endpoints under /comprehension_load/*
|
|
10
|
+
* connectWebUIRoutes() - Web UI HTML serving at /comprehension_load/
|
|
11
|
+
*
|
|
12
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
13
|
+
*/
|
|
14
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
15
|
+
|
|
16
|
+
const libPath = require('path');
|
|
17
|
+
|
|
18
|
+
const libPict = require('pict');
|
|
19
|
+
const libPictSessionManager = require('pict-sessionmanager');
|
|
20
|
+
|
|
21
|
+
// Resolve the meadow-integration IntegrationAdapter
|
|
22
|
+
const _MeadowIntegrationBase = libPath.dirname(require.resolve('meadow-integration/package.json'));
|
|
23
|
+
const libIntegrationAdapter = require(libPath.join(_MeadowIntegrationBase, 'source/Meadow-Service-Integration-Adapter.js'));
|
|
24
|
+
|
|
25
|
+
const defaultComprehensionLoaderOptions = (
|
|
26
|
+
{
|
|
27
|
+
RoutePrefix: '/comprehension_load'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
class RetoldDataServiceComprehensionLoader extends libFableServiceProviderBase
|
|
31
|
+
{
|
|
32
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
33
|
+
{
|
|
34
|
+
let tmpOptions = Object.assign({}, defaultComprehensionLoaderOptions, pOptions);
|
|
35
|
+
super(pFable, tmpOptions, pServiceHash);
|
|
36
|
+
|
|
37
|
+
this.serviceType = 'RetoldDataServiceComprehensionLoader';
|
|
38
|
+
|
|
39
|
+
// Load state - tracks session, schema, comprehension, and push progress
|
|
40
|
+
this._loadState = (
|
|
41
|
+
{
|
|
42
|
+
// Remote session
|
|
43
|
+
SessionConfigured: false,
|
|
44
|
+
SessionAuthenticated: false,
|
|
45
|
+
RemoteServerURL: '',
|
|
46
|
+
|
|
47
|
+
// Remote schema (fetched to discover valid entities)
|
|
48
|
+
RemoteSchema: false,
|
|
49
|
+
RemoteEntityList: [],
|
|
50
|
+
|
|
51
|
+
// Comprehension data (received from browser)
|
|
52
|
+
ComprehensionData: null,
|
|
53
|
+
ComprehensionEntityList: [],
|
|
54
|
+
ComprehensionRecordCounts: {},
|
|
55
|
+
ComprehensionTotalRecords: 0,
|
|
56
|
+
|
|
57
|
+
// Load progress
|
|
58
|
+
LoadRunning: false,
|
|
59
|
+
LoadStopping: false,
|
|
60
|
+
LoadProgress: {},
|
|
61
|
+
LoadStartTime: null,
|
|
62
|
+
LoadEndTime: null,
|
|
63
|
+
LoadEventLog: [],
|
|
64
|
+
LoadReport: null,
|
|
65
|
+
|
|
66
|
+
// Throughput sampling - records per 10-second window
|
|
67
|
+
ThroughputSamples: [],
|
|
68
|
+
ThroughputTimer: null
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Create an isolated Pict instance for remote session management
|
|
72
|
+
this._Pict = new libPict(
|
|
73
|
+
{
|
|
74
|
+
Product: 'ComprehensionLoaderSession',
|
|
75
|
+
TraceLog: true,
|
|
76
|
+
LogStreams:
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
streamtype: 'console'
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this._Pict.serviceManager.addServiceType('SessionManager', libPictSessionManager);
|
|
85
|
+
this._Pict.serviceManager.instantiateServiceProvider('SessionManager');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The route prefix for all comprehension loader endpoints.
|
|
90
|
+
*/
|
|
91
|
+
get routePrefix()
|
|
92
|
+
{
|
|
93
|
+
let tmpConfig = this.fable.RetoldDataService.options.ComprehensionLoader || {};
|
|
94
|
+
return tmpConfig.RoutePrefix || this.options.RoutePrefix || '/comprehension_load';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The load state object.
|
|
99
|
+
*/
|
|
100
|
+
get loadState()
|
|
101
|
+
{
|
|
102
|
+
return this._loadState;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The isolated Pict instance for session management.
|
|
107
|
+
*/
|
|
108
|
+
get pict()
|
|
109
|
+
{
|
|
110
|
+
return this._Pict;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The IntegrationAdapter class.
|
|
115
|
+
*/
|
|
116
|
+
get IntegrationAdapter()
|
|
117
|
+
{
|
|
118
|
+
return libIntegrationAdapter;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ================================================================
|
|
122
|
+
// Comprehension Management
|
|
123
|
+
// ================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Load comprehension data into state. Extracts entity names and counts.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} pComprehensionData - The comprehension JSON object
|
|
129
|
+
* @return {object} Summary with entity list and record counts
|
|
130
|
+
*/
|
|
131
|
+
loadComprehension(pComprehensionData)
|
|
132
|
+
{
|
|
133
|
+
this._loadState.ComprehensionData = pComprehensionData;
|
|
134
|
+
this._loadState.ComprehensionEntityList = Object.keys(pComprehensionData);
|
|
135
|
+
this._loadState.ComprehensionRecordCounts = {};
|
|
136
|
+
this._loadState.ComprehensionTotalRecords = 0;
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < this._loadState.ComprehensionEntityList.length; i++)
|
|
139
|
+
{
|
|
140
|
+
let tmpEntityName = this._loadState.ComprehensionEntityList[i];
|
|
141
|
+
let tmpEntityData = pComprehensionData[tmpEntityName];
|
|
142
|
+
let tmpCount = 0;
|
|
143
|
+
|
|
144
|
+
if (Array.isArray(tmpEntityData))
|
|
145
|
+
{
|
|
146
|
+
tmpCount = tmpEntityData.length;
|
|
147
|
+
}
|
|
148
|
+
else if (typeof tmpEntityData === 'object' && tmpEntityData !== null)
|
|
149
|
+
{
|
|
150
|
+
tmpCount = Object.keys(tmpEntityData).length;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this._loadState.ComprehensionRecordCounts[tmpEntityName] = tmpCount;
|
|
154
|
+
this._loadState.ComprehensionTotalRecords += tmpCount;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
{
|
|
159
|
+
EntityList: this._loadState.ComprehensionEntityList,
|
|
160
|
+
RecordCounts: this._loadState.ComprehensionRecordCounts,
|
|
161
|
+
TotalRecords: this._loadState.ComprehensionTotalRecords
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clear loaded comprehension data.
|
|
167
|
+
*/
|
|
168
|
+
clearComprehension()
|
|
169
|
+
{
|
|
170
|
+
this._loadState.ComprehensionData = null;
|
|
171
|
+
this._loadState.ComprehensionEntityList = [];
|
|
172
|
+
this._loadState.ComprehensionRecordCounts = {};
|
|
173
|
+
this._loadState.ComprehensionTotalRecords = 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ================================================================
|
|
177
|
+
// Push Orchestration
|
|
178
|
+
// ================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Helper to extract capital letters from a string for GUID prefix.
|
|
182
|
+
*/
|
|
183
|
+
getCapitalLettersAsString(pInputString)
|
|
184
|
+
{
|
|
185
|
+
let tmpRegex = /[A-Z]/g;
|
|
186
|
+
let tmpMatch = pInputString.match(tmpRegex);
|
|
187
|
+
return tmpMatch ? tmpMatch.join('') : 'UNK';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Push comprehension entities to the remote server, one at a time.
|
|
192
|
+
*
|
|
193
|
+
* @param {Array<string>} pEntityList - Entity names to push
|
|
194
|
+
*/
|
|
195
|
+
pushEntities(pEntityList)
|
|
196
|
+
{
|
|
197
|
+
this._loadState.LoadRunning = true;
|
|
198
|
+
this._loadState.LoadStopping = false;
|
|
199
|
+
this._loadState.LoadStartTime = new Date().toJSON();
|
|
200
|
+
this._loadState.LoadEndTime = null;
|
|
201
|
+
this._loadState.LoadEventLog = [];
|
|
202
|
+
this._loadState.LoadReport = null;
|
|
203
|
+
|
|
204
|
+
// Initialize progress for each entity
|
|
205
|
+
for (let i = 0; i < pEntityList.length; i++)
|
|
206
|
+
{
|
|
207
|
+
let tmpEntityName = pEntityList[i];
|
|
208
|
+
this._loadState.LoadProgress[tmpEntityName] = (
|
|
209
|
+
{
|
|
210
|
+
Status: 'Pending',
|
|
211
|
+
Total: this._loadState.ComprehensionRecordCounts[tmpEntityName] || 0,
|
|
212
|
+
Pushed: 0,
|
|
213
|
+
Errors: 0,
|
|
214
|
+
ErrorMessage: null,
|
|
215
|
+
StartTime: null,
|
|
216
|
+
EndTime: null
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.logLoadEvent('RunStart', `Load started: ${pEntityList.length} entities, ${this._loadState.ComprehensionTotalRecords} total records`);
|
|
221
|
+
|
|
222
|
+
this.startThroughputSampling();
|
|
223
|
+
|
|
224
|
+
let tmpEntityIndex = 0;
|
|
225
|
+
|
|
226
|
+
let fPushNextEntity = () =>
|
|
227
|
+
{
|
|
228
|
+
if (this._loadState.LoadStopping || tmpEntityIndex >= pEntityList.length)
|
|
229
|
+
{
|
|
230
|
+
this.stopThroughputSampling();
|
|
231
|
+
this._loadState.LoadRunning = false;
|
|
232
|
+
this._loadState.LoadEndTime = new Date().toJSON();
|
|
233
|
+
|
|
234
|
+
if (this._loadState.LoadStopping)
|
|
235
|
+
{
|
|
236
|
+
this.logLoadEvent('RunStopped', 'Load was stopped by user request.');
|
|
237
|
+
}
|
|
238
|
+
else
|
|
239
|
+
{
|
|
240
|
+
this.logLoadEvent('RunComplete', 'Load finished.');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this._loadState.LoadStopping = false;
|
|
244
|
+
this.generateLoadReport();
|
|
245
|
+
this.fable.log.info('Comprehension Loader: Load complete.');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let tmpEntityName = pEntityList[tmpEntityIndex];
|
|
250
|
+
tmpEntityIndex++;
|
|
251
|
+
|
|
252
|
+
let tmpProgress = this._loadState.LoadProgress[tmpEntityName];
|
|
253
|
+
if (!tmpProgress)
|
|
254
|
+
{
|
|
255
|
+
fPushNextEntity();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
tmpProgress.Status = 'Pushing';
|
|
260
|
+
tmpProgress.StartTime = new Date().toJSON();
|
|
261
|
+
|
|
262
|
+
this.logLoadEvent('EntityStart', `Push [${tmpEntityName}] - starting.`, { Entity: tmpEntityName });
|
|
263
|
+
this.fable.log.info(`Comprehension Loader: Push [${tmpEntityName}] - starting via IntegrationAdapter...`);
|
|
264
|
+
|
|
265
|
+
let tmpEntityData = this._loadState.ComprehensionData[tmpEntityName];
|
|
266
|
+
if (!tmpEntityData)
|
|
267
|
+
{
|
|
268
|
+
tmpProgress.Status = 'Complete';
|
|
269
|
+
tmpProgress.EndTime = new Date().toJSON();
|
|
270
|
+
this.logLoadEvent('EntityComplete', `Push [${tmpEntityName}] - no data.`, { Entity: tmpEntityName });
|
|
271
|
+
fPushNextEntity();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Use the IntegrationAdapter to marshal and push records
|
|
276
|
+
let tmpAdapterPrefix = this.getCapitalLettersAsString(tmpEntityName);
|
|
277
|
+
libIntegrationAdapter.getAdapter(this._Pict, tmpEntityName, tmpAdapterPrefix, { SimpleMarshal: true, ForceMarshal: true });
|
|
278
|
+
|
|
279
|
+
let tmpAdapter = this._Pict.servicesMap.IntegrationAdapter[tmpEntityName];
|
|
280
|
+
if (!tmpAdapter)
|
|
281
|
+
{
|
|
282
|
+
tmpProgress.Status = 'Error';
|
|
283
|
+
tmpProgress.ErrorMessage = 'Failed to create IntegrationAdapter.';
|
|
284
|
+
tmpProgress.EndTime = new Date().toJSON();
|
|
285
|
+
this.logLoadEvent('EntityError', `Push [${tmpEntityName}] - adapter creation failed.`, { Entity: tmpEntityName });
|
|
286
|
+
fPushNextEntity();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
tmpAdapter.options.ServerURL = this._loadState.RemoteServerURL;
|
|
291
|
+
|
|
292
|
+
// Add source records
|
|
293
|
+
if (Array.isArray(tmpEntityData))
|
|
294
|
+
{
|
|
295
|
+
for (let r = 0; r < tmpEntityData.length; r++)
|
|
296
|
+
{
|
|
297
|
+
tmpAdapter.addSourceRecord(tmpEntityData[r]);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (typeof tmpEntityData === 'object')
|
|
301
|
+
{
|
|
302
|
+
let tmpKeys = Object.keys(tmpEntityData);
|
|
303
|
+
for (let r = 0; r < tmpKeys.length; r++)
|
|
304
|
+
{
|
|
305
|
+
tmpAdapter.addSourceRecord(tmpEntityData[tmpKeys[r]]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Integrate records (marshal + push to remote)
|
|
310
|
+
tmpAdapter.integrateRecords(
|
|
311
|
+
(pError) =>
|
|
312
|
+
{
|
|
313
|
+
if (pError)
|
|
314
|
+
{
|
|
315
|
+
this.fable.log.error(`Comprehension Loader: Error pushing [${tmpEntityName}]: ${pError}`);
|
|
316
|
+
tmpProgress.Status = 'Error';
|
|
317
|
+
tmpProgress.ErrorMessage = `${pError}`;
|
|
318
|
+
tmpProgress.Errors = 1;
|
|
319
|
+
this.logLoadEvent('EntityError', `Push [${tmpEntityName}] - error: ${pError}`, { Entity: tmpEntityName, Error: `${pError}` });
|
|
320
|
+
}
|
|
321
|
+
else
|
|
322
|
+
{
|
|
323
|
+
tmpProgress.Status = 'Complete';
|
|
324
|
+
tmpProgress.Pushed = tmpProgress.Total;
|
|
325
|
+
this.fable.log.info(`Comprehension Loader: Push [${tmpEntityName}] - complete. ${tmpProgress.Total} records.`);
|
|
326
|
+
this.logLoadEvent('EntityComplete', `Push [${tmpEntityName}] - complete. ${tmpProgress.Total} records.`,
|
|
327
|
+
{ Entity: tmpEntityName, Total: tmpProgress.Total });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
tmpProgress.EndTime = new Date().toJSON();
|
|
331
|
+
fPushNextEntity();
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
fPushNextEntity();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ================================================================
|
|
339
|
+
// Throughput Sampling
|
|
340
|
+
// ================================================================
|
|
341
|
+
|
|
342
|
+
startThroughputSampling()
|
|
343
|
+
{
|
|
344
|
+
this.stopThroughputSampling();
|
|
345
|
+
this._loadState.ThroughputSamples = [];
|
|
346
|
+
this._loadState.ThroughputSamples.push({ t: Date.now(), pushed: 0 });
|
|
347
|
+
|
|
348
|
+
this._loadState.ThroughputTimer = setInterval(
|
|
349
|
+
() =>
|
|
350
|
+
{
|
|
351
|
+
let tmpTotalPushed = 0;
|
|
352
|
+
let tmpEntityNames = Object.keys(this._loadState.LoadProgress);
|
|
353
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
354
|
+
{
|
|
355
|
+
tmpTotalPushed += (this._loadState.LoadProgress[tmpEntityNames[i]].Pushed || 0);
|
|
356
|
+
}
|
|
357
|
+
this._loadState.ThroughputSamples.push({ t: Date.now(), pushed: tmpTotalPushed });
|
|
358
|
+
}, 10000);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
stopThroughputSampling()
|
|
362
|
+
{
|
|
363
|
+
if (this._loadState.ThroughputTimer)
|
|
364
|
+
{
|
|
365
|
+
clearInterval(this._loadState.ThroughputTimer);
|
|
366
|
+
this._loadState.ThroughputTimer = null;
|
|
367
|
+
}
|
|
368
|
+
if (this._loadState.ThroughputSamples && this._loadState.ThroughputSamples.length > 0)
|
|
369
|
+
{
|
|
370
|
+
let tmpTotalPushed = 0;
|
|
371
|
+
let tmpEntityNames = Object.keys(this._loadState.LoadProgress);
|
|
372
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
373
|
+
{
|
|
374
|
+
tmpTotalPushed += (this._loadState.LoadProgress[tmpEntityNames[i]].Pushed || 0);
|
|
375
|
+
}
|
|
376
|
+
this._loadState.ThroughputSamples.push({ t: Date.now(), pushed: tmpTotalPushed });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ================================================================
|
|
381
|
+
// Event Logging & Report
|
|
382
|
+
// ================================================================
|
|
383
|
+
|
|
384
|
+
logLoadEvent(pType, pMessage, pData)
|
|
385
|
+
{
|
|
386
|
+
this._loadState.LoadEventLog.push(
|
|
387
|
+
{
|
|
388
|
+
Timestamp: new Date().toJSON(),
|
|
389
|
+
Type: pType,
|
|
390
|
+
Message: pMessage,
|
|
391
|
+
Data: pData || undefined
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
generateLoadReport()
|
|
396
|
+
{
|
|
397
|
+
let tmpState = this._loadState;
|
|
398
|
+
let tmpEntityNames = Object.keys(tmpState.LoadProgress);
|
|
399
|
+
|
|
400
|
+
let tmpEntities = [];
|
|
401
|
+
let tmpTotalRecords = 0;
|
|
402
|
+
let tmpTotalPushed = 0;
|
|
403
|
+
let tmpTotalErrors = 0;
|
|
404
|
+
let tmpComplete = 0;
|
|
405
|
+
let tmpErrors = 0;
|
|
406
|
+
let tmpPending = 0;
|
|
407
|
+
|
|
408
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
409
|
+
{
|
|
410
|
+
let tmpName = tmpEntityNames[i];
|
|
411
|
+
let tmpP = tmpState.LoadProgress[tmpName];
|
|
412
|
+
|
|
413
|
+
let tmpDuration = 0;
|
|
414
|
+
if (tmpP.StartTime && tmpP.EndTime)
|
|
415
|
+
{
|
|
416
|
+
tmpDuration = Math.round((new Date(tmpP.EndTime).getTime() - new Date(tmpP.StartTime).getTime()) / 1000);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
tmpEntities.push(
|
|
420
|
+
{
|
|
421
|
+
Name: tmpName,
|
|
422
|
+
Status: tmpP.Status,
|
|
423
|
+
Total: tmpP.Total || 0,
|
|
424
|
+
Pushed: tmpP.Pushed || 0,
|
|
425
|
+
Errors: tmpP.Errors || 0,
|
|
426
|
+
ErrorMessage: tmpP.ErrorMessage || null,
|
|
427
|
+
StartTime: tmpP.StartTime || null,
|
|
428
|
+
EndTime: tmpP.EndTime || null,
|
|
429
|
+
DurationSeconds: tmpDuration
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
tmpTotalRecords += (tmpP.Total || 0);
|
|
433
|
+
tmpTotalPushed += (tmpP.Pushed || 0);
|
|
434
|
+
tmpTotalErrors += (tmpP.Errors || 0);
|
|
435
|
+
|
|
436
|
+
if (tmpP.Status === 'Complete') tmpComplete++;
|
|
437
|
+
else if (tmpP.Status === 'Error') tmpErrors++;
|
|
438
|
+
else tmpPending++;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
tmpEntities.sort((a, b) => b.DurationSeconds - a.DurationSeconds);
|
|
442
|
+
|
|
443
|
+
let tmpOutcome = 'Success';
|
|
444
|
+
if (tmpState.LoadStopping || (!tmpState.LoadRunning && tmpPending > 0))
|
|
445
|
+
{
|
|
446
|
+
tmpOutcome = 'Stopped';
|
|
447
|
+
}
|
|
448
|
+
else if (tmpErrors > 0)
|
|
449
|
+
{
|
|
450
|
+
tmpOutcome = 'Error';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let tmpAnomalies = [];
|
|
454
|
+
for (let i = 0; i < tmpEntities.length; i++)
|
|
455
|
+
{
|
|
456
|
+
let tmpE = tmpEntities[i];
|
|
457
|
+
if (tmpE.Status === 'Error')
|
|
458
|
+
{
|
|
459
|
+
tmpAnomalies.push(
|
|
460
|
+
{
|
|
461
|
+
Entity: tmpE.Name,
|
|
462
|
+
Type: 'Error',
|
|
463
|
+
Message: tmpE.ErrorMessage || 'Unknown error'
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else if (tmpE.Status === 'Pending')
|
|
467
|
+
{
|
|
468
|
+
tmpAnomalies.push(
|
|
469
|
+
{
|
|
470
|
+
Entity: tmpE.Name,
|
|
471
|
+
Type: 'Skipped',
|
|
472
|
+
Message: 'Load was stopped before this entity was processed'
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
let tmpRunDuration = 0;
|
|
478
|
+
if (tmpState.LoadStartTime && tmpState.LoadEndTime)
|
|
479
|
+
{
|
|
480
|
+
tmpRunDuration = Math.round((new Date(tmpState.LoadEndTime).getTime() - new Date(tmpState.LoadStartTime).getTime()) / 1000);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let tmpReport = (
|
|
484
|
+
{
|
|
485
|
+
ReportVersion: '1.0.0',
|
|
486
|
+
Outcome: tmpOutcome,
|
|
487
|
+
RunTimestamps:
|
|
488
|
+
{
|
|
489
|
+
Start: tmpState.LoadStartTime,
|
|
490
|
+
End: tmpState.LoadEndTime,
|
|
491
|
+
DurationSeconds: tmpRunDuration
|
|
492
|
+
},
|
|
493
|
+
Summary:
|
|
494
|
+
{
|
|
495
|
+
TotalEntities: tmpEntityNames.length,
|
|
496
|
+
Complete: tmpComplete,
|
|
497
|
+
Errors: tmpErrors,
|
|
498
|
+
Pending: tmpPending,
|
|
499
|
+
TotalRecords: tmpTotalRecords,
|
|
500
|
+
TotalPushed: tmpTotalPushed,
|
|
501
|
+
TotalErrors: tmpTotalErrors
|
|
502
|
+
},
|
|
503
|
+
Entities: tmpEntities,
|
|
504
|
+
Anomalies: tmpAnomalies,
|
|
505
|
+
EventLog: tmpState.LoadEventLog,
|
|
506
|
+
ThroughputSamples: tmpState.ThroughputSamples || []
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
tmpState.LoadReport = tmpReport;
|
|
510
|
+
return tmpReport;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ================================================================
|
|
514
|
+
// Route Registration
|
|
515
|
+
// ================================================================
|
|
516
|
+
|
|
517
|
+
connectRoutes(pOratorServiceServer)
|
|
518
|
+
{
|
|
519
|
+
require('./ComprehensionLoader-Command-Session.js')(this, pOratorServiceServer);
|
|
520
|
+
require('./ComprehensionLoader-Command-Schema.js')(this, pOratorServiceServer);
|
|
521
|
+
require('./ComprehensionLoader-Command-Load.js')(this, pOratorServiceServer);
|
|
522
|
+
|
|
523
|
+
this.fable.log.info('Retold Data Service ComprehensionLoader API routes registered.');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
connectWebUIRoutes(pOratorServiceServer)
|
|
527
|
+
{
|
|
528
|
+
require('./ComprehensionLoader-Command-WebUI.js')(this, pOratorServiceServer);
|
|
529
|
+
|
|
530
|
+
this.fable.log.info('Retold Data Service ComprehensionLoader Web UI routes registered.');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
module.exports = RetoldDataServiceComprehensionLoader;
|
|
535
|
+
module.exports.serviceType = 'RetoldDataServiceComprehensionLoader';
|
|
536
|
+
module.exports.default_configuration = defaultComprehensionLoaderOptions;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Name": "Retold Comprehension Loader",
|
|
3
|
+
"Hash": "ComprehensionLoader",
|
|
4
|
+
"MainViewportViewIdentifier": "ComprehensionLoader-Layout",
|
|
5
|
+
"MainViewportDestinationAddress": "#ComprehensionLoader-Application-Container",
|
|
6
|
+
"MainViewportDefaultDataAddress": "AppData.ComprehensionLoader",
|
|
7
|
+
"pict_configuration": { "Product": "ComprehensionLoader" },
|
|
8
|
+
"AutoRenderMainViewportViewAfterInitialize": false
|
|
9
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const libPictApplication = require('pict-application');
|
|
2
|
+
|
|
3
|
+
const libProvider = require('./providers/Pict-Provider-ComprehensionLoader.js');
|
|
4
|
+
|
|
5
|
+
const libViewLayout = require('./views/PictView-ComprehensionLoader-Layout.js');
|
|
6
|
+
const libViewSession = require('./views/PictView-ComprehensionLoader-Session.js');
|
|
7
|
+
const libViewSchema = require('./views/PictView-ComprehensionLoader-Schema.js');
|
|
8
|
+
const libViewSource = require('./views/PictView-ComprehensionLoader-Source.js');
|
|
9
|
+
const libViewLoad = require('./views/PictView-ComprehensionLoader-Load.js');
|
|
10
|
+
const libViewHistogram = require('pict-section-histogram');
|
|
11
|
+
|
|
12
|
+
class ComprehensionLoaderApplication extends libPictApplication
|
|
13
|
+
{
|
|
14
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
15
|
+
{
|
|
16
|
+
super(pFable, pOptions, pServiceHash);
|
|
17
|
+
|
|
18
|
+
// Register provider
|
|
19
|
+
this.pict.addProvider('ComprehensionLoader', libProvider.default_configuration, libProvider);
|
|
20
|
+
|
|
21
|
+
// Register views
|
|
22
|
+
this.pict.addView('ComprehensionLoader-Layout', libViewLayout.default_configuration, libViewLayout);
|
|
23
|
+
this.pict.addView('ComprehensionLoader-Session', libViewSession.default_configuration, libViewSession);
|
|
24
|
+
this.pict.addView('ComprehensionLoader-Schema', libViewSchema.default_configuration, libViewSchema);
|
|
25
|
+
this.pict.addView('ComprehensionLoader-Source', libViewSource.default_configuration, libViewSource);
|
|
26
|
+
this.pict.addView('ComprehensionLoader-Load', libViewLoad.default_configuration, libViewLoad);
|
|
27
|
+
this.pict.addView('ComprehensionLoader-StatusHistogram',
|
|
28
|
+
{
|
|
29
|
+
ViewIdentifier: 'ComprehensionLoader-StatusHistogram',
|
|
30
|
+
TargetElementAddress: '#ComprehensionLoader-Throughput-Histogram',
|
|
31
|
+
DefaultDestinationAddress: '#ComprehensionLoader-Throughput-Histogram',
|
|
32
|
+
RenderOnLoad: false,
|
|
33
|
+
Selectable: false,
|
|
34
|
+
Orientation: 'vertical',
|
|
35
|
+
FillContainer: true,
|
|
36
|
+
ShowValues: false,
|
|
37
|
+
ShowLabels: true,
|
|
38
|
+
MaxBarSize: 80,
|
|
39
|
+
BarColor: '#4a90d9',
|
|
40
|
+
Bins: []
|
|
41
|
+
}, libViewHistogram);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onAfterInitializeAsync(fCallback)
|
|
45
|
+
{
|
|
46
|
+
// Centralized state
|
|
47
|
+
this.pict.AppData.ComprehensionLoader =
|
|
48
|
+
{
|
|
49
|
+
FetchedEntities: [],
|
|
50
|
+
LastReport: null,
|
|
51
|
+
ServerBusyAtLoad: false,
|
|
52
|
+
LoadPollTimer: null,
|
|
53
|
+
LiveStatusTimer: null,
|
|
54
|
+
StatusDetailExpanded: false,
|
|
55
|
+
StatusDetailTimer: null,
|
|
56
|
+
StatusDetailData: null,
|
|
57
|
+
LastLiveStatus: null,
|
|
58
|
+
PersistFields: [
|
|
59
|
+
'serverURL', 'authMethod', 'authURI', 'checkURI',
|
|
60
|
+
'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker',
|
|
61
|
+
'userName', 'password', 'schemaURL',
|
|
62
|
+
'comprehensionSourceMode', 'comprehensionURL'
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Make pict available for inline onclick handlers
|
|
67
|
+
window.pict = this.pict;
|
|
68
|
+
|
|
69
|
+
// Render layout (which chains child view renders via onAfterRender)
|
|
70
|
+
this.pict.views['ComprehensionLoader-Layout'].render();
|
|
71
|
+
|
|
72
|
+
// Post-render initialization
|
|
73
|
+
this.pict.providers.ComprehensionLoader.initPersistence();
|
|
74
|
+
this.pict.providers.ComprehensionLoader.startLiveStatusPolling();
|
|
75
|
+
this.pict.providers.ComprehensionLoader.initAccordionPreviews();
|
|
76
|
+
this.pict.providers.ComprehensionLoader.updateAllPreviews();
|
|
77
|
+
this.pict.views['ComprehensionLoader-Layout'].collapseAllSections();
|
|
78
|
+
this.pict.providers.ComprehensionLoader.initAutoProcess();
|
|
79
|
+
|
|
80
|
+
return fCallback();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = ComprehensionLoaderApplication;
|
|
85
|
+
|
|
86
|
+
module.exports.default_configuration = require('./Pict-Application-ComprehensionLoader-Configuration.json');
|