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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-data-service",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.23",
|
|
4
4
|
"description": "Serve up a whole model!",
|
|
5
5
|
"main": "source/Retold-Data-Service.js",
|
|
6
6
|
"bin": {
|
|
@@ -61,24 +61,24 @@
|
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"meadow-connection-sqlite": "^1.0.18",
|
|
63
63
|
"puppeteer": "^24.38.0",
|
|
64
|
-
"quackage": "^1.0.
|
|
64
|
+
"quackage": "^1.0.65",
|
|
65
65
|
"stricture": "^4.0.2",
|
|
66
66
|
"supertest": "^7.2.2"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"bibliograph": "^0.1.4",
|
|
70
|
-
"fable": "^3.1.
|
|
70
|
+
"fable": "^3.1.67",
|
|
71
71
|
"fable-serviceproviderbase": "^3.0.19",
|
|
72
72
|
"meadow": "^2.0.33",
|
|
73
73
|
"meadow-connection-mysql": "^1.0.14",
|
|
74
|
-
"meadow-endpoints": "^4.0.
|
|
75
|
-
"meadow-integration": "^1.0.
|
|
74
|
+
"meadow-endpoints": "^4.0.15",
|
|
75
|
+
"meadow-integration": "^1.0.20",
|
|
76
76
|
"meadow-migrationmanager": "^0.0.4",
|
|
77
77
|
"orator": "^6.0.4",
|
|
78
78
|
"orator-http-proxy": "^1.0.5",
|
|
79
|
-
"orator-serviceserver-restify": "^2.0.
|
|
79
|
+
"orator-serviceserver-restify": "^2.0.10",
|
|
80
80
|
"orator-static-server": "^2.0.4",
|
|
81
|
-
"pict": "^1.0.
|
|
81
|
+
"pict": "^1.0.361",
|
|
82
82
|
"pict-section-histogram": "^1.0.0",
|
|
83
83
|
"pict-sessionmanager": "^1.0.2",
|
|
84
84
|
"stricture": "^4.0.2"
|
|
@@ -18,6 +18,7 @@ const libRetoldDataServiceMeadowIntegration = require('./services/meadow-integra
|
|
|
18
18
|
const libRetoldDataServiceMigrationManager = require('./services/migration-manager/Retold-Data-Service-MigrationManager.js');
|
|
19
19
|
const libRetoldDataServiceDataCloner = require('./services/data-cloner/Retold-Data-Service-DataCloner.js');
|
|
20
20
|
const libRetoldDataServiceIntegrationTelemetry = require('./services/integration-telemetry/Retold-Data-Service-IntegrationTelemetry.js');
|
|
21
|
+
const libRetoldDataServiceComprehensionLoader = require('./services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js');
|
|
21
22
|
|
|
22
23
|
const defaultDataServiceSettings = (
|
|
23
24
|
{
|
|
@@ -53,7 +54,11 @@ const defaultDataServiceSettings = (
|
|
|
53
54
|
// Data cloner web UI (GET /clone/)
|
|
54
55
|
DataClonerWebUI: false,
|
|
55
56
|
// Integration telemetry API endpoints (/telemetry/*)
|
|
56
|
-
IntegrationTelemetry: false
|
|
57
|
+
IntegrationTelemetry: false,
|
|
58
|
+
// Comprehension loader API endpoints (/comprehension_load/*)
|
|
59
|
+
ComprehensionLoader: false,
|
|
60
|
+
// Comprehension loader web UI (GET /comprehension_load/)
|
|
61
|
+
ComprehensionLoaderWebUI: false
|
|
57
62
|
},
|
|
58
63
|
|
|
59
64
|
// Migration manager configuration
|
|
@@ -79,6 +84,13 @@ const defaultDataServiceSettings = (
|
|
|
79
84
|
RoutePrefix: '/telemetry',
|
|
80
85
|
// Default tenant identifier when none is provided
|
|
81
86
|
DefaultTenantID: 'default'
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Comprehension loader configuration
|
|
90
|
+
ComprehensionLoader:
|
|
91
|
+
{
|
|
92
|
+
// Route prefix for all comprehension loader endpoints (API + web UI)
|
|
93
|
+
RoutePrefix: '/comprehension_load'
|
|
82
94
|
}
|
|
83
95
|
});
|
|
84
96
|
|
|
@@ -142,6 +154,10 @@ class RetoldDataService extends libFableServiceProviderBase
|
|
|
142
154
|
this.fable.serviceManager.addServiceType('RetoldDataServiceIntegrationTelemetry', libRetoldDataServiceIntegrationTelemetry);
|
|
143
155
|
this.fable.serviceManager.instantiateServiceProvider('RetoldDataServiceIntegrationTelemetry');
|
|
144
156
|
|
|
157
|
+
// Register and instantiate the ComprehensionLoader service
|
|
158
|
+
this.fable.serviceManager.addServiceType('RetoldDataServiceComprehensionLoader', libRetoldDataServiceComprehensionLoader);
|
|
159
|
+
this.fable.serviceManager.instantiateServiceProvider('RetoldDataServiceComprehensionLoader');
|
|
160
|
+
|
|
145
161
|
// Expose the DAL and MeadowEndpoints from the service on this object and on fable for backward compatibility
|
|
146
162
|
this._DAL = this.fable.RetoldDataServiceMeadowEndpoints._DAL;
|
|
147
163
|
this._MeadowEndpoints = this.fable.RetoldDataServiceMeadowEndpoints._MeadowEndpoints;
|
|
@@ -235,7 +251,7 @@ class RetoldDataService extends libFableServiceProviderBase
|
|
|
235
251
|
this.fable.log.info(`The Retold Data Service is initializing...`);
|
|
236
252
|
|
|
237
253
|
// Log endpoint configuration
|
|
238
|
-
let tmpGroupNames = ['ConnectionManager', 'ModelManagerWrite', 'Stricture', 'MeadowIntegration', 'MeadowEndpoints', 'MigrationManager', 'MigrationManagerWebUI', 'DataCloner', 'DataClonerWebUI', 'IntegrationTelemetry'];
|
|
254
|
+
let tmpGroupNames = ['ConnectionManager', 'ModelManagerWrite', 'Stricture', 'MeadowIntegration', 'MeadowEndpoints', 'MigrationManager', 'MigrationManagerWebUI', 'DataCloner', 'DataClonerWebUI', 'IntegrationTelemetry', 'ComprehensionLoader', 'ComprehensionLoaderWebUI'];
|
|
239
255
|
let tmpEnabledGroups = [];
|
|
240
256
|
let tmpDisabledGroups = [];
|
|
241
257
|
for (let i = 0; i < tmpGroupNames.length; i++)
|
|
@@ -336,6 +352,18 @@ class RetoldDataService extends libFableServiceProviderBase
|
|
|
336
352
|
this.fable.RetoldDataServiceIntegrationTelemetry.connectRoutes(this.fable.OratorServiceServer);
|
|
337
353
|
}
|
|
338
354
|
|
|
355
|
+
// ComprehensionLoader API routes (/comprehension_load/*)
|
|
356
|
+
if (this.isEndpointGroupEnabled('ComprehensionLoader'))
|
|
357
|
+
{
|
|
358
|
+
this.fable.RetoldDataServiceComprehensionLoader.connectRoutes(this.fable.OratorServiceServer);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ComprehensionLoader Web UI routes (GET /comprehension_load/)
|
|
362
|
+
if (this.isEndpointGroupEnabled('ComprehensionLoaderWebUI'))
|
|
363
|
+
{
|
|
364
|
+
this.fable.RetoldDataServiceComprehensionLoader.connectWebUIRoutes(this.fable.OratorServiceServer);
|
|
365
|
+
}
|
|
366
|
+
|
|
339
367
|
return fInitCallback();
|
|
340
368
|
});
|
|
341
369
|
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComprehensionLoader Load Management Routes
|
|
3
|
+
*
|
|
4
|
+
* Registers /comprehension_load/comprehension/* and /comprehension_load/load/*
|
|
5
|
+
* endpoints for receiving comprehension data, starting/stopping the push,
|
|
6
|
+
* and polling progress.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} pComprehensionLoaderService - The RetoldDataServiceComprehensionLoader instance
|
|
9
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
10
|
+
*/
|
|
11
|
+
module.exports = (pComprehensionLoaderService, pOratorServiceServer) =>
|
|
12
|
+
{
|
|
13
|
+
let tmpFable = pComprehensionLoaderService.fable;
|
|
14
|
+
let tmpLoadState = pComprehensionLoaderService.loadState;
|
|
15
|
+
let tmpPict = pComprehensionLoaderService.pict;
|
|
16
|
+
let tmpPrefix = pComprehensionLoaderService.routePrefix;
|
|
17
|
+
|
|
18
|
+
// POST /comprehension_load/comprehension/receive
|
|
19
|
+
pOratorServiceServer.postWithBodyParser(`${tmpPrefix}/comprehension/receive`,
|
|
20
|
+
(pRequest, pResponse, fNext) =>
|
|
21
|
+
{
|
|
22
|
+
let tmpBody = pRequest.body || {};
|
|
23
|
+
|
|
24
|
+
if (!tmpBody.Comprehension || (typeof tmpBody.Comprehension !== 'object'))
|
|
25
|
+
{
|
|
26
|
+
pResponse.send(400, { Success: false, Error: 'No valid Comprehension object provided in request body.' });
|
|
27
|
+
return fNext();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let tmpSummary = pComprehensionLoaderService.loadComprehension(tmpBody.Comprehension);
|
|
31
|
+
|
|
32
|
+
tmpFable.log.info(`Comprehension Loader: Received comprehension with ${tmpSummary.EntityList.length} entities, ${tmpSummary.TotalRecords} total records.`);
|
|
33
|
+
|
|
34
|
+
pResponse.send(200,
|
|
35
|
+
{
|
|
36
|
+
Success: true,
|
|
37
|
+
EntityCount: tmpSummary.EntityList.length,
|
|
38
|
+
EntityList: tmpSummary.EntityList,
|
|
39
|
+
RecordCounts: tmpSummary.RecordCounts,
|
|
40
|
+
TotalRecords: tmpSummary.TotalRecords
|
|
41
|
+
});
|
|
42
|
+
return fNext();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// POST /comprehension_load/comprehension/proxy-fetch
|
|
46
|
+
pOratorServiceServer.postWithBodyParser(`${tmpPrefix}/comprehension/proxy-fetch`,
|
|
47
|
+
(pRequest, pResponse, fNext) =>
|
|
48
|
+
{
|
|
49
|
+
let tmpBody = pRequest.body || {};
|
|
50
|
+
let tmpURL = tmpBody.URL;
|
|
51
|
+
|
|
52
|
+
if (!tmpURL || typeof tmpURL !== 'string')
|
|
53
|
+
{
|
|
54
|
+
pResponse.send(400, { Success: false, Error: 'URL is required.' });
|
|
55
|
+
return fNext();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tmpFable.log.info(`Comprehension Loader: Proxy-fetching comprehension from ${tmpURL}...`);
|
|
59
|
+
|
|
60
|
+
tmpPict.RestClient.getJSON(tmpURL,
|
|
61
|
+
(pError, pHTTPResponse, pData) =>
|
|
62
|
+
{
|
|
63
|
+
if (pError)
|
|
64
|
+
{
|
|
65
|
+
tmpFable.log.error(`Comprehension Loader: Proxy fetch error: ${pError.message || pError}`);
|
|
66
|
+
pResponse.send(500, { Success: false, Error: `Proxy fetch error: ${pError.message || pError}` });
|
|
67
|
+
return fNext();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!pHTTPResponse || pHTTPResponse.statusCode !== 200)
|
|
71
|
+
{
|
|
72
|
+
let tmpStatus = pHTTPResponse ? pHTTPResponse.statusCode : 'unknown';
|
|
73
|
+
pResponse.send(500, { Success: false, Error: `Proxy fetch returned HTTP ${tmpStatus}` });
|
|
74
|
+
return fNext();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!pData || typeof pData !== 'object')
|
|
78
|
+
{
|
|
79
|
+
pResponse.send(400, { Success: false, Error: 'Fetched content is not valid JSON.' });
|
|
80
|
+
return fNext();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let tmpSummary = pComprehensionLoaderService.loadComprehension(pData);
|
|
84
|
+
|
|
85
|
+
tmpFable.log.info(`Comprehension Loader: Proxy-fetched comprehension with ${tmpSummary.EntityList.length} entities, ${tmpSummary.TotalRecords} total records.`);
|
|
86
|
+
|
|
87
|
+
pResponse.send(200,
|
|
88
|
+
{
|
|
89
|
+
Success: true,
|
|
90
|
+
EntityCount: tmpSummary.EntityList.length,
|
|
91
|
+
EntityList: tmpSummary.EntityList,
|
|
92
|
+
RecordCounts: tmpSummary.RecordCounts,
|
|
93
|
+
TotalRecords: tmpSummary.TotalRecords
|
|
94
|
+
});
|
|
95
|
+
return fNext();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// GET /comprehension_load/comprehension
|
|
100
|
+
pOratorServiceServer.get(`${tmpPrefix}/comprehension`,
|
|
101
|
+
(pRequest, pResponse, fNext) =>
|
|
102
|
+
{
|
|
103
|
+
pResponse.send(200,
|
|
104
|
+
{
|
|
105
|
+
Loaded: tmpLoadState.ComprehensionData !== null,
|
|
106
|
+
EntityCount: tmpLoadState.ComprehensionEntityList.length,
|
|
107
|
+
EntityList: tmpLoadState.ComprehensionEntityList,
|
|
108
|
+
RecordCounts: tmpLoadState.ComprehensionRecordCounts,
|
|
109
|
+
TotalRecords: tmpLoadState.ComprehensionTotalRecords
|
|
110
|
+
});
|
|
111
|
+
return fNext();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// POST /comprehension_load/comprehension/clear
|
|
115
|
+
pOratorServiceServer.post(`${tmpPrefix}/comprehension/clear`,
|
|
116
|
+
(pRequest, pResponse, fNext) =>
|
|
117
|
+
{
|
|
118
|
+
pComprehensionLoaderService.clearComprehension();
|
|
119
|
+
|
|
120
|
+
tmpFable.log.info('Comprehension Loader: Comprehension data cleared.');
|
|
121
|
+
|
|
122
|
+
pResponse.send(200, { Success: true });
|
|
123
|
+
return fNext();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// POST /comprehension_load/load/start
|
|
127
|
+
pOratorServiceServer.post(`${tmpPrefix}/load/start`,
|
|
128
|
+
(pRequest, pResponse, fNext) =>
|
|
129
|
+
{
|
|
130
|
+
if (tmpLoadState.LoadRunning)
|
|
131
|
+
{
|
|
132
|
+
pResponse.send(409, { Success: false, Error: 'A load is already running.' });
|
|
133
|
+
return fNext();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!tmpLoadState.ComprehensionData)
|
|
137
|
+
{
|
|
138
|
+
pResponse.send(400, { Success: false, Error: 'No comprehension data loaded. Send data first via /comprehension/receive.' });
|
|
139
|
+
return fNext();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!tmpLoadState.SessionAuthenticated)
|
|
143
|
+
{
|
|
144
|
+
pResponse.send(400, { Success: false, Error: 'Session not authenticated. Configure and authenticate a session first.' });
|
|
145
|
+
return fNext();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let tmpBody = pRequest.body || {};
|
|
149
|
+
let tmpEntityList = tmpBody.Entities || tmpLoadState.ComprehensionEntityList;
|
|
150
|
+
|
|
151
|
+
// Reset progress
|
|
152
|
+
tmpLoadState.LoadProgress = {};
|
|
153
|
+
|
|
154
|
+
tmpFable.log.info(`Comprehension Loader: Starting load for ${tmpEntityList.length} entities...`);
|
|
155
|
+
|
|
156
|
+
// Start push asynchronously
|
|
157
|
+
pComprehensionLoaderService.pushEntities(tmpEntityList);
|
|
158
|
+
|
|
159
|
+
pResponse.send(200,
|
|
160
|
+
{
|
|
161
|
+
Success: true,
|
|
162
|
+
Entities: tmpEntityList,
|
|
163
|
+
TotalRecords: tmpLoadState.ComprehensionTotalRecords
|
|
164
|
+
});
|
|
165
|
+
return fNext();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// POST /comprehension_load/load/stop
|
|
169
|
+
pOratorServiceServer.post(`${tmpPrefix}/load/stop`,
|
|
170
|
+
(pRequest, pResponse, fNext) =>
|
|
171
|
+
{
|
|
172
|
+
if (!tmpLoadState.LoadRunning)
|
|
173
|
+
{
|
|
174
|
+
pResponse.send(200, { Success: true, Message: 'No load is running.' });
|
|
175
|
+
return fNext();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
tmpLoadState.LoadStopping = true;
|
|
179
|
+
tmpFable.log.info('Comprehension Loader: Stop requested.');
|
|
180
|
+
|
|
181
|
+
pResponse.send(200, { Success: true, Message: 'Stop signal sent.' });
|
|
182
|
+
return fNext();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// GET /comprehension_load/load/status
|
|
186
|
+
pOratorServiceServer.get(`${tmpPrefix}/load/status`,
|
|
187
|
+
(pRequest, pResponse, fNext) =>
|
|
188
|
+
{
|
|
189
|
+
pResponse.send(200,
|
|
190
|
+
{
|
|
191
|
+
Running: tmpLoadState.LoadRunning,
|
|
192
|
+
Entities: tmpLoadState.LoadProgress
|
|
193
|
+
});
|
|
194
|
+
return fNext();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// GET /comprehension_load/load/live-status
|
|
198
|
+
pOratorServiceServer.get(`${tmpPrefix}/load/live-status`,
|
|
199
|
+
(pRequest, pResponse, fNext) =>
|
|
200
|
+
{
|
|
201
|
+
let tmpEntityNames = Object.keys(tmpLoadState.LoadProgress);
|
|
202
|
+
let tmpTotalPushed = 0;
|
|
203
|
+
let tmpTotalRecords = tmpLoadState.ComprehensionTotalRecords;
|
|
204
|
+
let tmpCompleted = 0;
|
|
205
|
+
let tmpErrors = 0;
|
|
206
|
+
let tmpPending = 0;
|
|
207
|
+
let tmpActiveEntity = null;
|
|
208
|
+
let tmpActiveProgress = null;
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
211
|
+
{
|
|
212
|
+
let tmpName = tmpEntityNames[i];
|
|
213
|
+
let tmpP = tmpLoadState.LoadProgress[tmpName];
|
|
214
|
+
tmpTotalPushed += (tmpP.Pushed || 0);
|
|
215
|
+
|
|
216
|
+
if (tmpP.Status === 'Complete') tmpCompleted++;
|
|
217
|
+
else if (tmpP.Status === 'Error') { tmpErrors++; tmpCompleted++; }
|
|
218
|
+
else if (tmpP.Status === 'Pushing')
|
|
219
|
+
{
|
|
220
|
+
tmpActiveEntity = tmpName;
|
|
221
|
+
tmpActiveProgress = { Total: tmpP.Total, Pushed: tmpP.Pushed };
|
|
222
|
+
}
|
|
223
|
+
else tmpPending++;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Determine phase
|
|
227
|
+
let tmpPhase = 'idle';
|
|
228
|
+
let tmpMessage = 'Idle';
|
|
229
|
+
|
|
230
|
+
if (!tmpLoadState.ComprehensionData && !tmpLoadState.LoadRunning)
|
|
231
|
+
{
|
|
232
|
+
tmpPhase = 'idle';
|
|
233
|
+
tmpMessage = 'No comprehension data loaded';
|
|
234
|
+
}
|
|
235
|
+
else if (tmpLoadState.ComprehensionData && !tmpLoadState.LoadRunning && tmpEntityNames.length === 0)
|
|
236
|
+
{
|
|
237
|
+
tmpPhase = 'ready';
|
|
238
|
+
tmpMessage = `Ready to load ${tmpLoadState.ComprehensionEntityList.length} entities (${tmpTotalRecords} records)`;
|
|
239
|
+
}
|
|
240
|
+
else if (tmpLoadState.LoadStopping)
|
|
241
|
+
{
|
|
242
|
+
tmpPhase = 'stopping';
|
|
243
|
+
tmpMessage = 'Stopping...';
|
|
244
|
+
}
|
|
245
|
+
else if (tmpLoadState.LoadRunning)
|
|
246
|
+
{
|
|
247
|
+
tmpPhase = 'loading';
|
|
248
|
+
if (tmpActiveEntity)
|
|
249
|
+
{
|
|
250
|
+
tmpMessage = `Pushing ${tmpActiveEntity}: ${tmpActiveProgress.Pushed} / ${tmpActiveProgress.Total} records`;
|
|
251
|
+
}
|
|
252
|
+
else
|
|
253
|
+
{
|
|
254
|
+
tmpMessage = `Loading ${tmpEntityNames.length} entities`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (tmpEntityNames.length > 0 && !tmpLoadState.LoadRunning)
|
|
258
|
+
{
|
|
259
|
+
tmpPhase = 'complete';
|
|
260
|
+
tmpMessage = `Load complete: ${tmpCompleted} entities, ${tmpTotalPushed} records pushed`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Calculate elapsed time and ETA
|
|
264
|
+
let tmpElapsed = null;
|
|
265
|
+
let tmpETA = null;
|
|
266
|
+
if (tmpLoadState.LoadStartTime)
|
|
267
|
+
{
|
|
268
|
+
let tmpEndTime = tmpLoadState.LoadEndTime ? new Date(tmpLoadState.LoadEndTime).getTime() : Date.now();
|
|
269
|
+
let tmpElapsedMs = tmpEndTime - new Date(tmpLoadState.LoadStartTime).getTime();
|
|
270
|
+
let tmpElapsedSec = Math.round(tmpElapsedMs / 1000);
|
|
271
|
+
|
|
272
|
+
if (tmpElapsedSec < 60)
|
|
273
|
+
{
|
|
274
|
+
tmpElapsed = tmpElapsedSec + 's';
|
|
275
|
+
}
|
|
276
|
+
else
|
|
277
|
+
{
|
|
278
|
+
let tmpMin = Math.floor(tmpElapsedSec / 60);
|
|
279
|
+
let tmpSec = tmpElapsedSec % 60;
|
|
280
|
+
tmpElapsed = tmpMin + ':' + (tmpSec < 10 ? '0' : '') + tmpSec;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ETA based on record-weighted progress
|
|
284
|
+
if (tmpLoadState.LoadRunning && tmpTotalPushed > 0 && tmpTotalRecords > 0)
|
|
285
|
+
{
|
|
286
|
+
let tmpRemainingRecords = tmpTotalRecords - tmpTotalPushed;
|
|
287
|
+
let tmpMsPerRecord = tmpElapsedMs / tmpTotalPushed;
|
|
288
|
+
let tmpETAMs = tmpRemainingRecords * tmpMsPerRecord;
|
|
289
|
+
let tmpETASec = Math.round(tmpETAMs / 1000);
|
|
290
|
+
|
|
291
|
+
if (tmpETASec < 60)
|
|
292
|
+
{
|
|
293
|
+
tmpETA = tmpETASec + 's';
|
|
294
|
+
}
|
|
295
|
+
else
|
|
296
|
+
{
|
|
297
|
+
let tmpETAMin = Math.floor(tmpETASec / 60);
|
|
298
|
+
let tmpETAS = tmpETASec % 60;
|
|
299
|
+
tmpETA = tmpETAMin + ':' + (tmpETAS < 10 ? '0' : '') + tmpETAS;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
pResponse.send(200,
|
|
305
|
+
{
|
|
306
|
+
Phase: tmpPhase,
|
|
307
|
+
Message: tmpMessage,
|
|
308
|
+
TotalEntities: tmpEntityNames.length,
|
|
309
|
+
Completed: tmpCompleted,
|
|
310
|
+
Errors: tmpErrors,
|
|
311
|
+
Pending: tmpPending,
|
|
312
|
+
TotalPushed: tmpTotalPushed,
|
|
313
|
+
TotalRecords: tmpTotalRecords,
|
|
314
|
+
ActiveEntity: tmpActiveEntity,
|
|
315
|
+
ActiveProgress: tmpActiveProgress,
|
|
316
|
+
Elapsed: tmpElapsed,
|
|
317
|
+
ETA: tmpETA,
|
|
318
|
+
ThroughputSamples: tmpLoadState.ThroughputSamples || []
|
|
319
|
+
});
|
|
320
|
+
return fNext();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// GET /comprehension_load/load/report
|
|
324
|
+
pOratorServiceServer.get(`${tmpPrefix}/load/report`,
|
|
325
|
+
(pRequest, pResponse, fNext) =>
|
|
326
|
+
{
|
|
327
|
+
if (!tmpLoadState.LoadReport)
|
|
328
|
+
{
|
|
329
|
+
if (tmpLoadState.LoadStartTime && !tmpLoadState.LoadRunning)
|
|
330
|
+
{
|
|
331
|
+
pComprehensionLoaderService.generateLoadReport();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (tmpLoadState.LoadReport)
|
|
336
|
+
{
|
|
337
|
+
pResponse.send(200, tmpLoadState.LoadReport);
|
|
338
|
+
}
|
|
339
|
+
else
|
|
340
|
+
{
|
|
341
|
+
pResponse.send(200, { ReportVersion: null, Message: 'No load report available.' });
|
|
342
|
+
}
|
|
343
|
+
return fNext();
|
|
344
|
+
});
|
|
345
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComprehensionLoader Schema Management Routes
|
|
3
|
+
*
|
|
4
|
+
* Registers /comprehension_load/schema/* endpoints for fetching remote
|
|
5
|
+
* schemas to discover valid entities on the target server.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} pComprehensionLoaderService - The RetoldDataServiceComprehensionLoader instance
|
|
8
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
9
|
+
*/
|
|
10
|
+
module.exports = (pComprehensionLoaderService, pOratorServiceServer) =>
|
|
11
|
+
{
|
|
12
|
+
let tmpFable = pComprehensionLoaderService.fable;
|
|
13
|
+
let tmpLoadState = pComprehensionLoaderService.loadState;
|
|
14
|
+
let tmpPict = pComprehensionLoaderService.pict;
|
|
15
|
+
let tmpPrefix = pComprehensionLoaderService.routePrefix;
|
|
16
|
+
|
|
17
|
+
// POST /comprehension_load/schema/fetch
|
|
18
|
+
pOratorServiceServer.post(`${tmpPrefix}/schema/fetch`,
|
|
19
|
+
(pRequest, pResponse, fNext) =>
|
|
20
|
+
{
|
|
21
|
+
let tmpBody = pRequest.body || {};
|
|
22
|
+
let tmpSchemaURL = tmpBody.SchemaURL;
|
|
23
|
+
|
|
24
|
+
if (!tmpSchemaURL)
|
|
25
|
+
{
|
|
26
|
+
if (tmpLoadState.RemoteServerURL)
|
|
27
|
+
{
|
|
28
|
+
tmpSchemaURL = tmpLoadState.RemoteServerURL + 'Retold/Models';
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
{
|
|
32
|
+
pResponse.send(400, { Success: false, Error: 'SchemaURL is required (or configure a session first).' });
|
|
33
|
+
return fNext();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
tmpFable.log.info(`Comprehension Loader: Fetching remote schema from ${tmpSchemaURL}...`);
|
|
38
|
+
|
|
39
|
+
tmpPict.RestClient.getJSON(tmpSchemaURL,
|
|
40
|
+
(pError, pHTTPResponse, pData) =>
|
|
41
|
+
{
|
|
42
|
+
if (pError)
|
|
43
|
+
{
|
|
44
|
+
tmpFable.log.error(`Comprehension Loader: Schema fetch error: ${pError.message || pError}`);
|
|
45
|
+
pResponse.send(500, { Success: false, Error: `Schema fetch error: ${pError.message || pError}` });
|
|
46
|
+
return fNext();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!pHTTPResponse || pHTTPResponse.statusCode !== 200)
|
|
50
|
+
{
|
|
51
|
+
let tmpStatus = pHTTPResponse ? pHTTPResponse.statusCode : 'unknown';
|
|
52
|
+
tmpFable.log.error(`Comprehension Loader: Schema fetch returned HTTP ${tmpStatus}`);
|
|
53
|
+
pResponse.send(500, { Success: false, Error: `Schema fetch returned HTTP ${tmpStatus}` });
|
|
54
|
+
return fNext();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
tmpLoadState.RemoteSchema = pData;
|
|
58
|
+
|
|
59
|
+
let tmpEntityNames = [];
|
|
60
|
+
if (pData && pData.Tables)
|
|
61
|
+
{
|
|
62
|
+
tmpEntityNames = Object.keys(pData.Tables);
|
|
63
|
+
}
|
|
64
|
+
tmpLoadState.RemoteEntityList = tmpEntityNames;
|
|
65
|
+
|
|
66
|
+
tmpFable.log.info(`Comprehension Loader: Fetched schema with ${tmpEntityNames.length} entities: [${tmpEntityNames.join(', ')}]`);
|
|
67
|
+
|
|
68
|
+
pResponse.send(200,
|
|
69
|
+
{
|
|
70
|
+
Success: true,
|
|
71
|
+
SchemaURL: tmpSchemaURL,
|
|
72
|
+
EntityCount: tmpEntityNames.length,
|
|
73
|
+
Entities: tmpEntityNames
|
|
74
|
+
});
|
|
75
|
+
return fNext();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// GET /comprehension_load/schema
|
|
80
|
+
pOratorServiceServer.get(`${tmpPrefix}/schema`,
|
|
81
|
+
(pRequest, pResponse, fNext) =>
|
|
82
|
+
{
|
|
83
|
+
if (!tmpLoadState.RemoteSchema)
|
|
84
|
+
{
|
|
85
|
+
pResponse.send(200, { Fetched: false, Entities: [] });
|
|
86
|
+
return fNext();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pResponse.send(200,
|
|
90
|
+
{
|
|
91
|
+
Fetched: true,
|
|
92
|
+
EntityCount: tmpLoadState.RemoteEntityList.length,
|
|
93
|
+
Entities: tmpLoadState.RemoteEntityList
|
|
94
|
+
});
|
|
95
|
+
return fNext();
|
|
96
|
+
});
|
|
97
|
+
};
|