@finos/legend-application-studio 28.21.43 → 28.21.45

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 (150) 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 +10 -0
  14. package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
  15. package/lib/application/LegendStudioApplicationConfig.js +4 -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/dataProduct/DataProductEditor.d.ts.map +1 -1
  30. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +6 -16
  31. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  32. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  33. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -4
  34. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  35. package/lib/components/showcase/ShowcaseFeedback.d.ts +36 -0
  36. package/lib/components/showcase/ShowcaseFeedback.d.ts.map +1 -0
  37. package/lib/components/showcase/ShowcaseFeedback.js +70 -0
  38. package/lib/components/showcase/ShowcaseFeedback.js.map +1 -0
  39. package/lib/components/showcase/ShowcaseViewer.d.ts.map +1 -1
  40. package/lib/components/showcase/ShowcaseViewer.js +3 -1
  41. package/lib/components/showcase/ShowcaseViewer.js.map +1 -1
  42. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  43. package/lib/components/workspace-setup/WorkspaceSetup.js +2 -1
  44. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  45. package/lib/index.css +2 -2
  46. package/lib/index.css.map +1 -1
  47. package/lib/package.json +1 -1
  48. package/lib/stores/LegendStudioBaseStore.d.ts.map +1 -1
  49. package/lib/stores/LegendStudioBaseStore.js +1 -0
  50. package/lib/stores/LegendStudioBaseStore.js.map +1 -1
  51. package/lib/stores/ShowcaseManagerState.d.ts +5 -3
  52. package/lib/stores/ShowcaseManagerState.d.ts.map +1 -1
  53. package/lib/stores/ShowcaseManagerState.js +44 -11
  54. package/lib/stores/ShowcaseManagerState.js.map +1 -1
  55. package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
  56. package/lib/stores/editor/EditorGraphState.js +1 -1
  57. package/lib/stores/editor/EditorGraphState.js.map +1 -1
  58. package/lib/stores/editor/EditorStore.d.ts +2 -1
  59. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  60. package/lib/stores/editor/EditorStore.js +7 -2
  61. package/lib/stores/editor/EditorStore.js.map +1 -1
  62. package/lib/stores/editor/GraphEditFormModeState.d.ts.map +1 -1
  63. package/lib/stores/editor/GraphEditFormModeState.js +2 -2
  64. package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
  65. package/lib/stores/editor/GraphEditGrammarModeState.d.ts +19 -0
  66. package/lib/stores/editor/GraphEditGrammarModeState.d.ts.map +1 -1
  67. package/lib/stores/editor/GraphEditGrammarModeState.js +92 -4
  68. package/lib/stores/editor/GraphEditGrammarModeState.js.map +1 -1
  69. package/lib/stores/editor/GraphEditorMode.d.ts +1 -0
  70. package/lib/stores/editor/GraphEditorMode.d.ts.map +1 -1
  71. package/lib/stores/editor/GraphEditorMode.js.map +1 -1
  72. package/lib/stores/editor/StandardEditorMode.d.ts +16 -5
  73. package/lib/stores/editor/StandardEditorMode.d.ts.map +1 -1
  74. package/lib/stores/editor/StandardEditorMode.js +4 -2
  75. package/lib/stores/editor/StandardEditorMode.js.map +1 -1
  76. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +1 -11
  77. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  78. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +2 -64
  79. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  80. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts +0 -11
  81. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts.map +1 -1
  82. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js +2 -77
  83. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js.map +1 -1
  84. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.d.ts.map +1 -1
  85. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js +4 -4
  86. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js.map +1 -1
  87. package/lib/stores/editor/sidebar-state/LocalChangesState.d.ts +1 -0
  88. package/lib/stores/editor/sidebar-state/LocalChangesState.d.ts.map +1 -1
  89. package/lib/stores/editor/sidebar-state/LocalChangesState.js +27 -0
  90. package/lib/stores/editor/sidebar-state/LocalChangesState.js.map +1 -1
  91. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.d.ts.map +1 -1
  92. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js +7 -0
  93. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js.map +1 -1
  94. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  95. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +7 -2
  96. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  97. package/lib/stores/lazy-text-editor/LazyTextEditorStore.d.ts +1 -0
  98. package/lib/stores/lazy-text-editor/LazyTextEditorStore.d.ts.map +1 -1
  99. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js +3 -0
  100. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js.map +1 -1
  101. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
  102. package/lib/stores/project-reviewer/ProjectReviewerStore.js +1 -0
  103. package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
  104. package/lib/stores/project-view/ProjectViewerEditorMode.d.ts +2 -2
  105. package/lib/stores/project-view/ProjectViewerEditorMode.d.ts.map +1 -1
  106. package/lib/stores/project-view/ProjectViewerEditorMode.js +12 -0
  107. package/lib/stores/project-view/ProjectViewerEditorMode.js.map +1 -1
  108. package/lib/stores/project-view/ProjectViewerStore.d.ts.map +1 -1
  109. package/lib/stores/project-view/ProjectViewerStore.js +2 -1
  110. package/lib/stores/project-view/ProjectViewerStore.js.map +1 -1
  111. package/lib/stores/showcase/ShowcaseViewerEditorMode.d.ts.map +1 -1
  112. package/lib/stores/showcase/ShowcaseViewerEditorMode.js.map +1 -1
  113. package/lib/stores/showcase/ShowcaseViewerStore.d.ts.map +1 -1
  114. package/lib/stores/showcase/ShowcaseViewerStore.js +6 -3
  115. package/lib/stores/showcase/ShowcaseViewerStore.js.map +1 -1
  116. package/package.json +16 -16
  117. package/src/__lib__/LegendStudioEvent.ts +22 -6
  118. package/src/__lib__/LegendStudioTelemetryHelper.ts +425 -104
  119. package/src/__lib__/LegendStudioUserDataHelper.ts +115 -0
  120. package/src/application/LegendStudioApplicationConfig.ts +14 -0
  121. package/src/components/LegendStudioWebApplication.tsx +18 -2
  122. package/src/components/ShowcaseManager.tsx +24 -4
  123. package/src/components/editor/ActivityBar.tsx +5 -1
  124. package/src/components/editor/editor-group/GrammarTextEditor.tsx +18 -1
  125. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +4 -54
  126. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +4 -0
  127. package/src/components/showcase/ShowcaseFeedback.tsx +136 -0
  128. package/src/components/showcase/ShowcaseViewer.tsx +10 -0
  129. package/src/components/workspace-setup/WorkspaceSetup.tsx +6 -1
  130. package/src/stores/LegendStudioBaseStore.ts +1 -0
  131. package/src/stores/ShowcaseManagerState.ts +67 -8
  132. package/src/stores/editor/EditorGraphState.ts +1 -0
  133. package/src/stores/editor/EditorStore.ts +24 -4
  134. package/src/stores/editor/GraphEditFormModeState.ts +2 -0
  135. package/src/stores/editor/GraphEditGrammarModeState.ts +139 -1
  136. package/src/stores/editor/GraphEditorMode.ts +1 -0
  137. package/src/stores/editor/StandardEditorMode.ts +21 -7
  138. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +0 -106
  139. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.ts +1 -136
  140. package/src/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.ts +4 -0
  141. package/src/stores/editor/sidebar-state/LocalChangesState.ts +47 -0
  142. package/src/stores/editor/sidebar-state/WorkspaceUpdaterState.ts +18 -0
  143. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +7 -0
  144. package/src/stores/lazy-text-editor/LazyTextEditorStore.ts +4 -0
  145. package/src/stores/project-reviewer/ProjectReviewerStore.ts +2 -0
  146. package/src/stores/project-view/ProjectViewerEditorMode.ts +17 -4
  147. package/src/stores/project-view/ProjectViewerStore.ts +3 -0
  148. package/src/stores/showcase/ShowcaseViewerEditorMode.ts +4 -5
  149. package/src/stores/showcase/ShowcaseViewerStore.ts +10 -2
  150. package/tsconfig.json +1 -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,12 +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;
