@finos/legend-application-query 13.8.23 → 13.8.25

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 (36) hide show
  1. package/lib/__lib__/LegendQueryNavigation.d.ts +1 -0
  2. package/lib/__lib__/LegendQueryNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendQueryNavigation.js +3 -0
  4. package/lib/__lib__/LegendQueryNavigation.js.map +1 -1
  5. package/lib/components/DataSpaceArtifactInspector.d.ts +19 -0
  6. package/lib/components/DataSpaceArtifactInspector.d.ts.map +1 -0
  7. package/lib/components/DataSpaceArtifactInspector.js +866 -0
  8. package/lib/components/DataSpaceArtifactInspector.js.map +1 -0
  9. package/lib/components/LegendQueryWebApplication.d.ts.map +1 -1
  10. package/lib/components/LegendQueryWebApplication.js +2 -1
  11. package/lib/components/LegendQueryWebApplication.js.map +1 -1
  12. package/lib/components/QueryEditor.d.ts.map +1 -1
  13. package/lib/components/QueryEditor.js +48 -10
  14. package/lib/components/QueryEditor.js.map +1 -1
  15. package/lib/components/__test-utils__/QueryEditorComponentTestUtils.d.ts +1 -1
  16. package/lib/components/__test-utils__/QueryEditorComponentTestUtils.d.ts.map +1 -1
  17. package/lib/index.css +2 -2
  18. package/lib/index.css.map +1 -1
  19. package/lib/light-mode.css +2 -2
  20. package/lib/light-mode.css.map +1 -1
  21. package/lib/package.json +1 -1
  22. package/lib/stores/LegendQueryApplicationPlugin.d.ts +29 -4
  23. package/lib/stores/LegendQueryApplicationPlugin.d.ts.map +1 -1
  24. package/lib/stores/LegendQueryApplicationPlugin.js.map +1 -1
  25. package/lib/stores/QueryEditorStore.d.ts.map +1 -1
  26. package/lib/stores/QueryEditorStore.js +1 -1
  27. package/lib/stores/QueryEditorStore.js.map +1 -1
  28. package/package.json +11 -11
  29. package/src/__lib__/LegendQueryNavigation.ts +3 -0
  30. package/src/components/DataSpaceArtifactInspector.tsx +1469 -0
  31. package/src/components/LegendQueryWebApplication.tsx +7 -0
  32. package/src/components/QueryEditor.tsx +69 -23
  33. package/src/components/__test-utils__/QueryEditorComponentTestUtils.tsx +1 -1
  34. package/src/stores/LegendQueryApplicationPlugin.tsx +27 -4
  35. package/src/stores/QueryEditorStore.ts +1 -0
  36. package/tsconfig.json +1 -0
@@ -43,6 +43,7 @@ import { DataSpaceTemplateQueryCreator } from './data-space/DataSpaceTemplateQue
43
43
  import { QueryCreator } from './data-space/DataProductQueryCreator.js';
44
44
  import { DataProductSampleQueryCreator } from './data-product/DataProductSampleQueryCreator.js';
45
45
  import { ExistingQueryDataCubeViewer } from './data-cube/ExistingQueryDataCubeViewer.js';
