@finos/legend-application-studio 28.21.42 → 28.21.44

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 (164) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +18 -4
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +20 -5
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +177 -31
  6. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioTelemetryHelper.js +229 -72
  8. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  9. package/lib/__lib__/LegendStudioUserDataHelper.d.ts +18 -1
  10. package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendStudioUserDataHelper.js +71 -0
  12. package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
  13. package/lib/application/LegendStudioApplicationConfig.d.ts +12 -0
  14. package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
  15. package/lib/application/LegendStudioApplicationConfig.js +6 -0
  16. package/lib/application/LegendStudioApplicationConfig.js.map +1 -1
  17. package/lib/components/LegendStudioWebApplication.d.ts.map +1 -1
  18. package/lib/components/LegendStudioWebApplication.js +5 -2
  19. package/lib/components/LegendStudioWebApplication.js.map +1 -1
  20. package/lib/components/ShowcaseManager.d.ts.map +1 -1
  21. package/lib/components/ShowcaseManager.js +6 -4
  22. package/lib/components/ShowcaseManager.js.map +1 -1
  23. package/lib/components/editor/ActivityBar.d.ts.map +1 -1
  24. package/lib/components/editor/ActivityBar.js +2 -1
  25. package/lib/components/editor/ActivityBar.js.map +1 -1
  26. package/lib/components/editor/editor-group/GrammarTextEditor.d.ts.map +1 -1
  27. package/lib/components/editor/editor-group/GrammarTextEditor.js +7 -1
  28. package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
  29. package/lib/components/editor/editor-group/data-editor/RelationElementSampleValuesEditor.d.ts +37 -0
  30. package/lib/components/editor/editor-group/data-editor/RelationElementSampleValuesEditor.d.ts.map +1 -0
  31. package/lib/components/editor/editor-group/data-editor/RelationElementSampleValuesEditor.js +126 -0
  32. package/lib/components/editor/editor-group/data-editor/RelationElementSampleValuesEditor.js.map +1 -0
  33. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  34. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +22 -127
  35. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  36. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  37. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -4
  38. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  39. package/lib/components/showcase/ShowcaseFeedback.d.ts +36 -0
  40. package/lib/components/showcase/ShowcaseFeedback.d.ts.map +1 -0
  41. package/lib/components/showcase/ShowcaseFeedback.js +70 -0
  42. package/lib/components/showcase/ShowcaseFeedback.js.map +1 -0
  43. package/lib/components/showcase/ShowcaseViewer.d.ts.map +1 -1
  44. package/lib/components/showcase/ShowcaseViewer.js +3 -1
  45. package/lib/components/showcase/ShowcaseViewer.js.map +1 -1
  46. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  47. package/lib/components/workspace-setup/WorkspaceSetup.js +2 -1
  48. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  49. package/lib/index.css +2 -2
  50. package/lib/index.css.map +1 -1
  51. package/lib/index.d.ts +2 -1
  52. package/lib/index.d.ts.map +1 -1
  53. package/lib/index.js +2 -1
  54. package/lib/index.js.map +1 -1
  55. package/lib/package.json +1 -1
  56. package/lib/stores/LegendStudioBaseStore.d.ts.map +1 -1
  57. package/lib/stores/LegendStudioBaseStore.js +1 -0
  58. package/lib/stores/LegendStudioBaseStore.js.map +1 -1
  59. package/lib/stores/ShowcaseManagerState.d.ts +5 -3
  60. package/lib/stores/ShowcaseManagerState.d.ts.map +1 -1
  61. package/lib/stores/ShowcaseManagerState.js +44 -11
  62. package/lib/stores/ShowcaseManagerState.js.map +1 -1
  63. package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
  64. package/lib/stores/editor/EditorGraphState.js +1 -1
  65. package/lib/stores/editor/EditorGraphState.js.map +1 -1
  66. package/lib/stores/editor/EditorStore.d.ts +2 -1
  67. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  68. package/lib/stores/editor/EditorStore.js +8 -2
  69. package/lib/stores/editor/EditorStore.js.map +1 -1
  70. package/lib/stores/editor/GraphEditFormModeState.d.ts.map +1 -1
  71. package/lib/stores/editor/GraphEditFormModeState.js +2 -2
  72. package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
  73. package/lib/stores/editor/GraphEditGrammarModeState.d.ts +19 -0
  74. package/lib/stores/editor/GraphEditGrammarModeState.d.ts.map +1 -1
  75. package/lib/stores/editor/GraphEditGrammarModeState.js +92 -4
  76. package/lib/stores/editor/GraphEditGrammarModeState.js.map +1 -1
  77. package/lib/stores/editor/GraphEditorMode.d.ts +1 -0
  78. package/lib/stores/editor/GraphEditorMode.d.ts.map +1 -1
  79. package/lib/stores/editor/GraphEditorMode.js.map +1 -1
  80. package/lib/stores/editor/StandardEditorMode.d.ts +16 -5
  81. package/lib/stores/editor/StandardEditorMode.d.ts.map +1 -1
  82. package/lib/stores/editor/StandardEditorMode.js +4 -2
  83. package/lib/stores/editor/StandardEditorMode.js.map +1 -1
  84. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +2 -14
  85. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  86. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +5 -75
  87. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  88. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts +0 -11
  89. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts.map +1 -1
  90. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js +2 -77
  91. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js.map +1 -1
  92. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.d.ts.map +1 -1
  93. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js +4 -4
  94. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js.map +1 -1
  95. package/lib/stores/editor/sidebar-state/LocalChangesState.d.ts +1 -0
  96. package/lib/stores/editor/sidebar-state/LocalChangesState.d.ts.map +1 -1
  97. package/lib/stores/editor/sidebar-state/LocalChangesState.js +27 -0
  98. package/lib/stores/editor/sidebar-state/LocalChangesState.js.map +1 -1
  99. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.d.ts.map +1 -1
  100. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js +7 -0
  101. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js.map +1 -1
  102. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  103. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +7 -2
  104. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  105. package/lib/stores/lazy-text-editor/LazyTextEditorStore.d.ts +1 -0
  106. package/lib/stores/lazy-text-editor/LazyTextEditorStore.d.ts.map +1 -1
  107. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js +3 -0
  108. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js.map +1 -1
  109. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
  110. package/lib/stores/project-reviewer/ProjectReviewerStore.js +2 -0
  111. package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
  112. package/lib/stores/project-view/ProjectViewerEditorMode.d.ts +2 -2
  113. package/lib/stores/project-view/ProjectViewerEditorMode.d.ts.map +1 -1
  114. package/lib/stores/project-view/ProjectViewerEditorMode.js +12 -0
  115. package/lib/stores/project-view/ProjectViewerEditorMode.js.map +1 -1
  116. package/lib/stores/project-view/ProjectViewerStore.d.ts.map +1 -1
  117. package/lib/stores/project-view/ProjectViewerStore.js +3 -1
  118. package/lib/stores/project-view/ProjectViewerStore.js.map +1 -1
  119. package/lib/stores/showcase/ShowcaseViewerEditorMode.d.ts.map +1 -1
  120. package/lib/stores/showcase/ShowcaseViewerEditorMode.js.map +1 -1
  121. package/lib/stores/showcase/ShowcaseViewerStore.d.ts.map +1 -1
  122. package/lib/stores/showcase/ShowcaseViewerStore.js +7 -3
  123. package/lib/stores/showcase/ShowcaseViewerStore.js.map +1 -1
  124. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  125. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +1 -0
  126. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  127. package/package.json +16 -16
  128. package/src/__lib__/LegendStudioEvent.ts +22 -6
  129. package/src/__lib__/LegendStudioTelemetryHelper.ts +425 -104
  130. package/src/__lib__/LegendStudioUserDataHelper.ts +115 -0
  131. package/src/application/LegendStudioApplicationConfig.ts +19 -0
  132. package/src/components/LegendStudioWebApplication.tsx +18 -2
  133. package/src/components/ShowcaseManager.tsx +24 -4
  134. package/src/components/editor/ActivityBar.tsx +5 -1
  135. package/src/components/editor/editor-group/GrammarTextEditor.tsx +18 -1
  136. package/src/components/editor/editor-group/data-editor/RelationElementSampleValuesEditor.tsx +285 -0
  137. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +38 -323
  138. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +4 -0
  139. package/src/components/showcase/ShowcaseFeedback.tsx +136 -0
  140. package/src/components/showcase/ShowcaseViewer.tsx +10 -0
  141. package/src/components/workspace-setup/WorkspaceSetup.tsx +6 -1
  142. package/src/index.ts +2 -0
  143. package/src/stores/LegendStudioBaseStore.ts +1 -0
  144. package/src/stores/ShowcaseManagerState.ts +67 -8
  145. package/src/stores/editor/EditorGraphState.ts +1 -0
  146. package/src/stores/editor/EditorStore.ts +26 -4
  147. package/src/stores/editor/GraphEditFormModeState.ts +2 -0
  148. package/src/stores/editor/GraphEditGrammarModeState.ts +139 -1
  149. package/src/stores/editor/GraphEditorMode.ts +1 -0
  150. package/src/stores/editor/StandardEditorMode.ts +21 -7
  151. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +3 -119
  152. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.ts +1 -136
  153. package/src/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.ts +4 -0
  154. package/src/stores/editor/sidebar-state/LocalChangesState.ts +47 -0
  155. package/src/stores/editor/sidebar-state/WorkspaceUpdaterState.ts +18 -0
  156. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +7 -0
  157. package/src/stores/lazy-text-editor/LazyTextEditorStore.ts +4 -0
  158. package/src/stores/project-reviewer/ProjectReviewerStore.ts +4 -0
  159. package/src/stores/project-view/ProjectViewerEditorMode.ts +17 -4
  160. package/src/stores/project-view/ProjectViewerStore.ts +5 -0
  161. package/src/stores/showcase/ShowcaseViewerEditorMode.ts +4 -5
  162. package/src/stores/showcase/ShowcaseViewerStore.ts +12 -2
  163. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +2 -0
  164. package/tsconfig.json +2 -0
