@finos/legend-application-studio 28.21.4 → 28.21.6
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/lib/__lib__/LegendStudioEvent.d.ts +4 -1
- package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioEvent.js +3 -0
- package/lib/__lib__/LegendStudioEvent.js.map +1 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +2 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.js +11 -3
- package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts +41 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js +120 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +13 -35
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +20 -7
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +59 -22
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +2 -2
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
- package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
- package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts +22 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts.map +1 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js +80 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js.map +1 -0
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +61 -6
- package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/editor/EditorStore.d.ts.map +1 -1
- package/lib/stores/editor/EditorStore.js +31 -0
- package/lib/stores/editor/EditorStore.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +20 -48
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +9 -14
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +125 -78
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +216 -53
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.js +11 -0
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js +11 -0
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +57 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
- package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
- package/lib/stores/project-reviewer/ProjectReviewerStore.js +12 -0
- package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +17 -0
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js +61 -0
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
- package/package.json +16 -16
- package/src/__lib__/LegendStudioEvent.ts +3 -0
- package/src/__lib__/LegendStudioTelemetryHelper.ts +35 -11
- package/src/__lib__/LegendStudioUserDataHelper.ts +204 -1
- package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +4 -0
- package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +209 -187
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +26 -7
- package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +149 -86
- package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +3 -11
- package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
- package/src/components/workspace-setup/RecentWorkspacesPanel.tsx +161 -0
- package/src/components/workspace-setup/WorkspaceSetup.tsx +97 -8
- package/src/stores/editor/EditorStore.ts +44 -0
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +28 -50
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +164 -100
- package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +307 -72
- package/src/stores/editor/sidebar-state/ProjectOverviewState.ts +14 -0
- package/src/stores/editor/sidebar-state/WorkspaceReviewState.ts +14 -0
- package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +84 -1
- package/src/stores/project-reviewer/ProjectReviewerStore.ts +15 -0
- package/src/stores/workspace-setup/WorkspaceSetupStore.ts +93 -0
- package/tsconfig.json +1 -0
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
DEFAULT_TAB_SIZE,
|
|
94
94
|
} from '@finos/legend-application';
|
|
95
95
|
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
|
|
96
|
+
import { LegendStudioUserDataHelper } from '../../__lib__/LegendStudioUserDataHelper.js';
|
|
96
97
|
import type { EditorMode } from './EditorMode.js';
|
|
97
98
|
import { StandardEditorMode } from './StandardEditorMode.js';
|
|
98
99
|
import { WorkspaceUpdateConflictResolutionState } from './sidebar-state/WorkspaceUpdateConflictResolutionState.js';
|
|
@@ -691,6 +692,12 @@ export class EditorStore implements CommandRegistrar {
|
|
|
691
692
|
}),
|
|
692
693
|
);
|
|
693
694
|
if (!this.sdlcState.currentProject) {
|
|
695
|
+
// The project the user navigated to doesn't exist (or isn't accessible).
|
|
696
|
+
// Drop it from the recents cache so we don't keep offering a dead link.
|
|
697
|
+
LegendStudioUserDataHelper.workspaceSetup_removeRecentProject(
|
|
698
|
+
this.applicationStore.userDataService,
|
|
699
|
+
projectId,
|
|
700
|
+
);
|
|
694
701
|
// If the project is not found or the user does not have access to it,
|
|
695
702
|
// we will not automatically redirect them to the setup page as they will lose the URL
|
|
696
703
|
// instead, we give them the option to:
|
|
@@ -740,6 +747,15 @@ export class EditorStore implements CommandRegistrar {
|
|
|
740
747
|
),
|
|
741
748
|
);
|
|
742
749
|
if (!this.sdlcState.currentWorkspace) {
|
|
750
|
+
// The workspace the user navigated to doesn't exist anymore. Drop
|
|
751
|
+
// the matching entry from the recents cache (no-op for patch
|
|
752
|
+
// workspaces, which are never cached in the first place).
|
|
753
|
+
if (patchReleaseVersionId === undefined) {
|
|
754
|
+
LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(
|
|
755
|
+
this.applicationStore.userDataService,
|
|
756
|
+
{ projectId, workspaceId, workspaceType },
|
|
757
|
+
);
|
|
758
|
+
}
|
|
743
759
|
// If the workspace is not found,
|
|
744
760
|
// we will not automatically redirect the user to the setup page as they will lose the URL
|
|
745
761
|
// instead, we give them the option to:
|
|
@@ -810,6 +826,34 @@ export class EditorStore implements CommandRegistrar {
|
|
|
810
826
|
onLeave(false);
|
|
811
827
|
return;
|
|
812
828
|
}
|
|
829
|
+
// At this point both the project and the workspace have been confirmed
|
|
830
|
+
// to exist on the server (the guards above bail out otherwise), so this
|
|
831
|
+
// is the authoritative "the user actually opened this workspace" moment.
|
|
832
|
+
// Record it in the recents cache so the workspace setup screen can offer
|
|
833
|
+
// it instantly next time.
|
|
834
|
+
// NOTE: patch-based workspaces are intentionally excluded from recents
|
|
835
|
+
// (they are not surfaced in the recents UI). Sandbox projects ARE
|
|
836
|
+
// included — opening one is still a meaningful "I worked here" signal,
|
|
837
|
+
// and surfacing it alongside other recents gives a faster one-click
|
|
838
|
+
// re-entry than waiting for the dedicated sandbox loader.
|
|
839
|
+
if (this.sdlcState.currentWorkspace.source === undefined) {
|
|
840
|
+
LegendStudioUserDataHelper.workspaceSetup_recordRecentProject(
|
|
841
|
+
this.applicationStore.userDataService,
|
|
842
|
+
{
|
|
843
|
+
projectId: this.sdlcState.currentProject.projectId,
|
|
844
|
+
name: this.sdlcState.currentProject.name,
|
|
845
|
+
},
|
|
846
|
+
);
|
|
847
|
+
LegendStudioUserDataHelper.workspaceSetup_recordRecentWorkspace(
|
|
848
|
+
this.applicationStore.userDataService,
|
|
849
|
+
{
|
|
850
|
+
projectId: this.sdlcState.currentProject.projectId,
|
|
851
|
+
workspaceId: this.sdlcState.currentWorkspace.workspaceId,
|
|
852
|
+
workspaceType: this.sdlcState.currentWorkspace.workspaceType,
|
|
853
|
+
},
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
813
857
|
yield Promise.all([
|
|
814
858
|
this.sdlcState.fetchCurrentRevision(
|
|
815
859
|
projectId,
|
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
} from '@finos/legend-graph';
|
|
33
33
|
import {
|
|
34
34
|
ContentType,
|
|
35
|
+
csvDecodeValue,
|
|
36
|
+
csvEncodeValue,
|
|
37
|
+
csvStringify,
|
|
38
|
+
parseCSVContent,
|
|
35
39
|
guaranteeNonEmptyString,
|
|
36
40
|
tryToFormatLosslessJSONString,
|
|
37
41
|
UnsupportedOperationError,
|
|
@@ -273,10 +277,16 @@ export class RelationElementState {
|
|
|
273
277
|
|
|
274
278
|
updateRow(rowIndex: number, columnIndex: number, value: string): void {
|
|
275
279
|
if (this.relationElement.rows[rowIndex]) {
|
|
276
|
-
this.relationElement.rows[rowIndex].values[columnIndex] =
|
|
280
|
+
this.relationElement.rows[rowIndex].values[columnIndex] =
|
|
281
|
+
csvEncodeValue(value);
|
|
277
282
|
}
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
getDisplayValue(rowIndex: number, columnIndex: number): string {
|
|
286
|
+
const value = this.relationElement.rows[rowIndex]?.values[columnIndex];
|
|
287
|
+
return value !== undefined ? csvDecodeValue(value) : '';
|
|
288
|
+
}
|
|
289
|
+
|
|
280
290
|
clearAllData(): void {
|
|
281
291
|
this.relationElement.rows.splice(0);
|
|
282
292
|
}
|
|
@@ -285,7 +295,9 @@ export class RelationElementState {
|
|
|
285
295
|
return JSON.stringify(
|
|
286
296
|
{
|
|
287
297
|
columns: this.relationElement.columns,
|
|
288
|
-
data: this.relationElement.rows
|
|
298
|
+
data: this.relationElement.rows.map((row) => ({
|
|
299
|
+
values: row.values.map((v) => csvDecodeValue(v)),
|
|
300
|
+
})),
|
|
289
301
|
},
|
|
290
302
|
null,
|
|
291
303
|
2,
|
|
@@ -310,7 +322,7 @@ export class RelationElementState {
|
|
|
310
322
|
const insertStatements = this.relationElement.rows.map((row) => {
|
|
311
323
|
const values = this.relationElement.columns
|
|
312
324
|
.map((col, colIndex) => {
|
|
313
|
-
const value = row.values[colIndex] ?? '';
|
|
325
|
+
const value = csvDecodeValue(row.values[colIndex] ?? '');
|
|
314
326
|
if (value !== '') {
|
|
315
327
|
return `'${value.replace(/'/g, "''")}'`;
|
|
316
328
|
}
|
|
@@ -324,64 +336,30 @@ export class RelationElementState {
|
|
|
324
336
|
}
|
|
325
337
|
|
|
326
338
|
exportCSV(): string {
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const value = row.values[headerIndex] ?? '';
|
|
333
|
-
if (value.includes(',') || value.includes('"')) {
|
|
334
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
335
|
-
}
|
|
336
|
-
return value;
|
|
337
|
-
});
|
|
338
|
-
csvLines.push(values.join(','));
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
return csvLines.join('\n');
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
private parseCSVLine(line: string): string[] {
|
|
345
|
-
const result: string[] = [];
|
|
346
|
-
let current = '';
|
|
347
|
-
let inQuotes = false;
|
|
348
|
-
|
|
349
|
-
for (let i = 0; i < line.length; i++) {
|
|
350
|
-
const char = line[i];
|
|
351
|
-
if (char === '"') {
|
|
352
|
-
inQuotes = !inQuotes;
|
|
353
|
-
} else if (char === ',' && !inQuotes) {
|
|
354
|
-
result.push(current.trim());
|
|
355
|
-
current = '';
|
|
356
|
-
} else {
|
|
357
|
-
current += char;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
result.push(current.trim());
|
|
361
|
-
return result;
|
|
339
|
+
// decode so that csvStringify does not double encode
|
|
340
|
+
const data = this.relationElement.rows.map((row) =>
|
|
341
|
+
row.values.map((v) => csvDecodeValue(v)),
|
|
342
|
+
);
|
|
343
|
+
return csvStringify([this.relationElement.columns, ...data]);
|
|
362
344
|
}
|
|
363
345
|
|
|
364
346
|
importCSV(csvContent: string): void {
|
|
365
|
-
const
|
|
366
|
-
if (
|
|
347
|
+
const parsed = parseCSVContent(csvContent);
|
|
348
|
+
if (parsed.length === 0) {
|
|
367
349
|
return;
|
|
368
350
|
}
|
|
369
351
|
|
|
370
|
-
const
|
|
371
|
-
if (!
|
|
352
|
+
const headers = parsed[0];
|
|
353
|
+
if (!headers) {
|
|
372
354
|
return;
|
|
373
355
|
}
|
|
374
356
|
|
|
375
|
-
const headers = this.parseCSVLine(firstLine);
|
|
376
357
|
this.relationElement.columns = headers;
|
|
377
|
-
|
|
378
|
-
this.relationElement.rows = lines.slice(1).map((line) => {
|
|
379
|
-
const values = this.parseCSVLine(line);
|
|
358
|
+
this.relationElement.rows = parsed.slice(1).map((values) => {
|
|
380
359
|
const row = new RelationRowTestData();
|
|
381
|
-
row.values =
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
});
|
|
360
|
+
row.values = headers.map((_, index) =>
|
|
361
|
+
csvEncodeValue(values[index] ?? ''),
|
|
362
|
+
);
|
|
385
363
|
return observe_RelationRowTestData(row);
|
|
386
364
|
});
|
|
387
365
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
LakehouseAccessPoint,
|
|
27
27
|
DataProductTestSuite,
|
|
28
28
|
BaseDataResolver,
|
|
29
|
+
ReferenceDataResolver,
|
|
29
30
|
DataProductAccessPointTest,
|
|
30
31
|
RelationElementsData,
|
|
31
32
|
RelationElement,
|
|
@@ -34,11 +35,14 @@ import {
|
|
|
34
35
|
TestError,
|
|
35
36
|
TestExecutionStatus,
|
|
36
37
|
EqualToRelation,
|
|
38
|
+
RelationRowTestData,
|
|
37
39
|
observe_RelationElement,
|
|
40
|
+
observe_RelationRowTestData,
|
|
38
41
|
observe_RelationElementsData,
|
|
39
42
|
observe_DataProductTestSuite,
|
|
40
43
|
IngestDefinition,
|
|
41
44
|
getAccessorItemLabelForElement,
|
|
45
|
+
type AbstractPureGraphManager,
|
|
42
46
|
} from '@finos/legend-graph';
|
|
43
47
|
import {
|
|
44
48
|
type GeneratorFn,
|
|
@@ -59,7 +63,10 @@ import {
|
|
|
59
63
|
} from 'mobx';
|
|
60
64
|
import type { EditorStore } from '../../../../EditorStore.js';
|
|
61
65
|
import type { DataProductEditorState } from '../DataProductEditorState.js';
|
|
62
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
RelationElementsDataState,
|
|
68
|
+
RelationElementState,
|
|
69
|
+
} from '../../data/EmbeddedDataState.js';
|
|
63
70
|
import { TESTABLE_RESULT } from '../../../../sidebar-state/testable/GlobalTestRunnerState.js';
|
|
64
71
|
import { testSuite_addTest } from '../../../../../graph-modifier/Testable_GraphModifierHelper.js';
|
|
65
72
|
import {
|
|
@@ -71,10 +78,12 @@ const createEmptyRelationElement = (
|
|
|
71
78
|
itemId: string,
|
|
72
79
|
columns: string[] = [],
|
|
73
80
|
): RelationElement => {
|
|
81
|
+
const row = observe_RelationRowTestData(new RelationRowTestData());
|
|
82
|
+
row.values = columns.map(() => '');
|
|
74
83
|
const relationElement = new RelationElement();
|
|
75
84
|
relationElement.paths = [itemId];
|
|
76
85
|
relationElement.columns = columns;
|
|
77
|
-
relationElement.rows = [];
|
|
86
|
+
relationElement.rows = [row];
|
|
78
87
|
return observe_RelationElement(relationElement);
|
|
79
88
|
};
|
|
80
89
|
|
|
@@ -123,6 +132,7 @@ interface ElementDataItem {
|
|
|
123
132
|
|
|
124
133
|
const getElementDataItems = (
|
|
125
134
|
element: PackageableElement,
|
|
135
|
+
graphManager: AbstractPureGraphManager,
|
|
126
136
|
): ElementDataItem[] => {
|
|
127
137
|
if (element instanceof DataProduct) {
|
|
128
138
|
return element.accessPointGroups
|
|
@@ -133,10 +143,12 @@ const getElementDataItems = (
|
|
|
133
143
|
}));
|
|
134
144
|
}
|
|
135
145
|
if (element instanceof IngestDefinition) {
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
return graphManager
|
|
147
|
+
.getIngestDefinitionDatasetNames(element)
|
|
148
|
+
.map((name) => ({
|
|
149
|
+
id: name,
|
|
150
|
+
label: name,
|
|
151
|
+
}));
|
|
140
152
|
}
|
|
141
153
|
return [];
|
|
142
154
|
};
|
|
@@ -252,31 +264,21 @@ export class DataProductElementTestDataState {
|
|
|
252
264
|
readonly testDataState: DataProductTestDataState;
|
|
253
265
|
readonly testData: BaseDataResolver;
|
|
254
266
|
readonly editorStore: EditorStore;
|
|
255
|
-
|
|
256
|
-
/** Currently selected dataset/item within this element's RelationElementsData */
|
|
257
|
-
selectedItemId: string | undefined;
|
|
258
|
-
/** RelationElementState for the currently selected item */
|
|
259
|
-
relationElementState: RelationElementState | undefined;
|
|
267
|
+
readonly relationElementsDataState: RelationElementsDataState | undefined;
|
|
260
268
|
|
|
261
269
|
constructor(
|
|
262
270
|
testDataState: DataProductTestDataState,
|
|
263
271
|
testData: BaseDataResolver,
|
|
264
272
|
) {
|
|
265
|
-
makeObservable(this, {
|
|
266
|
-
selectedItemId: observable,
|
|
267
|
-
relationElementState: observable,
|
|
268
|
-
setSelectedItem: action,
|
|
269
|
-
});
|
|
270
273
|
this.testDataState = testDataState;
|
|
271
274
|
this.testData = testData;
|
|
272
275
|
this.editorStore = testDataState.editorStore;
|
|
273
|
-
// Select the first item that has data
|
|
274
276
|
if (testData.data instanceof RelationElementsData) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
this.relationElementsDataState = new RelationElementsDataState(
|
|
278
|
+
this.editorStore,
|
|
279
|
+
testData.data,
|
|
280
|
+
);
|
|
281
|
+
this.initAccessorOptions();
|
|
280
282
|
}
|
|
281
283
|
}
|
|
282
284
|
|
|
@@ -292,35 +294,81 @@ export class DataProductElementTestDataState {
|
|
|
292
294
|
return getAccessorItemLabelForElement(this.element as AccessorOwner);
|
|
293
295
|
}
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
297
|
+
private initAccessorOptions(): void {
|
|
298
|
+
const dataState = this.relationElementsDataState;
|
|
299
|
+
if (!dataState) {
|
|
300
|
+
return;
|
|
298
301
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
get configuredItems(): ElementDataItem[] {
|
|
303
|
-
const availableItemsById = new Map(
|
|
304
|
-
getElementDataItems(this.element).map((item) => [item.id, item.label]),
|
|
302
|
+
this.refreshAccessorOptions(dataState).catch(noop);
|
|
303
|
+
dataState.setRefreshAccessorOptions(() =>
|
|
304
|
+
this.refreshAccessorOptions(dataState),
|
|
305
305
|
);
|
|
306
|
-
return this.configuredItemIds.map((id) => ({
|
|
307
|
-
id,
|
|
308
|
-
label: availableItemsById.get(id) ?? id,
|
|
309
|
-
}));
|
|
310
306
|
}
|
|
311
307
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
this.relationElementState = undefined;
|
|
308
|
+
private async refreshAccessorOptions(
|
|
309
|
+
dataState: RelationElementsDataState,
|
|
310
|
+
): Promise<void> {
|
|
311
|
+
const element = this.element;
|
|
312
|
+
const graphManager = this.editorStore.graphManagerState.graphManager;
|
|
313
|
+
const graph = this.editorStore.graphManagerState.graph;
|
|
314
|
+
const items = getElementDataItems(element, graphManager);
|
|
315
|
+
if (items.length === 0) {
|
|
316
|
+
dataState.setAccessorOptions(undefined, undefined);
|
|
317
|
+
return;
|
|
323
318
|
}
|
|
319
|
+
const typeLabel = this.itemLabel;
|
|
320
|
+
const options = await Promise.all(
|
|
321
|
+
items.map(async (item) => {
|
|
322
|
+
let columns: string[] = [];
|
|
323
|
+
try {
|
|
324
|
+
if (element instanceof IngestDefinition) {
|
|
325
|
+
const accessor = graphManager.createAccessorFromPackageableElement(
|
|
326
|
+
element,
|
|
327
|
+
graph,
|
|
328
|
+
{ schemaName: undefined, tableName: item.id },
|
|
329
|
+
);
|
|
330
|
+
if (accessor) {
|
|
331
|
+
columns = accessor.relationType.columns.map((c) => c.name);
|
|
332
|
+
}
|
|
333
|
+
} else if (element instanceof DataProduct) {
|
|
334
|
+
const accessor = await graphManager.buildDataProductAccessor(
|
|
335
|
+
element,
|
|
336
|
+
graph,
|
|
337
|
+
{ tableName: item.id },
|
|
338
|
+
);
|
|
339
|
+
if (accessor) {
|
|
340
|
+
columns = accessor.relationType.columns.map((c) => c.name);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
// best-effort column resolution
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
label: item.label,
|
|
348
|
+
value: item.id,
|
|
349
|
+
columns,
|
|
350
|
+
};
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
runInAction(() => {
|
|
354
|
+
dataState.setAccessorOptions(options, typeLabel);
|
|
355
|
+
// Back-fill columns on existing relation elements that have none
|
|
356
|
+
const columnsByItem = new Map(
|
|
357
|
+
options
|
|
358
|
+
.filter((o) => o.columns.length > 0)
|
|
359
|
+
.map((o) => [o.value, o.columns]),
|
|
360
|
+
);
|
|
361
|
+
for (const relState of dataState.relationElementStates) {
|
|
362
|
+
const rel = relState.relationElement;
|
|
363
|
+
if (rel.columns.length === 0) {
|
|
364
|
+
const key = rel.paths[rel.paths.length - 1];
|
|
365
|
+
const cols = key ? columnsByItem.get(key) : undefined;
|
|
366
|
+
if (cols) {
|
|
367
|
+
rel.columns = cols;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
324
372
|
}
|
|
325
373
|
}
|
|
326
374
|
|
|
@@ -332,6 +380,7 @@ export class DataProductTestDataState {
|
|
|
332
380
|
|
|
333
381
|
elementTestDataStates: DataProductElementTestDataState[] = [];
|
|
334
382
|
selectedElementTestDataState: DataProductElementTestDataState | undefined;
|
|
383
|
+
showAddElementModal = false;
|
|
335
384
|
|
|
336
385
|
constructor(
|
|
337
386
|
suiteState: DataProductTestSuiteState,
|
|
@@ -343,7 +392,11 @@ export class DataProductTestDataState {
|
|
|
343
392
|
makeObservable(this, {
|
|
344
393
|
elementTestDataStates: observable,
|
|
345
394
|
selectedElementTestDataState: observable,
|
|
395
|
+
showAddElementModal: observable,
|
|
346
396
|
setSelectedElementTestDataState: action,
|
|
397
|
+
setShowAddElementModal: action,
|
|
398
|
+
addElement: action,
|
|
399
|
+
deleteElement: action,
|
|
347
400
|
refreshElementTestDataStates: action,
|
|
348
401
|
});
|
|
349
402
|
this.editorStore = suiteState.editorStore;
|
|
@@ -351,16 +404,64 @@ export class DataProductTestDataState {
|
|
|
351
404
|
this.refreshElementTestDataStates(options);
|
|
352
405
|
}
|
|
353
406
|
|
|
407
|
+
get availableElementsToAdd(): PackageableElement[] {
|
|
408
|
+
const suite = this.suiteState.suite;
|
|
409
|
+
const existingPaths = new Set(
|
|
410
|
+
(suite.testData ?? [])
|
|
411
|
+
.filter(
|
|
412
|
+
(td): td is BaseDataResolver | ReferenceDataResolver =>
|
|
413
|
+
td instanceof BaseDataResolver ||
|
|
414
|
+
td instanceof ReferenceDataResolver,
|
|
415
|
+
)
|
|
416
|
+
.map((td) => td.element.value.path),
|
|
417
|
+
);
|
|
418
|
+
const graph = this.editorStore.graphManagerState.graph;
|
|
419
|
+
const currentDpPath = this.suiteState.testableState.dataProduct.path;
|
|
420
|
+
const candidates: PackageableElement[] = [
|
|
421
|
+
...graph.ingests,
|
|
422
|
+
...graph.allElements.filter(
|
|
423
|
+
(e) => e instanceof DataProduct && e.path !== currentDpPath,
|
|
424
|
+
),
|
|
425
|
+
];
|
|
426
|
+
return candidates.filter((e) => !existingPaths.has(e.path));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
setShowAddElementModal(val: boolean): void {
|
|
430
|
+
this.showAddElementModal = val;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
addElement(path: string): void {
|
|
434
|
+
const element =
|
|
435
|
+
this.editorStore.graphManagerState.graph.getNullableElement(path);
|
|
436
|
+
if (!element) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const resolver = new BaseDataResolver();
|
|
440
|
+
resolver.element = PackageableElementExplicitReference.create(element);
|
|
441
|
+
const relData = new RelationElementsData();
|
|
442
|
+
relData.relationElements = [];
|
|
443
|
+
observe_RelationElementsData(relData);
|
|
444
|
+
resolver.data = relData;
|
|
445
|
+
const suite = this.suiteState.suite;
|
|
446
|
+
suite.testData = [...(suite.testData ?? []), resolver];
|
|
447
|
+
this.refreshElementTestDataStates({ selectedElementPath: path });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
deleteElement(elementState: DataProductElementTestDataState): void {
|
|
451
|
+
const suite = this.suiteState.suite;
|
|
452
|
+
if (suite.testData) {
|
|
453
|
+
const idx = suite.testData.indexOf(elementState.testData);
|
|
454
|
+
suite.testData.splice(idx, 1);
|
|
455
|
+
}
|
|
456
|
+
this.refreshElementTestDataStates();
|
|
457
|
+
}
|
|
458
|
+
|
|
354
459
|
refreshElementTestDataStates(options?: {
|
|
355
460
|
selectedElementPath?: string | undefined;
|
|
356
|
-
selectedItemId?: string | undefined;
|
|
357
461
|
}): void {
|
|
358
462
|
const previouslySelectedElementPath =
|
|
359
463
|
options?.selectedElementPath ??
|
|
360
464
|
this.selectedElementTestDataState?.element.path;
|
|
361
|
-
const previouslySelectedItemId =
|
|
362
|
-
options?.selectedItemId ??
|
|
363
|
-
this.selectedElementTestDataState?.selectedItemId;
|
|
364
465
|
const suite = this.suiteState.suite;
|
|
365
466
|
this.elementTestDataStates = (suite.testData ?? [])
|
|
366
467
|
.filter((td): td is BaseDataResolver => td instanceof BaseDataResolver)
|
|
@@ -370,14 +471,6 @@ export class DataProductTestDataState {
|
|
|
370
471
|
this.elementTestDataStates.find(
|
|
371
472
|
(state) => state.element.path === previouslySelectedElementPath,
|
|
372
473
|
) ?? this.elementTestDataStates[0];
|
|
373
|
-
|
|
374
|
-
if (this.selectedElementTestDataState && previouslySelectedItemId) {
|
|
375
|
-
const nextSelectedItemId =
|
|
376
|
-
this.selectedElementTestDataState.configuredItemIds.find(
|
|
377
|
-
(itemId) => itemId === previouslySelectedItemId,
|
|
378
|
-
) ?? this.selectedElementTestDataState.configuredItemIds[0];
|
|
379
|
-
this.selectedElementTestDataState.setSelectedItem(nextSelectedItemId);
|
|
380
|
-
}
|
|
381
474
|
}
|
|
382
475
|
|
|
383
476
|
setSelectedElementTestDataState(
|
|
@@ -427,8 +520,6 @@ export class DataProductTestSuiteState extends TestableTestSuiteEditorState {
|
|
|
427
520
|
this.testDataState = new DataProductTestDataState(this, {
|
|
428
521
|
selectedElementPath:
|
|
429
522
|
this.testDataState.selectedElementTestDataState?.element.path,
|
|
430
|
-
selectedItemId:
|
|
431
|
-
this.testDataState.selectedElementTestDataState?.selectedItemId,
|
|
432
523
|
});
|
|
433
524
|
}
|
|
434
525
|
|
|
@@ -540,42 +631,19 @@ export class DataProductTestSuiteState extends TestableTestSuiteEditorState {
|
|
|
540
631
|
}
|
|
541
632
|
}
|
|
542
633
|
} else {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
td instanceof BaseDataResolver &&
|
|
547
|
-
td.element.value === this.testableState.dataProduct &&
|
|
548
|
-
td.data instanceof RelationElementsData,
|
|
549
|
-
)?.data as RelationElementsData | undefined;
|
|
550
|
-
|
|
551
|
-
if (!relationData) {
|
|
552
|
-
const testData = new BaseDataResolver();
|
|
553
|
-
testData.element = PackageableElementExplicitReference.create(
|
|
554
|
-
this.testableState.dataProduct,
|
|
555
|
-
);
|
|
556
|
-
relationData = new RelationElementsData();
|
|
557
|
-
relationData.relationElements = [];
|
|
558
|
-
observe_RelationElementsData(relationData);
|
|
559
|
-
testData.data = relationData;
|
|
560
|
-
this.suite.testData = [...(this.suite.testData ?? []), testData];
|
|
561
|
-
}
|
|
562
|
-
if (
|
|
563
|
-
!relationData.relationElements.find(
|
|
564
|
-
(re) => re.paths[0] === accessPointId,
|
|
565
|
-
)
|
|
566
|
-
) {
|
|
567
|
-
relationData.relationElements.push(
|
|
568
|
-
createEmptyRelationElement(accessPointId, inferredColumns),
|
|
569
|
-
);
|
|
570
|
-
}
|
|
634
|
+
this.editorStore.applicationStore.notificationService.notifyWarning(
|
|
635
|
+
'Access Point accessors cannot be resolved',
|
|
636
|
+
);
|
|
571
637
|
}
|
|
572
638
|
|
|
573
639
|
const assertion = new EqualToRelation();
|
|
574
640
|
assertion.id = 'assert_1';
|
|
575
641
|
const expectedRelElement = new RelationElement();
|
|
642
|
+
const expectedRow = observe_RelationRowTestData(new RelationRowTestData());
|
|
643
|
+
expectedRow.values = inferredColumns.map(() => '');
|
|
576
644
|
expectedRelElement.paths = [accessPointId];
|
|
577
645
|
expectedRelElement.columns = inferredColumns;
|
|
578
|
-
expectedRelElement.rows = [];
|
|
646
|
+
expectedRelElement.rows = [expectedRow];
|
|
579
647
|
observe_RelationElement(expectedRelElement);
|
|
580
648
|
assertion.expected = expectedRelElement;
|
|
581
649
|
test.assertions = [assertion];
|
|
@@ -792,17 +860,11 @@ export class DataProductTestableState {
|
|
|
792
860
|
suite.testData.push(resolver);
|
|
793
861
|
}
|
|
794
862
|
}
|
|
795
|
-
//
|
|
863
|
+
// If no external sources were resolved, notify the user and leave test data empty
|
|
796
864
|
if (suite.testData.length === 0) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
relData.relationElements = [
|
|
801
|
-
createEmptyRelationElement(accessPointId, inferredColumns),
|
|
802
|
-
];
|
|
803
|
-
observe_RelationElementsData(relData);
|
|
804
|
-
testData.data = relData;
|
|
805
|
-
suite.testData = [testData];
|
|
865
|
+
this.editorStore.applicationStore.notificationService.notifyWarning(
|
|
866
|
+
'Access Point accessors cannot be resolved',
|
|
867
|
+
);
|
|
806
868
|
}
|
|
807
869
|
|
|
808
870
|
// Create one initial test with EqualToRelation assertion
|
|
@@ -814,9 +876,11 @@ export class DataProductTestableState {
|
|
|
814
876
|
const assertion = new EqualToRelation();
|
|
815
877
|
assertion.id = 'assert_1';
|
|
816
878
|
const expectedRelElement = new RelationElement();
|
|
879
|
+
const expectedRow = observe_RelationRowTestData(new RelationRowTestData());
|
|
880
|
+
expectedRow.values = inferredColumns.map(() => '');
|
|
817
881
|
expectedRelElement.paths = [accessPointId];
|
|
818
882
|
expectedRelElement.columns = inferredColumns;
|
|
819
|
-
expectedRelElement.rows = [];
|
|
883
|
+
expectedRelElement.rows = [expectedRow];
|
|
820
884
|
observe_RelationElement(expectedRelElement);
|
|
821
885
|
assertion.expected = expectedRelElement;
|
|
822
886
|
test.assertions = [assertion];
|