retold-facto 0.0.4
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/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- package/test/model/fable-configuration.json +14 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PictView-Facto-Full-Throughput
|
|
3
|
+
*
|
|
4
|
+
* Pipeline throughput visualization for the Full (hash-routed) Facto app.
|
|
5
|
+
* Shows live temporal histograms via pict-section-histogram instances.
|
|
6
|
+
* Each histogram has an inline time axis showing start / mid / end ticks.
|
|
7
|
+
*/
|
|
8
|
+
const libPictView = require('pict-view');
|
|
9
|
+
const libPictSectionHistogram = require('pict-section-histogram');
|
|
10
|
+
|
|
11
|
+
// Histogram definitions — one per pipeline stage plus a combined total
|
|
12
|
+
const HISTOGRAM_DEFS =
|
|
13
|
+
[
|
|
14
|
+
{ id: 'Facto-Full-Histogram-Extracted', stage: 'extracted', color: '#4a90d9', label: 'Extracted' },
|
|
15
|
+
{ id: 'Facto-Full-Histogram-Transformed', stage: 'transformed', color: '#d09818', label: 'Transformed' },
|
|
16
|
+
{ id: 'Facto-Full-Histogram-Written', stage: 'written', color: '#3a9468', label: 'Written' },
|
|
17
|
+
{ id: 'Facto-Full-Histogram-Total', stage: 'total', color: '#6366f1', label: 'Total' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// Bar geometry — must match _ensureHistogramViews config.
|
|
21
|
+
// Width drives the histogram's pixel span: BAR_STRIDE × bucketCount should
|
|
22
|
+
// be close to the container width (~568px for a 2-col 1200px layout).
|
|
23
|
+
// 30 buckets × 18px = 540px ≈ fills the half-column container.
|
|
24
|
+
const BAR_THICKNESS = 16;
|
|
25
|
+
const BAR_GAP = 2;
|
|
26
|
+
const BAR_STRIDE = BAR_THICKNESS + BAR_GAP; // 18px per bucket
|
|
27
|
+
|
|
28
|
+
const POLL_INTERVAL_MS = 500;
|
|
29
|
+
|
|
30
|
+
class FactoFullThroughputView extends libPictView
|
|
31
|
+
{
|
|
32
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
33
|
+
{
|
|
34
|
+
super(pFable, pOptions, pServiceHash);
|
|
35
|
+
this._pollTimer = null;
|
|
36
|
+
this._isPolling = false;
|
|
37
|
+
this._activeHistoricalRun = null;
|
|
38
|
+
this._activeDatasetFilter = null;
|
|
39
|
+
this._histogramsReady = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onAfterRender()
|
|
43
|
+
{
|
|
44
|
+
this._ensureHistogramViews();
|
|
45
|
+
// Render empty histograms immediately so containers aren't blank
|
|
46
|
+
this._renderHistograms({ buckets: [], interval: 1000 });
|
|
47
|
+
this.loadRunHistory();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─────────────────────────────────────────────
|
|
51
|
+
// Histogram view management
|
|
52
|
+
// ─────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
_ensureHistogramViews()
|
|
55
|
+
{
|
|
56
|
+
if (this._histogramsReady) return;
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < HISTOGRAM_DEFS.length; i++)
|
|
59
|
+
{
|
|
60
|
+
let tmpDef = HISTOGRAM_DEFS[i];
|
|
61
|
+
// Container id is deterministic: stage name capitalised
|
|
62
|
+
let tmpContainerId = 'Facto-Full-Histogram-' + _capitalise(tmpDef.stage) + '-Container';
|
|
63
|
+
|
|
64
|
+
if (!this.pict.views[tmpDef.id])
|
|
65
|
+
{
|
|
66
|
+
this.pict.addView(tmpDef.id,
|
|
67
|
+
{
|
|
68
|
+
ViewIdentifier: tmpDef.id,
|
|
69
|
+
AutoRender: false,
|
|
70
|
+
RenderMode: 'browser',
|
|
71
|
+
Orientation: 'vertical',
|
|
72
|
+
TargetElementAddress: '#' + tmpContainerId,
|
|
73
|
+
DefaultDestinationAddress: '#' + tmpContainerId,
|
|
74
|
+
MaxBarSize: 100,
|
|
75
|
+
BarThickness: BAR_THICKNESS,
|
|
76
|
+
BarGap: BAR_GAP,
|
|
77
|
+
ShowValues: false,
|
|
78
|
+
ShowLabels: false,
|
|
79
|
+
BarColor: tmpDef.color,
|
|
80
|
+
Bins: [],
|
|
81
|
+
},
|
|
82
|
+
libPictSectionHistogram);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._histogramsReady = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert a bucket array to a Bins array for pict-section-histogram.
|
|
91
|
+
* Labels are always empty strings — we draw our own time axis instead.
|
|
92
|
+
*/
|
|
93
|
+
_bucketsToHistogramBins(pBuckets, pStage)
|
|
94
|
+
{
|
|
95
|
+
if (!pBuckets || pBuckets.length === 0) return [];
|
|
96
|
+
return pBuckets.map((pBucket) => ({ Label: '', Value: pBucket[pStage] || 0 }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Render a per-histogram time axis: start | mid | end tick labels.
|
|
101
|
+
* The div is sized to match the histogram pixel width so labels align.
|
|
102
|
+
*/
|
|
103
|
+
_renderTimeAxis(pAxisId, pBuckets)
|
|
104
|
+
{
|
|
105
|
+
let tmpAxisEl = document.getElementById(pAxisId);
|
|
106
|
+
if (!tmpAxisEl) return;
|
|
107
|
+
|
|
108
|
+
if (!pBuckets || pBuckets.length < 2)
|
|
109
|
+
{
|
|
110
|
+
tmpAxisEl.innerHTML = '';
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let tmpWidth = pBuckets.length * BAR_STRIDE;
|
|
115
|
+
tmpAxisEl.style.width = tmpWidth + 'px';
|
|
116
|
+
|
|
117
|
+
let tmpFirst = pBuckets[0].time;
|
|
118
|
+
let tmpMid = pBuckets[Math.floor((pBuckets.length - 1) / 2)].time;
|
|
119
|
+
let tmpLast = pBuckets[pBuckets.length - 1].time;
|
|
120
|
+
|
|
121
|
+
tmpAxisEl.innerHTML =
|
|
122
|
+
'<span>' + _hhmmss(new Date(tmpFirst)) + '</span>'
|
|
123
|
+
+ '<span style="text-align:center;">' + _hhmmss(new Date(tmpMid)) + '</span>'
|
|
124
|
+
+ '<span style="text-align:right;">' + _hhmmss(new Date(tmpLast)) + '</span>';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─────────────────────────────────────────────
|
|
128
|
+
// Live monitoring
|
|
129
|
+
// ─────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
startMonitoring()
|
|
132
|
+
{
|
|
133
|
+
if (this._isPolling) return;
|
|
134
|
+
this._isPolling = true;
|
|
135
|
+
let tmpBtn = document.getElementById('facto-tp-toggle');
|
|
136
|
+
if (tmpBtn) { tmpBtn.textContent = 'Stop Monitoring'; tmpBtn.className = 'facto-btn facto-btn-danger'; }
|
|
137
|
+
this._poll();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
stopMonitoring()
|
|
141
|
+
{
|
|
142
|
+
this._isPolling = false;
|
|
143
|
+
if (this._pollTimer) { clearTimeout(this._pollTimer); this._pollTimer = null; }
|
|
144
|
+
let tmpBtn = document.getElementById('facto-tp-toggle');
|
|
145
|
+
if (tmpBtn) { tmpBtn.textContent = 'Start Live Monitoring'; tmpBtn.className = 'facto-btn facto-btn-primary'; }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
toggleMonitoring()
|
|
149
|
+
{
|
|
150
|
+
this._isPolling ? this.stopMonitoring() : this.startMonitoring();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_poll()
|
|
154
|
+
{
|
|
155
|
+
if (!this._isPolling) return;
|
|
156
|
+
fetch('/facto/throughput?duration=30')
|
|
157
|
+
.then((r) => r.json())
|
|
158
|
+
.then((d) => { this._renderHistograms(d); this._pollTimer = setTimeout(() => this._poll(), POLL_INTERVAL_MS); })
|
|
159
|
+
.catch(() => { this._pollTimer = setTimeout(() => this._poll(), POLL_INTERVAL_MS * 4); });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
loadRunHistory()
|
|
163
|
+
{
|
|
164
|
+
fetch('/facto/throughput/runs?limit=10')
|
|
165
|
+
.then((r) => r.json())
|
|
166
|
+
.then((runs) => { this._renderRunHistory(runs); })
|
|
167
|
+
.catch(() => {});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
loadHistoricalRun(pLabel)
|
|
171
|
+
{
|
|
172
|
+
this._activeHistoricalRun = pLabel;
|
|
173
|
+
this._activeDatasetFilter = null;
|
|
174
|
+
fetch('/facto/throughput/run/' + encodeURIComponent(pLabel))
|
|
175
|
+
.then((r) => r.json())
|
|
176
|
+
.then((d) => { d.historicalRun = pLabel; this._renderHistograms(d); this._loadDatasetBreakdown(pLabel); })
|
|
177
|
+
.catch(() => {});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
filterByDataset(pDataset)
|
|
181
|
+
{
|
|
182
|
+
if (!this._activeHistoricalRun) return;
|
|
183
|
+
this._activeDatasetFilter = pDataset;
|
|
184
|
+
let tmpUrl = '/facto/throughput/run/' + encodeURIComponent(this._activeHistoricalRun);
|
|
185
|
+
if (pDataset) tmpUrl += '?dataset=' + encodeURIComponent(pDataset);
|
|
186
|
+
fetch(tmpUrl)
|
|
187
|
+
.then((r) => r.json())
|
|
188
|
+
.then((d) => { d.historicalRun = this._activeHistoricalRun; d.datasetFilter = pDataset; this._renderHistograms(d); })
|
|
189
|
+
.catch(() => {});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
_loadDatasetBreakdown(pLabel)
|
|
193
|
+
{
|
|
194
|
+
fetch('/facto/throughput/run/' + encodeURIComponent(pLabel) + '/datasets')
|
|
195
|
+
.then((r) => r.json())
|
|
196
|
+
.then((ds) => { this._renderDatasetBreakdown(ds); })
|
|
197
|
+
.catch(() => {});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─────────────────────────────────────────────
|
|
201
|
+
// Rendering
|
|
202
|
+
// ─────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
_renderHistograms(pData)
|
|
205
|
+
{
|
|
206
|
+
let tmpBuckets = pData.buckets || [];
|
|
207
|
+
let tmpInterval = pData.interval || 1000;
|
|
208
|
+
|
|
209
|
+
// Compute totals for header badges
|
|
210
|
+
let tmpTotals = { extracted: 0, transformed: 0, written: 0 };
|
|
211
|
+
for (let i = 0; i < tmpBuckets.length; i++)
|
|
212
|
+
{
|
|
213
|
+
tmpTotals.extracted += tmpBuckets[i].extracted || 0;
|
|
214
|
+
tmpTotals.transformed += tmpBuckets[i].transformed || 0;
|
|
215
|
+
tmpTotals.written += tmpBuckets[i].written || 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Header badges + run/active info
|
|
219
|
+
let tmpHeaderEl = document.getElementById('facto-tp-header');
|
|
220
|
+
if (tmpHeaderEl)
|
|
221
|
+
{
|
|
222
|
+
let tmpHtml = '<div style="display:flex; gap:20px; flex-wrap:wrap; align-items:center;">';
|
|
223
|
+
tmpHtml += this._badge('Extracted', tmpTotals.extracted, '#4a90d9');
|
|
224
|
+
tmpHtml += this._badge('Transformed', tmpTotals.transformed, '#d09818');
|
|
225
|
+
tmpHtml += this._badge('Written', tmpTotals.written, '#3a9468');
|
|
226
|
+
|
|
227
|
+
if (pData.activeRun)
|
|
228
|
+
{
|
|
229
|
+
let tmpElapsed = ((Date.now() - pData.activeRun.startTime) / 1000).toFixed(1);
|
|
230
|
+
tmpHtml += '<span style="font-size:0.85em; color:var(--facto-text-secondary); display:flex; align-items:center; gap:6px;">'
|
|
231
|
+
+ '<span style="width:8px; height:8px; border-radius:50%; background:var(--facto-success); animation:pulse 1s infinite;"></span>'
|
|
232
|
+
+ pData.activeRun.label + ' (' + tmpElapsed + 's)</span>';
|
|
233
|
+
}
|
|
234
|
+
if (pData.historicalRun)
|
|
235
|
+
{
|
|
236
|
+
tmpHtml += '<span style="font-size:0.85em; color:var(--facto-text-secondary);">Viewing: ' + pData.historicalRun
|
|
237
|
+
+ (pData.datasetFilter ? ' → ' + pData.datasetFilter : '') + '</span>';
|
|
238
|
+
}
|
|
239
|
+
tmpHtml += '</div>';
|
|
240
|
+
tmpHeaderEl.innerHTML = tmpHtml;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Feed each histogram view + update its inline time axis
|
|
244
|
+
this._ensureHistogramViews();
|
|
245
|
+
for (let i = 0; i < HISTOGRAM_DEFS.length; i++)
|
|
246
|
+
{
|
|
247
|
+
let tmpDef = HISTOGRAM_DEFS[i];
|
|
248
|
+
let tmpView = this.pict.views[tmpDef.id];
|
|
249
|
+
if (!tmpView) continue;
|
|
250
|
+
|
|
251
|
+
let tmpBins = this._bucketsToHistogramBins(tmpBuckets, tmpDef.stage);
|
|
252
|
+
tmpView.setBins(tmpBins);
|
|
253
|
+
tmpView.renderHistogram();
|
|
254
|
+
|
|
255
|
+
this._renderTimeAxis('Facto-Full-Histogram-' + _capitalise(tmpDef.stage) + '-Axis', tmpBuckets);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_badge(pLabel, pCount, pColor)
|
|
260
|
+
{
|
|
261
|
+
return '<div style="display:flex; align-items:center; gap:6px;">'
|
|
262
|
+
+ '<div style="width:10px; height:10px; border-radius:2px; background:' + pColor + ';"></div>'
|
|
263
|
+
+ '<span style="font-weight:600; font-size:0.88em;">' + pLabel + ':</span>'
|
|
264
|
+
+ '<span style="font-size:0.88em; color:var(--facto-text-secondary);">' + pCount.toLocaleString() + '</span>'
|
|
265
|
+
+ '</div>';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_renderRunHistory(pRuns)
|
|
269
|
+
{
|
|
270
|
+
let tmpEl = document.getElementById('facto-tp-history');
|
|
271
|
+
if (!tmpEl) return;
|
|
272
|
+
if (!pRuns || pRuns.length === 0)
|
|
273
|
+
{
|
|
274
|
+
tmpEl.innerHTML = '<p style="color:var(--facto-text-tertiary); font-size:0.85em; font-style:italic;">No historical runs yet. Run a pipeline ingest first.</p>';
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let tmpHtml = '<div style="font-size:0.85em; font-weight:600; color:var(--facto-text-heading); margin-bottom:8px;">Run History</div>'
|
|
279
|
+
+ '<div style="display:flex; flex-wrap:wrap; gap:8px;">';
|
|
280
|
+
for (let i = 0; i < pRuns.length; i++)
|
|
281
|
+
{
|
|
282
|
+
let tmpRun = pRuns[i];
|
|
283
|
+
let tmpDate = new Date(tmpRun.startTime).toLocaleString();
|
|
284
|
+
tmpHtml += '<button class="facto-btn facto-btn-secondary" style="font-size:0.8em;" '
|
|
285
|
+
+ 'onclick="pict.views[\'Facto-Full-Throughput\'].loadHistoricalRun(\'' + tmpRun.label.replace(/'/g, "\\'") + '\')" '
|
|
286
|
+
+ 'title="' + tmpDate + ' — ' + tmpRun.eventCount + ' events">'
|
|
287
|
+
+ tmpRun.label.substring(0, 35) + (tmpRun.label.length > 35 ? '...' : '') + '</button>';
|
|
288
|
+
}
|
|
289
|
+
tmpHtml += '</div>';
|
|
290
|
+
tmpEl.innerHTML = tmpHtml;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_renderDatasetBreakdown(pDatasets)
|
|
294
|
+
{
|
|
295
|
+
let tmpEl = document.getElementById('facto-tp-datasets');
|
|
296
|
+
if (!tmpEl) return;
|
|
297
|
+
if (!pDatasets || pDatasets.length === 0) { tmpEl.innerHTML = ''; return; }
|
|
298
|
+
|
|
299
|
+
let tmpMaxTotal = 1;
|
|
300
|
+
for (let i = 0; i < pDatasets.length; i++)
|
|
301
|
+
{
|
|
302
|
+
if (pDatasets[i].total > tmpMaxTotal) tmpMaxTotal = pDatasets[i].total;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let tmpHtml = '<div style="font-size:0.85em; font-weight:600; color:var(--facto-text-heading); margin-bottom:8px; margin-top:16px;">Per-Dataset Breakdown</div>';
|
|
306
|
+
tmpHtml += '<div style="font-size:0.8em; margin-bottom:6px; color:var(--facto-text-tertiary);">Click a dataset to filter the histogram:</div>';
|
|
307
|
+
for (let i = 0; i < pDatasets.length; i++)
|
|
308
|
+
{
|
|
309
|
+
let tmpDS = pDatasets[i];
|
|
310
|
+
let tmpActive = this._activeDatasetFilter === tmpDS.dataset;
|
|
311
|
+
let tmpDatasetEsc = tmpDS.dataset.replace(/'/g, "\\'");
|
|
312
|
+
tmpHtml += '<div style="display:flex; align-items:center; gap:8px; margin-bottom:4px; cursor:pointer; padding:3px 8px; border-radius:4px; '
|
|
313
|
+
+ (tmpActive ? 'background:var(--facto-brand-a15);' : '') + '" '
|
|
314
|
+
+ 'onclick="pict.views[\'Facto-Full-Throughput\'].filterByDataset(' + (tmpActive ? 'null' : '\'' + tmpDatasetEsc + '\'') + ')">';
|
|
315
|
+
tmpHtml += '<span style="min-width:220px; font-family:monospace; font-size:0.9em; color:var(--facto-text);">' + tmpDS.dataset + '</span>';
|
|
316
|
+
tmpHtml += '<div style="flex:1; height:16px; display:flex; border-radius:3px; overflow:hidden; background:var(--facto-bg-elevated);">';
|
|
317
|
+
if (tmpDS.extracted > 0) tmpHtml += '<div style="width:' + ((tmpDS.extracted / tmpMaxTotal) * 100) + '%; background:#4a90d9;" title="Extracted: ' + tmpDS.extracted + '"></div>';
|
|
318
|
+
if (tmpDS.transformed > 0) tmpHtml += '<div style="width:' + ((tmpDS.transformed / tmpMaxTotal) * 100) + '%; background:#d09818;" title="Transformed: ' + tmpDS.transformed + '"></div>';
|
|
319
|
+
if (tmpDS.written > 0) tmpHtml += '<div style="width:' + ((tmpDS.written / tmpMaxTotal) * 100) + '%; background:#3a9468;" title="Written: ' + tmpDS.written + '"></div>';
|
|
320
|
+
tmpHtml += '</div>';
|
|
321
|
+
tmpHtml += '<span style="min-width:60px; text-align:right; font-size:0.85em; color:var(--facto-text-secondary);">' + tmpDS.total.toLocaleString() + '</span></div>';
|
|
322
|
+
}
|
|
323
|
+
tmpEl.innerHTML = tmpHtml;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ─────────────────────────────────────────────
|
|
328
|
+
// Helpers
|
|
329
|
+
// ─────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
function _hhmmss(pDate)
|
|
332
|
+
{
|
|
333
|
+
return String(pDate.getHours()).padStart(2, '0')
|
|
334
|
+
+ ':' + String(pDate.getMinutes()).padStart(2, '0')
|
|
335
|
+
+ ':' + String(pDate.getSeconds()).padStart(2, '0');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function _capitalise(pStr)
|
|
339
|
+
{
|
|
340
|
+
if (!pStr) return '';
|
|
341
|
+
return pStr.charAt(0).toUpperCase() + pStr.slice(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = FactoFullThroughputView;
|
|
345
|
+
|
|
346
|
+
module.exports.default_configuration =
|
|
347
|
+
{
|
|
348
|
+
ViewIdentifier: 'Facto-Full-Throughput',
|
|
349
|
+
DefaultRenderable: 'Facto-Full-Throughput-Content',
|
|
350
|
+
DefaultDestinationAddress: '#Facto-Full-Content-Container',
|
|
351
|
+
AutoRender: false,
|
|
352
|
+
Templates:
|
|
353
|
+
[
|
|
354
|
+
{
|
|
355
|
+
Hash: 'Facto-Full-Throughput-Template',
|
|
356
|
+
Template: /*html*/`
|
|
357
|
+
<div style="max-width:1200px; margin:0 auto;">
|
|
358
|
+
<h2 style="margin-bottom:4px;">Pipeline Throughput</h2>
|
|
359
|
+
<p style="color:var(--facto-text-secondary); margin-bottom:12px; font-size:0.9em;">
|
|
360
|
+
Temporal histograms showing record flow through extraction, transformation, and storage stages.
|
|
361
|
+
</p>
|
|
362
|
+
|
|
363
|
+
<div style="display:flex; gap:8px; margin-bottom:12px; flex-wrap:wrap;">
|
|
364
|
+
<button id="facto-tp-toggle" class="facto-btn facto-btn-primary" onclick="pict.views['Facto-Full-Throughput'].toggleMonitoring()">Start Live Monitoring</button>
|
|
365
|
+
<button class="facto-btn facto-btn-secondary" onclick="pict.views['Facto-Full-Throughput'].loadRunHistory()">Refresh History</button>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<div id="facto-tp-header" style="margin-bottom:12px;"></div>
|
|
369
|
+
|
|
370
|
+
<div id="facto-tp-history" style="margin-bottom:16px;"></div>
|
|
371
|
+
|
|
372
|
+
<div id="facto-tp-charts">
|
|
373
|
+
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px;">
|
|
374
|
+
|
|
375
|
+
<div style="background:rgba(74,144,217,0.1); border-radius:8px; padding:12px;">
|
|
376
|
+
<div style="font-size:0.85em; font-weight:600; color:#4a90d9; margin-bottom:6px;">Extracted</div>
|
|
377
|
+
<div style="overflow-x:auto;">
|
|
378
|
+
<div id="Facto-Full-Histogram-Extracted-Container"></div>
|
|
379
|
+
<div id="Facto-Full-Histogram-Extracted-Axis"
|
|
380
|
+
style="display:flex; justify-content:space-between; font-size:0.72em; font-family:monospace;
|
|
381
|
+
color:var(--facto-text-tertiary); margin-top:3px; padding-top:3px;
|
|
382
|
+
border-top:1px solid rgba(74,144,217,0.3);"></div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<div style="background:rgba(208,152,24,0.1); border-radius:8px; padding:12px;">
|
|
387
|
+
<div style="font-size:0.85em; font-weight:600; color:#d09818; margin-bottom:6px;">Transformed</div>
|
|
388
|
+
<div style="overflow-x:auto;">
|
|
389
|
+
<div id="Facto-Full-Histogram-Transformed-Container"></div>
|
|
390
|
+
<div id="Facto-Full-Histogram-Transformed-Axis"
|
|
391
|
+
style="display:flex; justify-content:space-between; font-size:0.72em; font-family:monospace;
|
|
392
|
+
color:var(--facto-text-tertiary); margin-top:3px; padding-top:3px;
|
|
393
|
+
border-top:1px solid rgba(208,152,24,0.3);"></div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
<div style="background:rgba(58,148,104,0.1); border-radius:8px; padding:12px;">
|
|
398
|
+
<div style="font-size:0.85em; font-weight:600; color:#3a9468; margin-bottom:6px;">Written</div>
|
|
399
|
+
<div style="overflow-x:auto;">
|
|
400
|
+
<div id="Facto-Full-Histogram-Written-Container"></div>
|
|
401
|
+
<div id="Facto-Full-Histogram-Written-Axis"
|
|
402
|
+
style="display:flex; justify-content:space-between; font-size:0.72em; font-family:monospace;
|
|
403
|
+
color:var(--facto-text-tertiary); margin-top:3px; padding-top:3px;
|
|
404
|
+
border-top:1px solid rgba(58,148,104,0.3);"></div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<div style="background:var(--facto-bg-elevated); border:1px solid var(--facto-border-subtle); border-radius:8px; padding:12px;">
|
|
409
|
+
<div style="font-size:0.85em; font-weight:600; color:#6366f1; margin-bottom:6px;">Total</div>
|
|
410
|
+
<div style="overflow-x:auto;">
|
|
411
|
+
<div id="Facto-Full-Histogram-Total-Container"></div>
|
|
412
|
+
<div id="Facto-Full-Histogram-Total-Axis"
|
|
413
|
+
style="display:flex; justify-content:space-between; font-size:0.72em; font-family:monospace;
|
|
414
|
+
color:var(--facto-text-tertiary); margin-top:3px; padding-top:3px;
|
|
415
|
+
border-top:1px solid rgba(99,102,241,0.3);"></div>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<div id="facto-tp-datasets"></div>
|
|
423
|
+
|
|
424
|
+
<style>
|
|
425
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
426
|
+
</style>
|
|
427
|
+
</div>
|
|
428
|
+
`
|
|
429
|
+
}
|
|
430
|
+
],
|
|
431
|
+
Renderables:
|
|
432
|
+
[
|
|
433
|
+
{
|
|
434
|
+
RenderableHash: 'Facto-Full-Throughput-Content',
|
|
435
|
+
TemplateHash: 'Facto-Full-Throughput-Template',
|
|
436
|
+
DestinationAddress: '#Facto-Full-Content-Container'
|
|
437
|
+
}
|
|
438
|
+
]
|
|
439
|
+
};
|