@finos/legend-application-studio 28.21.27 → 28.21.29

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.
Files changed (51) hide show
  1. package/lib/__lib__/LegendStudioDocumentation.d.ts +1 -0
  2. package/lib/__lib__/LegendStudioDocumentation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioDocumentation.js +2 -0
  4. package/lib/__lib__/LegendStudioDocumentation.js.map +1 -1
  5. package/lib/__lib__/LegendStudioEvent.d.ts +6 -0
  6. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioEvent.js +7 -0
  8. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  9. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +6 -0
  10. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendStudioTelemetryHelper.js +19 -0
  12. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  13. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  14. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +15 -1
  15. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  16. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  17. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +100 -6
  18. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  19. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  20. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +16 -3
  21. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  22. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts +4 -0
  23. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts.map +1 -1
  24. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js +10 -6
  25. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js.map +1 -1
  26. package/lib/index.css +1 -1
  27. package/lib/index.d.ts +3 -0
  28. package/lib/index.d.ts.map +1 -1
  29. package/lib/index.js +3 -0
  30. package/lib/index.js.map +1 -1
  31. package/lib/package.json +1 -1
  32. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +6 -1
  33. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  34. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +1 -1
  35. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  36. package/lib/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.d.ts +41 -0
  37. package/lib/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -0
  38. package/lib/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.js +17 -0
  39. package/lib/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.js.map +1 -0
  40. package/package.json +16 -16
  41. package/src/__lib__/LegendStudioDocumentation.ts +3 -0
  42. package/src/__lib__/LegendStudioEvent.ts +8 -0
  43. package/src/__lib__/LegendStudioTelemetryHelper.ts +64 -0
  44. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +24 -0
  45. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +373 -4
  46. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +29 -3
  47. package/src/components/editor/editor-group/testable/LakehouseTestableEditor.tsx +26 -5
  48. package/src/index.ts +3 -0
  49. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +1 -1
  50. package/src/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.ts +54 -0
  51. package/tsconfig.json +1 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) 2020-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
+ export {};
17
+ //# sourceMappingURL=DSL_DataSpace_LegendStudioApplicationPlugin_Extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DSL_DataSpace_LegendStudioApplicationPlugin_Extension.js","sourceRoot":"","sources":["../../../src/stores/extensions/DSL_DataSpace_LegendStudioApplicationPlugin_Extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-application-studio",
3
- "version": "28.21.27",
3
+ "version": "28.21.29",
4
4
  "description": "Legend Studio application core",
