@finos/legend-application-studio 28.21.26 → 28.21.28

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 (40) 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 +2 -0
  6. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioEvent.js +2 -0
  8. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  9. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +2 -0
  10. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendStudioTelemetryHelper.js +6 -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/package.json +1 -1
  28. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +6 -1
  29. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  30. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +1 -1
  31. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  32. package/package.json +16 -16
  33. package/src/__lib__/LegendStudioDocumentation.ts +3 -0
  34. package/src/__lib__/LegendStudioEvent.ts +2 -0
  35. package/src/__lib__/LegendStudioTelemetryHelper.ts +22 -0
  36. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +24 -0
  37. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +373 -4
  38. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +29 -3
  39. package/src/components/editor/editor-group/testable/LakehouseTestableEditor.tsx +26 -5
  40. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +1 -1
@@ -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 && (
@@ -37,7 +37,13 @@ import {
37
37
  PanelContentLists,
38
38
  SparkleIcon,
39
39
  } from '@finos/legend-art';
40
- import { debounce, prettyCONSTName } from '@finos/legend-shared';
40
+ import {
41
+ assertErrorThrown,
42
+ debounce,
43
+ HttpStatus,
44
+ NetworkClientError,
45
+ prettyCONSTName,
46
+ } from '@finos/legend-shared';
41
47
  import { ServiceExecutionEditor } from './ServiceExecutionEditor.js';
42
48
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
43
49
  import { ServiceRegistrationEditor } from './ServiceRegistrationEditor.js';
@@ -70,11 +76,11 @@ import {
70
76
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
71
77
  import { ServiceTestableEditor } from './testable/ServiceTestableEditor.js';
72
78
  import { flowResult } from 'mobx';
73
- import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../__lib__/LegendStudioDocumentation.js';
74
- import { DocumentationLink } from '@finos/legend-lego/application';
75
79
  import { ServicePostValidationsEditor } from './ServicePostValidationEditor.js';
76
80
  import type { DSL_Service_LegendStudioApplicationPlugin_Extension } from '../../../../stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.js';
77
81
  import { LegendStudioTelemetryHelper } from '../../../../__lib__/LegendStudioTelemetryHelper.js';
82
+ import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../__lib__/LegendStudioDocumentation.js';
83
+ import { DocumentationLink } from '@finos/legend-lego/application';
78
84
 
79
85
  type UserOption = { label: string; value: string };
80
86
 
@@ -278,6 +284,26 @@ const ServiceGeneralEditor = observer(() => {
278
284
  ]);
279
285
  const suggestion = await aiDocSuggester(serviceGrammar, legendAIUrl);
280
286
  setAIDocSuggestion(suggestion);
287
+ } catch (error) {
288
+ assertErrorThrown(error);
289
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestFailure(
290
+ applicationStore.telemetryService,
291
+ service.path,
292
+ error.message,
293
+ );
294
+ if (
295
+ error instanceof NetworkClientError &&
296
+ (error.response.status === HttpStatus.UNAUTHORIZED ||
297
+ error.response.status === HttpStatus.FORBIDDEN)
298
+ ) {
299
+ const docEntry = applicationStore.documentationService.getDocEntry(
300
+ LEGEND_STUDIO_DOCUMENTATION_KEY.LEGENDAI_HOW_TO_GET_ENTITLEMENTS,
301
+ );
302
+ if (docEntry?.url) {
303
+ error.message = `${error.message}. Please check how to get entitlements: ${docEntry.url}`;
304
+ }
305
+ }
306
+ throw error;
281
307
  } finally {
282
308
  setIsSuggestingWithAI(false);
283
309
  }
@@ -55,7 +55,7 @@ import {
55
55
  } from '@finos/legend-art';
56
56
  import type { AtomicTest, TestSuite } from '@finos/legend-graph';
57
57
  import type { ActionState, GeneratorFn } from '@finos/legend-shared';
58
- import { useState } from 'react';
58
+ import { useState, type ReactNode } from 'react';
59
59
  import type { TestableTestEditorState } from '../../../../stores/editor/editor-state/element-editor-state/testable/TestableEditorState.js';
60
60
  import {
61
61
  TESTABLE_RESULT,
@@ -341,8 +341,14 @@ export const LakehouseTestDataEditor = observer(
341
341
  // ─── Test editor: assertion viewer ────────────────────────────────────────────
342
342
 
343
343
  export const LakehouseTestEditor = observer(
344
- (props: { testState: TestableTestEditorState; isReadOnly: boolean }) => {
345
- const { testState } = props;
344
+ (props: {
345
+ testState: TestableTestEditorState;
346
+ isReadOnly: boolean;
347
+ renderTestStateExtension?: (
348
+ testState: TestableTestEditorState,
349
+ ) => ReactNode;
350
+ }) => {
351
+ const { testState, renderTestStateExtension } = props;
346
352
  const selectedAssertion = testState.selectedAsertionState;
347
353
 
348
354
  return (
@@ -353,6 +359,7 @@ export const LakehouseTestEditor = observer(
353
359
  </div>
354
360
  </div>
355
361
  <div className="panel">
362
+ {renderTestStateExtension?.(testState)}
356
363
  {selectedAssertion && (
357
364
  <TestAssertionEditor testAssertionState={selectedAssertion} />
358
365
  )}
@@ -449,8 +456,12 @@ export const LakehouseTestsEditor = observer(
449
456
  suiteState: LakehouseSuiteStateForEditor;
450
457
  testableState: LakehouseTestableStateForEditor;
451
458
  isReadOnly: boolean;
459
+ renderTestStateExtension?: (
460
+ testState: TestableTestEditorState,
461
+ ) => ReactNode;
452
462
  }) => {
453
- const { suiteState, testableState, isReadOnly } = props;
463
+ const { suiteState, testableState, isReadOnly, renderTestStateExtension } =
464
+ props;
454
465
  const selectedTest = suiteState.selectTestState;
455
466
 
456
467
  return (
@@ -513,6 +524,9 @@ export const LakehouseTestsEditor = observer(
513
524
  <LakehouseTestEditor
514
525
  testState={selectedTest}
515
526
  isReadOnly={isReadOnly}
527
+ {...(renderTestStateExtension
528
+ ? { renderTestStateExtension }
529
+ : {})}
516
530
  />
517
531
  ) : (
518
532
  <BlankPanelPlaceholder
@@ -535,8 +549,12 @@ export const LakehouseTestSuiteEditor = observer(
535
549
  suiteState: LakehouseSuiteStateForEditor;
536
550
  testableState: LakehouseTestableStateForEditor;
537
551
  isReadOnly: boolean;
552
+ renderTestStateExtension?: (
553
+ testState: TestableTestEditorState,
554
+ ) => ReactNode;
538
555
  }) => {
539
- const { suiteState, testableState, isReadOnly } = props;
556
+ const { suiteState, testableState, isReadOnly, renderTestStateExtension } =
557
+ props;
540
558
 
541
559
  return (
542
560
  <div className="service-test-suite-editor">
@@ -555,6 +573,9 @@ export const LakehouseTestSuiteEditor = observer(
555
573
  suiteState={suiteState}
556
574
  testableState={testableState}
557
575
  isReadOnly={isReadOnly}
576
+ {...(renderTestStateExtension
577
+ ? { renderTestStateExtension }
578
+ : {})}
558
579
  />
559
580
  </ResizablePanel>
560
581
  </ResizablePanelGroup>
@@ -93,7 +93,7 @@ export type { LakehouseElementTestDataState as DataProductElementTestDataState }
93
93
  * Returns the lambda for an access point (handles both LakehouseAccessPoint
94
94
  * and FunctionAccessPoint).
95
95
  */
96
- const getAccessPointLambda = (
96
+ export const getAccessPointLambda = (
97
97
  accessPoint: AccessPoint,
98
98
  ): RawLambda | undefined =>
99
99
  accessPoint instanceof LakehouseAccessPoint