216
223
  useCookieAuthOnly?: boolean;
224
+ queryClientName?: string;
217
225
  };
218
226
  query?: { url: string };
219
227
  showcase?: { url: string };
@@ -226,12 +234,14 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
226
234
 
227
235
  readonly engineServerUrl: string;
228
236
  readonly engineQueryServerUrl?: string | undefined;
237
+ readonly engineQueryClientName?: string | undefined;
229
238
  readonly engineUseCookieAuthOnly: boolean;
230
239
  readonly depotServerUrl: string;
231
240
  readonly sdlcServerUrl: string;
232
241
  readonly sdlcServerBaseHeaders?: RequestHeaders | undefined;
233
242
  readonly sdlcServerClient?: string | undefined;
234
243
  readonly sdlcEnablePopupReAuth: boolean;
244
+ readonly sdlcUseCookieAuthOnly: boolean;
235
245
  readonly queryApplicationUrl?: string | undefined;
236
246
  readonly showcaseServerUrl?: string | undefined;
237
247
  readonly pctReportUrl?: string | undefined;
@@ -258,6 +268,7 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
258
268
  input.configData.engine.queryUrl,
259
269
  );
260
270
  }
271
+ this.engineQueryClientName = input.configData.engine.queryClientName;
261
272
  this.engineUseCookieAuthOnly = Boolean(
262
273
  input.configData.engine.useCookieAuthOnly,
263
274
  );
