@finos/legend-application-studio 28.21.23 → 28.21.24
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__/LegendStudioDocumentation.d.ts +2 -2
- package/lib/__lib__/LegendStudioDocumentation.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioDocumentation.js +1 -1
- package/lib/__lib__/LegendStudioDocumentation.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +2 -5
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.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 +13 -239
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +17 -3
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
- package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts +22 -0
- package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js +134 -0
- package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js.map +1 -0
- package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts +89 -0
- package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js +144 -0
- package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js.map +1 -0
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +14 -8
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/ingest-definition-editor.css +1 -1
- package/lib/components/editor/ingest-definition-editor.css.map +1 -1
- package/lib/components/project-reviewer/ProjectReviewer.d.ts.map +1 -1
- package/lib/components/project-reviewer/ProjectReviewer.js +2 -1
- package/lib/components/project-reviewer/ProjectReviewer.js.map +1 -1
- package/lib/components/project-view/ProjectViewer.d.ts.map +1 -1
- package/lib/components/project-view/ProjectViewer.js +2 -2
- package/lib/components/project-view/ProjectViewer.js.map +1 -1
- package/lib/components/showcase/ShowcaseViewer.d.ts.map +1 -1
- package/lib/components/showcase/ShowcaseViewer.js +2 -2
- package/lib/components/showcase/ShowcaseViewer.js.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +98 -43
- 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/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +1 -2
- 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/DataProductEditorState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +8 -30
- 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 +7 -147
- 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.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +32 -28
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts +8 -0
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js +15 -0
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts +86 -0
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js +420 -0
- package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts +62 -0
- package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js +138 -0
- package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js.map +1 -0
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +10 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js +105 -12
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
- package/package.json +11 -11
- package/src/__lib__/LegendStudioDocumentation.ts +1 -1
- package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +2 -5
- package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +19 -869
- package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +90 -34
- package/src/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.tsx +418 -0
- package/src/components/editor/editor-group/testable/LakehouseTestableEditor.tsx +564 -0
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +17 -9
- package/src/components/project-reviewer/ProjectReviewer.tsx +2 -0
- package/src/components/project-view/ProjectViewer.tsx +2 -1
- package/src/components/showcase/ShowcaseViewer.tsx +2 -1
- package/src/components/workspace-setup/WorkspaceSetup.tsx +169 -75
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +1 -2
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +1 -0
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +18 -204
- package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +41 -36
- package/src/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.ts +16 -0
- package/src/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.ts +654 -0
- package/src/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.ts +224 -0
- package/src/stores/workspace-setup/WorkspaceSetupStore.ts +125 -8
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type Accessor,
|
|
19
|
+
BaseDataResolver,
|
|
20
|
+
DataProduct,
|
|
21
|
+
EqualToRelation,
|
|
22
|
+
type IngestDefinition,
|
|
23
|
+
IngestMatViewTest,
|
|
24
|
+
IngestTestSuite,
|
|
25
|
+
PackageableElementExplicitReference,
|
|
26
|
+
type PackageableElement,
|
|
27
|
+
RelationElement,
|
|
28
|
+
RelationElementsData,
|
|
29
|
+
RelationRowTestData,
|
|
30
|
+
ReferenceDataResolver,
|
|
31
|
+
type TestSuite,
|
|
32
|
+
observe_RelationElement,
|
|
33
|
+
observe_RelationElementsData,
|
|
34
|
+
observe_RelationRowTestData,
|
|
35
|
+
} from '@finos/legend-graph';
|
|
36
|
+
import {
|
|
37
|
+
type GeneratorFn,
|
|
38
|
+
addUniqueEntry,
|
|
39
|
+
deleteEntry,
|
|
40
|
+
noop,
|
|
41
|
+
uuid,
|
|
42
|
+
} from '@finos/legend-shared';
|
|
43
|
+
import {
|
|
44
|
+
action,
|
|
45
|
+
flow,
|
|
46
|
+
flowResult,
|
|
47
|
+
makeObservable,
|
|
48
|
+
observable,
|
|
49
|
+
runInAction,
|
|
50
|
+
} from 'mobx';
|
|
51
|
+
import { testSuite_addTest } from '../../../../graph-modifier/Testable_GraphModifierHelper.js';
|
|
52
|
+
import {
|
|
53
|
+
TestableTestEditorState,
|
|
54
|
+
TestableTestSuiteEditorState,
|
|
55
|
+
} from '../testable/TestableEditorState.js';
|
|
56
|
+
import type { EditorStore } from '../../../EditorStore.js';
|
|
57
|
+
import type { IngestDefinitionEditorState } from './IngestDefinitionEditorState.js';
|
|
58
|
+
import { RelationElementState } from '../data/EmbeddedDataState.js';
|
|
59
|
+
import {
|
|
60
|
+
buildRelationElementsDataWithColumns,
|
|
61
|
+
createEmptyRelationElement,
|
|
62
|
+
isIngestOrDataProductAccessor,
|
|
63
|
+
LakehouseElementTestDataState,
|
|
64
|
+
} from '../testable/LakehouseTestableUtils.js';
|
|
65
|
+
|
|
66
|
+
export type { LakehouseElementTestDataState as IngestElementTestDataState };
|
|
67
|
+
|
|
68
|
+
// ─── Ingest-specific helpers ──────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const createExpectedRelationElement = (
|
|
71
|
+
datasetName: string,
|
|
72
|
+
columns: string[],
|
|
73
|
+
): RelationElement => {
|
|
74
|
+
const expected = new RelationElement();
|
|
75
|
+
const row = observe_RelationRowTestData(new RelationRowTestData());
|
|
76
|
+
row.values = columns.map(() => '');
|
|
77
|
+
expected.paths = [datasetName];
|
|
78
|
+
expected.columns = columns;
|
|
79
|
+
expected.rows = [row];
|
|
80
|
+
return observe_RelationElement(expected);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const inferIngestDatasetColumns = async (
|
|
84
|
+
editorStore: EditorStore,
|
|
85
|
+
ingest: IngestDefinition,
|
|
86
|
+
datasetName: string,
|
|
87
|
+
): Promise<string[] | undefined> => {
|
|
88
|
+
const dataset = ingest.TEMPORARY_MATVIEW_FUNCTION_DATA_SETS?.find(
|
|
89
|
+
(candidate: { name: string }) => candidate.name === datasetName,
|
|
90
|
+
);
|
|
91
|
+
if (!dataset) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const relationType =
|
|
96
|
+
await editorStore.graphManagerState.graphManager.getLambdaRelationType(
|
|
97
|
+
dataset.source.function,
|
|
98
|
+
editorStore.graphManagerState.graph,
|
|
99
|
+
);
|
|
100
|
+
return relationType.columns.map((column) => column.name);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ─── Per-test state ───────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export class IngestTestState extends TestableTestEditorState {
|
|
106
|
+
readonly suiteState: IngestTestSuiteState;
|
|
107
|
+
override test: IngestMatViewTest;
|
|
108
|
+
readonly uuid = uuid();
|
|
109
|
+
testDataRelationState: RelationElementState | undefined;
|
|
110
|
+
|
|
111
|
+
constructor(suiteState: IngestTestSuiteState, test: IngestMatViewTest) {
|
|
112
|
+
super(
|
|
113
|
+
suiteState.testableState.ingest,
|
|
114
|
+
test,
|
|
115
|
+
suiteState.testableState.ingestDefinitionEditorState.isReadOnly,
|
|
116
|
+
suiteState.editorStore,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
makeObservable(this, {
|
|
120
|
+
selectedAsertionState: observable,
|
|
121
|
+
selectedTab: observable,
|
|
122
|
+
assertionToRename: observable,
|
|
123
|
+
assertionEditorStates: observable,
|
|
124
|
+
testResultState: observable,
|
|
125
|
+
runningTestAction: observable,
|
|
126
|
+
testDataRelationState: observable,
|
|
127
|
+
setSelectedTab: action,
|
|
128
|
+
setAssertionToRename: action,
|
|
129
|
+
addAssertion: action,
|
|
130
|
+
deleteAssertion: action,
|
|
131
|
+
openAssertion: action,
|
|
132
|
+
resetResult: action,
|
|
133
|
+
handleTestResult: action,
|
|
134
|
+
runTest: flow,
|
|
135
|
+
setDatasetId: action,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.suiteState = suiteState;
|
|
139
|
+
this.test = test;
|
|
140
|
+
this.backfillDatasetIdIfMissing();
|
|
141
|
+
this.buildTestDataRelationState().catch(noop());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setDatasetId(val: string): void {
|
|
145
|
+
this.test.datasetId = val;
|
|
146
|
+
this.resetResult();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get datasetLabel(): string {
|
|
150
|
+
if (this.test.datasetId) {
|
|
151
|
+
return this.test.datasetId;
|
|
152
|
+
}
|
|
153
|
+
const assertion = this.test.assertions.find(
|
|
154
|
+
(candidate): candidate is EqualToRelation =>
|
|
155
|
+
candidate instanceof EqualToRelation,
|
|
156
|
+
);
|
|
157
|
+
const inferred = assertion?.expected.paths[0];
|
|
158
|
+
if (inferred) {
|
|
159
|
+
return inferred;
|
|
160
|
+
}
|
|
161
|
+
const availableDatasets =
|
|
162
|
+
this.suiteState.testableState.ingestDefinitionEditorState.getMatviewFuncNames();
|
|
163
|
+
return availableDatasets[0] ?? 'N/A';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private backfillDatasetIdIfMissing(): void {
|
|
167
|
+
if (this.test.datasetId) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const assertion = this.test.assertions.find(
|
|
171
|
+
(candidate): candidate is EqualToRelation =>
|
|
172
|
+
candidate instanceof EqualToRelation,
|
|
173
|
+
);
|
|
174
|
+
const fallbackDatasetId = assertion?.expected.paths[0];
|
|
175
|
+
if (fallbackDatasetId) {
|
|
176
|
+
this.test.datasetId = fallbackDatasetId;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const availableDatasets =
|
|
180
|
+
this.suiteState.testableState.ingestDefinitionEditorState.getMatviewFuncNames();
|
|
181
|
+
if (availableDatasets[0]) {
|
|
182
|
+
this.test.datasetId = availableDatasets[0];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async buildTestDataRelationState(): Promise<void> {
|
|
187
|
+
const assertion = this.test.assertions.find(
|
|
188
|
+
(candidate): candidate is EqualToRelation =>
|
|
189
|
+
candidate instanceof EqualToRelation,
|
|
190
|
+
);
|
|
191
|
+
if (!assertion) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (assertion.expected.columns.length === 0) {
|
|
196
|
+
try {
|
|
197
|
+
const engineColumns = await inferIngestDatasetColumns(
|
|
198
|
+
this.editorStore,
|
|
199
|
+
this.suiteState.testableState.ingest,
|
|
200
|
+
this.datasetLabel,
|
|
201
|
+
);
|
|
202
|
+
if (engineColumns?.length) {
|
|
203
|
+
runInAction(() => {
|
|
204
|
+
assertion.expected.columns = engineColumns;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
// best-effort only
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
runInAction(() => {
|
|
213
|
+
this.testDataRelationState = new RelationElementState(assertion.expected);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─── Per-element test data state ─────────────────────────────────────────────
|
|
219
|
+
// Replaced by the shared LakehouseElementTestDataState from LakehouseTestableUtils.
|
|
220
|
+
// The type alias above maintains backward compatibility for any existing imports.
|
|
221
|
+
|
|
222
|
+
// ─── Test data state for a suite ─────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
export class IngestTestDataState {
|
|
225
|
+
readonly editorStore: EditorStore;
|
|
226
|
+
readonly suiteState: IngestTestSuiteState;
|
|
227
|
+
|
|
228
|
+
elementTestDataStates: LakehouseElementTestDataState[] = [];
|
|
229
|
+
selectedElementTestDataState: LakehouseElementTestDataState | undefined;
|
|
230
|
+
showAddElementModal = false;
|
|
231
|
+
|
|
232
|
+
constructor(
|
|
233
|
+
suiteState: IngestTestSuiteState,
|
|
234
|
+
options?: {
|
|
235
|
+
selectedElementPath?: string | undefined;
|
|
236
|
+
},
|
|
237
|
+
) {
|
|
238
|
+
makeObservable(this, {
|
|
239
|
+
elementTestDataStates: observable,
|
|
240
|
+
selectedElementTestDataState: observable,
|
|
241
|
+
showAddElementModal: observable,
|
|
242
|
+
setSelectedElementTestDataState: action,
|
|
243
|
+
setShowAddElementModal: action,
|
|
244
|
+
addElement: action,
|
|
245
|
+
deleteElement: action,
|
|
246
|
+
refreshElementTestDataStates: action,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
this.editorStore = suiteState.editorStore;
|
|
250
|
+
this.suiteState = suiteState;
|
|
251
|
+
this.refreshElementTestDataStates(options);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
get availableElementsToAdd(): PackageableElement[] {
|
|
255
|
+
const suite = this.suiteState.suite;
|
|
256
|
+
const existingPaths = new Set(
|
|
257
|
+
suite.testData
|
|
258
|
+
.filter(
|
|
259
|
+
(testData): testData is BaseDataResolver | ReferenceDataResolver =>
|
|
260
|
+
testData instanceof BaseDataResolver ||
|
|
261
|
+
testData instanceof ReferenceDataResolver,
|
|
262
|
+
)
|
|
263
|
+
.map((testData) => testData.element.value.path),
|
|
264
|
+
);
|
|
265
|
+
const graph = this.editorStore.graphManagerState.graph;
|
|
266
|
+
const currentIngestPath = this.suiteState.testableState.ingest.path;
|
|
267
|
+
const candidates: PackageableElement[] = [
|
|
268
|
+
...graph.ingests.filter((ingest) => ingest.path !== currentIngestPath),
|
|
269
|
+
...graph.allElements.filter((e) => e instanceof DataProduct),
|
|
270
|
+
];
|
|
271
|
+
return candidates.filter((element) => !existingPaths.has(element.path));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
setShowAddElementModal(val: boolean): void {
|
|
275
|
+
this.showAddElementModal = val;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
addElement(path: string): void {
|
|
279
|
+
const element =
|
|
280
|
+
this.editorStore.graphManagerState.graph.getNullableElement(path);
|
|
281
|
+
if (!element) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const resolver = new BaseDataResolver();
|
|
286
|
+
resolver.element = PackageableElementExplicitReference.create(element);
|
|
287
|
+
const relationElementsData = new RelationElementsData();
|
|
288
|
+
relationElementsData.relationElements = [];
|
|
289
|
+
observe_RelationElementsData(relationElementsData);
|
|
290
|
+
resolver.data = relationElementsData;
|
|
291
|
+
|
|
292
|
+
const suite = this.suiteState.suite;
|
|
293
|
+
suite.testData = [...suite.testData, resolver];
|
|
294
|
+
this.refreshElementTestDataStates({ selectedElementPath: path });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
deleteElement(elementState: LakehouseElementTestDataState): void {
|
|
298
|
+
const suite = this.suiteState.suite;
|
|
299
|
+
const index = suite.testData.indexOf(elementState.testData);
|
|
300
|
+
suite.testData.splice(index, 1);
|
|
301
|
+
this.refreshElementTestDataStates();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
refreshElementTestDataStates(options?: {
|
|
305
|
+
selectedElementPath?: string | undefined;
|
|
306
|
+
}): void {
|
|
307
|
+
const previouslySelectedElementPath =
|
|
308
|
+
options?.selectedElementPath ??
|
|
309
|
+
this.selectedElementTestDataState?.element.path;
|
|
310
|
+
const suite = this.suiteState.suite;
|
|
311
|
+
|
|
312
|
+
this.elementTestDataStates = suite.testData
|
|
313
|
+
.filter(
|
|
314
|
+
(testData): testData is BaseDataResolver =>
|
|
315
|
+
testData instanceof BaseDataResolver,
|
|
316
|
+
)
|
|
317
|
+
.map(
|
|
318
|
+
(testData) =>
|
|
319
|
+
new LakehouseElementTestDataState(testData, this.editorStore),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
this.selectedElementTestDataState =
|
|
323
|
+
this.elementTestDataStates.find(
|
|
324
|
+
(state) => state.element.path === previouslySelectedElementPath,
|
|
325
|
+
) ?? this.elementTestDataStates[0];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
setSelectedElementTestDataState(
|
|
329
|
+
val: LakehouseElementTestDataState | undefined,
|
|
330
|
+
): void {
|
|
331
|
+
this.selectedElementTestDataState = val;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Per-suite state ──────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
export class IngestTestSuiteState extends TestableTestSuiteEditorState {
|
|
338
|
+
readonly testableState: IngestTestableState;
|
|
339
|
+
override suite: IngestTestSuite;
|
|
340
|
+
override testStates: IngestTestState[] = [];
|
|
341
|
+
declare selectTestState: IngestTestState | undefined;
|
|
342
|
+
testDataState: IngestTestDataState;
|
|
343
|
+
readonly uuid = uuid();
|
|
344
|
+
|
|
345
|
+
constructor(
|
|
346
|
+
editorStore: EditorStore,
|
|
347
|
+
testableState: IngestTestableState,
|
|
348
|
+
suite: IngestTestSuite,
|
|
349
|
+
) {
|
|
350
|
+
super(
|
|
351
|
+
testableState.ingest,
|
|
352
|
+
suite,
|
|
353
|
+
testableState.ingestDefinitionEditorState.isReadOnly,
|
|
354
|
+
editorStore,
|
|
355
|
+
);
|
|
356
|
+
makeObservable(this, {
|
|
357
|
+
testStates: observable,
|
|
358
|
+
runningSuiteState: observable,
|
|
359
|
+
selectTestState: observable,
|
|
360
|
+
testDataState: observable,
|
|
361
|
+
changeTest: action,
|
|
362
|
+
deleteTest: action,
|
|
363
|
+
removeTestState: action,
|
|
364
|
+
addNewTest: flow,
|
|
365
|
+
runSuite: flow,
|
|
366
|
+
runFailingTests: flow,
|
|
367
|
+
buildTestStates: action,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
this.testableState = testableState;
|
|
371
|
+
this.suite = suite;
|
|
372
|
+
this.testDataState = new IngestTestDataState(this);
|
|
373
|
+
this.buildTestStates();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
refreshTestDataState(): void {
|
|
377
|
+
this.testDataState = new IngestTestDataState(this, {
|
|
378
|
+
selectedElementPath:
|
|
379
|
+
this.testDataState.selectedElementTestDataState?.element.path,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
buildTestStates(): void {
|
|
384
|
+
this.suite.tests.forEach((test) => {
|
|
385
|
+
test.__parent = this.suite;
|
|
386
|
+
if (test instanceof IngestMatViewTest && !test.datasetId) {
|
|
387
|
+
const relationAssertion = test.assertions.find(
|
|
388
|
+
(candidate): candidate is EqualToRelation =>
|
|
389
|
+
candidate instanceof EqualToRelation,
|
|
390
|
+
);
|
|
391
|
+
test.datasetId =
|
|
392
|
+
relationAssertion?.expected.paths[0] ??
|
|
393
|
+
this.testableState.datasetNames[0] ??
|
|
394
|
+
'';
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
this.testStates = this.suite.tests.map(
|
|
399
|
+
(test) => new IngestTestState(this, test as IngestMatViewTest),
|
|
400
|
+
);
|
|
401
|
+
this.selectTestState = this.testStates[0];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
override deleteTest(test: IngestMatViewTest): void {
|
|
405
|
+
super.deleteTest(test);
|
|
406
|
+
if (this.suite.tests.length === 0) {
|
|
407
|
+
// Ingest requires at least one test per suite; remove emptied suites.
|
|
408
|
+
this.testableState.deleteSuite(this);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
*addNewTest(
|
|
413
|
+
testName: string,
|
|
414
|
+
datasetName: string,
|
|
415
|
+
): GeneratorFn<string | undefined> {
|
|
416
|
+
if (this.suite.tests.find((test) => test.id === testName)) {
|
|
417
|
+
return 'Duplicated test name';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let inferredColumns: string[] = [];
|
|
421
|
+
try {
|
|
422
|
+
const columns = (yield inferIngestDatasetColumns(
|
|
423
|
+
this.editorStore,
|
|
424
|
+
this.testableState.ingest,
|
|
425
|
+
datasetName,
|
|
426
|
+
)) as string[] | undefined;
|
|
427
|
+
if (columns) {
|
|
428
|
+
inferredColumns = columns;
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
// Best effort only; continue with an empty expected relation.
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const dataset =
|
|
435
|
+
this.testableState.ingest.TEMPORARY_MATVIEW_FUNCTION_DATA_SETS?.find(
|
|
436
|
+
(candidate) => candidate.name === datasetName,
|
|
437
|
+
);
|
|
438
|
+
const rawLambda = dataset?.source.function;
|
|
439
|
+
|
|
440
|
+
if (rawLambda) {
|
|
441
|
+
const all =
|
|
442
|
+
(yield this.editorStore.graphManagerState.graphManager.collectAccessorsInRawLambda(
|
|
443
|
+
rawLambda,
|
|
444
|
+
this.editorStore.graphManagerState.graph,
|
|
445
|
+
)) as Accessor[];
|
|
446
|
+
const externalAccessors = all.filter((accessor) =>
|
|
447
|
+
isIngestOrDataProductAccessor(accessor),
|
|
448
|
+
);
|
|
449
|
+
const byElement = new Map<string, Accessor[]>();
|
|
450
|
+
for (const accessor of externalAccessors) {
|
|
451
|
+
const group = byElement.get(accessor.parentElement.path) ?? [];
|
|
452
|
+
group.push(accessor);
|
|
453
|
+
byElement.set(accessor.parentElement.path, group);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (const [elementPath, accessors] of byElement) {
|
|
457
|
+
const element =
|
|
458
|
+
this.editorStore.graphManagerState.graph.getNullableElement(
|
|
459
|
+
elementPath,
|
|
460
|
+
);
|
|
461
|
+
if (!element) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const existingResolver = this.suite.testData.find(
|
|
466
|
+
(testData): testData is BaseDataResolver =>
|
|
467
|
+
testData instanceof BaseDataResolver &&
|
|
468
|
+
testData.element.value === element,
|
|
469
|
+
);
|
|
470
|
+
const relationElementsData =
|
|
471
|
+
existingResolver?.data instanceof RelationElementsData
|
|
472
|
+
? existingResolver.data
|
|
473
|
+
: undefined;
|
|
474
|
+
|
|
475
|
+
if (!existingResolver || !relationElementsData) {
|
|
476
|
+
const resolver = new BaseDataResolver();
|
|
477
|
+
resolver.element =
|
|
478
|
+
PackageableElementExplicitReference.create(element);
|
|
479
|
+
const relationData = buildRelationElementsDataWithColumns(accessors);
|
|
480
|
+
observe_RelationElementsData(relationData);
|
|
481
|
+
resolver.data = relationData;
|
|
482
|
+
this.suite.testData = [...this.suite.testData, resolver];
|
|
483
|
+
} else {
|
|
484
|
+
for (const accessor of accessors) {
|
|
485
|
+
const itemId = accessor.accessor;
|
|
486
|
+
if (
|
|
487
|
+
!relationElementsData.relationElements.find(
|
|
488
|
+
(relationElement) => relationElement.paths[0] === itemId,
|
|
489
|
+
)
|
|
490
|
+
) {
|
|
491
|
+
const columns = accessor.relationType.columns.map(
|
|
492
|
+
(column) => column.name,
|
|
493
|
+
);
|
|
494
|
+
relationElementsData.relationElements.push(
|
|
495
|
+
createEmptyRelationElement(itemId, columns),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
this.editorStore.applicationStore.notificationService.notifyWarning(
|
|
503
|
+
'Dataset accessors cannot be resolved; using default ingest test data',
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (this.suite.testData.length === 0) {
|
|
508
|
+
// Ingest test suites require testData >= 1; seed a minimal editable resolver.
|
|
509
|
+
const resolver = new BaseDataResolver();
|
|
510
|
+
resolver.element = PackageableElementExplicitReference.create(
|
|
511
|
+
this.testableState.ingest,
|
|
512
|
+
);
|
|
513
|
+
const relationData = new RelationElementsData();
|
|
514
|
+
relationData.relationElements = [
|
|
515
|
+
createEmptyRelationElement(datasetName, inferredColumns),
|
|
516
|
+
];
|
|
517
|
+
observe_RelationElementsData(relationData);
|
|
518
|
+
resolver.data = relationData;
|
|
519
|
+
this.suite.testData = [resolver];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const test = new IngestMatViewTest();
|
|
523
|
+
test.id = testName;
|
|
524
|
+
test.datasetId = datasetName;
|
|
525
|
+
test.__parent = this.suite;
|
|
526
|
+
|
|
527
|
+
const assertion = new EqualToRelation();
|
|
528
|
+
assertion.id = 'assert_1';
|
|
529
|
+
assertion.expected = createExpectedRelationElement(
|
|
530
|
+
datasetName,
|
|
531
|
+
inferredColumns,
|
|
532
|
+
);
|
|
533
|
+
test.assertions = [assertion];
|
|
534
|
+
|
|
535
|
+
testSuite_addTest(
|
|
536
|
+
this.suite,
|
|
537
|
+
test,
|
|
538
|
+
this.editorStore.changeDetectionState.observerContext,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
this.refreshTestDataState();
|
|
542
|
+
|
|
543
|
+
const testState = new IngestTestState(this, test);
|
|
544
|
+
this.testStates.push(testState);
|
|
545
|
+
this.selectTestState = testState;
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ─── Top-level testable state ─────────────────────────────────────────────────
|
|
551
|
+
|
|
552
|
+
export class IngestTestableState {
|
|
553
|
+
readonly editorStore: EditorStore;
|
|
554
|
+
readonly ingestDefinitionEditorState: IngestDefinitionEditorState;
|
|
555
|
+
|
|
556
|
+
suiteStates: IngestTestSuiteState[] = [];
|
|
557
|
+
selectedSuiteState: IngestTestSuiteState | undefined;
|
|
558
|
+
showCreateSuiteModal = false;
|
|
559
|
+
showCreateTestModal = false;
|
|
560
|
+
suiteToRename: TestSuite | undefined;
|
|
561
|
+
|
|
562
|
+
constructor(ingestDefinitionEditorState: IngestDefinitionEditorState) {
|
|
563
|
+
makeObservable(this, {
|
|
564
|
+
suiteStates: observable,
|
|
565
|
+
selectedSuiteState: observable,
|
|
566
|
+
showCreateSuiteModal: observable,
|
|
567
|
+
showCreateTestModal: observable,
|
|
568
|
+
suiteToRename: observable,
|
|
569
|
+
setSelectedSuiteState: action,
|
|
570
|
+
setShowCreateSuiteModal: action,
|
|
571
|
+
setShowCreateTestModal: action,
|
|
572
|
+
setSuiteToRename: action,
|
|
573
|
+
changeSuite: action,
|
|
574
|
+
init: action,
|
|
575
|
+
createSuite: flow,
|
|
576
|
+
deleteSuite: action,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
this.editorStore = ingestDefinitionEditorState.editorStore;
|
|
580
|
+
this.ingestDefinitionEditorState = ingestDefinitionEditorState;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
get ingest(): IngestDefinition {
|
|
584
|
+
return this.ingestDefinitionEditorState.ingest;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
get datasetNames(): string[] {
|
|
588
|
+
return this.ingestDefinitionEditorState.getMatviewFuncNames();
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
setSelectedSuiteState(val: IngestTestSuiteState | undefined): void {
|
|
592
|
+
this.selectedSuiteState = val;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
setShowCreateSuiteModal(val: boolean): void {
|
|
596
|
+
this.showCreateSuiteModal = val;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
setShowCreateTestModal(val: boolean): void {
|
|
600
|
+
this.showCreateTestModal = val;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
setSuiteToRename(val: TestSuite | undefined): void {
|
|
604
|
+
this.suiteToRename = val;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
changeSuite(suite: TestSuite): void {
|
|
608
|
+
this.selectedSuiteState = this.suiteStates.find((s) => s.suite === suite);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
init(): void {
|
|
612
|
+
this.suiteStates = this.ingest.tests.map(
|
|
613
|
+
(suite) => new IngestTestSuiteState(this.editorStore, this, suite),
|
|
614
|
+
);
|
|
615
|
+
this.selectedSuiteState = this.suiteStates[0];
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
*createSuite(
|
|
619
|
+
suiteName: string,
|
|
620
|
+
testName: string,
|
|
621
|
+
datasetName: string,
|
|
622
|
+
): GeneratorFn<string | undefined> {
|
|
623
|
+
if (this.ingest.tests.find((suite) => suite.id === suiteName)) {
|
|
624
|
+
return 'Duplicated suite name';
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const suite = new IngestTestSuite();
|
|
628
|
+
suite.id = suiteName;
|
|
629
|
+
suite.tests = [];
|
|
630
|
+
|
|
631
|
+
const suiteState = new IngestTestSuiteState(this.editorStore, this, suite);
|
|
632
|
+
|
|
633
|
+
const error = (yield flowResult(
|
|
634
|
+
suiteState.addNewTest(testName, datasetName),
|
|
635
|
+
)) as string | undefined;
|
|
636
|
+
|
|
637
|
+
if (error) {
|
|
638
|
+
return error;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
addUniqueEntry(this.ingest.tests, suite);
|
|
642
|
+
this.suiteStates.push(suiteState);
|
|
643
|
+
this.selectedSuiteState = suiteState;
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
deleteSuite(suiteState: IngestTestSuiteState): void {
|
|
648
|
+
deleteEntry(this.ingest.tests, suiteState.suite);
|
|
649
|
+
deleteEntry(this.suiteStates, suiteState);
|
|
650
|
+
if (this.selectedSuiteState === suiteState) {
|
|
651
|
+
this.selectedSuiteState = this.suiteStates[0];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|