retold-data-service 2.0.18 → 2.0.20
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/package.json +2 -2
- package/source/services/Retold-Data-Service-MeadowEndpoints.js +12 -2
- package/source/services/data-cloner/DataCloner-Command-Schema.js +4 -3
- package/source/services/data-cloner/DataCloner-Command-Sync.js +31 -4
- package/source/services/data-cloner/DataCloner-ProviderRegistry.js +1 -1
- package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +47 -7
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +2 -1
- package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +102 -2
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +2 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +28 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +7 -0
- package/source/services/data-cloner/web/data-cloner.js +117 -3
- 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/run-integration-tests.js +52 -12
- package/test/run-integration-tests.sh +221 -0
- package/test/integration-report.json +0 -311
|
@@ -4892,7 +4892,7 @@
|
|
|
4892
4892
|
StatusDetailTimer: null,
|
|
4893
4893
|
StatusDetailData: null,
|
|
4894
4894
|
LastLiveStatus: null,
|
|
4895
|
-
PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit', 'solrHost', 'solrPort', 'solrCore', 'solrPath', 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit', 'rocksdbFolder', 'bibliographFolder']
|
|
4895
|
+
PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit', 'solrHost', 'solrPort', 'solrCore', 'solrPath', 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit', 'rocksdbFolder', 'bibliographFolder', 'syncMaxRecords']
|
|
4896
4896
|
};
|
|
4897
4897
|
|
|
4898
4898
|
// Make pict available for inline onclick handlers
|
|
@@ -5161,6 +5161,11 @@
|
|
|
5161
5161
|
if (tmpSolrSecure !== null) {
|
|
5162
5162
|
document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
|
|
5163
5163
|
}
|
|
5164
|
+
// Restore advanced ID pagination checkbox
|
|
5165
|
+
let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
|
|
5166
|
+
if (tmpAdvancedIDPagination !== null) {
|
|
5167
|
+
document.getElementById('syncAdvancedIDPagination').checked = tmpAdvancedIDPagination === 'true';
|
|
5168
|
+
}
|
|
5164
5169
|
}
|
|
5165
5170
|
initPersistence() {
|
|
5166
5171
|
let tmpSelf = this;
|
|
@@ -5203,6 +5208,14 @@
|
|
|
5203
5208
|
});
|
|
5204
5209
|
}
|
|
5205
5210
|
|
|
5211
|
+
// Persist advanced ID pagination checkbox
|
|
5212
|
+
let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
|
|
5213
|
+
if (tmpAdvancedIDPaginationEl) {
|
|
5214
|
+
tmpAdvancedIDPaginationEl.addEventListener('change', function () {
|
|
5215
|
+
localStorage.setItem('dataCloner_syncAdvancedIDPagination', this.checked);
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5206
5219
|
// Persist auto-process checkboxes
|
|
5207
5220
|
let tmpAutoIds = ['auto1', 'auto2', 'auto3', 'auto4', 'auto5'];
|
|
5208
5221
|
for (let a = 0; a < tmpAutoIds.length; a++) {
|
|
@@ -5287,7 +5300,8 @@
|
|
|
5287
5300
|
tmpMetaParts.push('<span class="live-status-meta-item">' + tmpGrandTotal + ' records to sync</span>');
|
|
5288
5301
|
}
|
|
5289
5302
|
if (pData.PreCountProgress && pData.PreCountProgress.Counted < pData.PreCountProgress.TotalTables) {
|
|
5290
|
-
|
|
5303
|
+
let tmpCountedSoFar = pData.PreCountGrandTotal > 0 ? ' (' + pData.PreCountGrandTotal.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' records found)' : '';
|
|
5304
|
+
tmpMetaParts.push('<span class="live-status-meta-item">counting: ' + pData.PreCountProgress.Counted + ' / ' + pData.PreCountProgress.TotalTables + ' tables' + tmpCountedSoFar + '</span>');
|
|
5291
5305
|
}
|
|
5292
5306
|
if (pData.Errors > 0) {
|
|
5293
5307
|
tmpMetaParts.push('<span class="live-status-meta-item" style="color:#dc3545"><strong>' + pData.Errors + '</strong> error' + (pData.Errors === 1 ? '' : 's') + '</span>');
|
|
@@ -5302,7 +5316,10 @@
|
|
|
5302
5316
|
|
|
5303
5317
|
// Update progress bar
|
|
5304
5318
|
let tmpPct = 0;
|
|
5305
|
-
if (pData.Phase === 'syncing' && pData.
|
|
5319
|
+
if (pData.Phase === 'syncing' && pData.PreCountProgress && pData.PreCountProgress.Counted < pData.PreCountProgress.TotalTables) {
|
|
5320
|
+
// During counting phase, show table counting progress
|
|
5321
|
+
tmpPct = Math.min(pData.PreCountProgress.Counted / pData.PreCountProgress.TotalTables * 100, 99);
|
|
5322
|
+
} else if (pData.Phase === 'syncing' && pData.PreCountGrandTotal > 0 && pData.TotalSynced > 0) {
|
|
5306
5323
|
tmpPct = Math.min(pData.TotalSynced / pData.PreCountGrandTotal * 100, 99.9);
|
|
5307
5324
|
} else if (pData.Phase === 'syncing' && pData.TotalTables > 0) {
|
|
5308
5325
|
let tmpTablePct = pData.Completed / pData.TotalTables * 100;
|
|
@@ -5317,6 +5334,14 @@
|
|
|
5317
5334
|
}
|
|
5318
5335
|
tmpProgressFill.style.width = Math.min(100, Math.round(tmpPct)) + '%';
|
|
5319
5336
|
|
|
5337
|
+
// Auto-expand the detail view when sync starts so users see counting progress
|
|
5338
|
+
if ((pData.Phase === 'syncing' || pData.Phase === 'stopping') && !this.pict.AppData.DataCloner.StatusDetailExpanded) {
|
|
5339
|
+
let tmpLayoutView = this.pict.views['DataCloner-Layout'];
|
|
5340
|
+
if (tmpLayoutView && typeof tmpLayoutView.toggleStatusDetail === 'function') {
|
|
5341
|
+
tmpLayoutView.toggleStatusDetail();
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5320
5345
|
// If the detail view is expanded, re-render it with fresh data
|
|
5321
5346
|
if (this.pict.AppData.DataCloner.StatusDetailExpanded) {
|
|
5322
5347
|
this.renderStatusDetail();
|
|
@@ -5370,6 +5395,49 @@
|
|
|
5370
5395
|
tmpSelf.renderStatusDetail();
|
|
5371
5396
|
}).catch(function () {/* ignore poll errors */});
|
|
5372
5397
|
}
|
|
5398
|
+
renderCountingPhaseDetail(pContainer, pPreCountProgress) {
|
|
5399
|
+
let tmpTables = pPreCountProgress.Tables || [];
|
|
5400
|
+
let tmpCounted = pPreCountProgress.Counted || 0;
|
|
5401
|
+
let tmpTotal = pPreCountProgress.TotalTables || 0;
|
|
5402
|
+
let tmpRunningTotal = 0;
|
|
5403
|
+
let tmpHtml = '<div class="status-detail-section">';
|
|
5404
|
+
tmpHtml += '<div class="status-detail-section-title">Counting Records (' + tmpCounted + ' / ' + tmpTotal + ' tables)</div>';
|
|
5405
|
+
if (tmpTables.length > 0) {
|
|
5406
|
+
tmpHtml += '<table class="precount-table">';
|
|
5407
|
+
tmpHtml += '<thead><tr><th>Table</th><th style="text-align:right">Records</th><th style="text-align:right">Time</th></tr></thead>';
|
|
5408
|
+
tmpHtml += '<tbody>';
|
|
5409
|
+
for (let i = 0; i < tmpTables.length; i++) {
|
|
5410
|
+
let tmpT = tmpTables[i];
|
|
5411
|
+
tmpRunningTotal += tmpT.Count;
|
|
5412
|
+
let tmpCountFmt = this.formatNumber(tmpT.Count);
|
|
5413
|
+
let tmpTimeFmt = tmpT.ElapsedMs < 1000 ? tmpT.ElapsedMs + 'ms' : (tmpT.ElapsedMs / 1000).toFixed(1) + 's';
|
|
5414
|
+
let tmpRowClass = tmpT.Error ? ' class="precount-error"' : '';
|
|
5415
|
+
tmpHtml += '<tr' + tmpRowClass + '>';
|
|
5416
|
+
tmpHtml += '<td>' + this.escapeHtml(tmpT.Name) + '</td>';
|
|
5417
|
+
tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums">' + tmpCountFmt + '</td>';
|
|
5418
|
+
tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums; color:#888">' + tmpTimeFmt + '</td>';
|
|
5419
|
+
tmpHtml += '</tr>';
|
|
5420
|
+
}
|
|
5421
|
+
tmpHtml += '</tbody>';
|
|
5422
|
+
tmpHtml += '<tfoot><tr>';
|
|
5423
|
+
tmpHtml += '<td><strong>Total</strong></td>';
|
|
5424
|
+
tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums"><strong>' + this.formatNumber(tmpRunningTotal) + '</strong></td>';
|
|
5425
|
+
tmpHtml += '<td></td>';
|
|
5426
|
+
tmpHtml += '</tr></tfoot>';
|
|
5427
|
+
tmpHtml += '</table>';
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
// Show pending indicator for remaining tables
|
|
5431
|
+
let tmpRemaining = tmpTotal - tmpCounted;
|
|
5432
|
+
if (tmpRemaining > 0) {
|
|
5433
|
+
tmpHtml += '<div class="precount-pending">';
|
|
5434
|
+
tmpHtml += '<span class="precount-spinner"></span> ';
|
|
5435
|
+
tmpHtml += tmpRemaining + ' table' + (tmpRemaining === 1 ? '' : 's') + ' remaining…';
|
|
5436
|
+
tmpHtml += '</div>';
|
|
5437
|
+
}
|
|
5438
|
+
tmpHtml += '</div>';
|
|
5439
|
+
pContainer.innerHTML = tmpHtml;
|
|
5440
|
+
}
|
|
5373
5441
|
renderStatusDetail() {
|
|
5374
5442
|
let tmpContainer = document.getElementById('DataCloner-StatusDetail-Container');
|
|
5375
5443
|
if (!tmpContainer) return;
|
|
@@ -5378,6 +5446,15 @@
|
|
|
5378
5446
|
let tmpStatusData = tmpAppData.StatusDetailData;
|
|
5379
5447
|
let tmpReport = tmpAppData.LastReport;
|
|
5380
5448
|
|
|
5449
|
+
// During the counting phase, show per-table counts as they arrive
|
|
5450
|
+
if (tmpLiveStatus && tmpLiveStatus.PreCountProgress && tmpLiveStatus.PreCountProgress.Tables && tmpLiveStatus.Phase === 'syncing' && tmpLiveStatus.PreCountProgress.Counted < tmpLiveStatus.PreCountProgress.TotalTables) {
|
|
5451
|
+
this.renderCountingPhaseDetail(tmpContainer, tmpLiveStatus.PreCountProgress);
|
|
5452
|
+
// Hide histogram during counting
|
|
5453
|
+
let tmpHistContainer = document.getElementById('DataCloner-Throughput-Histogram');
|
|
5454
|
+
if (tmpHistContainer) tmpHistContainer.style.display = 'none';
|
|
5455
|
+
return;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5381
5458
|
// Determine data source: live during sync, report after sync
|
|
5382
5459
|
let tmpTables = {};
|
|
5383
5460
|
let tmpThroughputSamples = [];
|
|
@@ -6266,6 +6343,7 @@
|
|
|
6266
6343
|
if (!isNaN(tmpPrecision) && tmpPrecision !== 1000) tmpConfig.Sync.DateTimePrecisionMS = tmpPrecision;
|
|
6267
6344
|
let tmpMaxRecords = parseInt(document.getElementById('syncMaxRecords').value, 10);
|
|
6268
6345
|
if (tmpMaxRecords > 0) tmpConfig.Sync.MaxRecords = tmpMaxRecords;
|
|
6346
|
+
if (document.getElementById('syncAdvancedIDPagination').checked) tmpConfig.Sync.UseAdvancedIDPagination = true;
|
|
6269
6347
|
return tmpConfig;
|
|
6270
6348
|
}
|
|
6271
6349
|
buildMeadowIntegrationConfig() {
|
|
@@ -6332,6 +6410,7 @@
|
|
|
6332
6410
|
let tmpSelectedTables = this.pict.views['DataCloner-Schema'].getSelectedTables();
|
|
6333
6411
|
tmpConfig.Sync.SyncEntityList = tmpSelectedTables.length > 0 ? tmpSelectedTables : [];
|
|
6334
6412
|
tmpConfig.Sync.SyncEntityOptions = {};
|
|
6413
|
+
if (document.getElementById('syncAdvancedIDPagination').checked) tmpConfig.Sync.UseAdvancedIDPagination = true;
|
|
6335
6414
|
|
|
6336
6415
|
// ---- SessionManager ----
|
|
6337
6416
|
tmpConfig.SessionManager = {
|
|
@@ -6909,6 +6988,34 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
|
|
|
6909
6988
|
font-size: 0.78em; color: #888; margin-top: 4px; padding-left: 18px;
|
|
6910
6989
|
font-family: monospace; max-height: 80px; overflow-y: auto;
|
|
6911
6990
|
}
|
|
6991
|
+
|
|
6992
|
+
/* Pre-count Table */
|
|
6993
|
+
.precount-table {
|
|
6994
|
+
width: 100%; border-collapse: collapse; font-size: 0.88em;
|
|
6995
|
+
}
|
|
6996
|
+
.precount-table thead th {
|
|
6997
|
+
text-align: left; font-weight: 600; color: #555; font-size: 0.85em;
|
|
6998
|
+
text-transform: uppercase; letter-spacing: 0.3px;
|
|
6999
|
+
padding: 4px 8px 6px; border-bottom: 2px solid #e9ecef;
|
|
7000
|
+
}
|
|
7001
|
+
.precount-table tbody td {
|
|
7002
|
+
padding: 4px 8px; border-bottom: 1px solid #f0f0f0;
|
|
7003
|
+
}
|
|
7004
|
+
.precount-table tbody tr:last-child td { border-bottom: 1px solid #e9ecef; }
|
|
7005
|
+
.precount-table tfoot td {
|
|
7006
|
+
padding: 6px 8px 2px; font-size: 0.95em;
|
|
7007
|
+
}
|
|
7008
|
+
.precount-error td { color: #dc3545; }
|
|
7009
|
+
.precount-pending {
|
|
7010
|
+
color: #888; font-size: 0.85em; font-style: italic; padding: 8px 0 2px;
|
|
7011
|
+
display: flex; align-items: center; gap: 6px;
|
|
7012
|
+
}
|
|
7013
|
+
.precount-spinner {
|
|
7014
|
+
display: inline-block; width: 12px; height: 12px;
|
|
7015
|
+
border: 2px solid #ddd; border-top-color: #4a90d9;
|
|
7016
|
+
border-radius: 50%; animation: precount-spin 0.8s linear infinite;
|
|
7017
|
+
}
|
|
7018
|
+
@keyframes precount-spin { to { transform: rotate(360deg); } }
|
|
6912
7019
|
`,
|
|
6913
7020
|
Templates: [{
|
|
6914
7021
|
Hash: 'DataCloner-Layout',
|
|
@@ -7353,6 +7460,7 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
|
|
|
7353
7460
|
let tmpSyncMode = document.querySelector('input[name="syncMode"]:checked').value;
|
|
7354
7461
|
let tmpMaxRecords = parseInt(document.getElementById('syncMaxRecords').value, 10) || 0;
|
|
7355
7462
|
let tmpLogToFile = document.getElementById('syncLogFile').checked;
|
|
7463
|
+
let tmpAdvancedIDPagination = document.getElementById('syncAdvancedIDPagination').checked;
|
|
7356
7464
|
if (tmpSelectedTables.length === 0) {
|
|
7357
7465
|
this.pict.providers.DataCloner.setStatus('syncStatus', 'No tables selected for sync.', 'error');
|
|
7358
7466
|
this.pict.providers.DataCloner.setSectionPhase(5, 'error');
|
|
@@ -7370,6 +7478,7 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
|
|
|
7370
7478
|
};
|
|
7371
7479
|
if (tmpMaxRecords > 0) tmpPostBody.MaxRecordsPerEntity = tmpMaxRecords;
|
|
7372
7480
|
if (tmpLogToFile) tmpPostBody.LogToFile = true;
|
|
7481
|
+
if (tmpAdvancedIDPagination) tmpPostBody.UseAdvancedIDPagination = true;
|
|
7373
7482
|
this.pict.providers.DataCloner.api('POST', '/clone/sync/start', tmpPostBody).then(function (pData) {
|
|
7374
7483
|
if (pData.Success) {
|
|
7375
7484
|
let tmpMsg = pData.SyncMode + ' sync started for ' + pData.Tables.length + ' tables.';
|
|
@@ -7760,6 +7869,11 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
|
|
|
7760
7869
|
<label for="syncDeletedRecords">Sync deleted records (fetch records marked Deleted=1 on source and mirror locally)</label>
|
|
7761
7870
|
</div>
|
|
7762
7871
|
|
|
7872
|
+
<div class="checkbox-row">
|
|
7873
|
+
<input type="checkbox" id="syncAdvancedIDPagination">
|
|
7874
|
+
<label for="syncAdvancedIDPagination">Use advanced ID pagination (faster for large tables; uses keyset pagination instead of OFFSET)</label>
|
|
7875
|
+
</div>
|
|
7876
|
+
|
|
7763
7877
|
<div class="inline-group" style="margin-top:8px; margin-bottom:4px">
|
|
7764
7878
|
<div style="flex:0 0 200px">
|
|
7765
7879
|
<label for="syncMaxRecords">Max Records per Entity</label>
|