@finos/legend-application-studio 28.19.21 → 28.19.23

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.
@@ -70,6 +70,7 @@ import {
70
70
  EyeIcon,
71
71
  CloseEyeIcon,
72
72
  Checkbox,
73
+ BugIcon,
73
74
  } from '@finos/legend-art';
74
75
  import React, {
75
76
  useRef,
@@ -90,6 +91,9 @@ import {
90
91
  type DataProduct,
91
92
  type LakehouseAccessPoint,
92
93
  StereotypeExplicitReference,
94
+ type V1_DataProductArtifactAccessPointGroup,
95
+ type V1_DataProductArtifactAccessPointImplementation,
96
+ type V1_DataProductArtifactGeneration,
93
97
  } from '@finos/legend-graph';
94
98
  import {
95
99
  accessPointGroup_setDescription,
@@ -162,6 +166,7 @@ const HoverTextArea: React.FC<HoverTextAreaProps> = ({
162
166
  onMouseOver={handleMouseOver}
163
167
  onMouseOut={handleMouseOut}
164
168
  className={clsx(className)}
169
+ style={{ whiteSpace: 'pre-line' }}
165
170
  >
166
171
  {text}
167
172
  </div>
@@ -190,7 +195,7 @@ const AccessPointTitle = observer(
190
195
  };
191
196
  const updateAccessPointName: React.ChangeEventHandler<HTMLTextAreaElement> =
192
197
  action((event) => {
193
- if (!event.target.value.includes(' ')) {
198
+ if (event.target.value.match(/^[0-9a-zA-Z_]+$/)) {
194
199
  accessPoint.id = event.target.value;
195
200
  }
196
201
  });
@@ -211,8 +216,13 @@ const AccessPointTitle = observer(
211
216
  }}
212
217
  />
213
218
  ) : (
214
- <div onClick={handleNameEdit} title="Click to edit access point title">
215
- <div className="access-point-editor__name__label">{accessPoint.id}</div>
219
+ <div
220
+ className="access-point-editor__name__label"
221
+ onClick={handleNameEdit}
222
+ title="Click to edit access point title"
223
+ style={{ flex: '1 1 auto' }}
224
+ >
225
+ {accessPoint.id}
216
226
  </div>
217
227
  );
218
228
  },
@@ -225,7 +235,7 @@ const AccessPointClassification = observer(
225
235
  }) => {
226
236
  const { accessPoint, groupState } = props;
227
237
  const applicationStore = useEditorStore().applicationStore;
228
- const CHOOSE_CLASSIFICATION = 'Choose Classification';
238
+ const CHOOSE_CLASSIFICATION = 'Classification';
229
239
  const updateAccessPointClassificationTextbox: React.ChangeEventHandler<HTMLTextAreaElement> =
230
240
  action((event) => {
231
241
  accessPoint.classification = event.target.value;
@@ -282,7 +292,15 @@ const AccessPointClassification = observer(
282
292
  return (
283
293
  <div className="access-point-editor__classification">
284
294
  {classificationOptions.length > 1 ? (
285
- <div>
295
+ <div
296
+ style={{
297
+ borderWidth: 'thin',
298
+ borderColor:
299
+ currentClassification.label === CHOOSE_CLASSIFICATION
300
+ ? 'var(--color-red-300)'
301
+ : 'transparent',
302
+ }}
303
+ >
286
304
  <CustomSelectorInput
287
305
  className="explorer__new-element-modal__driver__dropdown"
288
306
  options={classificationOptions}
@@ -323,6 +341,62 @@ const AccessPointClassification = observer(
323
341
  },
324
342
  );
325
343
 
344
+ const AccessPointGenerationViewer = observer(
345
+ (props: {
346
+ accessPointState: LakehouseAccessPointState;
347
+ generationOutput: string;
348
+ }) => {
349
+ const { accessPointState, generationOutput } = props;
350
+ const editorStore = accessPointState.state.state.editorStore;
351
+ const closeDebug = (): void => {
352
+ accessPointState.setShowDebug(false);
353
+ };
354
+
355
+ return (
356
+ <Dialog
357
+ open={accessPointState.showDebug}
358
+ onClose={closeDebug}
359
+ classes={{
360
+ root: 'editor-modal__root-container',
361
+ container: 'editor-modal__container',
362
+ paper: 'editor-modal__content',
363
+ }}
364
+ >
365
+ <Modal
366
+ className="editor-modal"
367
+ darkMode={
368
+ !editorStore.applicationStore.layoutService
369
+ .TEMPORARY__isLightColorThemeEnabled
370
+ }
371
+ >
372
+ <ModalHeader
373
+ title={`${accessPointState.accessPoint.id} Plan Generation`}
374
+ />
375
+ <ModalBody>
376
+ <div className="panel__content execution-plan-viewer__panel__content">
377
+ <CodeEditor
378
+ inputValue={generationOutput}
379
+ isReadOnly={true}
380
+ language={CODE_EDITOR_LANGUAGE.JSON}
381
+ hidePadding={true}
382
+ hideMinimap={true}
383
+ />
384
+ </div>
385
+ </ModalBody>
386
+ <ModalFooter>
387
+ <ModalFooterButton
388
+ title="Close plan generation modal"
389
+ onClick={closeDebug}
390
+ text="Close"
391
+ type="secondary"
392
+ />
393
+ </ModalFooter>
394
+ </Modal>
395
+ </Dialog>
396
+ );
397
+ },
398
+ );
399
+
326
400
  export const LakehouseDataProductAcccessPointEditor = observer(
327
401
  (props: {
328
402
  accessPointState: LakehouseAccessPointState;
@@ -339,6 +413,7 @@ export const LakehouseDataProductAcccessPointEditor = observer(
339
413
  const [editingDescription, setEditingDescription] = useState(false);
340
414
  const [isHovering, setIsHovering] = useState(false);
341
415
  const ref = useRef<HTMLDivElement>(null);
416
+ const [debugOutput, setDebugOutput] = useState('');
342
417
 
343
418
  const handleDescriptionEdit = () => setEditingDescription(true);
344
419
  const handleDescriptionBlur = () => {
@@ -361,6 +436,51 @@ export const LakehouseDataProductAcccessPointEditor = observer(
361
436
  },
362
437
  );
363
438
 
439
+ const debugPlanGeneration = async (): Promise<void> => {
440
+ try {
441
+ const generatedArtifacts =
442
+ await editorStore.graphManagerState.graphManager.generateArtifacts(
443
+ editorStore.graphManagerState.graph,
444
+ editorStore.graphEditorMode.getGraphTextInputOption(),
445
+ [groupState.state.elementPath],
446
+ );
447
+ const dataProductExtension = 'dataProduct';
448
+ const dataProductArtifact = generatedArtifacts.values.filter(
449
+ (artifact) => artifact.extension === dataProductExtension,
450
+ );
451
+ const dataProductContent =
452
+ dataProductArtifact[0]?.artifactsByExtensionElements[0]?.files[0]
453
+ ?.content ?? null;
454
+
455
+ if (dataProductContent) {
456
+ const contentJson = JSON.parse(
457
+ dataProductContent,
458
+ ) as V1_DataProductArtifactGeneration;
459
+ const apPlanGeneration = contentJson.accessPointGroups
460
+ .find(
461
+ (group: V1_DataProductArtifactAccessPointGroup) =>
462
+ group.id === groupState.value.id,
463
+ )
464
+ ?.accessPointImplementations.find(
465
+ (
466
+ apImplementation: V1_DataProductArtifactAccessPointImplementation,
467
+ ) => apImplementation.id === accessPoint.id,
468
+ );
469
+
470
+ setDebugOutput(JSON.stringify(apPlanGeneration, null, 2));
471
+ accessPointState.setShowDebug(true);
472
+ } else {
473
+ throw new Error(
474
+ 'Could not find contents of this data product artifact',
475
+ );
476
+ }
477
+ } catch (error) {
478
+ editorStore.applicationStore.notificationService.notifyError(
479
+ `Failed to fetch access point plan generation: ${error}`,
480
+ );
481
+ }
482
+ };
483
+
364
484
  const handleRemoveAccessPoint = (): void => {
365
485
  editorStore.applicationStore.alertService.setActionAlertInfo({
366
486
  message: `Are you sure you want to delete Access Point ${accessPoint.id}?`,
@@ -461,7 +581,19 @@ export const LakehouseDataProductAcccessPointEditor = observer(
461
581
  style={{ padding: 0, margin: 0 }}
462
582
  />
463
583
  <Tooltip
464
- title="This access point is reproducible based on a specific Lakehouse batch in time"
584
+ title={
585
+ <div
586
+ style={{
587
+ maxWidth: '400px',
588
+ whiteSpace: 'normal',
589
+ wordWrap: 'break-word',
590
+ }}
591
+ >
592
+ Marking as &quot;reproducible&quot; means consumers can
593
+ consistently retrieve the exact historical data as it
594
+ existed at any specific Lakehouse batch.
595
+ </div>
596
+ }
465
597
  arrow={true}
466
598
  placement={'top'}
467
599
  >
@@ -526,10 +658,11 @@ export const LakehouseDataProductAcccessPointEditor = observer(
526
658
  placeholder="Access Point description"
527
659
  onBlur={handleDescriptionBlur}
528
660
  style={{
529
- overflow: 'hidden',
530
- resize: 'none',
661
+ resize: 'vertical',
531
662
  padding: '0.25rem',
532
663
  marginLeft: '0.5rem',
664
+ marginTop: '0.5rem',
665
+ height: 'auto',
533
666
  }}
534
667
  />
535
668
  ) : (
@@ -573,18 +706,36 @@ export const LakehouseDataProductAcccessPointEditor = observer(
573
706
  </div>
574
707
  </div>
575
708
  <button
576
- className="access-point-editor__generic-entry__remove-btn"
709
+ className="access-point-editor__generic-entry__remove-btn__debug"
577
710
  onClick={() => {
578
- handleRemoveAccessPoint();
711
+ debugPlanGeneration().catch(
712
+ editorStore.applicationStore.alertUnhandledError,
713
+ );
579
714
  }}
580
715
  tabIndex={-1}
581
- title="Remove"
716
+ title="AP Plan Generation"
582
717
  >
583
- <TimesIcon />
718
+ <BugIcon />
584
719
  </button>
585
720
  </div>
586
721
  </div>
587
722
  </div>
723
+ <button
724
+ className="access-point-editor__generic-entry__remove-btn"
725
+ onClick={() => {
726
+ handleRemoveAccessPoint();
727
+ }}
728
+ tabIndex={-1}
729
+ title="Remove"
730
+ >
731
+ <TimesIcon />
732
+ </button>
733
+ {accessPointState.showDebug && (
734
+ <AccessPointGenerationViewer
735
+ accessPointState={accessPointState}
736
+ generationOutput={debugOutput}
737
+ />
738
+ )}
588
739
  </div>
589
740
  </PanelDnDEntry>
590
741
  );
@@ -724,7 +875,7 @@ const AccessPointGroupEditor = observer(
724
875
  setIsHoveringName(false);
725
876
  };
726
877
  const updateGroupName = (val: string): void => {
727
- if (val && !val.includes(' ')) {
878
+ if (val.match(/^[0-9a-zA-Z_]+$/)) {
728
879
  accessPointGroup_setName(groupState.value, val);
729
880
  }
730
881
  };
@@ -821,8 +972,8 @@ const AccessPointGroupEditor = observer(
821
972
  onBlur={handleDescriptionBlur}
822
973
  style={{
823
974
  overflow: 'hidden',
824
- resize: 'none',
825
975
  padding: '0.25rem',
976
+ height: 'auto',
826
977
  }}
827
978
  />
828
979
  ) : (
@@ -214,14 +214,22 @@ export class LakehouseAccessPointState extends AccessPointState {
214
214
  declare accessPoint: LakehouseAccessPoint;
215
215
  lambdaState: AccessPointLambdaEditorState;
216
216
 
217
+ showDebug = false;
218
+
217
219
  constructor(val: LakehouseAccessPoint, editorState: AccessPointGroupState) {
218
220
  super(val, editorState);
219
221
  makeObservable(this, {
220
222
  lambdaState: observable,
223
+ showDebug: observable,
224
+ setShowDebug: action,
221
225
  });
222
226
  this.accessPoint = val;
223
227
  this.lambdaState = new AccessPointLambdaEditorState(this);
224
228
  }
229
+
230
+ setShowDebug(value: boolean): void {
231
+ this.showDebug = value;
232
+ }
225
233
  }
226
234
 
227
235
  export class AccessPointGroupState {