@finos/legend-application-studio 28.21.46 → 28.21.47

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 (122) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +8 -0
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +13 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +170 -6
  6. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioTelemetryHelper.js +116 -1
  8. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  9. package/lib/components/editor/editor-group/LegendAISuggestTelemetryHooks.d.ts +30 -0
  10. package/lib/components/editor/editor-group/LegendAISuggestTelemetryHooks.d.ts.map +1 -0
  11. package/lib/components/editor/editor-group/LegendAISuggestTelemetryHooks.js +43 -0
  12. package/lib/components/editor/editor-group/LegendAISuggestTelemetryHooks.js.map +1 -0
  13. package/lib/components/editor/editor-group/RuntimeEditor.d.ts +4 -3
  14. package/lib/components/editor/editor-group/RuntimeEditor.d.ts.map +1 -1
  15. package/lib/components/editor/editor-group/RuntimeEditor.js +90 -36
  16. package/lib/components/editor/editor-group/RuntimeEditor.js.map +1 -1
  17. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  18. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +154 -35
  19. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  20. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.d.ts.map +1 -1
  21. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.js +4 -6
  22. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.js.map +1 -1
  23. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  24. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +31 -7
  25. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  26. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.d.ts.map +1 -1
  27. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.js +4 -8
  28. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.js.map +1 -1
  29. package/lib/index.css +1 -1
  30. package/lib/index.d.ts +3 -1
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +3 -1
  33. package/lib/index.js.map +1 -1
  34. package/lib/package.json +1 -1
  35. package/lib/stores/editor/EditorStore.d.ts +2 -0
  36. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  37. package/lib/stores/editor/EditorStore.js +2 -0
  38. package/lib/stores/editor/EditorStore.js.map +1 -1
  39. package/lib/stores/editor/GraphEditFormModeState.d.ts.map +1 -1
  40. package/lib/stores/editor/GraphEditFormModeState.js +21 -10
  41. package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
  42. package/lib/stores/editor/LegendAISuggestTelemetry.d.ts +143 -0
  43. package/lib/stores/editor/LegendAISuggestTelemetry.d.ts.map +1 -0
  44. package/lib/stores/editor/LegendAISuggestTelemetry.js +376 -0
  45. package/lib/stores/editor/LegendAISuggestTelemetry.js.map +1 -0
  46. package/lib/stores/editor/LegendSQLStudioPlaygroundState.js +3 -3
  47. package/lib/stores/editor/LegendSQLStudioPlaygroundState.js.map +1 -1
  48. package/lib/stores/editor/editor-state/GraphGenerationState.d.ts +14 -3
  49. package/lib/stores/editor/editor-state/GraphGenerationState.d.ts.map +1 -1
  50. package/lib/stores/editor/editor-state/GraphGenerationState.js +33 -21
  51. package/lib/stores/editor/editor-state/GraphGenerationState.js.map +1 -1
  52. package/lib/stores/editor/editor-state/element-editor-state/RuntimeEditorState.d.ts +35 -6
  53. package/lib/stores/editor/editor-state/element-editor-state/RuntimeEditorState.d.ts.map +1 -1
  54. package/lib/stores/editor/editor-state/element-editor-state/RuntimeEditorState.js +74 -29
  55. package/lib/stores/editor/editor-state/element-editor-state/RuntimeEditorState.js.map +1 -1
  56. package/lib/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.d.ts +1 -0
  57. package/lib/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.d.ts.map +1 -1
  58. package/lib/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.js +5 -5
  59. package/lib/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.js.map +1 -1
  60. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +12 -0
  61. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  62. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +60 -14
  63. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  64. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +2 -2
  65. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  66. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.d.ts.map +1 -1
  67. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js +34 -15
  68. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js.map +1 -1
  69. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.d.ts +1 -1
  70. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.d.ts.map +1 -1
  71. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js +3 -11
  72. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.js.map +1 -1
  73. package/lib/stores/editor/sidebar-state/BulkServiceRegistrationState.d.ts.map +1 -1
  74. package/lib/stores/editor/sidebar-state/BulkServiceRegistrationState.js +30 -2
  75. package/lib/stores/editor/sidebar-state/BulkServiceRegistrationState.js.map +1 -1
  76. package/lib/stores/editor/sidebar-state/LocalChangesState.d.ts.map +1 -1
  77. package/lib/stores/editor/sidebar-state/LocalChangesState.js +1 -0
  78. package/lib/stores/editor/sidebar-state/LocalChangesState.js.map +1 -1
  79. package/lib/stores/editor/sidebar-state/WorkflowManagerState.d.ts +1 -1
  80. package/lib/stores/editor/sidebar-state/WorkflowManagerState.d.ts.map +1 -1
  81. package/lib/stores/editor/sidebar-state/WorkflowManagerState.js +2 -12
  82. package/lib/stores/editor/sidebar-state/WorkflowManagerState.js.map +1 -1
  83. package/lib/stores/editor/utils/MockDataUtils.d.ts +12 -2
  84. package/lib/stores/editor/utils/MockDataUtils.d.ts.map +1 -1
  85. package/lib/stores/editor/utils/MockDataUtils.js +28 -28
  86. package/lib/stores/editor/utils/MockDataUtils.js.map +1 -1
  87. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +2 -0
  88. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
  89. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +13 -0
  90. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
  91. package/lib/stores/graph-modifier/DSL_LakehouseRuntime_GraphModifierHelper.d.ts +20 -2
  92. package/lib/stores/graph-modifier/DSL_LakehouseRuntime_GraphModifierHelper.d.ts.map +1 -1
  93. package/lib/stores/graph-modifier/DSL_LakehouseRuntime_GraphModifierHelper.js +38 -4
  94. package/lib/stores/graph-modifier/DSL_LakehouseRuntime_GraphModifierHelper.js.map +1 -1
  95. package/package.json +9 -9
  96. package/src/__lib__/LegendStudioEvent.ts +14 -0
  97. package/src/__lib__/LegendStudioTelemetryHelper.ts +277 -4
  98. package/src/components/editor/editor-group/LegendAISuggestTelemetryHooks.ts +57 -0
  99. package/src/components/editor/editor-group/RuntimeEditor.tsx +282 -132
  100. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +293 -106
  101. package/src/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.ts +3 -13
  102. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +41 -23
  103. package/src/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.tsx +5 -11
  104. package/src/index.ts +13 -0
  105. package/src/stores/editor/EditorStore.ts +3 -0
  106. package/src/stores/editor/GraphEditFormModeState.ts +31 -15
  107. package/src/stores/editor/LegendAISuggestTelemetry.ts +592 -0
  108. package/src/stores/editor/LegendSQLStudioPlaygroundState.ts +3 -3
  109. package/src/stores/editor/editor-state/GraphGenerationState.ts +48 -42
  110. package/src/stores/editor/editor-state/element-editor-state/RuntimeEditorState.ts +92 -31
  111. package/src/stores/editor/editor-state/element-editor-state/connection/DatabaseBuilderState.ts +6 -6
  112. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +75 -17
  113. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +2 -2
  114. package/src/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.ts +41 -23
  115. package/src/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestDataState.ts +12 -21
  116. package/src/stores/editor/sidebar-state/BulkServiceRegistrationState.ts +49 -6
  117. package/src/stores/editor/sidebar-state/LocalChangesState.ts +4 -0
  118. package/src/stores/editor/sidebar-state/WorkflowManagerState.ts +3 -12
  119. package/src/stores/editor/utils/MockDataUtils.ts +33 -32
  120. package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +18 -0
  121. package/src/stores/graph-modifier/DSL_LakehouseRuntime_GraphModifierHelper.ts +62 -7
  122. package/tsconfig.json +2 -0