@@ -45,6 +45,11 @@ export enum LEGEND_STUDIO_USER_DATA_KEY {
45
45
  // sandbox-tag project search on every mount. Revalidated against SDLC in
46
46
  // the background; invalidated automatically on 404 or after the TTL.
47
47
  WORKSPACE_SETUP_SANDBOX_INFO = 'studio-editor.workspace-setup.sandboxInfo',
48
+ // Per-browser cache of the user's showcase feedback votes (thumbs up /
49
+ // thumbs down). Purely a UX state store so the viewer can highlight the
50
+ // vote the user already cast; the source of truth for aggregate analysis
51
+ // is the `showcase.viewer.feedback.submit` telemetry event.
52
+ SHOWCASE_FEEDBACK_VOTES = 'studio-editor.showcase.feedback.votes',
48
53
  }
49
54
 
50
55
  // --- Workspace setup recents -------------------------------------------------
@@ -171,6 +176,86 @@ export class CachedSandboxInfo {
171
176
  );
172
177
  }
173
178
 
179
+ // --- Showcase feedback votes -------------------------------------------------
180
+
181
+ const SHOWCASE_FEEDBACK_VOTES_VERSION = 1;
182
+ // Cap the store so it can't grow without bound as users vote across many
183
+ // showcases. Older votes fall off first — this is purely a UX cache so
184
+ // losing very old entries just means the button state won't be pre-filled;
185
+ // the telemetry event is still the source of truth.
186
+ const MAX_SHOWCASE_FEEDBACK_VOTES = 200;
187
+
188
+ export class ShowcaseFeedbackVote {
189
+ showcasePath!: string;
190
+ vote!: 'up' | 'down';
191
+ votedAt!: number;
192
+
193
+ static readonly serialization = new SerializationFactory(
194
+ createModelSchema(ShowcaseFeedbackVote, {
195
+ showcasePath: primitive(),
196
+ // Stored as a plain string; validated on read.
197
+ vote: primitive(),
198
+ votedAt: primitive(),
199
+ }),
200
+ );
201
+ }
202
+
203
+ export class ShowcaseFeedbackVotes {
204
+ version: number = SHOWCASE_FEEDBACK_VOTES_VERSION;
205
+ votes: ShowcaseFeedbackVote[] = [];
206
+
207
+ static readonly serialization = new SerializationFactory(
208
+ createModelSchema(ShowcaseFeedbackVotes, {
209
+ version: primitive(),
210
+ votes: list(usingModelSchema(ShowcaseFeedbackVote.serialization.schema)),
211
+ }),
212
+ );
213
+ }
214
+
215
+ const isValidShowcaseVote = (v: unknown): v is 'up' | 'down' =>
216
+ v === 'up' || v === 'down';
217
+
218
+ const emptyShowcaseFeedbackVotes = (): ShowcaseFeedbackVotes =>
219
+ new ShowcaseFeedbackVotes();
220
+
221
+ const readShowcaseFeedbackVotes = (
222
+ service: UserDataService,
223
+ ): ShowcaseFeedbackVotes => {
224
+ const raw = returnUndefOnError(() =>
225
+ service.getObjectValue(LEGEND_STUDIO_USER_DATA_KEY.SHOWCASE_FEEDBACK_VOTES),
226
+ );
227
+ if (!raw) {
228
+ return emptyShowcaseFeedbackVotes();
229
+ }
230
+ const parsed = returnUndefOnError(() =>
231
+ ShowcaseFeedbackVotes.serialization.fromJson(
232
+ raw as PlainObject<ShowcaseFeedbackVotes>,
233
+ ),
234
+ );
235
+ if (!parsed) {
236
+ return emptyShowcaseFeedbackVotes();
237
+ }
238
+ // Reset the store on any future schema-version bump; old entries were
239
+ // written under a different shape and can't be trusted.
240
+ if (parsed.version !== SHOWCASE_FEEDBACK_VOTES_VERSION) {
241
+ return emptyShowcaseFeedbackVotes();
242
+ }
243
+ parsed.votes = parsed.votes
244
+ .filter((v) => isValidShowcaseVote(v.vote))
245
+ .slice(0, MAX_SHOWCASE_FEEDBACK_VOTES);
246
+ return parsed;
247
+ };
248
+
249
+ const writeShowcaseFeedbackVotes = (
250
+ service: UserDataService,
251
+ store: ShowcaseFeedbackVotes,
252
+ ): void => {
253
+ service.persistValue(
254
+ LEGEND_STUDIO_USER_DATA_KEY.SHOWCASE_FEEDBACK_VOTES,
255
+ ShowcaseFeedbackVotes.serialization.toJson(store),
256
+ );
257
+ };
258
+
174
259
  export class LegendStudioUserDataHelper {
175
260
  static globalTestRunner_getShowDependencyPanel(
176
261
  service: UserDataService,
@@ -378,4 +463,34 @@ export class LegendStudioUserDataHelper {
378
463
  undefined,
379
464
  );
380
465
  }
466
+
467
+ // --- Showcase feedback votes -------------------------------------------
468
+
469
+ static showcaseFeedback_getVote(
470
+ service: UserDataService,
471
+ showcasePath: string,
472
+ ): ShowcaseFeedbackVote | undefined {
473
+ return readShowcaseFeedbackVotes(service).votes.find(
474
+ (v) => v.showcasePath === showcasePath,
475
+ );
476
+ }
477
+
478
+ static showcaseFeedback_recordVote(
479
+ service: UserDataService,
480
+ entry: {
481
+ showcasePath: string;
482
+ vote: 'up' | 'down';
483
+ },
484
+ ): void {
485
+ const store = readShowcaseFeedbackVotes(service);
486
+ const next = new ShowcaseFeedbackVote();
487
+ next.showcasePath = entry.showcasePath;
488
+ next.vote = entry.vote;
489
+ next.votedAt = Date.now();
490
+ store.votes = [
491
+ next,
492
+ ...store.votes.filter((v) => v.showcasePath !== entry.showcasePath),
493
+ ].slice(0, MAX_SHOWCASE_FEEDBACK_VOTES);
494
+ writeShowcaseFeedbackVotes(service, store);
495
+ }
381
496
  }