46
+ import { DataSpaceArtifactInspector } from './DataSpaceArtifactInspector.js';
46
47
  import {
47
48
  AuthProvider,
48
49
  withAuthenticationRequired,
@@ -121,6 +122,12 @@ const LegendQueryWebApplicationRouter = observer(() => {
121
122
  element={<MappingQueryCreator />}
122
123
  />
123
124
 
125
+ {/* Developer-only diagnostic page (artifact size inspector) */}
126
+ <Route
127
+ path={LEGEND_QUERY_ROUTE_PATTERN.DEV_DATA_SPACE_INSPECTOR}
128
+ element={<DataSpaceArtifactInspector />}
129
+ />
130
+
124
131
  {/* LEGACY DATA SPACE */}
125
132
  <Route
126
133
  path={generateExtensionUrlPattern(
@@ -101,6 +101,46 @@ import { QueryEditorDataProductInfoModal } from './data-product/DataProductInfo.
101
101
  import { DataSpaceQueryBuilderState } from '@finos/legend-extension-dsl-data-space/application';
102
102
  import { LegendQueryBareQueryBuilderState } from '../stores/data-space/LegendQueryBareQueryBuilderState.js';
103
103
  import { extractQueryParams } from './utils/QueryParameterUtils.js';
104
+ import type { QueryTitleDescriptionAISuggestionRequest } from '../stores/LegendQueryApplicationPlugin.js';
105
+
106
+ const buildAISuggestionRequest = async (
107
+ queryBuilderState: QueryBuilderState,
108
+ name?: string,
109
+ ): Promise<QueryTitleDescriptionAISuggestionRequest> => {
110
+ const graphManager = queryBuilderState.graphManagerState.graphManager;
111
+ const lambda = queryBuilderState.buildQuery();
112
+ const content = await graphManager.lambdaToPureCode(lambda);
113
+ const request: QueryTitleDescriptionAISuggestionRequest = { content };
114
+ const execContext = queryBuilderState.getQueryExecutionContext();
115
+ if (execContext instanceof QueryDataSpaceExecutionContext) {
116
+ request.executionContext = {
117
+ dataSpacePath: execContext.dataSpacePath,
118
+ executionKey: execContext.executionKey,
119
+ };
120
+ } else if (execContext instanceof QueryExplicitExecutionContext) {
121
+ request.executionContext = {
122
+ mapping: execContext.mapping.value.path,
123
+ runtime: execContext.runtime.value.path,
124
+ };
125
+ }
126
+ const parameterValues = queryBuilderState.getCurrentParameterValues();
127
+ if (parameterValues && parameterValues.size > 0) {
128
+ request.defaultParameterValues = [];
129
+ for (const [paramName, valueSpec] of parameterValues) {
130
+ const serialized = graphManager.serializeValueSpecification(valueSpec);
131
+ const paramContent =
132
+ await graphManager.valueSpecificationToPureCode(serialized);
133
+ request.defaultParameterValues.push({
134
+ name: paramName,
135
+ content: paramContent,
136
+ });
137
+ }
138
+ }
139
+ if (name) {
140
+ request.name = name;
141
+ }
142
+ return request;
143
+ };
104
144
 
105
145
  const CreateQueryDialog = observer(() => {
106
146
  const editorStore = useQueryEditorStore();
@@ -117,8 +157,8 @@ const CreateQueryDialog = observer(() => {
117
157
  const isEmptyName = !createQueryState.queryName;
118
158
  const isDescriptionEmptyOrValid =
119
159
  !description || /[a-zA-Z0-9]/.test(description);
120
- const descriptionInputRef = useRef<HTMLInputElement>(null);
121
- const changeDescription: React.ChangeEventHandler<HTMLInputElement> = (
160
+ const descriptionInputRef = useRef<HTMLTextAreaElement>(null);
161
+ const changeDescription: React.ChangeEventHandler<HTMLTextAreaElement> = (
122
162
  event,
123
163
  ) => {
124
164
  createQueryState.setQueryDescription(event.target.value);
@@ -143,10 +183,10 @@ const CreateQueryDialog = observer(() => {
143
183
  setIsSuggestingWithAI(true);
144
184
  setAISuggestion(undefined);
145
185
  try {
146
- const suggestion = await aiSuggester(
186
+ const request = await buildAISuggestionRequest(
147
187
  editorStore.queryBuilderState,
148
- legendAIUrl,
149
188
  );
189
+ const suggestion = await aiSuggester(request, legendAIUrl);
150
190
  setAISuggestion(suggestion);
151
191
  } finally {
152
192
  setIsSuggestingWithAI(false);
@@ -218,8 +258,7 @@ const CreateQueryDialog = observer(() => {
218
258
  Enter Query Name
219
259
  {aiSuggestion && (
220
260
  <span className="query-editor__ai-suggestion-badge">
221
- <SparkleIcon />
222
- AI Suggestion
261
+ <SparkleIcon /> <span>AI Suggestion</span>
223
262
  </span>
224
263
  )}
225
264
  </div>
@@ -252,11 +291,14 @@ const CreateQueryDialog = observer(() => {
252
291
  <div className="input-section" style={{ marginTop: '1rem' }}>
253
292
  <div className="input-label">Enter Query Description</div>
254
293
  <div className="input--with-validation">
255
- <input
294
+ <textarea
256
295
  ref={descriptionInputRef}
257
- className={clsx('input input--dark', {
258
- 'input--ai-suggested': Boolean(aiSuggestion),
259
- })}
296
+ className={clsx(
297
+ 'input input--dark query-editor__description__textarea',
298
+ {
299
+ 'input--ai-suggested': Boolean(aiSuggestion),
300
+ },
301
+ )}
260
302
  spellCheck={true}
261
303
  value={
262
304
  aiSuggestion
@@ -288,7 +330,7 @@ const CreateQueryDialog = observer(() => {
288
330
  ) : (
289
331
  aiSuggester && (
290
332
  <button
291
- className="btn btn--dark query-editor__ai-suggest-btn"
333
+ className="btn btn--dark modal__footer__btn modal__footer__btn--primary query-editor__ai-suggest-btn"
292
334
  onClick={(): void => {
293
335
  suggestWithAI().catch(applicationStore.alertUnhandledError);
294
336
  }}
@@ -309,7 +351,8 @@ const CreateQueryDialog = observer(() => {
309
351
  createQueryState.editorStore.isPerformingBlockingAction ||
310
352
  Boolean(isExistingQueryName) ||
311
353
  isEmptyName ||
312
- !isDescriptionEmptyOrValid
354
+ !isDescriptionEmptyOrValid ||
355
+ isSuggestingWithAI
313
356
  }
314
357
  onClick={create}
315
358
  />
@@ -431,10 +474,11 @@ const RenameQueryDialog = observer(
431
474
  setIsSuggestingWithAI(true);
432
475
  setAISuggestion(undefined);
433
476
  try {
434
- const suggestion = await aiSuggester(
477
+ const request = await buildAISuggestionRequest(
435
478
  existingEditorStore.queryBuilderState,
436
- legendAIUrl,
479
+ existingEditorStore.lightQuery.name,
437
480
  );
481
+ const suggestion = await aiSuggester(request, legendAIUrl);
438
482
  setAISuggestion(suggestion);
439
483
  } finally {
440
484
  setIsSuggestingWithAI(false);
@@ -463,7 +507,7 @@ const RenameQueryDialog = observer(
463
507
  setQueryRenameName(event.target.value);
464
508
  };
465
509
 
466
- const changeDescription: React.ChangeEventHandler<HTMLInputElement> = (
510
+ const changeDescription: React.ChangeEventHandler<HTMLTextAreaElement> = (
467
511
  event,
468
512
  ) => {
469
513
  setQueryDescription(event.target.value);
@@ -523,8 +567,7 @@ const RenameQueryDialog = observer(
523
567
  Update Query Name
524
568
  {aiSuggestion && (
525
569
  <span className="query-editor__ai-suggestion-badge">
526
- <SparkleIcon />
527
- AI Suggestion
570
+ <SparkleIcon /> <span>AI Suggestion</span>
528
571
  </span>
529
572
  )}
530
573
  </div>
@@ -555,10 +598,13 @@ const RenameQueryDialog = observer(
555
598
  <div className="input-section" style={{ marginTop: '1rem' }}>
556
599
  <div className="input-label">Update Query Description</div>
557
600
  <div className="input--with-validation">
558
- <input
559
- className={clsx('input input--dark', {
560
- 'input--ai-suggested': Boolean(aiSuggestion),
561
- })}
601
+ <textarea
602
+ className={clsx(
603
+ 'input input--dark query-editor__description__textarea',
604
+ {
605
+ 'input--ai-suggested': Boolean(aiSuggestion),
606
+ },
607
+ )}
562
608
  spellCheck={true}
563
609
  value={
564
610
  aiSuggestion ? aiSuggestion.description : queryDescription
@@ -588,7 +634,7 @@ const RenameQueryDialog = observer(
588
634
  ) : (
589
635
  aiSuggester && (
590
636
  <button
591
- className="btn btn--dark query-editor__ai-suggest-btn"
637
+ className="btn btn--dark modal__footer__btn modal__footer__btn--primary query-editor__ai-suggest-btn"
592
638
  onClick={(): void => {
593
639
  suggestWithAI().catch(applicationStore.alertUnhandledError);
594
640
  }}
@@ -598,7 +644,7 @@ const RenameQueryDialog = observer(
598
644
  title="Use AI to suggest name and description based on the current query"
599
645
  >
600
646
  <SparkleIcon />
601
- <span>
647
+ <span style={{ marginLeft: '0.4rem' }}>
602
648
  {isSuggestingWithAI ? 'Suggesting...' : 'Suggest with AI'}
603
649
  </span>
604
650
  </button>
@@ -53,9 +53,9 @@ import { TEST__getTestLegendQueryApplicationConfig } from '../../stores/__test-u
53
53
  import { LegendQueryPluginManager } from '../../application/LegendQueryPluginManager.js';
54
54
  import { ExistingQueryEditor } from '../QueryEditor.js';
55
55
  import type {
56
+ StoredFileGeneration,
56
57
  EntitiesWithOrigin,
57
58
  Entity,
58
- StoredFileGeneration,
59
59
  } from '@finos/legend-storage';
60
60
  import {
61
61
  ExistingQueryEditorStore,
@@ -84,6 +84,29 @@ export type QueryTitleDescriptionSuggestion = {
84
84
  description: string;
85
85
  };
86
86
 
87
+ export type QueryTitleDescriptionAISuggestionRequest = {
88
+ /**
89
+ * Legend Pure expression (the lambda body).
90
+ */
91
+ content: string;
92
+ /**
93
+ * Execution context (e.g. { dataSpacePath, executionKey } or { mapping, runtime }).
94
+ */
95
+ executionContext?: Record<string, string | undefined>;
96
+ /**
97
+ * Default parameter values, each with 'name' and 'content' keys.
98
+ */
99
+ defaultParameterValues?: { name: string; content: string }[];
100
+ /**
101
+ * LLM model override (uses server default if omitted).
102
+ */
103
+ model?: string;
104
+ /**
105
+ * Existing query title. If omitted, the LLM generates one.
106
+ */
107
+ name?: string;
108
+ };
109
+
87
110
  export class LegendQueryApplicationPlugin
88
111
  extends LegendApplicationPlugin
89
112
  implements QueryBuilder_LegendApplicationPlugin_Extension
@@ -105,12 +128,12 @@ export class LegendQueryApplicationPlugin
105
128
 
106
129
  /**
107
130
  * Get a function that uses AI to suggest a title and description for the current query.
108
- * The function receives the current query builder state (which contains the query lambda,
109
- * execution context, etc.) and should return a promise that resolves to the suggested
110
- * name and description. Return undefined to indicate no AI suggestion capability.
131
+ * The function receives the request payload (Pure expression, execution context,
132
+ * parameter values, etc.) and the Legend AI URL, and should return a promise that
133
+ * resolves to the suggested name and description.
111
134
  */
112
135
  getExtraQueryTitleDescriptionAISuggester?(): (
113
- queryBuilderState: QueryBuilderState,
136
+ request: QueryTitleDescriptionAISuggestionRequest,
114
137
  legendAIUrl: string,
115
138
  ) => Promise<QueryTitleDescriptionSuggestion>;
116
139
  }
@@ -803,6 +803,7 @@ export abstract class QueryEditorStore {
803
803
  retrieveDataspaceArtifactsCache(
804
804
  project,
805
805
  versionId,
806
+ dataSpacePath,
806
807
  this.depotServerClient,
807
808
  ),
808
809
  undefined,
package/tsconfig.json CHANGED
@@ -92,6 +92,7 @@
92
92
  "./src/components/CloneQueryServiceSetup.tsx",
93
93
  "./src/components/Core_LegendQueryApplicationPlugin.tsx",
94
94
  "./src/components/CreateMappingQuerySetup.tsx",
95
+ "./src/components/DataSpaceArtifactInspector.tsx",
95
96
  "./src/components/EditExistingQuerySetup.tsx",
96
97
  "./src/components/LegendQueryAppInfo.tsx",
97
98
  "./src/components/LegendQueryFrameworkProvider.tsx",