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,173 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class FactoDatasetsView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onAfterRender()
|
|
11
|
+
{
|
|
12
|
+
this.pict.providers.Facto.loadDatasets().then(
|
|
13
|
+
() =>
|
|
14
|
+
{
|
|
15
|
+
this.refreshList();
|
|
16
|
+
}).catch(
|
|
17
|
+
(pError) =>
|
|
18
|
+
{
|
|
19
|
+
this.pict.views['Pict-Section-Modal'].toast('Error loading datasets: ' + pError.message, {type: 'error'});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
refreshList()
|
|
24
|
+
{
|
|
25
|
+
let tmpContainer = document.getElementById('facto-datasets-list');
|
|
26
|
+
if (!tmpContainer) return;
|
|
27
|
+
|
|
28
|
+
let tmpDatasets = this.pict.AppData.Facto.Datasets;
|
|
29
|
+
if (!tmpDatasets || tmpDatasets.length === 0)
|
|
30
|
+
{
|
|
31
|
+
tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No datasets created yet.</p>';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let tmpBadgeClass = { Raw: 'badge-raw', Compositional: 'badge-compositional', Projection: 'badge-projection', Derived: 'badge-derived' };
|
|
36
|
+
|
|
37
|
+
let tmpHtml = '<table><thead><tr><th>ID</th><th>Name</th><th>Type</th><th>Description</th><th>Actions</th></tr></thead><tbody>';
|
|
38
|
+
for (let i = 0; i < tmpDatasets.length; i++)
|
|
39
|
+
{
|
|
40
|
+
let tmpDataset = tmpDatasets[i];
|
|
41
|
+
let tmpBadge = tmpBadgeClass[tmpDataset.Type] || 'badge-raw';
|
|
42
|
+
tmpHtml += '<tr>';
|
|
43
|
+
tmpHtml += '<td>' + (tmpDataset.IDDataset || '') + '</td>';
|
|
44
|
+
tmpHtml += '<td>' + (tmpDataset.Name || '') + '</td>';
|
|
45
|
+
tmpHtml += '<td><span class="badge ' + tmpBadge + '">' + (tmpDataset.Type || '') + '</span></td>';
|
|
46
|
+
tmpHtml += '<td style="max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">' + (tmpDataset.Description || '') + '</td>';
|
|
47
|
+
tmpHtml += '<td><button class="secondary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Datasets\'].viewStats(' + tmpDataset.IDDataset + ')">Stats</button></td>';
|
|
48
|
+
tmpHtml += '</tr>';
|
|
49
|
+
}
|
|
50
|
+
tmpHtml += '</tbody></table>';
|
|
51
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
viewStats(pIDDataset)
|
|
55
|
+
{
|
|
56
|
+
this.pict.providers.Facto.loadDatasetStats(pIDDataset).then(
|
|
57
|
+
(pResponse) =>
|
|
58
|
+
{
|
|
59
|
+
let tmpMsg = 'Dataset: ' + (pResponse.Dataset ? pResponse.Dataset.Name : '#' + pIDDataset);
|
|
60
|
+
tmpMsg += ' | Records: ' + (pResponse.RecordCount || 0);
|
|
61
|
+
tmpMsg += ' | Linked Sources: ' + (pResponse.SourceCount || 0);
|
|
62
|
+
this.pict.views['Pict-Section-Modal'].toast(tmpMsg, {type: 'info'});
|
|
63
|
+
}).catch(
|
|
64
|
+
(pError) =>
|
|
65
|
+
{
|
|
66
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
addDataset()
|
|
71
|
+
{
|
|
72
|
+
let tmpName = this.pict.providers.FactoUI.getVal('facto-dataset-name');
|
|
73
|
+
let tmpType = this.pict.providers.FactoUI.getVal('facto-dataset-type') || 'Raw';
|
|
74
|
+
let tmpDescription = this.pict.providers.FactoUI.getVal('facto-dataset-desc');
|
|
75
|
+
|
|
76
|
+
if (!tmpName)
|
|
77
|
+
{
|
|
78
|
+
this.pict.views['Pict-Section-Modal'].toast('Name is required', {type: 'warning'});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.pict.providers.Facto.createDataset(
|
|
83
|
+
{
|
|
84
|
+
Name: tmpName,
|
|
85
|
+
Type: tmpType,
|
|
86
|
+
Description: tmpDescription
|
|
87
|
+
}).then(
|
|
88
|
+
(pResponse) =>
|
|
89
|
+
{
|
|
90
|
+
if (pResponse && pResponse.IDDataset)
|
|
91
|
+
{
|
|
92
|
+
this.pict.views['Pict-Section-Modal'].toast('Dataset created: ' + pResponse.Name, {type: 'success'});
|
|
93
|
+
if (document.getElementById('facto-dataset-name')) document.getElementById('facto-dataset-name').value = '';
|
|
94
|
+
if (document.getElementById('facto-dataset-desc')) document.getElementById('facto-dataset-desc').value = '';
|
|
95
|
+
return this.pict.providers.Facto.loadDatasets();
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
{
|
|
99
|
+
this.pict.views['Pict-Section-Modal'].toast('Error creating dataset', {type: 'error'});
|
|
100
|
+
}
|
|
101
|
+
}).then(
|
|
102
|
+
() =>
|
|
103
|
+
{
|
|
104
|
+
this.refreshList();
|
|
105
|
+
}).catch(
|
|
106
|
+
(pError) =>
|
|
107
|
+
{
|
|
108
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = FactoDatasetsView;
|
|
114
|
+
|
|
115
|
+
module.exports.default_configuration =
|
|
116
|
+
{
|
|
117
|
+
ViewIdentifier: 'Facto-Datasets',
|
|
118
|
+
DefaultRenderable: 'Facto-Datasets',
|
|
119
|
+
DefaultDestinationAddress: '#Facto-Section-Datasets',
|
|
120
|
+
Templates:
|
|
121
|
+
[
|
|
122
|
+
{
|
|
123
|
+
Hash: 'Facto-Datasets',
|
|
124
|
+
Template: /*html*/`
|
|
125
|
+
<div class="accordion-row">
|
|
126
|
+
<div class="accordion-number">2</div>
|
|
127
|
+
<div class="accordion-card open" id="facto-card-datasets">
|
|
128
|
+
<div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-datasets')">
|
|
129
|
+
<span class="accordion-title">Datasets</span>
|
|
130
|
+
<span class="accordion-preview">Raw, Compositional, Projection, Derived</span>
|
|
131
|
+
<span class="accordion-toggle">▼</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="accordion-body">
|
|
134
|
+
<p style="margin-bottom:12px; color:#666; font-size:0.9em;">Datasets are named collections of records. Types: Raw (ingested), Compositional (merged), Projection (flattened), Derived (computed).</p>
|
|
135
|
+
<div id="facto-datasets-list"></div>
|
|
136
|
+
|
|
137
|
+
<h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Create Dataset</h3>
|
|
138
|
+
<div class="inline-group">
|
|
139
|
+
<div>
|
|
140
|
+
<label for="facto-dataset-name">Name</label>
|
|
141
|
+
<input type="text" id="facto-dataset-name" placeholder="e.g. Census Population 2020">
|
|
142
|
+
</div>
|
|
143
|
+
<div>
|
|
144
|
+
<label for="facto-dataset-type">Type</label>
|
|
145
|
+
<select id="facto-dataset-type">
|
|
146
|
+
<option value="Raw">Raw</option>
|
|
147
|
+
<option value="Compositional">Compositional</option>
|
|
148
|
+
<option value="Projection">Projection</option>
|
|
149
|
+
<option value="Derived">Derived</option>
|
|
150
|
+
</select>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<label for="facto-dataset-desc">Description</label>
|
|
155
|
+
<input type="text" id="facto-dataset-desc" placeholder="Brief description of the dataset">
|
|
156
|
+
</div>
|
|
157
|
+
<button class="primary" onclick="pict.views['Facto-Datasets'].addDataset()">Create Dataset</button>
|
|
158
|
+
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
Renderables:
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
RenderableHash: 'Facto-Datasets',
|
|
169
|
+
TemplateHash: 'Facto-Datasets',
|
|
170
|
+
DestinationAddress: '#Facto-Section-Datasets'
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class FactoIngestView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onAfterRender()
|
|
11
|
+
{
|
|
12
|
+
this.pict.providers.Facto.loadIngestJobs().then(
|
|
13
|
+
() =>
|
|
14
|
+
{
|
|
15
|
+
this.refreshList();
|
|
16
|
+
}).catch(
|
|
17
|
+
(pError) =>
|
|
18
|
+
{
|
|
19
|
+
this.pict.views['Pict-Section-Modal'].toast('Error loading jobs: ' + pError.message, {type: 'error'});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
refreshList()
|
|
24
|
+
{
|
|
25
|
+
let tmpContainer = document.getElementById('facto-ingest-list');
|
|
26
|
+
if (!tmpContainer) return;
|
|
27
|
+
|
|
28
|
+
let tmpJobs = this.pict.AppData.Facto.IngestJobs;
|
|
29
|
+
if (!tmpJobs || tmpJobs.length === 0)
|
|
30
|
+
{
|
|
31
|
+
tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No ingest jobs yet.</p>';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let tmpStatusColors = { Pending: '#ffc107', Running: '#17a2b8', Completed: '#28a745', Failed: '#dc3545', Cancelled: '#6c757d' };
|
|
36
|
+
|
|
37
|
+
let tmpHtml = '<table><thead><tr><th>ID</th><th>Status</th><th>Source</th><th>Dataset</th><th>Processed</th><th>Created</th><th>Errors</th><th>Start</th><th>Actions</th></tr></thead><tbody>';
|
|
38
|
+
for (let i = 0; i < tmpJobs.length; i++)
|
|
39
|
+
{
|
|
40
|
+
let tmpJob = tmpJobs[i];
|
|
41
|
+
let tmpColor = tmpStatusColors[tmpJob.Status] || '#888';
|
|
42
|
+
tmpHtml += '<tr>';
|
|
43
|
+
tmpHtml += '<td>' + (tmpJob.IDIngestJob || '') + '</td>';
|
|
44
|
+
tmpHtml += '<td><span style="color:' + tmpColor + '; font-weight:600;">' + (tmpJob.Status || '') + '</span></td>';
|
|
45
|
+
tmpHtml += '<td>' + (tmpJob.IDSource || '') + '</td>';
|
|
46
|
+
tmpHtml += '<td>' + (tmpJob.IDDataset || '') + '</td>';
|
|
47
|
+
tmpHtml += '<td>' + (tmpJob.RecordsProcessed || 0) + '</td>';
|
|
48
|
+
tmpHtml += '<td>' + (tmpJob.RecordsCreated || 0) + '</td>';
|
|
49
|
+
tmpHtml += '<td>' + (tmpJob.RecordsErrored || 0) + '</td>';
|
|
50
|
+
tmpHtml += '<td>' + (tmpJob.StartDate || '-') + '</td>';
|
|
51
|
+
tmpHtml += '<td>';
|
|
52
|
+
if (tmpJob.Status === 'Pending')
|
|
53
|
+
{
|
|
54
|
+
tmpHtml += '<button class="success" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Ingest\'].startJob(' + tmpJob.IDIngestJob + ')">Start</button>';
|
|
55
|
+
}
|
|
56
|
+
tmpHtml += '<button class="secondary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Ingest\'].viewLog(' + tmpJob.IDIngestJob + ')">Log</button>';
|
|
57
|
+
tmpHtml += '</td>';
|
|
58
|
+
tmpHtml += '</tr>';
|
|
59
|
+
}
|
|
60
|
+
tmpHtml += '</tbody></table>';
|
|
61
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
startJob(pIDIngestJob)
|
|
65
|
+
{
|
|
66
|
+
this.pict.providers.Facto.startIngestJob(pIDIngestJob).then(
|
|
67
|
+
() =>
|
|
68
|
+
{
|
|
69
|
+
this.pict.views['Pict-Section-Modal'].toast('Job #' + pIDIngestJob + ' started', {type: 'success'});
|
|
70
|
+
return this.pict.providers.Facto.loadIngestJobs();
|
|
71
|
+
}).then(
|
|
72
|
+
() =>
|
|
73
|
+
{
|
|
74
|
+
this.refreshList();
|
|
75
|
+
}).catch(
|
|
76
|
+
(pError) =>
|
|
77
|
+
{
|
|
78
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
viewLog(pIDIngestJob)
|
|
83
|
+
{
|
|
84
|
+
this.pict.providers.Facto.loadIngestJobDetails(pIDIngestJob).then(
|
|
85
|
+
(pResponse) =>
|
|
86
|
+
{
|
|
87
|
+
if (pResponse && pResponse.Job)
|
|
88
|
+
{
|
|
89
|
+
let tmpLog = pResponse.Job.Log || '(empty)';
|
|
90
|
+
let tmpLogContainer = document.getElementById('facto-ingest-log');
|
|
91
|
+
if (tmpLogContainer)
|
|
92
|
+
{
|
|
93
|
+
tmpLogContainer.textContent = 'Job #' + pIDIngestJob + ' Log:\n' + tmpLog;
|
|
94
|
+
tmpLogContainer.style.display = 'block';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}).catch(
|
|
98
|
+
(pError) =>
|
|
99
|
+
{
|
|
100
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ingestPastedContent()
|
|
105
|
+
{
|
|
106
|
+
let tmpIDDataset = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-paste-dataset'), 10) || 0;
|
|
107
|
+
let tmpIDSource = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-paste-source'), 10) || 0;
|
|
108
|
+
let tmpFormat = this.pict.providers.FactoUI.getVal('facto-ingest-paste-format') || 'Auto';
|
|
109
|
+
let tmpType = this.pict.providers.FactoUI.getVal('facto-ingest-paste-type') || 'data';
|
|
110
|
+
let tmpContent = this.pict.providers.FactoUI.getVal('facto-ingest-paste-content');
|
|
111
|
+
|
|
112
|
+
if (!tmpContent.trim())
|
|
113
|
+
{
|
|
114
|
+
this.pict.views['Pict-Section-Modal'].toast('Content is required', {type: 'warning'});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.pict.views['Pict-Section-Modal'].toast('Ingesting...', {type: 'info'});
|
|
119
|
+
|
|
120
|
+
this.pict.providers.Facto.ingestFileContent(tmpIDDataset, tmpIDSource, tmpContent, tmpFormat, tmpType).then(
|
|
121
|
+
(pResponse) =>
|
|
122
|
+
{
|
|
123
|
+
if (pResponse && pResponse.Success)
|
|
124
|
+
{
|
|
125
|
+
this.pict.views['Pict-Section-Modal'].toast('Ingested ' + (pResponse.RecordsCreated || 0) + ' records', {type: 'success'});
|
|
126
|
+
if (document.getElementById('facto-ingest-paste-content'))
|
|
127
|
+
{
|
|
128
|
+
document.getElementById('facto-ingest-paste-content').value = '';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else
|
|
132
|
+
{
|
|
133
|
+
this.pict.views['Pict-Section-Modal'].toast('Ingest error: ' + ((pResponse && pResponse.Error) || 'Unknown'), {type: 'error'});
|
|
134
|
+
}
|
|
135
|
+
}).catch(
|
|
136
|
+
(pError) =>
|
|
137
|
+
{
|
|
138
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
createJob()
|
|
143
|
+
{
|
|
144
|
+
let tmpIDSource = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-source'), 10) || 0;
|
|
145
|
+
let tmpIDDataset = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-dataset'), 10) || 0;
|
|
146
|
+
|
|
147
|
+
if (!tmpIDSource || !tmpIDDataset)
|
|
148
|
+
{
|
|
149
|
+
this.pict.views['Pict-Section-Modal'].toast('Source ID and Dataset ID are required', {type: 'warning'});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.pict.providers.Facto.createIngestJob(tmpIDSource, tmpIDDataset).then(
|
|
154
|
+
(pResponse) =>
|
|
155
|
+
{
|
|
156
|
+
if (pResponse && pResponse.Success)
|
|
157
|
+
{
|
|
158
|
+
this.pict.views['Pict-Section-Modal'].toast('Ingest job created: #' + pResponse.Job.IDIngestJob, {type: 'success'});
|
|
159
|
+
if (document.getElementById('facto-ingest-source')) document.getElementById('facto-ingest-source').value = '';
|
|
160
|
+
if (document.getElementById('facto-ingest-dataset')) document.getElementById('facto-ingest-dataset').value = '';
|
|
161
|
+
return this.pict.providers.Facto.loadIngestJobs();
|
|
162
|
+
}
|
|
163
|
+
else
|
|
164
|
+
{
|
|
165
|
+
this.pict.views['Pict-Section-Modal'].toast('Error creating job', {type: 'error'});
|
|
166
|
+
}
|
|
167
|
+
}).then(
|
|
168
|
+
() =>
|
|
169
|
+
{
|
|
170
|
+
this.refreshList();
|
|
171
|
+
}).catch(
|
|
172
|
+
(pError) =>
|
|
173
|
+
{
|
|
174
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = FactoIngestView;
|
|
180
|
+
|
|
181
|
+
module.exports.default_configuration =
|
|
182
|
+
{
|
|
183
|
+
ViewIdentifier: 'Facto-Ingest',
|
|
184
|
+
DefaultRenderable: 'Facto-Ingest',
|
|
185
|
+
DefaultDestinationAddress: '#Facto-Section-Ingest',
|
|
186
|
+
Templates:
|
|
187
|
+
[
|
|
188
|
+
{
|
|
189
|
+
Hash: 'Facto-Ingest',
|
|
190
|
+
Template: /*html*/`
|
|
191
|
+
<div class="accordion-row">
|
|
192
|
+
<div class="accordion-number">4</div>
|
|
193
|
+
<div class="accordion-card" id="facto-card-ingest">
|
|
194
|
+
<div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-ingest')">
|
|
195
|
+
<span class="accordion-title">Ingest Jobs</span>
|
|
196
|
+
<span class="accordion-preview">Download, parse, and store datasets</span>
|
|
197
|
+
<span class="accordion-toggle">▼</span>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="accordion-body">
|
|
200
|
+
<p style="margin-bottom:12px; color:#666; font-size:0.9em;">Track data ingest operations from configured sources into datasets.</p>
|
|
201
|
+
<div id="facto-ingest-list"></div>
|
|
202
|
+
|
|
203
|
+
<h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Create Ingest Job</h3>
|
|
204
|
+
<div class="inline-group">
|
|
205
|
+
<div>
|
|
206
|
+
<label for="facto-ingest-source">Source ID</label>
|
|
207
|
+
<input type="number" id="facto-ingest-source" placeholder="1">
|
|
208
|
+
</div>
|
|
209
|
+
<div>
|
|
210
|
+
<label for="facto-ingest-dataset">Dataset ID</label>
|
|
211
|
+
<input type="number" id="facto-ingest-dataset" placeholder="1">
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
<button class="primary" onclick="pict.views['Facto-Ingest'].createJob()">Create Job</button>
|
|
215
|
+
|
|
216
|
+
<pre id="facto-ingest-log" style="display:none; margin-top:12px; padding:12px; background:#f8f9fa; border:1px solid #e9ecef; border-radius:4px; font-size:0.85em; max-height:200px; overflow:auto; white-space:pre-wrap;"></pre>
|
|
217
|
+
|
|
218
|
+
<h3 style="margin-top:20px; margin-bottom:8px; font-size:1em; color:#444;">Paste & Ingest</h3>
|
|
219
|
+
<p style="margin-bottom:8px; color:#666; font-size:0.85em;">Paste CSV or JSON content directly to ingest records.</p>
|
|
220
|
+
<div class="inline-group">
|
|
221
|
+
<div>
|
|
222
|
+
<label for="facto-ingest-paste-dataset">Dataset ID</label>
|
|
223
|
+
<input type="number" id="facto-ingest-paste-dataset" placeholder="1">
|
|
224
|
+
</div>
|
|
225
|
+
<div>
|
|
226
|
+
<label for="facto-ingest-paste-source">Source ID</label>
|
|
227
|
+
<input type="number" id="facto-ingest-paste-source" placeholder="1">
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<label for="facto-ingest-paste-format">Format</label>
|
|
231
|
+
<select id="facto-ingest-paste-format">
|
|
232
|
+
<option value="Auto">Auto-Detect</option>
|
|
233
|
+
<option value="CSV">CSV</option>
|
|
234
|
+
<option value="JSON">JSON</option>
|
|
235
|
+
</select>
|
|
236
|
+
</div>
|
|
237
|
+
<div>
|
|
238
|
+
<label for="facto-ingest-paste-type">Record Type</label>
|
|
239
|
+
<input type="text" id="facto-ingest-paste-type" placeholder="data">
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
<textarea id="facto-ingest-paste-content" rows="6" style="width:100%; padding:8px 12px; border:1px solid #ccc; border-radius:4px; font-size:0.9em; font-family:monospace; margin-bottom:10px;" placeholder="Paste CSV or JSON data here..."></textarea>
|
|
243
|
+
<button class="primary" onclick="pict.views['Facto-Ingest'].ingestPastedContent()">Ingest</button>
|
|
244
|
+
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
`
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
Renderables:
|
|
252
|
+
[
|
|
253
|
+
{
|
|
254
|
+
RenderableHash: 'Facto-Ingest',
|
|
255
|
+
TemplateHash: 'Facto-Ingest',
|
|
256
|
+
DestinationAddress: '#Facto-Section-Ingest'
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class FactoLayoutView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onAfterRender()
|
|
11
|
+
{
|
|
12
|
+
// Render all section views into their containers
|
|
13
|
+
if (this.pict.views['Facto-Throughput']) this.pict.views['Facto-Throughput'].render();
|
|
14
|
+
this.pict.views['Facto-Scanner'].render();
|
|
15
|
+
this.pict.views['Facto-Catalog'].render();
|
|
16
|
+
this.pict.views['Facto-Sources'].render();
|
|
17
|
+
this.pict.views['Facto-Datasets'].render();
|
|
18
|
+
this.pict.views['Facto-Records'].render();
|
|
19
|
+
this.pict.views['Facto-Ingest'].render();
|
|
20
|
+
this.pict.views['Facto-Projections'].render();
|
|
21
|
+
|
|
22
|
+
this.pict.CSSMap.injectCSS();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toggleSection(pSectionId)
|
|
26
|
+
{
|
|
27
|
+
let tmpCard = document.getElementById(pSectionId);
|
|
28
|
+
if (!tmpCard) return;
|
|
29
|
+
tmpCard.classList.toggle('open');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
expandAllSections()
|
|
33
|
+
{
|
|
34
|
+
let tmpCards = document.querySelectorAll('.accordion-card');
|
|
35
|
+
for (let i = 0; i < tmpCards.length; i++)
|
|
36
|
+
{
|
|
37
|
+
tmpCards[i].classList.add('open');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
collapseAllSections()
|
|
42
|
+
{
|
|
43
|
+
let tmpCards = document.querySelectorAll('.accordion-card');
|
|
44
|
+
for (let i = 0; i < tmpCards.length; i++)
|
|
45
|
+
{
|
|
46
|
+
tmpCards[i].classList.remove('open');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = FactoLayoutView;
|
|
52
|
+
|
|
53
|
+
module.exports.default_configuration =
|
|
54
|
+
{
|
|
55
|
+
ViewIdentifier: 'Facto-Layout',
|
|
56
|
+
DefaultRenderable: 'Facto-Layout',
|
|
57
|
+
DefaultDestinationAddress: '#Facto-Application-Container',
|
|
58
|
+
CSS: /*css*/`
|
|
59
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
60
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 20px; }
|
|
61
|
+
h1 { margin-bottom: 20px; color: #1a1a1a; }
|
|
62
|
+
h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px solid #ddd; padding-bottom: 6px; }
|
|
63
|
+
|
|
64
|
+
.section { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
65
|
+
|
|
66
|
+
/* Accordion layout */
|
|
67
|
+
.accordion-row { display: flex; gap: 0; margin-bottom: 16px; align-items: stretch; }
|
|
68
|
+
.accordion-number {
|
|
69
|
+
flex: 0 0 48px; display: flex; align-items: flex-start; justify-content: center;
|
|
70
|
+
padding-top: 16px; font-size: 1.6em; font-weight: 700; color: #4a90d9;
|
|
71
|
+
user-select: none;
|
|
72
|
+
}
|
|
73
|
+
.accordion-card {
|
|
74
|
+
flex: 1; background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
75
|
+
overflow: hidden; min-width: 0;
|
|
76
|
+
}
|
|
77
|
+
.accordion-header {
|
|
78
|
+
display: flex; align-items: center; padding: 14px 20px; cursor: pointer;
|
|
79
|
+
user-select: none; gap: 12px; transition: background 0.15s; line-height: 1.4;
|
|
80
|
+
}
|
|
81
|
+
.accordion-header:hover { background: #fafafa; }
|
|
82
|
+
.accordion-title { font-weight: 600; color: #333; font-size: 1.05em; white-space: nowrap; }
|
|
83
|
+
.accordion-preview { flex: 1; font-style: italic; color: #888; font-size: 0.9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
84
|
+
.accordion-toggle {
|
|
85
|
+
flex: 0 0 20px; display: flex; align-items: center; justify-content: center;
|
|
86
|
+
border-radius: 4px; transition: background 0.15s, transform 0.25s; font-size: 0.7em; color: #888;
|
|
87
|
+
}
|
|
88
|
+
.accordion-header:hover .accordion-toggle { background: #eee; color: #555; }
|
|
89
|
+
.accordion-card.open .accordion-toggle { transform: rotate(180deg); }
|
|
90
|
+
.accordion-body { padding: 0 20px 20px; display: none; }
|
|
91
|
+
.accordion-card.open .accordion-body { display: block; }
|
|
92
|
+
.accordion-card.open .accordion-header { border-bottom: 1px solid #eee; }
|
|
93
|
+
.accordion-card.open .accordion-preview { display: none; }
|
|
94
|
+
|
|
95
|
+
.accordion-controls {
|
|
96
|
+
display: flex; gap: 8px; margin-bottom: 12px; justify-content: flex-end;
|
|
97
|
+
}
|
|
98
|
+
.accordion-controls button {
|
|
99
|
+
padding: 4px 10px; font-size: 0.82em; font-weight: 500; background: none;
|
|
100
|
+
border: 1px solid #ccc; border-radius: 4px; color: #666; cursor: pointer; margin: 0;
|
|
101
|
+
}
|
|
102
|
+
.accordion-controls button:hover { background: #f0f0f0; border-color: #aaa; color: #333; }
|
|
103
|
+
|
|
104
|
+
label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 0.9em; }
|
|
105
|
+
input[type="text"], input[type="password"], input[type="number"] {
|
|
106
|
+
width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;
|
|
107
|
+
font-size: 0.95em; margin-bottom: 10px;
|
|
108
|
+
}
|
|
109
|
+
input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
|
|
110
|
+
outline: none; border-color: #4a90d9;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
button {
|
|
114
|
+
padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;
|
|
115
|
+
font-size: 0.9em; font-weight: 600; margin-right: 8px; margin-bottom: 8px;
|
|
116
|
+
}
|
|
117
|
+
button.primary { background: #4a90d9; color: #fff; }
|
|
118
|
+
button.primary:hover { background: #357abd; }
|
|
119
|
+
button.secondary { background: #6c757d; color: #fff; }
|
|
120
|
+
button.secondary:hover { background: #5a6268; }
|
|
121
|
+
button.danger { background: #dc3545; color: #fff; }
|
|
122
|
+
button.danger:hover { background: #c82333; }
|
|
123
|
+
button.success { background: #28a745; color: #fff; }
|
|
124
|
+
button.success:hover { background: #218838; }
|
|
125
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
126
|
+
|
|
127
|
+
.status { padding: 8px 12px; border-radius: 4px; margin-top: 10px; font-size: 0.9em; }
|
|
128
|
+
.status.ok { background: #d4edda; color: #155724; }
|
|
129
|
+
.status.error { background: #f8d7da; color: #721c24; }
|
|
130
|
+
.status.info { background: #d1ecf1; color: #0c5460; }
|
|
131
|
+
.status.warn { background: #fff3cd; color: #856404; }
|
|
132
|
+
|
|
133
|
+
.inline-group { display: flex; gap: 8px; align-items: flex-end; margin-bottom: 10px; }
|
|
134
|
+
.inline-group > div { flex: 1; }
|
|
135
|
+
|
|
136
|
+
a { color: #4a90d9; }
|
|
137
|
+
|
|
138
|
+
select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.95em; margin-bottom: 10px; }
|
|
139
|
+
|
|
140
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
|
|
141
|
+
th { text-align: left; font-weight: 600; padding: 8px; border-bottom: 2px solid #ddd; color: #555; }
|
|
142
|
+
td { padding: 8px; border-bottom: 1px solid #eee; }
|
|
143
|
+
tr:hover { background: #fafafa; }
|
|
144
|
+
|
|
145
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.8em; font-weight: 600; }
|
|
146
|
+
.badge-raw { background: #d1ecf1; color: #0c5460; }
|
|
147
|
+
.badge-compositional { background: #d4edda; color: #155724; }
|
|
148
|
+
.badge-projection { background: #fff3cd; color: #856404; }
|
|
149
|
+
.badge-derived { background: #e2d5f1; color: #4a2d73; }
|
|
150
|
+
|
|
151
|
+
.certainty-bar { display: inline-block; width: 60px; height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; vertical-align: middle; }
|
|
152
|
+
.certainty-fill { height: 100%; border-radius: 4px; }
|
|
153
|
+
.certainty-low { background: #dc3545; }
|
|
154
|
+
.certainty-mid { background: #ffc107; }
|
|
155
|
+
.certainty-high { background: #28a745; }
|
|
156
|
+
`,
|
|
157
|
+
Templates:
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
Hash: 'Facto-Layout',
|
|
161
|
+
Template: /*html*/`
|
|
162
|
+
<h1>Retold Facto</h1>
|
|
163
|
+
<p style="color:#666; margin-bottom:20px; font-size:0.95em;">Data Warehouse & Knowledge Graph Storage</p>
|
|
164
|
+
|
|
165
|
+
<!-- Expand / Collapse All -->
|
|
166
|
+
<div class="accordion-controls">
|
|
167
|
+
<button onclick="pict.views['Facto-Layout'].expandAllSections()">Expand All</button>
|
|
168
|
+
<button onclick="pict.views['Facto-Layout'].collapseAllSections()">Collapse All</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<!-- Section containers -->
|
|
172
|
+
<div id="Facto-Section-Throughput"></div>
|
|
173
|
+
<div id="Facto-Section-Scanner"></div>
|
|
174
|
+
<div id="Facto-Section-Catalog"></div>
|
|
175
|
+
<div id="Facto-Section-Sources"></div>
|
|
176
|
+
<div id="Facto-Section-Datasets"></div>
|
|
177
|
+
<div id="Facto-Section-Records"></div>
|
|
178
|
+
<div id="Facto-Section-Ingest"></div>
|
|
179
|
+
<div id="Facto-Section-Projections"></div>
|
|
180
|
+
`
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
Renderables:
|
|
184
|
+
[
|
|
185
|
+
{
|
|
186
|
+
RenderableHash: 'Facto-Layout',
|
|
187
|
+
TemplateHash: 'Facto-Layout',
|
|
188
|
+
DestinationAddress: '#Facto-Application-Container'
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|