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
package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js
ADDED
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
const libPictProvider = require('pict-provider');
|
|
2
|
+
|
|
3
|
+
class ComprehensionLoaderProvider extends libPictProvider
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// ================================================================
|
|
11
|
+
// API Helper
|
|
12
|
+
// ================================================================
|
|
13
|
+
|
|
14
|
+
api(pMethod, pPath, pBody)
|
|
15
|
+
{
|
|
16
|
+
let tmpOpts = { method: pMethod, headers: {} };
|
|
17
|
+
if (pBody)
|
|
18
|
+
{
|
|
19
|
+
tmpOpts.headers['Content-Type'] = 'application/json';
|
|
20
|
+
tmpOpts.body = JSON.stringify(pBody);
|
|
21
|
+
}
|
|
22
|
+
return fetch(pPath, tmpOpts).then(function(pResponse) { return pResponse.json(); });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setStatus(pElementId, pMessage, pType)
|
|
26
|
+
{
|
|
27
|
+
let tmpEl = document.getElementById(pElementId);
|
|
28
|
+
if (!tmpEl) return;
|
|
29
|
+
tmpEl.className = 'status ' + (pType || 'info');
|
|
30
|
+
tmpEl.textContent = pMessage;
|
|
31
|
+
tmpEl.style.display = 'block';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
escapeHtml(pStr)
|
|
35
|
+
{
|
|
36
|
+
let tmpDiv = document.createElement('div');
|
|
37
|
+
tmpDiv.appendChild(document.createTextNode(pStr));
|
|
38
|
+
return tmpDiv.innerHTML;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ================================================================
|
|
42
|
+
// Phase status indicators
|
|
43
|
+
// ================================================================
|
|
44
|
+
|
|
45
|
+
setSectionPhase(pSection, pState)
|
|
46
|
+
{
|
|
47
|
+
let tmpEl = document.getElementById('phase' + pSection);
|
|
48
|
+
if (!tmpEl) return;
|
|
49
|
+
|
|
50
|
+
tmpEl.className = 'accordion-phase';
|
|
51
|
+
|
|
52
|
+
if (pState === 'ok')
|
|
53
|
+
{
|
|
54
|
+
tmpEl.innerHTML = '✓';
|
|
55
|
+
tmpEl.classList.add('visible', 'accordion-phase-ok');
|
|
56
|
+
}
|
|
57
|
+
else if (pState === 'error')
|
|
58
|
+
{
|
|
59
|
+
tmpEl.innerHTML = '✗';
|
|
60
|
+
tmpEl.classList.add('visible', 'accordion-phase-error');
|
|
61
|
+
}
|
|
62
|
+
else if (pState === 'busy')
|
|
63
|
+
{
|
|
64
|
+
tmpEl.innerHTML = '<span class="phase-spinner"></span>';
|
|
65
|
+
tmpEl.classList.add('visible', 'accordion-phase-busy');
|
|
66
|
+
}
|
|
67
|
+
else
|
|
68
|
+
{
|
|
69
|
+
tmpEl.innerHTML = '';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ================================================================
|
|
74
|
+
// Accordion Previews
|
|
75
|
+
// ================================================================
|
|
76
|
+
|
|
77
|
+
updateAllPreviews()
|
|
78
|
+
{
|
|
79
|
+
// Section 1 — Remote Session
|
|
80
|
+
let tmpServerURL = document.getElementById('serverURL');
|
|
81
|
+
if (!tmpServerURL) return;
|
|
82
|
+
tmpServerURL = tmpServerURL.value;
|
|
83
|
+
let tmpUserName = document.getElementById('userName').value;
|
|
84
|
+
if (tmpServerURL)
|
|
85
|
+
{
|
|
86
|
+
let tmpPreview1 = tmpServerURL;
|
|
87
|
+
if (tmpUserName) tmpPreview1 += ' as ' + tmpUserName;
|
|
88
|
+
document.getElementById('preview1').textContent = tmpPreview1;
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
document.getElementById('preview1').textContent = 'Configure remote server URL and credentials';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Section 2 — Remote Schema
|
|
96
|
+
let tmpEntities = this.pict.AppData.ComprehensionLoader.FetchedEntities || [];
|
|
97
|
+
if (tmpEntities.length > 0)
|
|
98
|
+
{
|
|
99
|
+
document.getElementById('preview2').textContent = tmpEntities.length + ' entit' + (tmpEntities.length === 1 ? 'y' : 'ies') + ' discovered';
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
{
|
|
103
|
+
let tmpSchemaURL = document.getElementById('schemaURL').value;
|
|
104
|
+
if (tmpSchemaURL)
|
|
105
|
+
{
|
|
106
|
+
document.getElementById('preview2').textContent = 'Schema from ' + tmpSchemaURL;
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
{
|
|
110
|
+
document.getElementById('preview2').textContent = 'Fetch entity schema from the remote server';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Section 3 — Comprehension Source
|
|
115
|
+
let tmpSourceMode = document.querySelector('input[name="comprehensionSourceMode"]:checked');
|
|
116
|
+
let tmpModeName = tmpSourceMode ? tmpSourceMode.value : 'url';
|
|
117
|
+
if (tmpModeName === 'url')
|
|
118
|
+
{
|
|
119
|
+
let tmpURL = document.getElementById('comprehensionURL').value;
|
|
120
|
+
if (tmpURL)
|
|
121
|
+
{
|
|
122
|
+
document.getElementById('preview3').textContent = 'URL: ' + tmpURL;
|
|
123
|
+
}
|
|
124
|
+
else
|
|
125
|
+
{
|
|
126
|
+
document.getElementById('preview3').textContent = 'Provide a comprehension JSON URL or upload files';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
{
|
|
131
|
+
document.getElementById('preview3').textContent = 'File upload mode';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Section 4 — Load
|
|
135
|
+
let tmpPreview4El = document.getElementById('preview4');
|
|
136
|
+
if (tmpPreview4El)
|
|
137
|
+
{
|
|
138
|
+
tmpPreview4El.textContent = 'Push comprehension data to the remote server';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
initAccordionPreviews()
|
|
143
|
+
{
|
|
144
|
+
let tmpSelf = this;
|
|
145
|
+
|
|
146
|
+
let tmpPreviewFields = [
|
|
147
|
+
'serverURL', 'userName',
|
|
148
|
+
'schemaURL',
|
|
149
|
+
'comprehensionURL'
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
let tmpHandler = function() { tmpSelf.updateAllPreviews(); };
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < tmpPreviewFields.length; i++)
|
|
155
|
+
{
|
|
156
|
+
let tmpEl = document.getElementById(tmpPreviewFields[i]);
|
|
157
|
+
if (tmpEl)
|
|
158
|
+
{
|
|
159
|
+
tmpEl.addEventListener('input', tmpHandler);
|
|
160
|
+
tmpEl.addEventListener('change', tmpHandler);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
document.querySelectorAll('input[name="comprehensionSourceMode"]').forEach(function(pEl)
|
|
165
|
+
{
|
|
166
|
+
pEl.addEventListener('change', tmpHandler);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ================================================================
|
|
171
|
+
// LocalStorage Persistence
|
|
172
|
+
// ================================================================
|
|
173
|
+
|
|
174
|
+
saveField(pFieldId)
|
|
175
|
+
{
|
|
176
|
+
let tmpEl = document.getElementById(pFieldId);
|
|
177
|
+
if (tmpEl)
|
|
178
|
+
{
|
|
179
|
+
localStorage.setItem('comprehensionLoader_' + pFieldId, tmpEl.value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
restoreFields()
|
|
184
|
+
{
|
|
185
|
+
let tmpPersistFields = this.pict.AppData.ComprehensionLoader.PersistFields;
|
|
186
|
+
for (let i = 0; i < tmpPersistFields.length; i++)
|
|
187
|
+
{
|
|
188
|
+
let tmpId = tmpPersistFields[i];
|
|
189
|
+
let tmpSaved = localStorage.getItem('comprehensionLoader_' + tmpId);
|
|
190
|
+
if (tmpSaved !== null)
|
|
191
|
+
{
|
|
192
|
+
let tmpEl = document.getElementById(tmpId);
|
|
193
|
+
if (tmpEl) tmpEl.value = tmpSaved;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Restore source mode radio
|
|
198
|
+
let tmpSourceMode = localStorage.getItem('comprehensionLoader_comprehensionSourceMode');
|
|
199
|
+
if (tmpSourceMode === 'file')
|
|
200
|
+
{
|
|
201
|
+
let tmpFileRadio = document.getElementById('sourceMode_file');
|
|
202
|
+
if (tmpFileRadio) tmpFileRadio.checked = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
initPersistence()
|
|
207
|
+
{
|
|
208
|
+
let tmpSelf = this;
|
|
209
|
+
this.restoreFields();
|
|
210
|
+
|
|
211
|
+
let tmpPersistFields = this.pict.AppData.ComprehensionLoader.PersistFields;
|
|
212
|
+
for (let i = 0; i < tmpPersistFields.length; i++)
|
|
213
|
+
{
|
|
214
|
+
(function(pId)
|
|
215
|
+
{
|
|
216
|
+
let tmpEl = document.getElementById(pId);
|
|
217
|
+
if (tmpEl)
|
|
218
|
+
{
|
|
219
|
+
tmpEl.addEventListener('input', function() { tmpSelf.saveField(pId); });
|
|
220
|
+
tmpEl.addEventListener('change', function() { tmpSelf.saveField(pId); });
|
|
221
|
+
}
|
|
222
|
+
})(tmpPersistFields[i]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Persist source mode radio
|
|
226
|
+
document.querySelectorAll('input[name="comprehensionSourceMode"]').forEach(function(pEl)
|
|
227
|
+
{
|
|
228
|
+
pEl.addEventListener('change', function()
|
|
229
|
+
{
|
|
230
|
+
localStorage.setItem('comprehensionLoader_comprehensionSourceMode', this.value);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Persist auto-process checkboxes
|
|
235
|
+
let tmpAutoIds = ['auto1', 'auto2', 'auto3', 'auto4'];
|
|
236
|
+
for (let a = 0; a < tmpAutoIds.length; a++)
|
|
237
|
+
{
|
|
238
|
+
(function(pId)
|
|
239
|
+
{
|
|
240
|
+
let tmpEl = document.getElementById(pId);
|
|
241
|
+
if (tmpEl)
|
|
242
|
+
{
|
|
243
|
+
let tmpSaved = localStorage.getItem('comprehensionLoader_' + pId);
|
|
244
|
+
if (tmpSaved !== null) tmpEl.checked = tmpSaved === 'true';
|
|
245
|
+
tmpEl.addEventListener('change', function()
|
|
246
|
+
{
|
|
247
|
+
localStorage.setItem('comprehensionLoader_' + pId, this.checked);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
})(tmpAutoIds[a]);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ================================================================
|
|
255
|
+
// Live Status Indicator
|
|
256
|
+
// ================================================================
|
|
257
|
+
|
|
258
|
+
startLiveStatusPolling()
|
|
259
|
+
{
|
|
260
|
+
let tmpAppData = this.pict.AppData.ComprehensionLoader;
|
|
261
|
+
if (tmpAppData.LiveStatusTimer) clearInterval(tmpAppData.LiveStatusTimer);
|
|
262
|
+
this.pollLiveStatus();
|
|
263
|
+
let tmpSelf = this;
|
|
264
|
+
tmpAppData.LiveStatusTimer = setInterval(function() { tmpSelf.pollLiveStatus(); }, 1500);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
pollLiveStatus()
|
|
268
|
+
{
|
|
269
|
+
let tmpSelf = this;
|
|
270
|
+
this.api('GET', '/comprehension_load/load/live-status')
|
|
271
|
+
.then(function(pData)
|
|
272
|
+
{
|
|
273
|
+
tmpSelf.renderLiveStatus(pData);
|
|
274
|
+
})
|
|
275
|
+
.catch(function()
|
|
276
|
+
{
|
|
277
|
+
tmpSelf.renderLiveStatus({ Phase: 'disconnected', Message: 'Cannot reach server', TotalPushed: 0, TotalRecords: 0 });
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
renderLiveStatus(pData)
|
|
282
|
+
{
|
|
283
|
+
// Cache the live status data for the detail view
|
|
284
|
+
this.pict.AppData.ComprehensionLoader.LastLiveStatus = pData;
|
|
285
|
+
|
|
286
|
+
let tmpBar = document.getElementById('liveStatusBar');
|
|
287
|
+
let tmpMsg = document.getElementById('liveStatusMessage');
|
|
288
|
+
let tmpMeta = document.getElementById('liveStatusMeta');
|
|
289
|
+
let tmpProgressFill = document.getElementById('liveStatusProgressFill');
|
|
290
|
+
if (!tmpBar) return;
|
|
291
|
+
|
|
292
|
+
// Update phase class (preserve expanded class if present)
|
|
293
|
+
let tmpWasExpanded = tmpBar.classList.contains('expanded');
|
|
294
|
+
tmpBar.className = 'live-status-bar phase-' + (pData.Phase || 'idle');
|
|
295
|
+
if (tmpWasExpanded) tmpBar.classList.add('expanded');
|
|
296
|
+
|
|
297
|
+
// Update message
|
|
298
|
+
tmpMsg.textContent = pData.Message || 'Idle';
|
|
299
|
+
|
|
300
|
+
// Update meta info
|
|
301
|
+
let tmpMetaParts = [];
|
|
302
|
+
if (pData.Phase === 'loading' || pData.Phase === 'stopping')
|
|
303
|
+
{
|
|
304
|
+
if (pData.Elapsed)
|
|
305
|
+
{
|
|
306
|
+
tmpMetaParts.push('<span class="live-status-meta-item">\u23F1 ' + pData.Elapsed + '</span>');
|
|
307
|
+
}
|
|
308
|
+
if (pData.ETA)
|
|
309
|
+
{
|
|
310
|
+
tmpMetaParts.push('<span class="live-status-meta-item">~' + pData.ETA + ' remaining</span>');
|
|
311
|
+
}
|
|
312
|
+
if (pData.TotalEntities > 0)
|
|
313
|
+
{
|
|
314
|
+
tmpMetaParts.push('<span class="live-status-meta-item"><strong>' + pData.Completed + '</strong> / ' + pData.TotalEntities + ' entities</span>');
|
|
315
|
+
}
|
|
316
|
+
if (pData.TotalPushed > 0)
|
|
317
|
+
{
|
|
318
|
+
let tmpPushed = pData.TotalPushed.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
319
|
+
if (pData.TotalRecords > 0)
|
|
320
|
+
{
|
|
321
|
+
let tmpTotal = pData.TotalRecords.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
322
|
+
tmpMetaParts.push('<span class="live-status-meta-item"><strong>' + tmpPushed + '</strong> / ' + tmpTotal + ' records</span>');
|
|
323
|
+
}
|
|
324
|
+
else
|
|
325
|
+
{
|
|
326
|
+
tmpMetaParts.push('<span class="live-status-meta-item"><strong>' + tmpPushed + '</strong> records</span>');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (pData.Errors > 0)
|
|
330
|
+
{
|
|
331
|
+
tmpMetaParts.push('<span class="live-status-meta-item" style="color:#dc3545"><strong>' + pData.Errors + '</strong> error' + (pData.Errors === 1 ? '' : 's') + '</span>');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else if (pData.Phase === 'complete')
|
|
335
|
+
{
|
|
336
|
+
if (pData.TotalPushed > 0)
|
|
337
|
+
{
|
|
338
|
+
let tmpPushed = pData.TotalPushed.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
339
|
+
tmpMetaParts.push('<span class="live-status-meta-item"><strong>' + tmpPushed + '</strong> records pushed</span>');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
tmpMeta.innerHTML = tmpMetaParts.join('');
|
|
343
|
+
|
|
344
|
+
// Update progress bar
|
|
345
|
+
let tmpPct = 0;
|
|
346
|
+
if (pData.Phase === 'loading' && pData.TotalRecords > 0 && pData.TotalPushed > 0)
|
|
347
|
+
{
|
|
348
|
+
tmpPct = Math.min((pData.TotalPushed / pData.TotalRecords) * 100, 99.9);
|
|
349
|
+
}
|
|
350
|
+
else if (pData.Phase === 'loading' && pData.TotalEntities > 0)
|
|
351
|
+
{
|
|
352
|
+
tmpPct = (pData.Completed / pData.TotalEntities) * 100;
|
|
353
|
+
}
|
|
354
|
+
else if (pData.Phase === 'complete')
|
|
355
|
+
{
|
|
356
|
+
tmpPct = 100;
|
|
357
|
+
}
|
|
358
|
+
tmpProgressFill.style.width = Math.min(100, Math.round(tmpPct)) + '%';
|
|
359
|
+
|
|
360
|
+
// Auto-expand the detail view when load starts
|
|
361
|
+
if ((pData.Phase === 'loading' || pData.Phase === 'stopping') && !this.pict.AppData.ComprehensionLoader.StatusDetailExpanded)
|
|
362
|
+
{
|
|
363
|
+
let tmpLayoutView = this.pict.views['ComprehensionLoader-Layout'];
|
|
364
|
+
if (tmpLayoutView && typeof tmpLayoutView.toggleStatusDetail === 'function')
|
|
365
|
+
{
|
|
366
|
+
tmpLayoutView.toggleStatusDetail();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// If the detail view is expanded, re-render it with fresh data
|
|
371
|
+
if (this.pict.AppData.ComprehensionLoader.StatusDetailExpanded)
|
|
372
|
+
{
|
|
373
|
+
this.renderStatusDetail();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Auto-fetch the load report when we detect a completed load but haven't loaded the report yet
|
|
377
|
+
if (pData.Phase === 'complete' && !this.pict.AppData.ComprehensionLoader.LastReport)
|
|
378
|
+
{
|
|
379
|
+
let tmpSelf = this;
|
|
380
|
+
this.api('GET', '/comprehension_load/load/report')
|
|
381
|
+
.then(function(pReportData)
|
|
382
|
+
{
|
|
383
|
+
if (pReportData && pReportData.ReportVersion)
|
|
384
|
+
{
|
|
385
|
+
tmpSelf.pict.AppData.ComprehensionLoader.LastReport = pReportData;
|
|
386
|
+
if (tmpSelf.pict.AppData.ComprehensionLoader.StatusDetailExpanded)
|
|
387
|
+
{
|
|
388
|
+
tmpSelf.renderStatusDetail();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
.catch(function() { /* ignore fetch errors */ });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ================================================================
|
|
397
|
+
// Status Detail Expansion
|
|
398
|
+
// ================================================================
|
|
399
|
+
|
|
400
|
+
onStatusDetailExpanded()
|
|
401
|
+
{
|
|
402
|
+
let tmpAppData = this.pict.AppData.ComprehensionLoader;
|
|
403
|
+
tmpAppData.StatusDetailExpanded = true;
|
|
404
|
+
|
|
405
|
+
// Immediate render from whatever data we have
|
|
406
|
+
this.renderStatusDetail();
|
|
407
|
+
|
|
408
|
+
// Start detail polling (poll /load/status for per-entity data)
|
|
409
|
+
if (tmpAppData.StatusDetailTimer) clearInterval(tmpAppData.StatusDetailTimer);
|
|
410
|
+
let tmpSelf = this;
|
|
411
|
+
tmpAppData.StatusDetailTimer = setInterval(function() { tmpSelf.pollStatusDetail(); }, 2000);
|
|
412
|
+
this.pollStatusDetail();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
onStatusDetailCollapsed()
|
|
416
|
+
{
|
|
417
|
+
let tmpAppData = this.pict.AppData.ComprehensionLoader;
|
|
418
|
+
tmpAppData.StatusDetailExpanded = false;
|
|
419
|
+
|
|
420
|
+
if (tmpAppData.StatusDetailTimer)
|
|
421
|
+
{
|
|
422
|
+
clearInterval(tmpAppData.StatusDetailTimer);
|
|
423
|
+
tmpAppData.StatusDetailTimer = null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
pollStatusDetail()
|
|
428
|
+
{
|
|
429
|
+
let tmpSelf = this;
|
|
430
|
+
this.api('GET', '/comprehension_load/load/status')
|
|
431
|
+
.then(function(pData)
|
|
432
|
+
{
|
|
433
|
+
tmpSelf.pict.AppData.ComprehensionLoader.StatusDetailData = pData;
|
|
434
|
+
tmpSelf.renderStatusDetail();
|
|
435
|
+
})
|
|
436
|
+
.catch(function() { /* ignore poll errors */ });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
renderStatusDetail()
|
|
440
|
+
{
|
|
441
|
+
let tmpContainer = document.getElementById('ComprehensionLoader-StatusDetail-Container');
|
|
442
|
+
if (!tmpContainer) return;
|
|
443
|
+
|
|
444
|
+
let tmpAppData = this.pict.AppData.ComprehensionLoader;
|
|
445
|
+
let tmpLiveStatus = tmpAppData.LastLiveStatus;
|
|
446
|
+
let tmpStatusData = tmpAppData.StatusDetailData;
|
|
447
|
+
let tmpReport = tmpAppData.LastReport;
|
|
448
|
+
|
|
449
|
+
// Determine data source: live during load, report after load
|
|
450
|
+
let tmpEntities = {};
|
|
451
|
+
let tmpThroughputSamples = [];
|
|
452
|
+
let tmpIsLive = false;
|
|
453
|
+
|
|
454
|
+
if (tmpLiveStatus && (tmpLiveStatus.Phase === 'loading' || tmpLiveStatus.Phase === 'stopping'))
|
|
455
|
+
{
|
|
456
|
+
tmpIsLive = true;
|
|
457
|
+
if (tmpStatusData && tmpStatusData.Entities) tmpEntities = tmpStatusData.Entities;
|
|
458
|
+
if (tmpLiveStatus.ThroughputSamples) tmpThroughputSamples = tmpLiveStatus.ThroughputSamples;
|
|
459
|
+
}
|
|
460
|
+
else if (tmpReport && tmpReport.ReportVersion)
|
|
461
|
+
{
|
|
462
|
+
// Build entities object from report
|
|
463
|
+
for (let i = 0; i < tmpReport.Entities.length; i++)
|
|
464
|
+
{
|
|
465
|
+
let tmpE = tmpReport.Entities[i];
|
|
466
|
+
tmpEntities[tmpE.Name] = tmpE;
|
|
467
|
+
}
|
|
468
|
+
tmpThroughputSamples = tmpReport.ThroughputSamples || [];
|
|
469
|
+
}
|
|
470
|
+
else if (tmpStatusData && tmpStatusData.Entities)
|
|
471
|
+
{
|
|
472
|
+
tmpEntities = tmpStatusData.Entities;
|
|
473
|
+
if (tmpLiveStatus && tmpLiveStatus.ThroughputSamples)
|
|
474
|
+
{
|
|
475
|
+
tmpThroughputSamples = tmpLiveStatus.ThroughputSamples;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Categorize entities
|
|
480
|
+
let tmpRunning = [];
|
|
481
|
+
let tmpPending = [];
|
|
482
|
+
let tmpCompleted = [];
|
|
483
|
+
let tmpErrors = [];
|
|
484
|
+
let tmpEntityNames = Object.keys(tmpEntities);
|
|
485
|
+
|
|
486
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
487
|
+
{
|
|
488
|
+
let tmpName = tmpEntityNames[i];
|
|
489
|
+
let tmpE = tmpEntities[tmpName];
|
|
490
|
+
if (tmpE.Status === 'Pushing')
|
|
491
|
+
{
|
|
492
|
+
tmpRunning.push({ Name: tmpName, Data: tmpE });
|
|
493
|
+
}
|
|
494
|
+
else if (tmpE.Status === 'Pending')
|
|
495
|
+
{
|
|
496
|
+
tmpPending.push(tmpName);
|
|
497
|
+
}
|
|
498
|
+
else if (tmpE.Status === 'Complete')
|
|
499
|
+
{
|
|
500
|
+
tmpCompleted.push({ Name: tmpName, Data: tmpE });
|
|
501
|
+
}
|
|
502
|
+
else if (tmpE.Status === 'Error')
|
|
503
|
+
{
|
|
504
|
+
tmpErrors.push({ Name: tmpName, Data: tmpE });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let tmpHtml = '';
|
|
509
|
+
|
|
510
|
+
// === Section 1: Running Operations ===
|
|
511
|
+
if (tmpRunning.length > 0 || tmpPending.length > 0)
|
|
512
|
+
{
|
|
513
|
+
tmpHtml += '<div class="status-detail-section">';
|
|
514
|
+
tmpHtml += '<div class="status-detail-section-title">Running</div>';
|
|
515
|
+
for (let i = 0; i < tmpRunning.length; i++)
|
|
516
|
+
{
|
|
517
|
+
let tmpOp = tmpRunning[i];
|
|
518
|
+
let tmpPct = tmpOp.Data.Total > 0 ? Math.round((tmpOp.Data.Pushed / tmpOp.Data.Total) * 100) : 0;
|
|
519
|
+
let tmpPushedFmt = this.formatNumber(tmpOp.Data.Pushed || 0);
|
|
520
|
+
let tmpTotalFmt = this.formatNumber(tmpOp.Data.Total || 0);
|
|
521
|
+
tmpHtml += '<div class="running-op-row">';
|
|
522
|
+
tmpHtml += ' <div class="running-op-name">' + this.escapeHtml(tmpOp.Name) + '</div>';
|
|
523
|
+
tmpHtml += ' <div class="running-op-bar"><div class="running-op-bar-fill" style="width:' + tmpPct + '%"></div></div>';
|
|
524
|
+
tmpHtml += ' <div class="running-op-count">' + tmpPushedFmt + ' / ' + tmpTotalFmt + ' (' + tmpPct + '%)</div>';
|
|
525
|
+
tmpHtml += '</div>';
|
|
526
|
+
}
|
|
527
|
+
if (tmpPending.length > 0)
|
|
528
|
+
{
|
|
529
|
+
tmpHtml += '<div class="running-op-pending">' + tmpPending.length + ' entit' + (tmpPending.length === 1 ? 'y' : 'ies') + ' waiting</div>';
|
|
530
|
+
}
|
|
531
|
+
tmpHtml += '</div>';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// === Section 2: Completed Operations ===
|
|
535
|
+
if (tmpCompleted.length > 0)
|
|
536
|
+
{
|
|
537
|
+
tmpHtml += '<div class="status-detail-section">';
|
|
538
|
+
tmpHtml += '<div class="status-detail-section-title">Completed (' + tmpCompleted.length + ')</div>';
|
|
539
|
+
for (let i = 0; i < tmpCompleted.length; i++)
|
|
540
|
+
{
|
|
541
|
+
let tmpOp = tmpCompleted[i];
|
|
542
|
+
let tmpPushedFmt = this.formatNumber(tmpOp.Data.Pushed || tmpOp.Data.Total || 0);
|
|
543
|
+
tmpHtml += '<div class="completed-op-row">';
|
|
544
|
+
tmpHtml += '<div class="completed-op-header">';
|
|
545
|
+
tmpHtml += ' <span class="completed-op-checkmark">\u2713</span>';
|
|
546
|
+
tmpHtml += ' <span class="completed-op-name">' + this.escapeHtml(tmpOp.Name) + '</span>';
|
|
547
|
+
tmpHtml += ' <span class="completed-op-stats">' + tmpPushedFmt + ' records</span>';
|
|
548
|
+
tmpHtml += '</div>';
|
|
549
|
+
tmpHtml += '</div>';
|
|
550
|
+
}
|
|
551
|
+
tmpHtml += '</div>';
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// === Section 3: Errors ===
|
|
555
|
+
if (tmpErrors.length > 0)
|
|
556
|
+
{
|
|
557
|
+
tmpHtml += '<div class="status-detail-section">';
|
|
558
|
+
tmpHtml += '<div class="status-detail-section-title">Errors (' + tmpErrors.length + ')</div>';
|
|
559
|
+
for (let i = 0; i < tmpErrors.length; i++)
|
|
560
|
+
{
|
|
561
|
+
let tmpOp = tmpErrors[i];
|
|
562
|
+
let tmpPushedFmt = this.formatNumber(tmpOp.Data.Pushed || 0);
|
|
563
|
+
let tmpTotalFmt = this.formatNumber(tmpOp.Data.Total || 0);
|
|
564
|
+
tmpHtml += '<div class="error-op-row">';
|
|
565
|
+
tmpHtml += '<div class="error-op-header">';
|
|
566
|
+
tmpHtml += ' <span style="color:#dc3545">\u2717</span>';
|
|
567
|
+
tmpHtml += ' <span class="error-op-name">' + this.escapeHtml(tmpOp.Name) + '</span>';
|
|
568
|
+
tmpHtml += ' <span class="error-op-status">' + tmpPushedFmt + ' / ' + tmpTotalFmt + '</span>';
|
|
569
|
+
tmpHtml += '</div>';
|
|
570
|
+
if (tmpOp.Data.ErrorMessage)
|
|
571
|
+
{
|
|
572
|
+
tmpHtml += '<div class="error-op-message">' + this.escapeHtml(tmpOp.Data.ErrorMessage) + '</div>';
|
|
573
|
+
}
|
|
574
|
+
tmpHtml += '</div>';
|
|
575
|
+
}
|
|
576
|
+
tmpHtml += '</div>';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (tmpHtml === '')
|
|
580
|
+
{
|
|
581
|
+
if (tmpIsLive)
|
|
582
|
+
{
|
|
583
|
+
tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">Load in progress, waiting for entity data\u2026</div>';
|
|
584
|
+
}
|
|
585
|
+
else
|
|
586
|
+
{
|
|
587
|
+
tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">No load data available. Run a load to see operation details here.</div>';
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
592
|
+
|
|
593
|
+
// Update the throughput histogram
|
|
594
|
+
this.updateThroughputHistogram(tmpThroughputSamples);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
updateThroughputHistogram(pSamples)
|
|
598
|
+
{
|
|
599
|
+
let tmpHistContainer = document.getElementById('ComprehensionLoader-Throughput-Histogram');
|
|
600
|
+
if (!tmpHistContainer) return;
|
|
601
|
+
|
|
602
|
+
if (!pSamples || pSamples.length < 2)
|
|
603
|
+
{
|
|
604
|
+
tmpHistContainer.style.display = 'none';
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Compute raw deltas per interval
|
|
609
|
+
let tmpRawDeltas = [];
|
|
610
|
+
for (let i = 1; i < pSamples.length; i++)
|
|
611
|
+
{
|
|
612
|
+
let tmpDelta = pSamples[i].pushed - pSamples[i - 1].pushed;
|
|
613
|
+
if (tmpDelta < 0) tmpDelta = 0;
|
|
614
|
+
tmpRawDeltas.push({ delta: tmpDelta, t: pSamples[i].t });
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Downsample if too many bars
|
|
618
|
+
let tmpContainerWidth = tmpHistContainer.clientWidth || 800;
|
|
619
|
+
let tmpMaxBars = Math.max(20, Math.floor(tmpContainerWidth / 6));
|
|
620
|
+
let tmpAggregated = tmpRawDeltas;
|
|
621
|
+
|
|
622
|
+
if (tmpRawDeltas.length > tmpMaxBars)
|
|
623
|
+
{
|
|
624
|
+
let tmpBucketSize = Math.ceil(tmpRawDeltas.length / tmpMaxBars);
|
|
625
|
+
tmpAggregated = [];
|
|
626
|
+
for (let i = 0; i < tmpRawDeltas.length; i += tmpBucketSize)
|
|
627
|
+
{
|
|
628
|
+
let tmpSum = 0;
|
|
629
|
+
let tmpLastT = 0;
|
|
630
|
+
for (let j = i; j < Math.min(i + tmpBucketSize, tmpRawDeltas.length); j++)
|
|
631
|
+
{
|
|
632
|
+
tmpSum += tmpRawDeltas[j].delta;
|
|
633
|
+
tmpLastT = tmpRawDeltas[j].t;
|
|
634
|
+
}
|
|
635
|
+
tmpAggregated.push({ delta: tmpSum, t: tmpLastT });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Check for data
|
|
640
|
+
let tmpHasData = false;
|
|
641
|
+
for (let i = 0; i < tmpAggregated.length; i++)
|
|
642
|
+
{
|
|
643
|
+
if (tmpAggregated[i].delta > 0) { tmpHasData = true; break; }
|
|
644
|
+
}
|
|
645
|
+
if (!tmpHasData)
|
|
646
|
+
{
|
|
647
|
+
tmpHistContainer.style.display = 'none';
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Build bins
|
|
652
|
+
let tmpStartT = pSamples[0].t;
|
|
653
|
+
let tmpBins = [];
|
|
654
|
+
for (let i = 0; i < tmpAggregated.length; i++)
|
|
655
|
+
{
|
|
656
|
+
let tmpElapsedSec = Math.round((tmpAggregated[i].t - tmpStartT) / 1000);
|
|
657
|
+
tmpBins.push({
|
|
658
|
+
Label: this.formatElapsed(tmpElapsedSec),
|
|
659
|
+
Value: tmpAggregated[i].delta
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Update the histogram view
|
|
664
|
+
tmpHistContainer.style.display = '';
|
|
665
|
+
let tmpHistView = this.pict.views['ComprehensionLoader-StatusHistogram'];
|
|
666
|
+
if (tmpHistView)
|
|
667
|
+
{
|
|
668
|
+
tmpHistView.setBins(tmpBins);
|
|
669
|
+
tmpHistView.renderHistogram();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
formatElapsed(pSec)
|
|
674
|
+
{
|
|
675
|
+
if (pSec < 60) return pSec + 's';
|
|
676
|
+
if (pSec < 3600)
|
|
677
|
+
{
|
|
678
|
+
let tmpM = Math.floor(pSec / 60);
|
|
679
|
+
let tmpS = pSec % 60;
|
|
680
|
+
return tmpM + ':' + (tmpS < 10 ? '0' : '') + tmpS;
|
|
681
|
+
}
|
|
682
|
+
let tmpH = Math.floor(pSec / 3600);
|
|
683
|
+
let tmpM = Math.floor((pSec % 3600) / 60);
|
|
684
|
+
return tmpH + 'h' + (tmpM < 10 ? '0' : '') + tmpM;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
formatCompact(pNum)
|
|
688
|
+
{
|
|
689
|
+
if (pNum >= 1000000) return (pNum / 1000000).toFixed(1) + 'M';
|
|
690
|
+
if (pNum >= 10000) return (pNum / 1000).toFixed(0) + 'K';
|
|
691
|
+
if (pNum >= 1000) return (pNum / 1000).toFixed(1) + 'K';
|
|
692
|
+
return pNum.toString();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
formatNumber(pNum)
|
|
696
|
+
{
|
|
697
|
+
return pNum.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ================================================================
|
|
701
|
+
// Auto-Process
|
|
702
|
+
// ================================================================
|
|
703
|
+
|
|
704
|
+
initAutoProcess()
|
|
705
|
+
{
|
|
706
|
+
let tmpSelf = this;
|
|
707
|
+
this.api('GET', '/comprehension_load/load/live-status')
|
|
708
|
+
.then(function(pData)
|
|
709
|
+
{
|
|
710
|
+
if (pData.Phase === 'loading' || pData.Phase === 'stopping')
|
|
711
|
+
{
|
|
712
|
+
tmpSelf.pict.AppData.ComprehensionLoader.ServerBusyAtLoad = true;
|
|
713
|
+
tmpSelf.setSectionPhase(4, 'busy');
|
|
714
|
+
tmpSelf.pict.views['ComprehensionLoader-Load'].startPolling();
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
tmpSelf.runAutoProcessChain();
|
|
718
|
+
})
|
|
719
|
+
.catch(function()
|
|
720
|
+
{
|
|
721
|
+
// Server unreachable — don't auto-process
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
runAutoProcessChain()
|
|
726
|
+
{
|
|
727
|
+
let tmpSelf = this;
|
|
728
|
+
let tmpDelay = 0;
|
|
729
|
+
let tmpStepDelay = 2000;
|
|
730
|
+
|
|
731
|
+
if (document.getElementById('auto1') && document.getElementById('auto1').checked)
|
|
732
|
+
{
|
|
733
|
+
setTimeout(function() { tmpSelf.pict.views['ComprehensionLoader-Session'].goAction(); }, tmpDelay);
|
|
734
|
+
tmpDelay += tmpStepDelay + 1500;
|
|
735
|
+
}
|
|
736
|
+
if (document.getElementById('auto2') && document.getElementById('auto2').checked)
|
|
737
|
+
{
|
|
738
|
+
setTimeout(function() { tmpSelf.pict.views['ComprehensionLoader-Schema'].fetchSchema(); }, tmpDelay);
|
|
739
|
+
tmpDelay += tmpStepDelay;
|
|
740
|
+
}
|
|
741
|
+
if (document.getElementById('auto3') && document.getElementById('auto3').checked)
|
|
742
|
+
{
|
|
743
|
+
setTimeout(function() { tmpSelf.pict.views['ComprehensionLoader-Source'].goAction(); }, tmpDelay);
|
|
744
|
+
tmpDelay += tmpStepDelay;
|
|
745
|
+
}
|
|
746
|
+
if (document.getElementById('auto4') && document.getElementById('auto4').checked)
|
|
747
|
+
{
|
|
748
|
+
setTimeout(function() { tmpSelf.pict.views['ComprehensionLoader-Load'].startLoad(); }, tmpDelay);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
module.exports = ComprehensionLoaderProvider;
|
|
754
|
+
|
|
755
|
+
module.exports.default_configuration =
|
|
756
|
+
{
|
|
757
|
+
ProviderIdentifier: 'ComprehensionLoader',
|
|
758
|
+
AutoInitialize: true,
|
|
759
|
+
AutoInitializeOrdinal: 0
|
|
760
|
+
};
|