@@ -290,6 +301,9 @@ export class LegendStudioApplicationConfig extends LegendApplicationConfig {
290
301
  this.sdlcEnablePopupReAuth = Boolean(
291
302
  input.configData.sdlc.enablePopupReAuth,
292
303
  );
304
+ this.sdlcUseCookieAuthOnly = Boolean(
305
+ input.configData.sdlc.useCookieAuthOnly,
306
+ );
293
307
 
294
308
  // query
295
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
@@ -53,7 +53,6 @@ import {
53
53
  ModalFooter,
54
54
  ModalFooterButton,
55
55
  ModalHeader,
56
- ModalTitle,
57
56
  PanelContent,
58
57
  PanelDnDEntry,
59
58
  PanelEntryDragHandle,
@@ -1054,56 +1053,6 @@ export const LakehouseDataProductAccessPointEditor = observer(
1054
1053
  },
1055
1054
  );
1056
1055
 
1057
- const DataProductDeploymentResponseModal = observer(
1058
- (props: { state: DataProductEditorState }) => {
1059
- const { state } = props;
1060
- const applicationStore = state.editorStore.applicationStore;
1061
- const closeModal = (): void => state.setDeployResponse(undefined);
1062
- return (
1063
- <Dialog
1064
- open={Boolean(state.deployResponse)}
1065
- classes={{
1066
- root: 'editor-modal__root-container',
1067
- container: 'editor-modal__container',
1068
- paper: 'editor-modal__content',
1069
- }}
1070
- onClose={closeModal}
1071
- >
1072
- <Modal
1073
- darkMode={
1074
- !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
1075
- }
1076
- className="editor-modal"
1077
- >
1078
- <ModalHeader>
1079
- <ModalTitle title="Data Product Deployment Response" />
1080
- </ModalHeader>
1081
- <ModalBody>
1082
- <PanelContent>
1083
- <CodeEditor
1084
- inputValue={JSON.stringify(
1085
- state.deployResponse?.content ?? {},
1086
- null,
1087
- 2,
1088
- )}
1089
- isReadOnly={true}
1090
- language={CODE_EDITOR_LANGUAGE.JSON}
1091
- />
1092
- </PanelContent>
1093
- </ModalBody>
1094
- <ModalFooter>
1095
- <ModalFooterButton
1096
- onClick={closeModal}
1097
- text="Close"
1098
- type="secondary"
1099
- />
1100
- </ModalFooter>
1101
- </Modal>
1102
- </Dialog>
1103
- );
1104
- },
1105
- );
1106
-
1107
1056
  const AccessPointGroupPublicToggle = observer(
1108
1057
  (props: { groupState: AccessPointGroupState }) => {
1109
1058
  const { groupState } = props;
@@ -1345,6 +1294,7 @@ const AccessPointGroupEditor = observer(
1345
1294
  LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestLaunched(
1346
1295
  editorStore.applicationStore.telemetryService,
1347
1296
  productEditorState.product.path,
1297
+ editorStore.editorMode.getSourceInfo(),
1348
1298
  );
1349
1299
  setIsSuggestingWithAI(true);
1350
1300
  setAISuggestion(undefined);
@@ -1367,6 +1317,7 @@ const AccessPointGroupEditor = observer(
1367
1317
  editorStore.applicationStore.telemetryService,
1368
1318
  productEditorState.product.path,
1369
1319
  error.message,
1320
+ editorStore.editorMode.getSourceInfo(),
1370
1321
  );
1371
1322
  if (
1372
1323
  error instanceof NetworkClientError &&
@@ -1395,6 +1346,7 @@ const AccessPointGroupEditor = observer(
1395
1346
  LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestApplied(
1396
1347
  editorStore.applicationStore.telemetryService,
1397
1348
  productEditorState.product.path,
1349
+ editorStore.editorMode.getSourceInfo(),
1398
1350
  );
1399
1351
  // Find matching group in the response
1400
1352
  const gIdx =
@@ -1437,6 +1389,7 @@ const AccessPointGroupEditor = observer(
1437
1389
  LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestDiscarded(
1438
1390
  editorStore.applicationStore.telemetryService,
1439
1391
  productEditorState.product.path,
1392
+ editorStore.editorMode.getSourceInfo(),
1440
1393
  );
1441
1394
  setAISuggestion(undefined);
1442
1395
  };
@@ -1963,9 +1916,6 @@ const AccessPointGroupTab = observer(
1963
1916
  />
1964
1917
  )}
1965
1918
  </PanelContent>
1966
- {dataProductEditorState.deployResponse && (
1967
- <DataProductDeploymentResponseModal state={dataProductEditorState} />
1968
- )}
1969
1919
  </div>
1970
1920
  );
1971
1921
  },
@@ -274,6 +274,7 @@ const ServiceGeneralEditor = observer(() => {
274
274
  LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestLaunched(
275
275
  applicationStore.telemetryService,
276
276
  service.path,
277
+ editorStore.editorMode.getSourceInfo(),
277
278
  );
278
279
  setIsSuggestingWithAI(true);
279
280
  setAIDocSuggestion(undefined);
@@ -290,6 +291,7 @@ const ServiceGeneralEditor = observer(() => {
290
291
  applicationStore.telemetryService,
291
292
  service.path,
292
293
  error.message,
294
+ editorStore.editorMode.getSourceInfo(),
293
295
  );
294
296
  if (
295
297
  error instanceof NetworkClientError &&
@@ -315,6 +317,7 @@ const ServiceGeneralEditor = observer(() => {
315
317
  LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestApplied(
316
318
  applicationStore.telemetryService,
317
319
  service.path,
320
+ editorStore.editorMode.getSourceInfo(),
318
321
  );
319
322
  service_setDocumentation(service, aiDocSuggestion);
320
323
  setAIDocSuggestion(undefined);
@@ -323,6 +326,7 @@ const ServiceGeneralEditor = observer(() => {
323
326
  LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestDiscarded(
324
327
  applicationStore.telemetryService,
325
328
  service.path,
329
+ editorStore.editorMode.getSourceInfo(),
326
330
  );
327
331
  setAIDocSuggestion(undefined);
328
332
  };
@@ -0,0 +1,136 @@
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 { useEffect, useRef, useState } from 'react';
18
+ import { clsx, ThumbsUpIcon, ThumbsDownIcon } from '@finos/legend-art';
19
+ import { useApplicationStore } from '@finos/legend-application';
20
+ import { LegendStudioUserDataHelper } from '../../__lib__/LegendStudioUserDataHelper.js';
21
+ import {
22
+ LegendStudioTelemetryHelper,
23
+ SHOWCASE_FEEDBACK_VOTE,
24
+ type SHOWCASE_FEEDBACK_SURFACE,
25
+ } from '../../__lib__/LegendStudioTelemetryHelper.js';
26
+
27
+ const THANKS_DISPLAY_DURATION_MS = 2500;
28
+
29
+ /**
30
+ * "Was this showcase helpful?" widget.
31
+ *
32
+ * v1 storage strategy: telemetry is the source of truth for aggregate
33
+ * analysis (`showcase.viewer.feedback.submit`). The user's vote per
34
+ * showcase is cached in browser `UserDataService` (localStorage) purely
35
+ * so we can suppress re-prompting on revisit — there is no backend today.
36
+ *
37
+ * UX: once a user votes, the buttons are replaced with a brief
38
+ * "Thanks for the feedback!" message and then the whole widget unmounts.
39
+ * On subsequent visits to the same showcase, the widget renders nothing.
40
+ */
41
+ export const ShowcaseFeedback = (props: {
42
+ showcasePath: string;
43
+ title?: string | undefined;
44
+ surface: SHOWCASE_FEEDBACK_SURFACE;
45
+ /** Optional CSS class hook so the two mount points can size / align. */
46
+ className?: string | undefined;
47
+ }) => {
48
+ const { showcasePath, title, surface, className } = props;
49
+ const applicationStore = useApplicationStore();
50
+ const alreadyVoted = Boolean(
51
+ LegendStudioUserDataHelper.showcaseFeedback_getVote(
52
+ applicationStore.userDataService,
53
+ showcasePath,
54
+ ),
55
+ );
56
+ const [phase, setPhase] = useState<'prompt' | 'thanks' | 'done'>(
57
+ alreadyVoted ? 'done' : 'prompt',
58
+ );
59
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
60
+ undefined,
61
+ );
62
+
63
+ useEffect(
64
+ () => (): void => {
65
+ if (timeoutRef.current !== undefined) {
66
+ clearTimeout(timeoutRef.current);
67
+ }
68
+ },
69
+ [],
70
+ );
71
+
72
+ const submit = (vote: SHOWCASE_FEEDBACK_VOTE): void => {
73
+ LegendStudioUserDataHelper.showcaseFeedback_recordVote(
74
+ applicationStore.userDataService,
75
+ {
76
+ showcasePath,
77
+ vote: vote === SHOWCASE_FEEDBACK_VOTE.UP ? 'up' : 'down',
78
+ },
79
+ );
80
+ LegendStudioTelemetryHelper.logEvent_ShowcaseFeedbackSubmit(
81
+ applicationStore.telemetryService,
82
+ {
83
+ showcasePath,
84
+ title,
85
+ vote,
86
+ surface,
87
+ },
88
+ );
89
+ setPhase('thanks');
90
+ timeoutRef.current = setTimeout(() => {
91
+ setPhase('done');
92
+ }, THANKS_DISPLAY_DURATION_MS);
93
+ };
94
+
95
+ if (phase === 'done') {
96
+ return null;
97
+ }
98
+
99
+ if (phase === 'thanks') {
100
+ return (
101
+ <div
102
+ className={clsx('showcase-feedback', className)}
103
+ role="status"
104
+ aria-live="polite"
105
+ >
106
+ <div className="showcase-feedback__prompt">
107
+ Thanks for the feedback!
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ return (
114
+ <div className={clsx('showcase-feedback', className)}>
115
+ <div className="showcase-feedback__prompt">Was this helpful?</div>
116
+ <button
117
+ type="button"
118
+ className="showcase-feedback__btn"
119
+ tabIndex={-1}
120
+ title="Thumbs up"
121
+ onClick={() => submit(SHOWCASE_FEEDBACK_VOTE.UP)}
122
+ >
123
+ <ThumbsUpIcon />
124
+ </button>
125
+ <button
126
+ type="button"
127
+ className="showcase-feedback__btn"
128
+ tabIndex={-1}
129
+ title="Thumbs down"
130
+ onClick={() => submit(SHOWCASE_FEEDBACK_VOTE.DOWN)}
131
+ >
132
+ <ThumbsDownIcon />
133
+ </button>
134
+ </div>
135
+ );
136
+ };
@@ -62,6 +62,8 @@ import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js';
62
62
  import { Explorer } from '../editor/side-bar/Explorer.js';
63
63
  import { PanelGroup } from '../editor/panel-group/PanelGroup.js';
64
64
  import { QueryDataCubeViewer } from '@finos/legend-query-builder';
65
+ import { ShowcaseFeedback } from './ShowcaseFeedback.js';
66
+ import { SHOWCASE_FEEDBACK_SURFACE } from '../../__lib__/LegendStudioTelemetryHelper.js';
65
67
 
66
68
  const ShowcaseViewerStatusBar = observer(() => {
67
69
  const editorStore = useEditorStore();
@@ -109,6 +111,14 @@ const ShowcaseViewerStatusBar = observer(() => {
109
111
  </div>
110
112
  </div>
111
113
  )}
114
+ {showcase && (
115
+ <ShowcaseFeedback
116
+ className="showcase-feedback--status-bar"
117
+ showcasePath={showcaseStore.showcase.path}
118
+ title={showcase.title}
119
+ surface={SHOWCASE_FEEDBACK_SURFACE.DEEP_LINK_VIEWER}
120
+ />
121
+ )}
112
122
  </div>
113
123
  <div className="editor__status-bar__right">
114
124
  <button
@@ -80,6 +80,7 @@ import {
80
80
  import { Project } from '@finos/legend-server-sdlc';
81
81
  import { WorkspaceSetupStore } from '../../stores/workspace-setup/WorkspaceSetupStore.js';
82
82
  import { openShowcaseManager } from '../../stores/ShowcaseManagerState.js';
83
+ import { SHOWCASE_MANAGER_ENTRY_POINT } from '../../__lib__/LegendStudioTelemetryHelper.js';
83
84
 
84
85
  const WorkspaceSetupStoreContext = createContext<
85
86
  WorkspaceSetupStore | undefined
@@ -151,7 +152,11 @@ export const ShowcaseCard: React.FC<{ hideDocumentation?: boolean }> = (
151
152
  <OpenIcon />
152
153
  </div>
153
154
  ),
154
- action: () => openShowcaseManager(applicationStore),
155
+ action: () =>
156
+ openShowcaseManager(
157
+ applicationStore,
158
+ SHOWCASE_MANAGER_ENTRY_POINT.WORKSPACE_SETUP,
159
+ ),
155
160
  },
156
161
  ]}
157
162
  isStable={true}
@@ -159,6 +159,7 @@ export class LegendStudioBaseStore {
159
159
  serverUrl: this.applicationStore.config.sdlcServerUrl,
160
160
  baseHeaders: this.applicationStore.config.sdlcServerBaseHeaders,
161
161
  client: this.applicationStore.config.sdlcServerClient,
162
+ useCookieAuthOnly: this.applicationStore.config.sdlcUseCookieAuthOnly,
162
163
  // On a mid-session 401, auto-launch the popup re-auth flow. The
163
164
  // handler enforces a one-attempt-per-episode guard to prevent endless
164
165
  // retry loops; when it gives up, the StatusBar manual button remains