@@ -0,0 +1,592 @@
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 { NetworkClientError, uuid } from '@finos/legend-shared';
18
+ import { EntityChangeType, type EntityChange } from '@finos/legend-server-sdlc';
19
+ import type { TelemetryService } from '@finos/legend-application';
20
+ import type { LegendSourceInfo } from '@finos/legend-storage';
21
+ import {
22
+ LEGENDAI_SUGGEST_ABANDON_PHASE,
23
+ LEGENDAI_SUGGEST_ERROR_KIND,
24
+ LEGENDAI_SUGGEST_RETENTION,
25
+ LEGENDAI_SUGGEST_STAGE,
26
+ LEGENDAI_SUGGEST_SURFACE,
27
+ LegendStudioTelemetryHelper,
28
+ type LegendAISuggestDataProductApplyData,
29
+ type LegendAISuggestIdentity,
30
+ type LegendAISuggestTarget,
31
+ } from '../../__lib__/LegendStudioTelemetryHelper.js';
32
+ import type { EditorStore } from './EditorStore.js';
33
+
34
+ /**
35
+ * Edit ratios are computed with a full Levenshtein pass; skip it for texts
36
+ * longer than this so a push never pays for a quadratic diff on huge docs.
37
+ */
38
+ const MAX_EDIT_RATIO_TEXT_LENGTH = 2000;
39
+
40
+ const isNonEmptyText = (text: string | undefined): text is string =>
41
+ Boolean(text?.trim());
42
+
43
+ /**
44
+ * Normalized Levenshtein distance between two strings (0 = identical,
45
+ * 1 = completely different). Returns `undefined` when either string is too
46
+ * long to diff cheaply.
47
+ */
48
+ export const computeLegendAISuggestEditRatio = (
49
+ before: string,
50
+ after: string,
51
+ ): number | undefined => {
52
+ if (
53
+ before.length > MAX_EDIT_RATIO_TEXT_LENGTH ||
54
+ after.length > MAX_EDIT_RATIO_TEXT_LENGTH
55
+ ) {
56
+ return undefined;
57
+ }
58
+ const maxLength = Math.max(before.length, after.length);
59
+ if (maxLength === 0) {
60
+ return 0;
61
+ }
62
+ let previousRow = Array.from({ length: after.length + 1 }, (_, i) => i);
63
+ for (let i = 1; i <= before.length; i++) {
64
+ const currentRow = [i];
65
+ for (let j = 1; j <= after.length; j++) {
66
+ const substitutionCost = before[i - 1] === after[j - 1] ? 0 : 1;
67
+ currentRow[j] = Math.min(
68
+ (previousRow[j] as number) + 1,
69
+ (currentRow[j - 1] as number) + 1,
70
+ (previousRow[j - 1] as number) + substitutionCost,
71
+ );
72
+ }
73
+ previousRow = currentRow;
74
+ }
75
+ return (previousRow[after.length] as number) / maxLength;
76
+ };
77
+
78
+ export const classifyLegendAISuggestError = (
79
+ error: Error,
80
+ stage: LEGENDAI_SUGGEST_STAGE,
81
+ ): { errorKind: LEGENDAI_SUGGEST_ERROR_KIND; httpStatus?: number } => {
82
+ if (stage === LEGENDAI_SUGGEST_STAGE.SERIALIZE) {
83
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.SERIALIZATION };
84
+ }
85
+ if (error instanceof NetworkClientError) {
86
+ const httpStatus = error.response.status;
87
+ if (httpStatus === 401 || httpStatus === 403) {
88
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.ENTITLEMENT, httpStatus };
89
+ }
90
+ if (httpStatus >= 500) {
91
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.SERVER, httpStatus };
92
+ }
93
+ if (httpStatus >= 400) {
94
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.CLIENT, httpStatus };
95
+ }
96
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.OTHER, httpStatus };
97
+ }
98
+ return { errorKind: LEGENDAI_SUGGEST_ERROR_KIND.OTHER };
99
+ };
100
+
101
+ /**
102
+ * Reads the current value of the text an applied suggestion wrote, looking the
103
+ * element up by path in the current graph (element instances do not survive
104
+ * graph rebuilds, so readers must not close over them). Returns
105
+ * `{ found: false }` when the target no longer exists.
106
+ */
107
+ export type LegendAIAppliedTextReader = (
108
+ editorStore: EditorStore,
109
+ ) => { found: false } | { found: true; text: string | undefined };
110
+
111
+ type LegendAIAppliedSuggestion = {
112
+ identity: LegendAISuggestIdentity;
113
+ appliedText: string;
114
+ appliedAt: number;
115
+ readCurrentText: LegendAIAppliedTextReader;
116
+ supersededApplyCount: number;
117
+ };
118
+
119
+ const getTargetKey = (target: LegendAISuggestTarget): string =>
120
+ [target.surface, target.elementPath, target.accessPointGroupId ?? ''].join(
121
+ '\u0000',
122
+ );
123
+
124
+ /**
125
+ * Remembers suggestions applied in this editor session until the element they
126
+ * touched is pushed, then reports whether the applied text survived
127
+ * (`editor.legendai-suggest.persisted`). Only the latest apply per target is
128
+ * kept; earlier ones are counted in `supersededApplyCount`.
129
+ *
130
+ * NOTE: tracking is keyed by element path, so an element renamed after the
131
+ * apply is not reported.
132
+ */
133
+ export class LegendAIAppliedSuggestionRegistry {
134
+ private readonly appliedSuggestions = new Map<
135
+ string,
136
+ LegendAIAppliedSuggestion
137
+ >();
138
+
139
+ get size(): number {
140
+ return this.appliedSuggestions.size;
141
+ }
142
+
143
+ register(
144
+ suggestion: Omit<LegendAIAppliedSuggestion, 'supersededApplyCount'>,
145
+ ): void {
146
+ const key = getTargetKey(suggestion.identity);
147
+ const previous = this.appliedSuggestions.get(key);
148
+ this.appliedSuggestions.set(key, {
149
+ ...suggestion,
150
+ supersededApplyCount: previous ? previous.supersededApplyCount + 1 : 0,
151
+ });
152
+ }
153
+
154
+ reportPushedChanges(
155
+ editorStore: EditorStore,
156
+ pushedChanges: EntityChange[],
157
+ ): void {
158
+ if (!this.appliedSuggestions.size) {
159
+ return;
160
+ }
161
+ const changeTypeByPath = new Map(
162
+ pushedChanges.map((change) => [change.entityPath, change.type]),
163
+ );
164
+ const now = Date.now();
165
+ Array.from(this.appliedSuggestions.entries()).forEach(
166
+ ([key, suggestion]) => {
167
+ const changeType = changeTypeByPath.get(
168
+ suggestion.identity.elementPath,
169
+ );
170
+ if (changeType === undefined) {
171
+ // not part of this push, keep waiting
172
+ return;
173
+ }
174
+ this.appliedSuggestions.delete(key);
175
+ let retention: LEGENDAI_SUGGEST_RETENTION;
176
+ let editRatio: number | undefined;
177
+ const current =
178
+ changeType === EntityChangeType.DELETE
179
+ ? ({ found: false } as const)
180
+ : suggestion.readCurrentText(editorStore);
181
+ if (!current.found) {
182
+ retention = LEGENDAI_SUGGEST_RETENTION.ELEMENT_REMOVED;
183
+ } else if (!isNonEmptyText(current.text)) {
184
+ retention = LEGENDAI_SUGGEST_RETENTION.CLEARED;
185
+ } else if (current.text === suggestion.appliedText) {
186
+ retention = LEGENDAI_SUGGEST_RETENTION.UNCHANGED;
187
+ } else {
188
+ retention = LEGENDAI_SUGGEST_RETENTION.EDITED;
189
+ editRatio = computeLegendAISuggestEditRatio(
190
+ suggestion.appliedText,
191
+ current.text,
192
+ );
193
+ }
194
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestPersisted(
195
+ editorStore.applicationStore.telemetryService,
196
+ editorStore.editorMode.getSourceInfo(),
197
+ {
198
+ ...suggestion.identity,
199
+ retention,
200
+ editRatio,
201
+ msSinceApply: now - suggestion.appliedAt,
202
+ supersededApplyCount: suggestion.supersededApplyCount,
203
+ },
204
+ );
205
+ },
206
+ );
207
+ }
208
+ }
209
+
210
+ /**
211
+ * A single suggestion request, returned by
212
+ * {@link LegendAISuggestTelemetryTracker.launch} and passed back when the
213
+ * request settles so late responses are still attributed correctly.
214
+ */
215
+ export type LegendAISuggestRequest = {
216
+ readonly suggestionId: string;
217
+ readonly attempt: number;
218
+ readonly launchedAt: number;
219
+ readonly textAtLaunch: string | undefined;
220
+ };
221
+
222
+ type ShownSuggestion = {
223
+ request: LegendAISuggestRequest;
224
+ shownAt: number;
225
+ };
226
+
227
+ /**
228
+ * Drives the `editor.legendai-suggest.*` lifecycle for one editor instance
229
+ * (one service editor, one dataspace home tab, one data product access point
230
+ * group) and keeps the legacy per-surface events firing alongside it.
231
+ *
232
+ * Lifecycle: `launch` → `succeed` | `fail` → `apply` | `discard`, with
233
+ * `dispose` reporting an `abandon` when the editor goes away while a request
234
+ * is pending or a suggestion is on screen.
235
+ */
236
+ export class LegendAISuggestTelemetryTracker {
237
+ private readonly editorStore: EditorStore;
238
+ private target: LegendAISuggestTarget;
239
+ private attemptCount = 0;
240
+ private pending: LegendAISuggestRequest | undefined;
241
+ private shown: ShownSuggestion | undefined;
242
+
243
+ constructor(editorStore: EditorStore, target: LegendAISuggestTarget) {
244
+ this.editorStore = editorStore;
245
+ this.target = target;
246
+ }
247
+
248
+ /**
249
+ * Keeps the target fresh (e.g. after an element or group rename) without
250
+ * resetting the attempt count.
251
+ */
252
+ setTarget(target: LegendAISuggestTarget): void {
253
+ this.target = target;
254
+ }
255
+
256
+ private get telemetryService(): TelemetryService {
257
+ return this.editorStore.applicationStore.telemetryService;
258
+ }
259
+
260
+ private get sourceInfo(): LegendSourceInfo | undefined {
261
+ return this.editorStore.editorMode.getSourceInfo();
262
+ }
263
+
264
+ private identity(request: LegendAISuggestRequest): LegendAISuggestIdentity {
265
+ return {
266
+ ...this.target,
267
+ suggestionId: request.suggestionId,
268
+ attempt: request.attempt,
269
+ };
270
+ }
271
+
272
+ reportExposure(data: { available: boolean; isReadOnly: boolean }): void {
273
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestExposure(
274
+ this.telemetryService,
275
+ this.sourceInfo,
276
+ { ...this.target, ...data },
277
+ );
278
+ }
279
+
280
+ launch(data: {
281
+ existingText: string | undefined;
282
+ accessPointCount?: number | undefined;
283
+ }): LegendAISuggestRequest {
284
+ this.attemptCount += 1;
285
+ const request: LegendAISuggestRequest = {
286
+ suggestionId: uuid(),
287
+ attempt: this.attemptCount,
288
+ launchedAt: Date.now(),
289
+ textAtLaunch: data.existingText,
290
+ };
291
+ this.pending = request;
292
+ this.shown = undefined;
293
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestLaunched(
294
+ this.telemetryService,
295
+ this.sourceInfo,
296
+ {
297
+ ...this.identity(request),
298
+ hadExistingText: isNonEmptyText(data.existingText),
299
+ existingLength: data.existingText?.length ?? 0,
300
+ accessPointCount: data.accessPointCount,
301
+ },
302
+ );
303
+ this.logLegacyEvent('launch');
304
+ return request;
305
+ }
306
+
307
+ /**
308
+ * Records a LegendAI response. An empty suggestion is reported as a
309
+ * `failure` with `errorKind: empty-response` since nothing is shown.
310
+ *
311
+ * @returns whether the suggestion is non-empty and should be shown
312
+ */
313
+ succeed(
314
+ request: LegendAISuggestRequest,
315
+ data: {
316
+ suggestionText: string | undefined;
317
+ definitionsLength: number;
318
+ currentText: string | undefined;
319
+ confidence?: number | undefined;
320
+ accessPointSuggestionCount?: number | undefined;
321
+ },
322
+ ): boolean {
323
+ const isCurrent = this.pending === request;
324
+ if (isCurrent) {
325
+ this.pending = undefined;
326
+ }
327
+ const durationMs = Date.now() - request.launchedAt;
328
+ if (!isNonEmptyText(data.suggestionText)) {
329
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestFailure(
330
+ this.telemetryService,
331
+ this.sourceInfo,
332
+ {
333
+ ...this.identity(request),
334
+ durationMs,
335
+ stage: LEGENDAI_SUGGEST_STAGE.REQUEST,
336
+ errorKind: LEGENDAI_SUGGEST_ERROR_KIND.EMPTY_RESPONSE,
337
+ errorMessage: 'LegendAI returned an empty suggestion',
338
+ },
339
+ );
340
+ return false;
341
+ }
342
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestSucceeded(
343
+ this.telemetryService,
344
+ this.sourceInfo,
345
+ {
346
+ ...this.identity(request),
347
+ durationMs,
348
+ definitionsLength: data.definitionsLength,
349
+ suggestionLength: data.suggestionText.length,
350
+ confidence: data.confidence,
351
+ editedWhilePending: data.currentText !== request.textAtLaunch,
352
+ accessPointSuggestionCount: data.accessPointSuggestionCount,
353
+ },
354
+ );
355
+ if (isCurrent) {
356
+ this.shown = { request, shownAt: Date.now() };
357
+ }
358
+ return true;
359
+ }
360
+
361
+ /**
362
+ * Must be called before the caller decorates `error.message` (e.g. with the
363
+ * entitlement documentation link) so the raw server message is reported.
364
+ */
365
+ fail(
366
+ request: LegendAISuggestRequest,
367
+ error: Error,
368
+ stage: LEGENDAI_SUGGEST_STAGE,
369
+ ): void {
370
+ if (this.pending === request) {
371
+ this.pending = undefined;
372
+ }
373
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestFailure(
374
+ this.telemetryService,
375
+ this.sourceInfo,
376
+ {
377
+ ...this.identity(request),
378
+ durationMs: Date.now() - request.launchedAt,
379
+ stage,
380
+ ...classifyLegendAISuggestError(error, stage),
381
+ errorMessage: error.message,
382
+ },
383
+ );
384
+ this.logLegacyEvent('failure', error.message);
385
+ }
386
+
387
+ apply(data: {
388
+ existingText: string | undefined;
389
+ appliedText: string;
390
+ readCurrentText: LegendAIAppliedTextReader;
391
+ dataProduct?: LegendAISuggestDataProductApplyData | undefined;
392
+ }): void {
393
+ const shown = this.shown;
394
+ this.shown = undefined;
395
+ this.logLegacyEvent('apply');
396
+ if (!shown) {
397
+ return;
398
+ }
399
+ const identity = this.identity(shown.request);
400
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestApplied(
401
+ this.telemetryService,
402
+ this.sourceInfo,
403
+ {
404
+ ...identity,
405
+ ...data.dataProduct,
406
+ timeToDecisionMs: Date.now() - shown.shownAt,
407
+ hadExistingText: isNonEmptyText(data.existingText),
408
+ existingLength: data.existingText?.length ?? 0,
409
+ suggestionLength: data.appliedText.length,
410
+ },
411
+ );
412
+ if (!data.appliedText) {
413
+ // nothing was actually written, so there is nothing to follow to push
414
+ return;
415
+ }
416
+ this.editorStore.legendAIAppliedSuggestionRegistry.register({
417
+ identity,
418
+ appliedText: data.appliedText,
419
+ appliedAt: Date.now(),
420
+ readCurrentText: data.readCurrentText,
421
+ });
422
+ }
423
+
424
+ discard(): void {
425
+ const shown = this.shown;
426
+ this.shown = undefined;
427
+ this.logLegacyEvent('discard');
428
+ if (!shown) {
429
+ return;
430
+ }
431
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestDiscarded(
432
+ this.telemetryService,
433
+ this.sourceInfo,
434
+ {
435
+ ...this.identity(shown.request),
436
+ timeToDecisionMs: Date.now() - shown.shownAt,
437
+ },
438
+ );
439
+ }
440
+
441
+ /**
442
+ * Call when the hosting editor unmounts. A request that settles afterwards
443
+ * still reports its `success` / `failure`, but is no longer shown.
444
+ */
445
+ dispose(): void {
446
+ const now = Date.now();
447
+ if (this.pending) {
448
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestAbandoned(
449
+ this.telemetryService,
450
+ this.sourceInfo,
451
+ {
452
+ ...this.identity(this.pending),
453
+ phase: LEGENDAI_SUGGEST_ABANDON_PHASE.PENDING,
454
+ elapsedMs: now - this.pending.launchedAt,
455
+ },
456
+ );
457
+ } else if (this.shown) {
458
+ LegendStudioTelemetryHelper.logEvent_LegendAISuggestAbandoned(
459
+ this.telemetryService,
460
+ this.sourceInfo,
461
+ {
462
+ ...this.identity(this.shown.request),
463
+ phase: LEGENDAI_SUGGEST_ABANDON_PHASE.SHOWN,
464
+ elapsedMs: now - this.shown.shownAt,
465
+ },
466
+ );
467
+ }
468
+ this.pending = undefined;
469
+ this.shown = undefined;
470
+ }
471
+
472
+ /**
473
+ * Per-surface events kept for backward compatibility with existing
474
+ * dashboards (`editor.<surface>.legendai-suggest.*`).
475
+ */
476
+ private logLegacyEvent(
477
+ action: 'launch' | 'apply' | 'discard' | 'failure',
478
+ errorMessage?: string,
479
+ ): void {
480
+ const { telemetryService, sourceInfo } = this;
481
+ const path = this.target.elementPath;
482
+ switch (this.target.surface) {
483
+ case LEGENDAI_SUGGEST_SURFACE.SERVICE: {
484
+ switch (action) {
485
+ case 'launch':
486
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestLaunched(
487
+ telemetryService,
488
+ path,
489
+ sourceInfo,
490
+ );
491
+ return;
492
+ case 'apply':
493
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestApplied(
494
+ telemetryService,
495
+ path,
496
+ sourceInfo,
497
+ );
498
+ return;
499
+ case 'discard':
500
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestDiscarded(
501
+ telemetryService,
502
+ path,
503
+ sourceInfo,
504
+ );
505
+ return;
506
+ case 'failure':
507
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestFailure(
508
+ telemetryService,
509
+ path,
510
+ errorMessage ?? '',
511
+ sourceInfo,
512
+ );
513
+ return;
514
+ default:
515
+ return;
516
+ }
517
+ }
518
+ case LEGENDAI_SUGGEST_SURFACE.DATASPACE: {
519
+ switch (action) {
520
+ case 'launch':
521
+ LegendStudioTelemetryHelper.logEvent_DataSpaceLegendAISuggestLaunched(
522
+ telemetryService,
523
+ path,
524
+ sourceInfo,
525
+ );
526
+ return;
527
+ case 'apply':
528
+ LegendStudioTelemetryHelper.logEvent_DataSpaceLegendAISuggestApplied(
529
+ telemetryService,
530
+ path,
531
+ sourceInfo,
532
+ );
533
+ return;
534
+ case 'discard':
535
+ LegendStudioTelemetryHelper.logEvent_DataSpaceLegendAISuggestDiscarded(
536
+ telemetryService,
537
+ path,
538
+ sourceInfo,
539
+ );
540
+ return;
541
+ case 'failure':
542
+ LegendStudioTelemetryHelper.logEvent_DataSpaceLegendAISuggestFailure(
543
+ telemetryService,
544
+ path,
545
+ errorMessage ?? '',
546
+ sourceInfo,
547
+ );
548
+ return;
549
+ default:
550
+ return;
551
+ }
552
+ }
553
+ case LEGENDAI_SUGGEST_SURFACE.DATA_PRODUCT: {
554
+ switch (action) {
555
+ case 'launch':
556
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestLaunched(
557
+ telemetryService,
558
+ path,
559
+ sourceInfo,
560
+ );
561
+ return;
562
+ case 'apply':
563
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestApplied(
564
+ telemetryService,
565
+ path,
566
+ sourceInfo,
567
+ );
568
+ return;
569
+ case 'discard':
570
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestDiscarded(
571
+ telemetryService,
572
+ path,
573
+ sourceInfo,
574
+ );
575
+ return;
576
+ case 'failure':
577
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestFailure(
578
+ telemetryService,
579
+ path,
580
+ errorMessage ?? '',
581
+ sourceInfo,
582
+ );
583
+ return;
584
+ default:
585
+ return;
586
+ }
587
+ }
588
+ default:
589
+ return;
590
+ }
591
+ }
592
+ }
@@ -30,7 +30,7 @@ import {
30
30
  TDSExecutionResult,
31
31
  type RawLambda,
32
32
  type ExecutionResultWithMetadata,
33
- LakehouseRuntime,
33
+ LakehouseBaseRuntime,
34
34
  GRAPH_MANAGER_EVENT,
35
35
  } from '@finos/legend-graph';
36
36
  import type { EditorStore } from './EditorStore.js';
@@ -178,9 +178,9 @@ export class LegendSQLStudioPlaygroundState extends LegendSQLPlaygroundState {
178
178
  );
179
179
  return;
180
180
  }
181
- if (!(packageableRuntime.runtimeValue instanceof LakehouseRuntime)) {
181
+ if (!(packageableRuntime.runtimeValue instanceof LakehouseBaseRuntime)) {
182
182
  this.editorStore.applicationStore.notificationService.notifyError(
183
- new Error('Runtime must be a LakehouseRuntime'),
183
+ new Error('Runtime must be a Lakehouse runtime'),
184
184
  );
185
185
  return;
186
186
  }