@@ -208,11 +208,20 @@ export interface LegendStudioApplicationConfigurationData
208
208
  * the SDLC server's OAuth client allow-list.
209
209
  */
210
210
  enablePopupReAuth?: boolean;
211
+ /**
212
+ * When `true`, the SDLC server is authenticated exclusively via the
213
+ * session cookie, never via an `Authorization: Bearer` header.
214
+ *
215
+ * See `SDLCServerClientConfig.useCookieAuthOnly`.
216
+ */
217
+ useCookieAuthOnly?: boolean;
211
218
  };
212
219
  depot: { url: string };
213
220
  engine: {
214
221
  url: string;
215
222
  queryUrl?: string;
223
+ useCookieAuthOnly?: boolean;
224
+ queryClientName?: string;
216
225
  };
217
226
  query?: { url: string };
218
227
  showcase?: { url: string };
@@ -225,11 +234,14 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
225
234
 
226
235
  readonly engineServerUrl: string;
227
236
  readonly engineQueryServerUrl?: string | undefined;
237
+ readonly engineQueryClientName?: string | undefined;
238
+ readonly engineUseCookieAuthOnly: boolean;
228
239
  readonly depotServerUrl: string;
229
240
  readonly sdlcServerUrl: string;
230
241
  readonly sdlcServerBaseHeaders?: RequestHeaders | undefined;
