@finos/legend-application-studio 28.21.30 → 28.21.32
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__/LegendStudioApplicationNavigationContext.d.ts +2 -1
- package/lib/__lib__/LegendStudioApplicationNavigationContext.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioApplicationNavigationContext.js +1 -0
- package/lib/__lib__/LegendStudioApplicationNavigationContext.js.map +1 -1
- package/lib/components/ElementIconUtils.d.ts.map +1 -1
- package/lib/components/ElementIconUtils.js +2 -0
- package/lib/components/ElementIconUtils.js.map +1 -1
- package/lib/components/editor/availability-editor.css +1 -0
- package/lib/components/editor/availability-editor.css.map +1 -0
- package/lib/components/editor/editor-group/EditorGroup.d.ts.map +1 -1
- package/lib/components/editor/editor-group/EditorGroup.js +5 -0
- package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.d.ts +19 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.js +50 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.js.map +1 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.d.ts +22 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.js +205 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.js.map +1 -0
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts +5 -0
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/panel-group/DevToolPanel.d.ts.map +1 -1
- package/lib/components/editor/panel-group/DevToolPanel.js +27 -24
- package/lib/components/editor/panel-group/DevToolPanel.js.map +1 -1
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.d.ts.map +1 -1
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js +1 -0
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.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/EditorGraphState.d.ts.map +1 -1
- package/lib/stores/editor/EditorGraphState.js +4 -1
- package/lib/stores/editor/EditorGraphState.js.map +1 -1
- package/lib/stores/editor/EditorTabManagerState.d.ts.map +1 -1
- package/lib/stores/editor/EditorTabManagerState.js +5 -1
- package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.d.ts +32 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.js +46 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.d.ts +111 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js +425 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +3 -0
- 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 +8 -2
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.d.ts +2 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.d.ts.map +1 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.js +1 -0
- package/lib/stores/editor/utils/ModelClassifierUtils.js.map +1 -1
- package/package.json +12 -12
- package/src/__lib__/LegendStudioApplicationNavigationContext.ts +1 -0
- package/src/components/ElementIconUtils.tsx +2 -0
- package/src/components/editor/editor-group/EditorGroup.tsx +4 -0
- package/src/components/editor/editor-group/availability/AvailabilityEditor.tsx +114 -0
- package/src/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.tsx +829 -0
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +1 -1
- package/src/components/editor/panel-group/DevToolPanel.tsx +61 -60
- package/src/components/extensions/Core_LegendStudioApplicationPlugin.tsx +1 -0
- package/src/stores/editor/EditorGraphState.ts +3 -0
- package/src/stores/editor/EditorTabManagerState.ts +4 -0
- package/src/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.ts +56 -0
- package/src/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.ts +631 -0
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +13 -3
- package/src/stores/editor/utils/ModelClassifierUtils.ts +1 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,631 @@
|
|
|
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 Availability,
|
|
19
|
+
AvailabilityBarrierTest,
|
|
20
|
+
AvailabilityTestSuite,
|
|
21
|
+
EqualToJson,
|
|
22
|
+
ExternalFormatData,
|
|
23
|
+
RelationElement,
|
|
24
|
+
RelationRowTestData,
|
|
25
|
+
DEFAULT_TEST_PREFIX,
|
|
26
|
+
DEFAULT_TEST_SUITE_PREFIX,
|
|
27
|
+
DEFAULT_TEST_ASSERTION_PREFIX,
|
|
28
|
+
observe_AvailabilityBarrierTest,
|
|
29
|
+
observe_AvailabilityTestSuite,
|
|
30
|
+
observe_EqualToJson,
|
|
31
|
+
observe_ExternalFormatData,
|
|
32
|
+
observe_RelationElement,
|
|
33
|
+
observe_RelationRowTestData,
|
|
34
|
+
} from '@finos/legend-graph';
|
|
35
|
+
import { action, computed, flow, makeObservable, observable } from 'mobx';
|
|
36
|
+
import {
|
|
37
|
+
addUniqueEntry,
|
|
38
|
+
ContentType,
|
|
39
|
+
generateEnumerableNameFromToken,
|
|
40
|
+
type GeneratorFn,
|
|
41
|
+
guaranteeType,
|
|
42
|
+
isString,
|
|
43
|
+
} from '@finos/legend-shared';
|
|
44
|
+
import type { AvailabilityEditorState } from '../AvailabilityEditorState.js';
|
|
45
|
+
import {
|
|
46
|
+
getTestableResultFromTestResults,
|
|
47
|
+
type TESTABLE_RESULT,
|
|
48
|
+
} from '../../../../sidebar-state/testable/GlobalTestRunnerState.js';
|
|
49
|
+
import {
|
|
50
|
+
TestableTestEditorState,
|
|
51
|
+
TestableTestSuiteEditorState,
|
|
52
|
+
} from '../../testable/TestableEditorState.js';
|
|
53
|
+
import { RelationElementState } from '../../data/EmbeddedDataState.js';
|
|
54
|
+
import { atomicTest_addAssertion } from '../../../../../graph-modifier/Testable_GraphModifierHelper.js';
|
|
55
|
+
import { externalFormatData_setData } from '../../../../../graph-modifier/DSL_Data_GraphModifierHelper.js';
|
|
56
|
+
|
|
57
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export enum AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT {
|
|
60
|
+
DEFAULT = 'DEFAULT',
|
|
61
|
+
LITE = 'LITE',
|
|
62
|
+
ALLOY_QUERY = 'ALLOY_QUERY',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const DEFAULT_RELATION_PATH = 'barrier';
|
|
66
|
+
|
|
67
|
+
// Hard-coded columns shared by every availability barrier test. These match
|
|
68
|
+
// the schema the engine's barrier relation exposes to the test runtime.
|
|
69
|
+
export const AVAILABILITY_TEST_DATA_COLUMNS = [
|
|
70
|
+
'event_id',
|
|
71
|
+
'batch_id',
|
|
72
|
+
'ingest_path',
|
|
73
|
+
'appdir_id',
|
|
74
|
+
'batch_attributes',
|
|
75
|
+
'ingest_completed_ts_utc',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// Default fixed timestamp populated for new rows.
|
|
79
|
+
export const DEFAULT_INGEST_COMPLETED_TS_UTC = '2025-01-01T00:00:00Z';
|
|
80
|
+
|
|
81
|
+
const AVAILABILITY_COLUMN_DEFAULTS: Record<string, string> = {
|
|
82
|
+
ingest_completed_ts_utc: DEFAULT_INGEST_COMPLETED_TS_UTC,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// ─── Watermark expected-JSON models ─────────────────────────────────────────
|
|
86
|
+
// These types model the shape of the `EqualToJson.expected` payload for each
|
|
87
|
+
// watermark serialization format, so we can add/remove entries with typed
|
|
88
|
+
// mutations rather than juggling `unknown` and path-walking helpers.
|
|
89
|
+
|
|
90
|
+
type WatermarkBatch = {
|
|
91
|
+
batchId: number;
|
|
92
|
+
ingestDefinitionPath: string;
|
|
93
|
+
ingestDefinitionReference: { ingestDefinitionUrn: string };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type WatermarkExpectedDefault = {
|
|
97
|
+
availabilityDefinitionReference: { availabilityDefinitionUrn: string };
|
|
98
|
+
evaluatedWatermark: { watermarkBatches: WatermarkBatch[] };
|
|
99
|
+
evaluationResult: boolean;
|
|
100
|
+
eventId: number;
|
|
101
|
+
watermarkId: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type WatermarkLiteEntry = { ingestDefinitionPath: string; batchId: number };
|
|
105
|
+
|
|
106
|
+
type WatermarkAlloyQueryEntry = { batchId: number };
|
|
107
|
+
|
|
108
|
+
type WatermarkExpected =
|
|
109
|
+
| {
|
|
110
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT;
|
|
111
|
+
value: WatermarkExpectedDefault;
|
|
112
|
+
}
|
|
113
|
+
| {
|
|
114
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.LITE;
|
|
115
|
+
value: WatermarkLiteEntry[];
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.ALLOY_QUERY;
|
|
119
|
+
value: Record<string, WatermarkAlloyQueryEntry>;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const buildWatermarkBatch = (): WatermarkBatch => ({
|
|
123
|
+
batchId: 0,
|
|
124
|
+
ingestDefinitionPath: '',
|
|
125
|
+
ingestDefinitionReference: { ingestDefinitionUrn: '' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const buildLiteEntry = (): WatermarkLiteEntry => ({
|
|
129
|
+
ingestDefinitionPath: '',
|
|
130
|
+
batchId: 0,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const buildAlloyQueryEntry = (): WatermarkAlloyQueryEntry => ({ batchId: 0 });
|
|
134
|
+
|
|
135
|
+
const buildDefaultTemplate = (): WatermarkExpectedDefault => ({
|
|
136
|
+
availabilityDefinitionReference: { availabilityDefinitionUrn: '' },
|
|
137
|
+
evaluatedWatermark: { watermarkBatches: [buildWatermarkBatch()] },
|
|
138
|
+
evaluationResult: true,
|
|
139
|
+
eventId: 0,
|
|
140
|
+
watermarkId: '',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const stringifyExpected = (value: unknown): string =>
|
|
144
|
+
JSON.stringify(value, null, 2);
|
|
145
|
+
|
|
146
|
+
const buildExpectedTemplateString = (
|
|
147
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
148
|
+
): string => {
|
|
149
|
+
switch (format) {
|
|
150
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.LITE:
|
|
151
|
+
return stringifyExpected([buildLiteEntry()]);
|
|
152
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.ALLOY_QUERY:
|
|
153
|
+
return stringifyExpected({ '': buildAlloyQueryEntry() });
|
|
154
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT:
|
|
155
|
+
default:
|
|
156
|
+
return stringifyExpected(buildDefaultTemplate());
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Structural validators — user edits raw JSON in Monaco, so the parsed value
|
|
161
|
+
// might not match the expected format. These narrow the parse result to a
|
|
162
|
+
// typed `WatermarkExpected`; on failure the "Add entry" action is disabled.
|
|
163
|
+
const isRecord = (val: unknown): val is Record<string, unknown> =>
|
|
164
|
+
typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
165
|
+
|
|
166
|
+
const isDefaultShape = (val: unknown): val is WatermarkExpectedDefault =>
|
|
167
|
+
isRecord(val) &&
|
|
168
|
+
isRecord(val.evaluatedWatermark) &&
|
|
169
|
+
Array.isArray(val.evaluatedWatermark.watermarkBatches);
|
|
170
|
+
|
|
171
|
+
const isLiteShape = (val: unknown): val is WatermarkLiteEntry[] =>
|
|
172
|
+
Array.isArray(val);
|
|
173
|
+
|
|
174
|
+
const isAlloyQueryShape = (
|
|
175
|
+
val: unknown,
|
|
176
|
+
): val is Record<string, WatermarkAlloyQueryEntry> => isRecord(val);
|
|
177
|
+
|
|
178
|
+
const parseWatermarkExpected = (
|
|
179
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
180
|
+
raw: string,
|
|
181
|
+
): WatermarkExpected | undefined => {
|
|
182
|
+
if (!raw) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
let parsed: unknown;
|
|
186
|
+
try {
|
|
187
|
+
parsed = JSON.parse(raw);
|
|
188
|
+
} catch {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
switch (format) {
|
|
192
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT:
|
|
193
|
+
return isDefaultShape(parsed) ? { format, value: parsed } : undefined;
|
|
194
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.LITE:
|
|
195
|
+
return isLiteShape(parsed) ? { format, value: parsed } : undefined;
|
|
196
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.ALLOY_QUERY:
|
|
197
|
+
return isAlloyQueryShape(parsed) ? { format, value: parsed } : undefined;
|
|
198
|
+
default:
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Append a new entry to the parsed expected model. Returns the mutated value
|
|
204
|
+
// serialized as pretty JSON, ready to be written back onto the assertion.
|
|
205
|
+
const addWatermarkEntry = (expected: WatermarkExpected): string => {
|
|
206
|
+
switch (expected.format) {
|
|
207
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT: {
|
|
208
|
+
const next: WatermarkExpectedDefault = {
|
|
209
|
+
...expected.value,
|
|
210
|
+
evaluatedWatermark: {
|
|
211
|
+
watermarkBatches: [
|
|
212
|
+
...expected.value.evaluatedWatermark.watermarkBatches,
|
|
213
|
+
buildWatermarkBatch(),
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
return stringifyExpected(next);
|
|
218
|
+
}
|
|
219
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.LITE:
|
|
220
|
+
return stringifyExpected([...expected.value, buildLiteEntry()]);
|
|
221
|
+
case AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.ALLOY_QUERY: {
|
|
222
|
+
const next = { ...expected.value };
|
|
223
|
+
// Pick a fresh unique key so a second empty-string add is still visible.
|
|
224
|
+
let key = '';
|
|
225
|
+
if (Object.prototype.hasOwnProperty.call(next, key)) {
|
|
226
|
+
let counter = 1;
|
|
227
|
+
while (Object.prototype.hasOwnProperty.call(next, `path_${counter}`)) {
|
|
228
|
+
counter++;
|
|
229
|
+
}
|
|
230
|
+
key = `path_${counter}`;
|
|
231
|
+
}
|
|
232
|
+
next[key] = buildAlloyQueryEntry();
|
|
233
|
+
return stringifyExpected(next);
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
return stringifyExpected(expected);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ─── Test-data helpers ──────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
const createDefaultRelationElement = (
|
|
243
|
+
columns: readonly string[] = AVAILABILITY_TEST_DATA_COLUMNS,
|
|
244
|
+
): RelationElement => {
|
|
245
|
+
const element = new RelationElement();
|
|
246
|
+
element.paths = [DEFAULT_RELATION_PATH];
|
|
247
|
+
element.columns = [...columns];
|
|
248
|
+
const row = new RelationRowTestData();
|
|
249
|
+
row.values = columns.map((col) => AVAILABILITY_COLUMN_DEFAULTS[col] ?? '');
|
|
250
|
+
element.rows = [observe_RelationRowTestData(row)];
|
|
251
|
+
return observe_RelationElement(element);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Return the union of columns declared across all existing suites in the
|
|
255
|
+
// availability, preserving first-seen order. Used to seed a new suite's
|
|
256
|
+
// test data so users don't have to redeclare the whole schema.
|
|
257
|
+
const collectExistingColumns = (
|
|
258
|
+
availability: Availability,
|
|
259
|
+
): string[] | undefined => {
|
|
260
|
+
const seen = new Set<string>();
|
|
261
|
+
const ordered: string[] = [];
|
|
262
|
+
availability.tests.forEach((test) => {
|
|
263
|
+
if (!(test instanceof AvailabilityTestSuite)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
test.testData?.columns.forEach((column) => {
|
|
267
|
+
if (!seen.has(column)) {
|
|
268
|
+
seen.add(column);
|
|
269
|
+
ordered.push(column);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
return ordered.length > 0 ? ordered : undefined;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// ─── Assertion / test / suite factories ─────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
const createDefaultAssertion = (
|
|
279
|
+
test: AvailabilityBarrierTest,
|
|
280
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
281
|
+
): EqualToJson => {
|
|
282
|
+
const externalFormatData = new ExternalFormatData();
|
|
283
|
+
externalFormatData.contentType = ContentType.APPLICATION_JSON;
|
|
284
|
+
externalFormatData.data = buildExpectedTemplateString(format);
|
|
285
|
+
const assertion = new EqualToJson();
|
|
286
|
+
assertion.id = generateEnumerableNameFromToken(
|
|
287
|
+
test.assertions.map((value) => value.id),
|
|
288
|
+
DEFAULT_TEST_ASSERTION_PREFIX,
|
|
289
|
+
);
|
|
290
|
+
assertion.parentTest = test;
|
|
291
|
+
assertion.expected = observe_ExternalFormatData(externalFormatData);
|
|
292
|
+
return observe_EqualToJson(assertion);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const createDefaultTest = (
|
|
296
|
+
suite: AvailabilityTestSuite,
|
|
297
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
298
|
+
id?: string,
|
|
299
|
+
): AvailabilityBarrierTest => {
|
|
300
|
+
const test = new AvailabilityBarrierTest();
|
|
301
|
+
test.id =
|
|
302
|
+
id ??
|
|
303
|
+
generateEnumerableNameFromToken(
|
|
304
|
+
suite.tests.map((value) => value.id),
|
|
305
|
+
DEFAULT_TEST_PREFIX,
|
|
306
|
+
);
|
|
307
|
+
test.__parent = suite;
|
|
308
|
+
test.watermarkSerializationFormat = format;
|
|
309
|
+
const observedTest = observe_AvailabilityBarrierTest(test);
|
|
310
|
+
atomicTest_addAssertion(
|
|
311
|
+
observedTest,
|
|
312
|
+
createDefaultAssertion(observedTest, format),
|
|
313
|
+
);
|
|
314
|
+
return observedTest;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const createDefaultSuite = (
|
|
318
|
+
availability: Availability,
|
|
319
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
320
|
+
suiteId?: string,
|
|
321
|
+
testId?: string,
|
|
322
|
+
): AvailabilityTestSuite => {
|
|
323
|
+
const suite = new AvailabilityTestSuite();
|
|
324
|
+
suite.id =
|
|
325
|
+
suiteId ??
|
|
326
|
+
generateEnumerableNameFromToken(
|
|
327
|
+
availability.tests.map((value) => value.id),
|
|
328
|
+
DEFAULT_TEST_SUITE_PREFIX,
|
|
329
|
+
);
|
|
330
|
+
suite.__parent = availability;
|
|
331
|
+
suite.testData = createDefaultRelationElement(
|
|
332
|
+
collectExistingColumns(availability),
|
|
333
|
+
);
|
|
334
|
+
const observedSuite = observe_AvailabilityTestSuite(suite);
|
|
335
|
+
addUniqueEntry(
|
|
336
|
+
observedSuite.tests,
|
|
337
|
+
createDefaultTest(observedSuite, format, testId),
|
|
338
|
+
);
|
|
339
|
+
return observedSuite;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// ─── Per-test state ─────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
export class AvailabilityTestState extends TestableTestEditorState {
|
|
345
|
+
readonly suiteState: AvailabilityTestSuiteState;
|
|
346
|
+
override test: AvailabilityBarrierTest;
|
|
347
|
+
|
|
348
|
+
constructor(
|
|
349
|
+
suiteState: AvailabilityTestSuiteState,
|
|
350
|
+
test: AvailabilityBarrierTest,
|
|
351
|
+
) {
|
|
352
|
+
super(
|
|
353
|
+
suiteState.testableState.availability,
|
|
354
|
+
test,
|
|
355
|
+
suiteState.testableState.editorState.isReadOnly,
|
|
356
|
+
suiteState.editorStore,
|
|
357
|
+
);
|
|
358
|
+
makeObservable(this, {
|
|
359
|
+
selectedAsertionState: observable,
|
|
360
|
+
selectedTab: observable,
|
|
361
|
+
assertionToRename: observable,
|
|
362
|
+
assertionEditorStates: observable,
|
|
363
|
+
testResultState: observable,
|
|
364
|
+
debugTestResultState: observable,
|
|
365
|
+
runningTestAction: observable,
|
|
366
|
+
format: computed,
|
|
367
|
+
firstJsonAssertion: computed,
|
|
368
|
+
parsedExpected: computed,
|
|
369
|
+
setAssertionToRename: action,
|
|
370
|
+
setSelectedTab: action,
|
|
371
|
+
addAssertion: action,
|
|
372
|
+
deleteAssertion: action,
|
|
373
|
+
openAssertion: action,
|
|
374
|
+
handleTestResult: action,
|
|
375
|
+
resetResult: action,
|
|
376
|
+
setExpectedValue: action,
|
|
377
|
+
addExpectedEntry: action,
|
|
378
|
+
runTest: flow,
|
|
379
|
+
debugTest: flow,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
this.suiteState = suiteState;
|
|
383
|
+
this.test = test;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
get format(): AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT {
|
|
387
|
+
const raw = this.test.watermarkSerializationFormat;
|
|
388
|
+
if (
|
|
389
|
+
raw === AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.LITE ||
|
|
390
|
+
raw === AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.ALLOY_QUERY
|
|
391
|
+
) {
|
|
392
|
+
return raw;
|
|
393
|
+
}
|
|
394
|
+
return AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get firstJsonAssertion(): EqualToJson | undefined {
|
|
398
|
+
const first = this.test.assertions[0];
|
|
399
|
+
return first instanceof EqualToJson ? first : undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
get expectedValue(): string {
|
|
403
|
+
return this.firstJsonAssertion?.expected.data ?? '';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
setExpectedValue(val: string): void {
|
|
407
|
+
const assertion = this.firstJsonAssertion;
|
|
408
|
+
if (!assertion) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
externalFormatData_setData(assertion.expected, val);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
get parsedExpected(): WatermarkExpected | undefined {
|
|
415
|
+
return parseWatermarkExpected(this.format, this.expectedValue);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Append a new entry to the expected payload. No-op if the current text
|
|
419
|
+
// does not parse to a valid shape for the test's format.
|
|
420
|
+
addExpectedEntry(): void {
|
|
421
|
+
const parsed = this.parsedExpected;
|
|
422
|
+
if (!parsed) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.setExpectedValue(addWatermarkEntry(parsed));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ─── Per-suite state ────────────────────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
export class AvailabilityTestSuiteState extends TestableTestSuiteEditorState {
|
|
432
|
+
readonly testableState: AvailabilityTestableState;
|
|
433
|
+
override suite: AvailabilityTestSuite;
|
|
434
|
+
override testStates: AvailabilityTestState[] = [];
|
|
435
|
+
override selectTestState: AvailabilityTestState | undefined;
|
|
436
|
+
testToRename: AvailabilityBarrierTest | undefined;
|
|
437
|
+
readonly testDataState: RelationElementState;
|
|
438
|
+
|
|
439
|
+
constructor(
|
|
440
|
+
testableState: AvailabilityTestableState,
|
|
441
|
+
suite: AvailabilityTestSuite,
|
|
442
|
+
) {
|
|
443
|
+
super(
|
|
444
|
+
testableState.availability,
|
|
445
|
+
suite,
|
|
446
|
+
testableState.editorState.isReadOnly,
|
|
447
|
+
testableState.editorState.editorStore,
|
|
448
|
+
);
|
|
449
|
+
makeObservable(this, {
|
|
450
|
+
selectTestState: observable,
|
|
451
|
+
testStates: observable,
|
|
452
|
+
testToRename: observable,
|
|
453
|
+
runningSuiteState: observable,
|
|
454
|
+
setTestToRename: action,
|
|
455
|
+
changeTest: action,
|
|
456
|
+
addTest: action,
|
|
457
|
+
deleteTest: action,
|
|
458
|
+
runSuite: flow,
|
|
459
|
+
runFailingTests: flow,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
this.testableState = testableState;
|
|
463
|
+
this.suite = suite;
|
|
464
|
+
|
|
465
|
+
// Ensure the suite has test data with the canonical shape (hard-coded columns).
|
|
466
|
+
const testData = this.suite.testData ?? createDefaultRelationElement();
|
|
467
|
+
this.suite.testData = testData;
|
|
468
|
+
// Column editing is enabled so users can add/remove columns beyond the
|
|
469
|
+
// seeded schema; new rows are prefilled from `columnDefaults` so the fixed
|
|
470
|
+
// `ingest_completed_ts_utc` value doesn't need to be re-typed.
|
|
471
|
+
this.testDataState = new RelationElementState(testData, {
|
|
472
|
+
columnDefaults: AVAILABILITY_COLUMN_DEFAULTS,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
this.testStates = this.buildTestStates();
|
|
476
|
+
this.selectTestState = this.testStates[0];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private buildTestStates(): AvailabilityTestState[] {
|
|
480
|
+
return this.suite.tests
|
|
481
|
+
.map((value) => guaranteeType(value, AvailabilityBarrierTest))
|
|
482
|
+
.map((value) => new AvailabilityTestState(this, value));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
setTestToRename(test: AvailabilityBarrierTest | undefined): void {
|
|
486
|
+
this.testToRename = test;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
addTest(
|
|
490
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
491
|
+
id?: string,
|
|
492
|
+
): AvailabilityTestState {
|
|
493
|
+
const test = createDefaultTest(this.suite, format, id);
|
|
494
|
+
addUniqueEntry(this.suite.tests, test);
|
|
495
|
+
const state = new AvailabilityTestState(this, test);
|
|
496
|
+
addUniqueEntry(this.testStates, state);
|
|
497
|
+
this.selectTestState = state;
|
|
498
|
+
return state;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
override deleteTest(test: AvailabilityBarrierTest): void {
|
|
502
|
+
super.deleteTest(test);
|
|
503
|
+
this.selectTestState = this.testStates[0];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
get result(): TESTABLE_RESULT {
|
|
507
|
+
return getTestableResultFromTestResults(
|
|
508
|
+
this.testStates.map((state) => state.testResultState.result),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ─── Top-level testable state ───────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
export class AvailabilityTestableState {
|
|
516
|
+
readonly editorState: AvailabilityEditorState;
|
|
517
|
+
selectedSuiteState: AvailabilityTestSuiteState | undefined;
|
|
518
|
+
suiteToRename: AvailabilityTestSuite | undefined;
|
|
519
|
+
showCreateSuiteModal = false;
|
|
520
|
+
showCreateTestModal = false;
|
|
521
|
+
|
|
522
|
+
constructor(editorState: AvailabilityEditorState) {
|
|
523
|
+
this.editorState = editorState;
|
|
524
|
+
|
|
525
|
+
makeObservable(this, {
|
|
526
|
+
selectedSuiteState: observable,
|
|
527
|
+
suiteToRename: observable,
|
|
528
|
+
showCreateSuiteModal: observable,
|
|
529
|
+
showCreateTestModal: observable,
|
|
530
|
+
suites: computed,
|
|
531
|
+
suiteCount: computed,
|
|
532
|
+
testCount: computed,
|
|
533
|
+
initSuites: action,
|
|
534
|
+
changeSuite: action,
|
|
535
|
+
addSuite: action,
|
|
536
|
+
setSuiteToRename: action,
|
|
537
|
+
setShowCreateSuiteModal: action,
|
|
538
|
+
setShowCreateTestModal: action,
|
|
539
|
+
deleteSuite: action,
|
|
540
|
+
runSuite: flow,
|
|
541
|
+
runFailingSuiteTests: flow,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
this.initSuites();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
get availability(): Availability {
|
|
548
|
+
return this.editorState.availability;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
initSuites(): void {
|
|
552
|
+
const suite = this.availability.tests[0];
|
|
553
|
+
this.selectedSuiteState =
|
|
554
|
+
suite instanceof AvailabilityTestSuite
|
|
555
|
+
? new AvailabilityTestSuiteState(this, suite)
|
|
556
|
+
: undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
setSuiteToRename(suite: AvailabilityTestSuite | undefined): void {
|
|
560
|
+
this.suiteToRename = suite;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
setShowCreateSuiteModal(val: boolean): void {
|
|
564
|
+
this.showCreateSuiteModal = val;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
setShowCreateTestModal(val: boolean): void {
|
|
568
|
+
this.showCreateTestModal = val;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
changeSuite(suite: AvailabilityTestSuite): void {
|
|
572
|
+
this.selectedSuiteState = new AvailabilityTestSuiteState(this, suite);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
addSuite(
|
|
576
|
+
format: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
577
|
+
suiteId?: string,
|
|
578
|
+
testId?: string,
|
|
579
|
+
): AvailabilityTestSuiteState {
|
|
580
|
+
const suite = createDefaultSuite(
|
|
581
|
+
this.availability,
|
|
582
|
+
format,
|
|
583
|
+
isString(suiteId) && suiteId ? suiteId : undefined,
|
|
584
|
+
isString(testId) && testId ? testId : undefined,
|
|
585
|
+
);
|
|
586
|
+
addUniqueEntry(this.availability.tests, suite);
|
|
587
|
+
const state = new AvailabilityTestSuiteState(this, suite);
|
|
588
|
+
this.selectedSuiteState = state;
|
|
589
|
+
return state;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
deleteSuite(suite: AvailabilityTestSuite): void {
|
|
593
|
+
const idx = this.availability.tests.indexOf(suite);
|
|
594
|
+
if (idx !== -1) {
|
|
595
|
+
this.availability.tests.splice(idx, 1);
|
|
596
|
+
}
|
|
597
|
+
if (this.selectedSuiteState?.suite === suite) {
|
|
598
|
+
const nextSuite = this.availability.tests[0];
|
|
599
|
+
this.selectedSuiteState =
|
|
600
|
+
nextSuite instanceof AvailabilityTestSuite
|
|
601
|
+
? new AvailabilityTestSuiteState(this, nextSuite)
|
|
602
|
+
: undefined;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
*runSuite(): GeneratorFn<void> {
|
|
607
|
+
if (!this.selectedSuiteState) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
yield* this.selectedSuiteState.runSuite();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
*runFailingSuiteTests(): GeneratorFn<void> {
|
|
614
|
+
if (!this.selectedSuiteState) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
yield* this.selectedSuiteState.runFailingTests();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
get suites(): AvailabilityTestSuite[] {
|
|
621
|
+
return this.editorState.availability.tests;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
get suiteCount(): number {
|
|
625
|
+
return this.suites.length;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
get testCount(): number {
|
|
629
|
+
return this.suites.reduce((sum, suite) => sum + suite.tests.length, 0);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
@@ -216,10 +216,14 @@ export class ModelStoreDataState extends EmbeddedDataState {
|
|
|
216
216
|
export class RelationElementState {
|
|
217
217
|
relationElement: RelationElement;
|
|
218
218
|
supportsColumnEditing: boolean;
|
|
219
|
+
columnDefaults: Record<string, string>;
|
|
219
220
|
|
|
220
221
|
constructor(
|
|
221
222
|
relationElement: RelationElement,
|
|
222
|
-
options?: {
|
|
223
|
+
options?: {
|
|
224
|
+
supportsColumnEditing?: boolean;
|
|
225
|
+
columnDefaults?: Record<string, string>;
|
|
226
|
+
},
|
|
223
227
|
) {
|
|
224
228
|
makeObservable(this, {
|
|
225
229
|
relationElement: observable,
|
|
@@ -233,14 +237,20 @@ export class RelationElementState {
|
|
|
233
237
|
importCSV: action,
|
|
234
238
|
});
|
|
235
239
|
this.supportsColumnEditing = options?.supportsColumnEditing ?? true;
|
|
240
|
+
this.columnDefaults = options?.columnDefaults ?? {};
|
|
236
241
|
this.relationElement = relationElement;
|
|
237
242
|
this.relationElement = observe_RelationElement(relationElement);
|
|
238
243
|
}
|
|
239
244
|
|
|
245
|
+
private defaultValueForColumn(column: string): string {
|
|
246
|
+
return this.columnDefaults[column] ?? '';
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
addColumn(name: string): void {
|
|
241
250
|
this.relationElement.columns.push(name);
|
|
251
|
+
const defaultVal = this.defaultValueForColumn(name);
|
|
242
252
|
this.relationElement.rows.forEach((row) => {
|
|
243
|
-
row.values.push(
|
|
253
|
+
row.values.push(defaultVal);
|
|
244
254
|
});
|
|
245
255
|
}
|
|
246
256
|
|
|
@@ -265,7 +275,7 @@ export class RelationElementState {
|
|
|
265
275
|
row.values = [];
|
|
266
276
|
const newRow = observe_RelationRowTestData(row);
|
|
267
277
|
this.relationElement.columns.forEach((col) => {
|
|
268
|
-
newRow.values.push(
|
|
278
|
+
newRow.values.push(this.defaultValueForColumn(col));
|
|
269
279
|
});
|
|
270
280
|
this.relationElement.rows.push(newRow);
|
|
271
281
|
}
|
|
@@ -80,6 +80,7 @@ export enum PACKAGEABLE_ELEMENT_TYPE {
|
|
|
80
80
|
TEMPORARY__LOCAL_CONNECTION = 'LOCAL_CONNECTION',
|
|
81
81
|
INTERNAL__UnknownElement = 'UNKNOWN',
|
|
82
82
|
INGEST_DEFINITION = 'INGEST_DEFINITION',
|
|
83
|
+
AVAILABILITY = 'AVAILABILITY',
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
export enum PACKAGEABLE_ELEMENT_GROUP_BY_CATEGORY {
|
package/tsconfig.json
CHANGED
|
@@ -125,6 +125,8 @@
|
|
|
125
125
|
"./src/stores/editor/editor-state/element-editor-state/RuntimeEditorState.ts",
|
|
126
126
|
"./src/stores/editor/editor-state/element-editor-state/ToDelete_FunctionActivatorBuilderState.ts",
|
|
127
127
|
"./src/stores/editor/editor-state/element-editor-state/UMLEditorState.ts",
|
|
128
|
+
"./src/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.ts",
|
|
129
|
+
"./src/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.ts",
|
|
128
130
|
"./src/stores/editor/editor-state/element-editor-state/connection/ConnectionEditorState.ts",
|
|
129
131
|
"./src/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.ts",
|
|
130
132
|
"./src/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderWizardState.ts",
|
|
@@ -256,6 +258,8 @@
|
|
|
256
258
|
"./src/components/editor/editor-group/UnsupportedElementEditor.tsx",
|
|
257
259
|
"./src/components/editor/editor-group/accessor/AccessorQueryBuilder.tsx",
|
|
258
260
|
"./src/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.tsx",
|
|
261
|
+
"./src/components/editor/editor-group/availability/AvailabilityEditor.tsx",
|
|
262
|
+
"./src/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.tsx",
|
|
259
263
|
"./src/components/editor/editor-group/connection-editor/ConnectionEditor.tsx",
|
|
260
264
|
"./src/components/editor/editor-group/connection-editor/DatabaseBuilderWizard.tsx",
|
|
261
265
|
"./src/components/editor/editor-group/connection-editor/DatabaseEditorHelper.tsx",
|