5
5
  "keywords": [
6
6
  "legend",
@@ -47,21 +47,21 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@dagrejs/dagre": "1.1.4",
50
- "@finos/legend-application": "16.0.117",
51
- "@finos/legend-art": "7.1.157",
52
- "@finos/legend-code-editor": "2.0.191",
53
- "@finos/legend-data-cube": "0.3.108",
54
- "@finos/legend-extension-dsl-data-product": "0.0.101",
55
- "@finos/legend-extension-dsl-diagram": "8.1.255",
56
- "@finos/legend-graph": "32.6.22",
57
- "@finos/legend-lego": "2.0.211",
58
- "@finos/legend-query-builder": "4.18.30",
59
- "@finos/legend-server-depot": "6.1.16",
60
- "@finos/legend-server-lakehouse": "0.3.71",
61
- "@finos/legend-server-sdlc": "5.4.6",
62
- "@finos/legend-server-showcase": "0.2.67",
63
- "@finos/legend-shared": "11.0.26",
64
- "@finos/legend-storage": "3.0.148",
50
+ "@finos/legend-application": "16.0.118",
51
+ "@finos/legend-art": "7.1.158",
52
+ "@finos/legend-code-editor": "2.0.193",
53
+ "@finos/legend-data-cube": "0.3.110",
54
+ "@finos/legend-extension-dsl-data-product": "0.0.103",
55
+ "@finos/legend-extension-dsl-diagram": "8.1.257",
56
+ "@finos/legend-graph": "32.6.24",
57
+ "@finos/legend-lego": "2.0.213",
58
+ "@finos/legend-query-builder": "4.18.32",
59
+ "@finos/legend-server-depot": "6.1.17",
60
+ "@finos/legend-server-lakehouse": "0.3.73",
61
+ "@finos/legend-server-sdlc": "5.4.7",
62
+ "@finos/legend-server-showcase": "0.2.68",
63
+ "@finos/legend-shared": "11.0.27",
64
+ "@finos/legend-storage": "3.0.149",
65
65
  "@testing-library/dom": "10.4.0",
66
66
  "@testing-library/react": "16.2.0",
67
67
  "@types/react": "19.0.10",
@@ -86,6 +86,9 @@ export enum LEGEND_STUDIO_DOCUMENTATION_KEY {
86
86
  // sdlc
87
87
  QUESTION_WHAT_ARE_PROJECT_ROLES = 'question.what-are-project-roles',
88
88
 
89
+ // legend AI
90
+ LEGENDAI_HOW_TO_GET_ENTITLEMENTS = 'legend-ai.how-to-get-entitlements',
91
+
89
92
  // contexts
90
93
  CONTEXT_CLASS_EDITOR = 'context.class-editor',
91
94
  CONTEXT_SERVICE_TEST_EDITOR = 'context.service-editor.test',
@@ -90,11 +90,19 @@ export enum LEGEND_STUDIO_APP_EVENT {
90
90
  DATA_PRODUCT_LEGENDAI_SUGGEST__LAUNCH = 'editor.data-product.legendai-suggest.launch',
91
91
  DATA_PRODUCT_LEGENDAI_SUGGEST__APPLY = 'editor.data-product.legendai-suggest.apply',
92
92
  DATA_PRODUCT_LEGENDAI_SUGGEST__DISCARD = 'editor.data-product.legendai-suggest.discard',
93
+ DATA_PRODUCT_LEGENDAI_SUGGEST__FAILURE = 'editor.data-product.legendai-suggest.failure',
94
+
95
+ // dataspace
96
+ DATASPACE_LEGENDAI_SUGGEST__LAUNCH = 'editor.dataspace.legendai-suggest.launch',
97
+ DATASPACE_LEGENDAI_SUGGEST__APPLY = 'editor.dataspace.legendai-suggest.apply',
98
+ DATASPACE_LEGENDAI_SUGGEST__DISCARD = 'editor.dataspace.legendai-suggest.discard',
99
+ DATASPACE_LEGENDAI_SUGGEST__FAILURE = 'editor.dataspace.legendai-suggest.failure',
93
100
 
94
101
  // service
95
102
  SERVICE_LEGENDAI_SUGGEST__LAUNCH = 'editor.service-editor.legendai-suggest.launch',
96
103
  SERVICE_LEGENDAI_SUGGEST__APPLY = 'editor.service-editor.legendai-suggest.apply',
97
104
  SERVICE_LEGENDAI_SUGGEST__DISCARD = 'editor.service-editor.legendai-suggest.discard',
105
+ SERVICE_LEGENDAI_SUGGEST__FAILURE = 'editor.service-editor.legendai-suggest.failure',
98
106
 
99
107
  // push to dev
100
108
  METADATA_PUSH_TO_METADATA = 'editor.metadata.push-to-metadata',
@@ -214,6 +214,59 @@ export class LegendStudioTelemetryHelper {
214
214
  );
215
215
  }
216
216
 
217
+ static logEvent_ServiceLegendAISuggestFailure(
218
+ telemetryService: TelemetryService,
219
+ servicePath: string,
220
+ errorMessage: string,
221
+ ): void {
222
+ telemetryService.logEvent(
223
+ LEGEND_STUDIO_APP_EVENT.SERVICE_LEGENDAI_SUGGEST__FAILURE,
224
+ { servicePath, errorMessage },
225
+ );
226
+ }
227
+
228
+ // DataSpace Legend AI
229
+ static logEvent_DataSpaceLegendAISuggestLaunched(
230
+ telemetryService: TelemetryService,
231
+ dataSpacePath: string,
232
+ ): void {
233
+ telemetryService.logEvent(
234
+ LEGEND_STUDIO_APP_EVENT.DATASPACE_LEGENDAI_SUGGEST__LAUNCH,
235
+ { dataSpacePath },
236
+ );
237
+ }
238
+
239
+ static logEvent_DataSpaceLegendAISuggestApplied(
240
+ telemetryService: TelemetryService,
241
+ dataSpacePath: string,
242
+ ): void {
243
+ telemetryService.logEvent(
244
+ LEGEND_STUDIO_APP_EVENT.DATASPACE_LEGENDAI_SUGGEST__APPLY,
245
+ { dataSpacePath },
246
+ );
247
+ }
248
+
249
+ static logEvent_DataSpaceLegendAISuggestDiscarded(
250
+ telemetryService: TelemetryService,
251
+ dataSpacePath: string,
252
+ ): void {
253
+ telemetryService.logEvent(
254
+ LEGEND_STUDIO_APP_EVENT.DATASPACE_LEGENDAI_SUGGEST__DISCARD,
255
+ { dataSpacePath },
256
+ );
257
+ }
258
+
259
+ static logEvent_DataSpaceLegendAISuggestFailure(
260
+ telemetryService: TelemetryService,
261
+ dataSpacePath: string,
262
+ errorMessage: string,
263
+ ): void {
264
+ telemetryService.logEvent(
265
+ LEGEND_STUDIO_APP_EVENT.DATASPACE_LEGENDAI_SUGGEST__FAILURE,
266
+ { dataSpacePath, errorMessage },
267
+ );
268
+ }
269
+
217
270
  static logEvent_DataProductLegendAISuggestLaunched(
218
271
  telemetryService: TelemetryService,
219
272
  dataProductPath: string,
@@ -244,6 +297,17 @@ export class LegendStudioTelemetryHelper {
244
297
  );
245
298
  }
246
299
 
300
+ static logEvent_DataProductLegendAISuggestFailure(
301
+ telemetryService: TelemetryService,
302
+ dataProductPath: string,
303
+ errorMessage: string,
304
+ ): void {
305
+ telemetryService.logEvent(
306
+ LEGEND_STUDIO_APP_EVENT.DATA_PRODUCT_LEGENDAI_SUGGEST__FAILURE,
307
+ { dataProductPath, errorMessage },
308
+ );
309
+ }
310
+
247
311
  // Lakehouse
248
312
  static logEvent_LakehouseDeployIngest(
249
313
  service: TelemetryService,
@@ -95,7 +95,9 @@ import {
95
95
  assertErrorThrown,
96
96
  guaranteeNonNullable,
97
97
  guaranteeType,
98
+ HttpStatus,
98
99
  isNonNullable,
100
+ NetworkClientError,
99
101
  UserSearchService,
100
102
  } from '@finos/legend-shared';
101
103
  import {
@@ -209,6 +211,7 @@ import type {
209
211
  DSL_DataProduct_LegendStudioApplicationPlugin_Extension,
210
212
  } from '../../../../stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
211
213
  import { LegendStudioTelemetryHelper } from '../../../../__lib__/LegendStudioTelemetryHelper.js';
214
+ import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../__lib__/LegendStudioDocumentation.js';
212
215
 
213
216
  export enum AP_GROUP_MODAL_ERRORS {
214
217
  GROUP_NAME_EMPTY = 'Group Name is empty',
@@ -1872,6 +1875,27 @@ const AccessPointGroupEditor = observer(
1872
1875
  runInAction(() => {
1873
1876
  setAISuggestion(suggestion);
1874
1877
  });
1878
+ } catch (error) {
1879
+ assertErrorThrown(error);
1880
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestFailure(
1881
+ editorStore.applicationStore.telemetryService,
1882
+ productEditorState.product.path,
1883
+ error.message,
1884
+ );
1885
+ if (
1886
+ error instanceof NetworkClientError &&
1887
+ (error.response.status === HttpStatus.UNAUTHORIZED ||
1888
+ error.response.status === HttpStatus.FORBIDDEN)
1889
+ ) {
1890
+ const docEntry =
1891
+ editorStore.applicationStore.documentationService.getDocEntry(
1892
+ LEGEND_STUDIO_DOCUMENTATION_KEY.LEGENDAI_HOW_TO_GET_ENTITLEMENTS,
1893
+ );
1894
+ if (docEntry?.url) {
1895
+ error.message = `${error.message}. Please check how to get entitlements: ${docEntry.url}`;
1896
+ }
1897
+ }
1898
+ throw error;
1875
1899
  } finally {
1876
1900
  runInAction(() => {
1877
1901
  setIsSuggestingWithAI(false);
@@ -17,11 +17,13 @@
17
17
  import { observer } from 'mobx-react-lite';
18
18
  import { flowResult } from 'mobx';
19
19
  import {
20
+ BlankPanelContent,
20
21
  BlankPanelPlaceholder,
21
22
  clsx,
22
23
  ContextMenu,
23
24
  CustomSelectorInput,
24
25
  Dialog,
26
+ FilledWindowMaximizeIcon,
25
27
  MenuContent,
26
28
  MenuContentItem,
27
29
  Modal,
@@ -37,20 +39,42 @@ import {
37
39
  PanelHeaderActions,
38
40
  PanelLoadingIndicator,
39
41
  PlusIcon,
42
+ RefreshIcon,
43
+ TimesIcon,
40
44
  } from '@finos/legend-art';
41
- import type { DataProductTestSuite } from '@finos/legend-graph';
45
+ import {
46
+ PrimitiveInstanceValue,
47
+ PrimitiveType,
48
+ type DataProductTestSuite,
49
+ type ValueSpecification,
50
+ } from '@finos/legend-graph';
42
51
  import type { DataProductEditorState } from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
43
52
  import {
53
+ DataProductValueSpecificationTestParameterState,
54
+ type DataProductTestParameterState,
55
+ DataProductTestState,
56
+ getAccessPointLambda,
44
57
  type DataProductTestableState,
45
58
  type DataProductTestSuiteState,
46
59
  } from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js';
47
- import { forwardRef, useRef, useState } from 'react';
48
- import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js';
60
+ import { forwardRef, useEffect, useRef, useState } from 'react';
61
+ import {
62
+ getContentTypeWithParamFromQuery,
63
+ type TestParamContentType,
64
+ validateTestableId,
65
+ } from '../../../../../stores/editor/utils/TestableUtils.js';
49
66
  import { useEditorStore } from '../../../EditorStoreProvider.js';
50
67
  import { guaranteeNonNullable } from '@finos/legend-shared';
51
- import { RenameModal } from '../../testable/TestableSharedComponents.js';
68
+ import {
69
+ ExternalFormatParameterEditorModal,
70
+ RenameModal,
71
+ } from '../../testable/TestableSharedComponents.js';
52
72
  import { testSuite_setId } from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
53
73
  import { LakehouseTestSuiteEditor } from '../../testable/LakehouseTestableEditor.js';
74
+ import {
75
+ BasicValueSpecificationEditor,
76
+ instanceValue_setValue,
77
+ } from '@finos/legend-query-builder';
54
78
 
55
79
  // ─── Create Suite Modal ───────────────────────────────────────────────────────
56
80
 
@@ -291,6 +315,343 @@ const CreateTestModal = observer(
291
315
  },
292
316
  );
293
317
 
318
+ const DataProductTestParameterEditor = observer(
319
+ (props: {
320
+ testState: DataProductTestState;
321
+ paramState: DataProductTestParameterState;
322
+ isReadOnly: boolean;
323
+ contentTypeParamPair: TestParamContentType | undefined;
324
+ }) => {
325
+ const { testState, paramState, isReadOnly, contentTypeParamPair } = props;
326
+ const [showPopUp, setShowPopup] = useState(false);
327
+ const valueSpecParamState =
328
+ paramState instanceof DataProductValueSpecificationTestParameterState
329
+ ? paramState
330
+ : undefined;
331
+ const valueSpec = valueSpecParamState?.valueSpec;
332
+
333
+ const paramIsRequired = Boolean(
334
+ testState.queryVariableExpressions.find(
335
+ (v) =>
336
+ v.name === paramState.parameterValue.name &&
337
+ v.multiplicity.lowerBound > 0,
338
+ ),
339
+ );
340
+
341
+ const showCodeEditor =
342
+ Boolean(contentTypeParamPair) &&
343
+ valueSpecParamState?.varExpression.genericType?.value.rawType ===
344
+ PrimitiveType.STRING &&
345
+ valueSpec instanceof PrimitiveInstanceValue;
346
+
347
+ const type = contentTypeParamPair
348
+ ? contentTypeParamPair.contentType
349
+ : (valueSpecParamState?.varExpression.genericType?.value.rawType.name ??
350
+ 'unknown');
351
+
352
+ const openInPopUp = (): void => setShowPopup(!showPopUp);
353
+ const closePopUp = (): void => setShowPopup(false);
354
+
355
+ const updateParamValue = (val: string): void => {
356
+ if (
357
+ !(valueSpec instanceof PrimitiveInstanceValue) ||
358
+ !valueSpecParamState
359
+ ) {
360
+ return;
361
+ }
362
+ const nextValue =
363
+ valueSpecParamState.varExpression.genericType?.value.rawType ===
364
+ PrimitiveType.BYTE
365
+ ? btoa(val)
366
+ : val;
367
+ instanceValue_setValue(
368
+ valueSpec,
369
+ nextValue,
370
+ 0,
371
+ testState.editorStore.changeDetectionState.observerContext,
372
+ );
373
+ valueSpecParamState.updateValueSpecification(valueSpec);
374
+ };
375
+
376
+ return (
377
+ <div
378
+ key={paramState.parameterValue.name}
379
+ className="panel__content__form__section"
380
+ >
381
+ <div className="panel__content__form__section__header__label">
382
+ {paramState.parameterValue.name}
383
+ <button
384
+ className={clsx('type-tree__node__type__label', {})}
385
+ tabIndex={-1}
386
+ title={type}
387
+ >
388
+ {type}
389
+ </button>
390
+ </div>
391
+
392
+ {!valueSpecParamState && (
393
+ <BlankPanelContent>Unsupported parameter value</BlankPanelContent>
394
+ )}
395
+
396
+ {valueSpecParamState &&
397
+ showCodeEditor &&
398
+ valueSpec instanceof PrimitiveInstanceValue && (
399
+ <div className="data-product-test-editor__parameter__code-editor">
400
+ <textarea
401
+ className="panel__content__form__section__textarea value-spec-editor__input"
402
+ spellCheck={false}
403
+ value={
404
+ valueSpecParamState.varExpression.genericType?.value
405
+ .rawType === PrimitiveType.BYTE
406
+ ? atob(valueSpec.values[0] as string)
407
+ : (valueSpec.values[0] as string)
408
+ }
409
+ placeholder={
410
+ (valueSpec.values[0] as string) === '' ? '(empty)' : undefined
411
+ }
412
+ onChange={(event) => updateParamValue(event.target.value)}
413
+ />
414
+ {showPopUp && (
415
+ <ExternalFormatParameterEditorModal
416
+ valueSpec={valueSpecParamState.valueSpec}
417
+ varExpression={valueSpecParamState.varExpression}
418
+ onClose={closePopUp}
419
+ isReadOnly={isReadOnly}
420
+ updateParamValue={updateParamValue}
421
+ contentTypeParamPair={guaranteeNonNullable(
422
+ contentTypeParamPair,
423
+ )}
424
+ />
425
+ )}
426
+ <div className="data-product-test-editor__parameter__value__actions">
427
+ <button
428
+ className="data-product-test-editor__parameter__code-editor__expand-btn"
429
+ onClick={openInPopUp}
430
+ tabIndex={-1}
431
+ title="Open in a popup..."
432
+ >
433
+ <FilledWindowMaximizeIcon />
434
+ </button>
435
+ <button
436
+ className="btn--icon btn--dark btn--sm data-product-test-editor__parameter__code-editor__expand-btn"
437
+ disabled={isReadOnly || paramIsRequired}
438
+ onClick={(): void =>
439
+ testState.removeParamValueState(paramState)
440
+ }
441
+ tabIndex={-1}
442
+ title={
443
+ paramIsRequired ? 'Parameter Required' : 'Remove Parameter'
444
+ }
445
+ >
446
+ <TimesIcon />
447
+ </button>
448
+ </div>
449
+ </div>
450
+ )}
451
+
452
+ {valueSpecParamState && !showCodeEditor && (
453
+ <div className="data-product-test-editor__parameter__value">
454
+ <BasicValueSpecificationEditor
455
+ valueSpecification={valueSpecParamState.valueSpec}
456
+ setValueSpecification={(val: ValueSpecification): void => {
457
+ valueSpecParamState.updateValueSpecification(val);
458
+ }}
459
+ graph={testState.editorStore.graphManagerState.graph}
460
+ observerContext={
461
+ testState.editorStore.changeDetectionState.observerContext
462
+ }
463
+ typeCheckOption={{
464
+ expectedType:
465
+ valueSpecParamState.varExpression.genericType?.value
466
+ .rawType ?? PrimitiveType.STRING,
467
+ }}
468
+ className="query-builder__parameters__value__editor"
469
+ resetValue={(): void => {
470
+ valueSpecParamState.resetValueSpec();
471
+ }}
472
+ />
473
+ <div className="data-product-test-editor__parameter__value__actions">
474
+ <button
475
+ className="btn--icon btn--dark btn--sm"
476
+ disabled={isReadOnly || paramIsRequired}
477
+ onClick={(): void =>
478
+ testState.removeParamValueState(paramState)
479
+ }
480
+ tabIndex={-1}
481
+ title={
482
+ paramIsRequired ? 'Parameter Required' : 'Remove Parameter'
483
+ }
484
+ >
485
+ <TimesIcon />
486
+ </button>
487
+ </div>
488
+ </div>
489
+ )}
490
+ </div>
491
+ );
492
+ },
493
+ );
494
+
495
+ const NewDataProductParameterModal = observer(
496
+ (props: { testState: DataProductTestState; isReadOnly: boolean }) => {
497
+ const { testState, isReadOnly } = props;
498
+ const applicationStore = testState.editorStore.applicationStore;
499
+ const currentOption = {
500
+ value: testState.newParameterValueName,
501
+ label: testState.newParameterValueName,
502
+ };
503
+ const options = testState.newParamOptions;
504
+ const closeModal = (): void => testState.setShowNewParameterModal(false);
505
+ const onChange = (val: { label: string; value: string } | null): void => {
506
+ if (val === null) {
507
+ testState.setNewParameterValueName('');
508
+ } else if (val.value !== testState.newParameterValueName) {
509
+ testState.setNewParameterValueName(val.value);
510
+ }
511
+ };
512
+
513
+ return (
514
+ <Dialog
515
+ open={testState.showNewParameterModal}
516
+ onClose={closeModal}
517
+ classes={{ container: 'search-modal__container' }}
518
+ slotProps={{
519
+ paper: {
520
+ classes: { root: 'search-modal__inner-container' },
521
+ },
522
+ }}
523
+ >
524
+ <form
525
+ onSubmit={(event) => {
526
+ event.preventDefault();
527
+ testState.addParameterValue();
528
+ }}
529
+ className="modal modal--dark search-modal"
530
+ >
531
+ <div className="modal__title">New Test Parameter Value</div>
532
+ <CustomSelectorInput
533
+ className="panel__content__form__section__dropdown"
534
+ options={options}
535
+ onChange={onChange}
536
+ value={currentOption}
537
+ escapeClearsValue={true}
538
+ darkMode={
539
+ !applicationStore.layoutService
540
+ .TEMPORARY__isLightColorThemeEnabled
541
+ }
542
+ disabled={isReadOnly}
543
+ />
544
+ <div className="search-modal__actions">
545
+ <button className="btn btn--dark" disabled={isReadOnly}>
546
+ Add
547
+ </button>
548
+ </div>
549
+ </form>
550
+ </Dialog>
551
+ );
552
+ },
553
+ );
554
+
555
+ const DataProductTestEditorExtension = observer(
556
+ (props: { testState: DataProductTestState; isReadOnly: boolean }) => {
557
+ const { testState, isReadOnly } = props;
558
+
559
+ useEffect(() => {
560
+ testState.syncWithQuery();
561
+ }, [testState]);
562
+
563
+ const hasQueryParameters = Boolean(
564
+ testState.queryVariableExpressions.length,
565
+ );
566
+ if (!hasQueryParameters) {
567
+ return null;
568
+ }
569
+
570
+ const accessPoint = testState.suiteState.testableState.ownAccessPoints.find(
571
+ (ap) => ap.id === testState.test.accessPointId,
572
+ );
573
+ const query = accessPoint ? getAccessPointLambda(accessPoint) : undefined;
574
+ const contentTypeParamPairs = getContentTypeWithParamFromQuery(
575
+ query,
576
+ testState.editorStore,
577
+ );
578
+
579
+ const addParameter = (): void => {
580
+ testState.openNewParamModal();
581
+ };
582
+
583
+ const generateParameterValues = (): void => {
584
+ testState.generateTestParameterValues();
585
+ };
586
+
587
+ return (
588
+ <div className="data-product-test-editor__parameters-panel-container">
589
+ <div className="panel data-product-test-editor__parameters-panel">
590
+ <div className="data-product-test-editor__parameters-header">
591
+ <div className="data-product-test-editor__parameters-header__title">
592
+ <div className="data-product-test-editor__parameters-header__title__label">
593
+ Parameters
594
+ </div>
595
+ </div>
596
+ <div className="data-product-test-editor__parameters-header__actions">
597
+ <button
598
+ className="panel__header__action data-product-test-editor__generate-btn"
599
+ onClick={generateParameterValues}
600
+ disabled={!testState.newParamOptions.length}
601
+ title="Generate test parameter values"
602
+ tabIndex={-1}
603
+ >
604
+ <div className="data-product-test-editor__generate-btn__label">
605
+ <RefreshIcon className="data-product-test-editor__generate-btn__label__icon" />
606
+ <div className="data-product-test-editor__generate-btn__label__title">
607
+ Generate
608
+ </div>
609
+ </div>
610
+ </button>
611
+ <button
612
+ className="panel__header__action"
613
+ tabIndex={-1}
614
+ disabled={!testState.newParamOptions.length}
615
+ onClick={addParameter}
616
+ title="Add Parameter Value"
617
+ >
618
+ <PlusIcon />
619
+ </button>
620
+ </div>
621
+ </div>
622
+
623
+ <div className="data-product-test-editor__parameters">
624
+ {testState.parameterValueStates.map((paramState) => (
625
+ <DataProductTestParameterEditor
626
+ key={paramState.uuid}
627
+ isReadOnly={isReadOnly}
628
+ paramState={paramState}
629
+ testState={testState}
630
+ contentTypeParamPair={contentTypeParamPairs.find(
631
+ (pair) => pair.param === paramState.parameterValue.name,
632
+ )}
633
+ />
634
+ ))}
635
+ {testState.parameterValueStates.length === 0 && (
636
+ <BlankPanelPlaceholder
637
+ text="No parameter values"
638
+ tooltipText="Generate or add a parameter value"
639
+ />
640
+ )}
641
+ </div>
642
+ </div>
643
+
644
+ {testState.showNewParameterModal && (
645
+ <NewDataProductParameterModal
646
+ testState={testState}
647
+ isReadOnly={isReadOnly}
648
+ />
649
+ )}
650
+ </div>
651
+ );
652
+ },
653
+ );
654
+
294
655
  // ─── Suite Tab Context Menu ───────────────────────────────────────────────────
295
656
 
296
657
  const SuiteHeaderTabContextMenu = observer(
@@ -407,6 +768,14 @@ export const DataProductTestableEditor = observer(
407
768
  suiteState={selectedSuiteState}
408
769
  testableState={testableState}
409
770
  isReadOnly={isReadOnly}
771
+ renderTestStateExtension={(testState) =>
772
+ testState instanceof DataProductTestState ? (
773
+ <DataProductTestEditorExtension
774
+ testState={testState}
775
+ isReadOnly={isReadOnly}
776
+ />
777
+ ) : null
778
+ }
410
779
  />
411
780
  )}
412
781
  {!dp.tests.length && (