231
242
  readonly sdlcServerClient?: string | undefined;
232
243
  readonly sdlcEnablePopupReAuth: boolean;
244
+ readonly sdlcUseCookieAuthOnly: boolean;
233
245
  readonly queryApplicationUrl?: string | undefined;
234
246
  readonly showcaseServerUrl?: string | undefined;
235
247
  readonly pctReportUrl?: string | undefined;
@@ -256,6 +268,10 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
256
268
  input.configData.engine.queryUrl,
257
269
  );
258
270
  }
271
+ this.engineQueryClientName = input.configData.engine.queryClientName;
272
+ this.engineUseCookieAuthOnly = Boolean(
273
+ input.configData.engine.useCookieAuthOnly,
274
+ );
259
275
 
260
276
  // depot
261
277
  assertNonNullable(
@@ -285,6 +301,9 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
285
301
  this.sdlcEnablePopupReAuth = Boolean(
286
302
  input.configData.sdlc.enablePopupReAuth,
287
303
  );
304
+ this.sdlcUseCookieAuthOnly = Boolean(
305
+ input.configData.sdlc.useCookieAuthOnly,
306
+ );
288
307
 
289
308
  // query
290
309
  if (input.configData.query?.url) {
@@ -40,6 +40,8 @@ import {
40
40
  import {
41
41
  useApplicationStore,
42
42
  LegendTokenSync,
43
+ ExtensionPageBoundary,
44
+ LegendApplicationTelemetryHelper,
43
45
  } from '@finos/legend-application';
44
46
  import {
45
47
  BrowserEnvironmentProvider,
@@ -64,6 +66,13 @@ const NotFoundPage = observer(() => {
64
66
  const currentPath =
65
67
  applicationStore.navigationService.navigator.getCurrentLocation();
66
68
 
69
+ useEffect(() => {
70
+ LegendApplicationTelemetryHelper.logEvent_RouteNotFound(
71
+ applicationStore.telemetryService,
72
+ { path: currentPath },
73
+ );
74
+ }, [applicationStore, currentPath]);
75
+
67
76
  const documentation = applicationStore.documentationService.getDocEntry(
68
77
  LEGEND_STUDIO_DOCUMENTATION_KEY.NOT_FOUND_HELP,
69
78
  );
@@ -280,9 +289,16 @@ export const LegendStudioWebApplicationRouter = observer(() => {
280
289
  .map(generateExtensionUrlPattern)
281
290
  .map((path) => (
282
291
  <Route
283
- key={entry.key}
292
+ key={`${entry.key}:${path}`}
284
293
  path={path}
285
- element={entry.renderer()}
294
+ element={
295
+ <ExtensionPageBoundary
296
+ entryKey={entry.key}
297
+ pattern={path}
298
+ >
299
+ {entry.renderer()}
300
+ </ExtensionPageBoundary>
301
+ }
286
302
  />
287
303
  )),
288
304
  )}
@@ -42,6 +42,11 @@ import {
42
42
  type ShowcaseTextSearchMatchResult,
43
43
  type ShowcasesExplorerTreeNodeData,
44
44
  } from '../stores/ShowcaseManagerState.js';
45
+ import {
46
+ SHOWCASE_LAUNCH_ENTRY_POINT,
47
+ SHOWCASE_FEEDBACK_SURFACE,
48
+ } from '../__lib__/LegendStudioTelemetryHelper.js';
49
+ import { ShowcaseFeedback } from './showcase/ShowcaseFeedback.js';
45
50
  import { debounce, isNonNullable } from '@finos/legend-shared';
46
51
  import { flowResult } from 'mobx';
47
52
  import type { Showcase } from '@finos/legend-server-showcase';
@@ -86,9 +91,13 @@ const ShowcasesExplorerTreeNodeContainer = observer(
86
91
  if (!node.metadata) {
87
92
  toggleExpandNode(node);
88
93
  } else {
89
- flowResult(showcaseManagerState.openShowcase(node.metadata)).catch(
90
- applicationStore.alertUnhandledError,
91
- );
94
+ flowResult(
95
+ showcaseManagerState.openShowcase(
96
+ node.metadata,
97
+ undefined,
98
+ SHOWCASE_LAUNCH_ENTRY_POINT.EXPLORER,
99
+ ),
100
+ ).catch(applicationStore.alertUnhandledError);
92
101
  }
93
102
  };
94
103
 
@@ -254,7 +263,11 @@ const ShowcaseManagerCodeSearchResult = observer(
254
263
  }\n\nClick to open showcase`}
255
264
  onClick={() => {
256
265
  flowResult(
257
- showcaseManagerState.openShowcase(result.showcase),
266
+ showcaseManagerState.openShowcase(
267
+ result.showcase,
268
+ undefined,
269
+ SHOWCASE_LAUNCH_ENTRY_POINT.SEARCH_SHOWCASE_MATCH,
270
+ ),
258
271
  ).catch(applicationStore.alertUnhandledError);
259
272
  }}
260
273
  >
@@ -275,6 +288,7 @@ const ShowcaseManagerCodeSearchResult = observer(
275
288
  showcaseManagerState.openShowcase(
276
289
  result.showcase,
277
290
  entry.line,
291
+ SHOWCASE_LAUNCH_ENTRY_POINT.SEARCH_CODE_MATCH,
278
292
  ),
279
293
  ).catch(applicationStore.alertUnhandledError);
280
294
  }}
@@ -635,6 +649,12 @@ const ShowcaseViewer = observer(
635
649
  lineToScroll={showcaseManagerState.showcaseLineToScroll}
636
650
  />
637
651
  </div>
652
+ <ShowcaseFeedback
653
+ className="showcase-manager__viewer__feedback"
654
+ showcasePath={showcase.path}
655
+ title={showcase.title}
656
+ surface={SHOWCASE_FEEDBACK_SURFACE.ASSISTANT_PANEL}
657
+ />
638
658
  </div>
639
659
  </div>
640
660
  );
@@ -81,6 +81,7 @@ import {
81
81
  ShowcaseManagerState,
82
82
  openShowcaseManager,
83
83
  } from '../../stores/ShowcaseManagerState.js';
84
+ import { SHOWCASE_MANAGER_ENTRY_POINT } from '../../__lib__/LegendStudioTelemetryHelper.js';
84
85
  import { toggleShowcasePanel } from './ShowcaseSideBar.js';
85
86
  import { useAuth, type AuthContextProps } from 'react-oidc-context';
86
87
 
@@ -223,7 +224,10 @@ export const ActivityBarMenu: React.FC<{
223
224
  onClick={() =>
224
225
  openShowcasePanel
225
226
  ? openShowcasePanel()
226
- : openShowcaseManager(applicationStore)
227
+ : openShowcaseManager(
228
+ applicationStore,
229
+ SHOWCASE_MANAGER_ENTRY_POINT.ACTIVITY_BAR,
230
+ )
227
231
  }
228
232
  >
229
233
  See Showcases
@@ -132,6 +132,12 @@ import {
132
132
  GraphEditGrammarModeState,
133
133
  } from '../../../stores/editor/GraphEditGrammarModeState.js';
134
134
  import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
135
+ import {
136
+ LegendStudioTelemetryHelper,
137
+ TEXT_MODE_ACTION,
138
+ TEXT_MODE_ACTION_STATUS,
139
+ TEXT_MODE_TOGGLE_SOURCE,
140
+ } from '../../../__lib__/LegendStudioTelemetryHelper.js';
135
141
  import { FileSystem_FileViewer } from './ArtifactGenerationViewer.js';
136
142
  import type { FileSystem_File } from '../../../stores/editor/utils/FileSystemTreeUtils.js';
137
143
 
@@ -141,7 +147,9 @@ export const GrammarTextEditorHeaderTabContextMenu = observer(
141
147
  const editorStore = useEditorStore();
142
148
  const applicationStore = useApplicationStore();
143
149
  const leaveTextMode = applicationStore.guardUnhandledError(() =>
144
- flowResult(editorStore.toggleTextMode()),
150
+ flowResult(
151
+ editorStore.toggleTextMode(TEXT_MODE_TOGGLE_SOURCE.CONTEXT_MENU),
152
+ ),
145
153
  );
146
154
 
147
155
  return (
@@ -766,6 +774,14 @@ const goToElement = (
766
774
  LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__LAUNCH,
767
775
  ),
768
776
  );
777
+ LegendStudioTelemetryHelper.logEvent_TextModeAction(
778
+ grammarModeState.editorStore.applicationStore.telemetryService,
779
+ grammarModeState.editorStore.editorMode.getSourceInfo(),
780
+ {
781
+ action: TEXT_MODE_ACTION.GO_TO_DEFINITION,
782
+ status: TEXT_MODE_ACTION_STATUS.LAUNCH,
783
+ },
784
+ );
769
785
  const elementPath = resolveElementPathFromCurrentPosition(
770
786
  _editor,
771
787
  grammarModeState,
@@ -882,6 +898,7 @@ export const GrammarTextEditor = observer(() => {
882
898
  });
883
899
  _editor.onDidChangeModelContent(() => {
884
900
  grammarTextEditorState.setGraphGrammarText(getCodeEditorValue(_editor));
901
+ grammarModeState.notifyGrammarTextEdited();
885
902
  clearMarkers();
886
903
  // NOTE: we can technically can reset the current element label regex string here
887
904
  // but if we do that on first load, the cursor will not jump to the current element
@@ -0,0 +1,285 @@
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 { observer } from 'mobx-react-lite';
18
+ import { useState, type CSSProperties } from 'react';
19
+ import {
20
+ Dialog,
21
+ LongArrowRightIcon,
22
+ Modal,
23
+ ModalBody,
24
+ ModalFooter,
25
+ ModalFooterButton,
26
+ ModalHeader,
27
+ PencilEditIcon,
28
+ PlusIcon,
29
+ Tooltip,
30
+ } from '@finos/legend-art';
31
+ import {
32
+ ActionAlertActionType,
33
+ ActionAlertType,
34
+ } from '@finos/legend-application';
35
+ import type { RelationElementState } from '../../../../stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js';
36
+ import { RelationElementEditor } from './RelationElementsDataEditor.js';
37
+ import { useEditorStore } from '../../EditorStoreProvider.js';
38
+
39
+ export interface RelationElementSampleValuesReadOnlySource {
40
+ label: string;
41
+ message?: string | undefined;
42
+ onNavigate: () => void;
43
+ }
44
+
45
+ export const RelationElementSampleValuesEditor = observer(
46
+ (props: {
47
+ title: string;
48
+ relationElementState: RelationElementState | undefined;
49
+ isReadOnly: boolean;
50
+ isBusy?: boolean | undefined;
51
+ hideColumnDefinitions?: boolean | undefined;
52
+ deleteConfirmMessage: string;
53
+ onAdd: () => Promise<void> | void;
54
+ onDelete: () => void;
55
+ hasMismatch?: boolean | undefined;
56
+ mismatchMessage?: string | undefined;
57
+ readOnlySource?: RelationElementSampleValuesReadOnlySource | undefined;
58
+ }) => {
59
+ const {
60
+ title,
61
+ relationElementState,
62
+ isReadOnly,
63
+ isBusy,
64
+ hideColumnDefinitions,
65
+ deleteConfirmMessage,
66
+ onAdd,
67
+ onDelete,
68
+ hasMismatch,
69
+ mismatchMessage,
70
+ readOnlySource,
71
+ } = props;
72
+ const editorStore = useEditorStore();
73
+ const [isModalOpen, setIsModalOpen] = useState(false);
74
+ const isEditMode = Boolean(relationElementState) || Boolean(readOnlySource);
75
+
76
+ const closeModal = (): void => setIsModalOpen(false);
77
+
78
+ const handleClick = (): void => {
79
+ if (isEditMode) {
80
+ setIsModalOpen(true);
81
+ return;
82
+ }
83
+ Promise.resolve(onAdd())
84
+ .then(() => setIsModalOpen(true))
85
+ .catch(editorStore.applicationStore.alertUnhandledError);
86
+ };
87
+
88
+ const handleDelete = (): void => {
89
+ editorStore.applicationStore.alertService.setActionAlertInfo({
90
+ message: deleteConfirmMessage,
91
+ type: ActionAlertType.CAUTION,
92
+ actions: [
93
+ {
94
+ label: 'Confirm',
95
+ type: ActionAlertActionType.PROCEED_WITH_CAUTION,
96
+ handler: (): void => {
97
+ onDelete();
98
+ closeModal();
99
+ },
100
+ },
101
+ {
102
+ label: 'Cancel',
103
+ type: ActionAlertActionType.PROCEED,
104
+ default: true,
105
+ },
106
+ ],
107
+ });
108
+ };
109
+
110
+ const buttonStyle: CSSProperties = {
111
+ cursor: isReadOnly ? 'not-allowed' : 'pointer',
112
+ };
113
+ if (relationElementState && !readOnlySource) {
114
+ buttonStyle.border = hasMismatch
115
+ ? '2px solid var(--color-red-300)'
116
+ : '1px solid var(--color-blue-200)';
117
+ }
118
+
119
+ return (
120
+ <>
121
+ <button
122
+ className="access-point-editor__sample-values-btn"
123
+ onClick={handleClick}
124
+ disabled={isReadOnly || isBusy}
125
+ title={isEditMode ? 'Edit sample values' : 'Add sample values'}
126
+ style={buttonStyle}
127
+ >
128
+ {isEditMode ? <PencilEditIcon /> : <PlusIcon />}
129
+ <span>Sample Values</span>
130
+ </button>
131
+ {isModalOpen && (
132
+ <Dialog
133
+ open={isModalOpen}
134
+ onClose={closeModal}
135
+ classes={{
136
+ root: 'editor-modal__root-container',
137
+ container: 'editor-modal__container',
138
+ paper: 'editor-modal__content',
139
+ }}
140
+ >
141
+ <Modal
142
+ className="editor-modal"
143
+ darkMode={
144
+ !editorStore.applicationStore.layoutService
145
+ .TEMPORARY__isLightColorThemeEnabled
146
+ }
147
+ >
148
+ <ModalHeader title={title} />
149
+ <ModalBody>
150
+ <div
151
+ style={{
152
+ padding: 0,
153
+ height: '100%',
154
+ flex: 1,
155
+ overflowY: 'auto',
156
+ overflowX: 'hidden',
157
+ }}
158
+ >
159
+ {readOnlySource ? (
160
+ <div
161
+ style={{
162
+ padding: '1.5rem',
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ gap: '1rem',
167
+ }}
168
+ >
169
+ <span
170
+ style={{
171
+ color: 'var(--color-light-grey-200)',
172
+ whiteSpace: 'nowrap',
173
+ fontSize: '1.3rem',
174
+ fontWeight: 600,
175
+ }}
176
+ >
177
+ {readOnlySource.message ??
178
+ 'Sample Values already in Data Element'}
179
+ </span>
180
+ <div
181
+ style={{
182
+ display: 'flex',
183
+ alignItems: 'center',
184
+ gap: '0.5rem',
185
+ padding: '0.5rem 1rem',
186
+ background: 'var(--color-dark-grey-100)',
187
+ border: '1px solid var(--color-dark-grey-300)',
188
+ borderRadius: '0.2rem',
189
+ }}
190
+ >
191
+ <span
192
+ style={{
193
+ fontSize: '1.3rem',
194
+ fontWeight: 600,
195
+ color: 'var(--color-light-grey-200)',
196
+ }}
197
+ >
198
+ {readOnlySource.label}
199
+ </span>
200
+ <button
201
+ className="btn--sm btn--dark"
202
+ onClick={readOnlySource.onNavigate}
203
+ tabIndex={-1}
204
+ title="Navigate to data element"
205
+ style={{
206
+ padding: '0.3rem 0.5rem',
207
+ display: 'flex',
208
+ alignItems: 'center',
209
+ }}
210
+ >
211
+ <LongArrowRightIcon />
212
+ </button>
213
+ </div>
214
+ </div>
215
+ ) : (
216
+ <>
217
+ <div
218
+ style={{
219
+ color: 'var(--color-orange-200)',
220
+ fontWeight: 'bold',
221
+ fontSize: '14px',
222
+ padding: '0.5rem',
223
+ marginBottom: '1rem',
224
+ textAlign: 'center',
225
+ backgroundColor: 'var(--color-orange-50)',
226
+ border: '2px solid var(--color-orange-200)',
227
+ borderRadius: '4px',
228
+ }}
229
+ >
230
+ DO NOT ADD SENSITIVE DATA
231
+ </div>
232
+ {relationElementState && (
233
+ <Tooltip
234
+ title={mismatchMessage ?? ''}
235
+ arrow={true}
236
+ placement="top"
237
+ disableHoverListener={!hasMismatch}
238
+ >
239
+ <div
240
+ style={{
241
+ border: hasMismatch
242
+ ? '2px solid var(--color-red-300)'
243
+ : 'none',
244
+ borderRadius: '4px',
245
+ padding: hasMismatch ? '0.5rem' : '0',
246
+ }}
247
+ >
248
+ <RelationElementEditor
249
+ relationElementState={relationElementState}
250
+ isReadOnly={isReadOnly}
251
+ {...(hideColumnDefinitions !== undefined
252
+ ? { hideColumnDefinitions }
253
+ : {})}
254
+ />
255
+ </div>
256
+ </Tooltip>
257
+ )}
258
+ </>
259
+ )}
260
+ </div>
261
+ </ModalBody>
262
+ <ModalFooter>
263
+ {!readOnlySource && (
264
+ <ModalFooterButton
265
+ title="Delete sample values"
266
+ onClick={handleDelete}
267
+ text="Delete"
268
+ type="secondary"
269
+ disabled={isReadOnly}
270
+ />
271
+ )}
272
+ <ModalFooterButton
273
+ title="Close sample values editor"
274
+ onClick={closeModal}
275
+ text="Close"
276
+ type="secondary"
277
+ />
278
+ </ModalFooter>
279
+ </Modal>
280
+ </Dialog>
281
+ )}
282
+ </>
283
+ );
284
+ },
285
+ );