@finos/legend-application-query 13.8.7 → 13.8.9

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 (70) hide show
  1. package/lib/__lib__/LegendQueryNavigation.d.ts +16 -2
  2. package/lib/__lib__/LegendQueryNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendQueryNavigation.js +21 -2
  4. package/lib/__lib__/LegendQueryNavigation.js.map +1 -1
  5. package/lib/components/Core_LegendQueryApplicationPlugin.d.ts +2 -1
  6. package/lib/components/Core_LegendQueryApplicationPlugin.d.ts.map +1 -1
  7. package/lib/components/Core_LegendQueryApplicationPlugin.js +13 -0
  8. package/lib/components/Core_LegendQueryApplicationPlugin.js.map +1 -1
  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/__test-utils__/QueryEditorComponentTestUtils.d.ts +14 -2
  13. package/lib/components/__test-utils__/QueryEditorComponentTestUtils.d.ts.map +1 -1
  14. package/lib/components/__test-utils__/QueryEditorComponentTestUtils.js +93 -7
  15. package/lib/components/__test-utils__/QueryEditorComponentTestUtils.js.map +1 -1
  16. package/lib/components/data-product/DataProductInfo.d.ts.map +1 -1
  17. package/lib/components/data-product/DataProductInfo.js +1 -1
  18. package/lib/components/data-product/DataProductInfo.js.map +1 -1
  19. package/lib/components/data-product/DataProductSampleQueryCreator.d.ts +19 -0
  20. package/lib/components/data-product/DataProductSampleQueryCreator.d.ts.map +1 -0
  21. package/lib/components/data-product/DataProductSampleQueryCreator.js +53 -0
  22. package/lib/components/data-product/DataProductSampleQueryCreator.js.map +1 -0
  23. package/lib/components/data-product/DataProductSampleQueryPanel.d.ts +23 -0
  24. package/lib/components/data-product/DataProductSampleQueryPanel.d.ts.map +1 -0
  25. package/lib/components/data-product/DataProductSampleQueryPanel.js +95 -0
  26. package/lib/components/data-product/DataProductSampleQueryPanel.js.map +1 -0
  27. package/lib/index.css +1 -1
  28. package/lib/light-mode.css +1 -1
  29. package/lib/package.json +1 -1
  30. package/lib/stores/BaseTemplateQueryCreatorStore.d.ts +68 -0
  31. package/lib/stores/BaseTemplateQueryCreatorStore.d.ts.map +1 -0
  32. package/lib/stores/BaseTemplateQueryCreatorStore.js +120 -0
  33. package/lib/stores/BaseTemplateQueryCreatorStore.js.map +1 -0
  34. package/lib/stores/QueryEditorStore.d.ts +1 -1
  35. package/lib/stores/QueryEditorStore.d.ts.map +1 -1
  36. package/lib/stores/QueryEditorStore.js +43 -29
  37. package/lib/stores/QueryEditorStore.js.map +1 -1
  38. package/lib/stores/data-product/DataProductSampleQueryCreatorStore.d.ts +37 -0
  39. package/lib/stores/data-product/DataProductSampleQueryCreatorStore.d.ts.map +1 -0
  40. package/lib/stores/data-product/DataProductSampleQueryCreatorStore.js +60 -0
  41. package/lib/stores/data-product/DataProductSampleQueryCreatorStore.js.map +1 -0
  42. package/lib/stores/data-product/query-builder/DataProductArtifactHelper.d.ts.map +1 -1
  43. package/lib/stores/data-product/query-builder/DataProductArtifactHelper.js +9 -8
  44. package/lib/stores/data-product/query-builder/DataProductArtifactHelper.js.map +1 -1
  45. package/lib/stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.d.ts +12 -2
  46. package/lib/stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.d.ts.map +1 -1
  47. package/lib/stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.js +22 -2
  48. package/lib/stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.js.map +1 -1
  49. package/lib/stores/data-space/DataProductQueryCreatorStore.js +1 -1
  50. package/lib/stores/data-space/DataProductQueryCreatorStore.js.map +1 -1
  51. package/lib/stores/data-space/DataSpaceTemplateQueryCreatorStore.d.ts +14 -18
  52. package/lib/stores/data-space/DataSpaceTemplateQueryCreatorStore.d.ts.map +1 -1
  53. package/lib/stores/data-space/DataSpaceTemplateQueryCreatorStore.js +15 -80
  54. package/lib/stores/data-space/DataSpaceTemplateQueryCreatorStore.js.map +1 -1
  55. package/package.json +13 -13
  56. package/src/__lib__/LegendQueryNavigation.ts +43 -5
  57. package/src/components/Core_LegendQueryApplicationPlugin.tsx +17 -0
  58. package/src/components/LegendQueryWebApplication.tsx +5 -0
  59. package/src/components/__test-utils__/QueryEditorComponentTestUtils.tsx +237 -5
  60. package/src/components/data-product/DataProductInfo.tsx +12 -10
  61. package/src/components/data-product/DataProductSampleQueryCreator.tsx +114 -0
  62. package/src/components/data-product/DataProductSampleQueryPanel.tsx +219 -0
  63. package/src/stores/BaseTemplateQueryCreatorStore.ts +203 -0
  64. package/src/stores/QueryEditorStore.ts +64 -44
  65. package/src/stores/data-product/DataProductSampleQueryCreatorStore.ts +135 -0
  66. package/src/stores/data-product/query-builder/DataProductArtifactHelper.ts +12 -8
  67. package/src/stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.ts +36 -4
  68. package/src/stores/data-space/DataProductQueryCreatorStore.ts +1 -1
  69. package/src/stores/data-space/DataSpaceTemplateQueryCreatorStore.ts +31 -126
  70. package/tsconfig.json +4 -0
@@ -42,6 +42,7 @@ import {
42
42
  QueryDataProductNativeExecutionContextInfo,
43
43
  ModelAccessPointGroup,
44
44
  V1_PureGraphManager,
45
+ type V1_DataProductArtifact,
45
46
  } from '@finos/legend-graph';
46
47
  import { DepotServerClient } from '@finos/legend-server-depot';
47
48
  import {
@@ -76,6 +77,7 @@ import {
76
77
  } from '@finos/legend-application/browser';
77
78
  import {
78
79
  generateExistingQueryEditorRoute,
80
+ generateDataProductSampleQueryRoute,
79
81
  LEGEND_QUERY_ROUTE_PATTERN,
80
82
  } from '../../__lib__/LegendQueryNavigation.js';
81
83
  import {
@@ -90,6 +92,8 @@ import {
90
92
  import { LegendQueryDataProductQueryBuilderState } from '../../stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.js';
91
93
  import { DataProductSelectorState } from '../../stores/data-space/DataProductSelectorState.js';
92
94
  import { DataSpaceTemplateQueryCreatorStore } from '../../stores/data-space/DataSpaceTemplateQueryCreatorStore.js';
95
+ import { DataProductSampleQueryCreatorStore } from '../../stores/data-product/DataProductSampleQueryCreatorStore.js';
96
+ import { DataProductSampleQueryCreator } from '../data-product/DataProductSampleQueryCreator.js';
93
97
 
94
98
  const TEST_QUERY_ID = 'test-query-id';
95
99
  const TEST_GROUP_ID = 'test-group';
@@ -97,6 +101,8 @@ const TEST_ARTIFACT_ID = 'test-artifact';
97
101
  const TEST_VERSION_ID = 'test-version';
98
102
  const TEST_TEMPLATE_QUERY_ID = 'templateQuery';
99
103
  const TEST_DATA_SPACE_PATH = 'domain::COVIDDatapace';
104
+ const TEST_SAMPLE_QUERY_ID = 'sampleQuery';
105
+ const TEST_DATA_PRODUCT_PATH = 'test::MyDataProduct';
100
106
  export const TEST_QUERY_NAME = 'MyTestQuery';
101
107
 
102
108
  export const TEST__provideMockedQueryEditorStore = (customization?: {
@@ -775,13 +781,15 @@ export const TEST__setUpDataProductExistingQueryEditor = async (
775
781
  async () => {
776
782
  /* no-op for tests */
777
783
  },
784
+ async () => {
785
+ throw new Error('Not implemented in tests');
786
+ },
778
787
  new DataProductSelectorState(
779
788
  MOCK__editorStore.depotServerClient,
780
789
  MOCK__editorStore.applicationStore,
781
790
  ),
782
791
  undefined,
783
792
  undefined,
784
- undefined,
785
793
  MOCK__editorStore.applicationStore.config.options.queryBuilderConfig,
786
794
  {
787
795
  groupId: 'test.group',
@@ -790,7 +798,11 @@ export const TEST__setUpDataProductExistingQueryEditor = async (
790
798
  dataProduct: dataProductPath,
791
799
  },
792
800
  );
793
- mockQueryBuilderState.initWithDataProduct(dataProduct, executionState);
801
+ mockQueryBuilderState.initWithDataProduct(
802
+ dataProduct,
803
+ undefined,
804
+ executionState,
805
+ );
794
806
 
795
807
  MOCK__editorStore.buildGraph = createMock();
796
808
  MOCK__editorStore.buildFullGraph = createMock();
@@ -841,6 +853,7 @@ export const TEST__setUpDataProductNativeExistingQueryEditor = async (
841
853
  executionKey: string,
842
854
  lambda: RawLambda,
843
855
  entities: PlainObject<Entity>[],
856
+ artifact?: V1_DataProductArtifact | undefined,
844
857
  ): Promise<{
845
858
  renderResult: RenderResult;
846
859
  queryBuilderState: QueryBuilderState;
@@ -970,20 +983,22 @@ export const TEST__setUpDataProductNativeExistingQueryEditor = async (
970
983
  QueryBuilderDataBrowserWorkflow.INSTANCE,
971
984
  new QueryBuilderActionConfig_QueryApplication(MOCK__editorStore),
972
985
  dataProduct,
973
- undefined,
986
+ artifact,
974
987
  executionState,
975
988
  MOCK__editorStore.depotServerClient,
976
989
  { groupId: 'test.group', artifactId: 'test-artifact', versionId: '0.0.0' },
977
990
  async () => {
978
991
  /* no-op for tests */
979
992
  },
993
+ async () => {
994
+ throw new Error('Not implemented in tests');
995
+ },
980
996
  new DataProductSelectorState(
981
997
  MOCK__editorStore.depotServerClient,
982
998
  MOCK__editorStore.applicationStore,
983
999
  ),
984
1000
  undefined,
985
1001
  undefined,
986
- undefined,
987
1002
  MOCK__editorStore.applicationStore.config.options.queryBuilderConfig,
988
1003
  {
989
1004
  groupId: 'test.group',
@@ -992,7 +1007,11 @@ export const TEST__setUpDataProductNativeExistingQueryEditor = async (
992
1007
  dataProduct: dataProductPath,
993
1008
  },
994
1009
  );
995
- mockQueryBuilderState.initWithDataProduct(dataProduct, executionState);
1010
+ mockQueryBuilderState.initWithDataProduct(
1011
+ dataProduct,
1012
+ undefined,
1013
+ executionState,
1014
+ );
996
1015
 
997
1016
  MOCK__editorStore.buildGraph = createMock();
998
1017
  MOCK__editorStore.buildFullGraph = createMock();
@@ -1036,3 +1055,216 @@ export const TEST__setUpDataProductNativeExistingQueryEditor = async (
1036
1055
  ),
1037
1056
  };
1038
1057
  };
1058
+
1059
+ export const TEST__provideMockedDataProductSampleQueryCreatorStore =
1060
+ (customization?: {
1061
+ mock?: DataProductSampleQueryCreatorStore;
1062
+ applicationStore?: LegendQueryApplicationStore;
1063
+ pluginManager?: LegendQueryPluginManager;
1064
+ extraPlugins?: AbstractPlugin[];
1065
+ extraPresets?: AbstractPreset[];
1066
+ }): DataProductSampleQueryCreatorStore => {
1067
+ const pluginManager =
1068
+ customization?.pluginManager ?? LegendQueryPluginManager.create();
1069
+ pluginManager
1070
+ .usePlugins([
1071
+ new Core_LegendQueryApplicationPlugin(),
1072
+ ...(customization?.extraPlugins ?? []),
1073
+ ])
1074
+ .usePresets([...(customization?.extraPresets ?? [])])
1075
+ .install();
1076
+ const applicationStore =
1077
+ customization?.applicationStore ??
1078
+ new ApplicationStore(
1079
+ TEST__getTestLegendQueryApplicationConfig(),
1080
+ pluginManager,
1081
+ );
1082
+ const depotServerClient = new DepotServerClient({
1083
+ serverUrl: applicationStore.config.depotServerUrl,
1084
+ });
1085
+ depotServerClient.setTracerService(applicationStore.tracerService);
1086
+ const value =
1087
+ customization?.mock ??
1088
+ new DataProductSampleQueryCreatorStore(
1089
+ applicationStore,
1090
+ depotServerClient,
1091
+ TEST_GROUP_ID,
1092
+ TEST_ARTIFACT_ID,
1093
+ TEST_VERSION_ID,
1094
+ TEST_DATA_PRODUCT_PATH,
1095
+ TEST_SAMPLE_QUERY_ID,
1096
+ { fips: 'value' },
1097
+ );
1098
+ const MOCK__QueryEditorStoreProvider = require('../QueryEditorStoreProvider.js'); // eslint-disable-line @typescript-eslint/no-require-imports,@typescript-eslint/no-unsafe-assignment
1099
+ MOCK__QueryEditorStoreProvider.useQueryEditorStore = createMock();
1100
+ MOCK__QueryEditorStoreProvider.useQueryEditorStore.mockReturnValue(value);
1101
+ return value;
1102
+ };
1103
+
1104
+ export const TEST__setUpDataProductSampleQueryEditor = async (
1105
+ MOCK__editorStore: DataProductSampleQueryCreatorStore,
1106
+ dataProductPath: string,
1107
+ executionKey: string,
1108
+ lambda: RawLambda,
1109
+ entities: PlainObject<Entity>[],
1110
+ artifact: V1_DataProductArtifact,
1111
+ ): Promise<{
1112
+ renderResult: RenderResult;
1113
+ queryBuilderState: QueryBuilderState;
1114
+ }> => {
1115
+ const projectData = {
1116
+ id: 'test-id',
1117
+ groupId: MOCK__editorStore.groupId,
1118
+ artifactId: MOCK__editorStore.artifactId,
1119
+ projectId: 'test-project-id',
1120
+ versions: [MOCK__editorStore.versionId],
1121
+ latestVersion: MOCK__editorStore.versionId,
1122
+ };
1123
+
1124
+ const graphManagerState = MOCK__editorStore.graphManagerState;
1125
+
1126
+ await graphManagerState.graphManager.initialize({
1127
+ env: 'test',
1128
+ tabSize: 2,
1129
+ clientConfig: {},
1130
+ });
1131
+ await graphManagerState.initializeSystem();
1132
+ await graphManagerState.graphManager.buildGraph(
1133
+ graphManagerState.graph,
1134
+ entities as unknown as Entity[],
1135
+ graphManagerState.graphBuildState,
1136
+ );
1137
+
1138
+ const dataProduct = graphManagerState.graph.getDataProduct(dataProductPath);
1139
+ const nativeAccess = guaranteeNonNullable(
1140
+ dataProduct.nativeModelAccess,
1141
+ `Data product '${dataProductPath}' has no native model access`,
1142
+ );
1143
+ const executionState = guaranteeNonNullable(
1144
+ nativeAccess.nativeModelExecutionContexts.find(
1145
+ (ctx) => ctx.key === executionKey,
1146
+ ),
1147
+ `Can't find native execution context '${executionKey}'`,
1148
+ );
1149
+
1150
+ createSpy(
1151
+ MOCK__editorStore.depotServerClient,
1152
+ 'getProject',
1153
+ ).mockResolvedValue(projectData);
1154
+ createSpy(
1155
+ MOCK__editorStore.depotServerClient,
1156
+ 'getEntities',
1157
+ ).mockResolvedValue(entities);
1158
+ createSpy(
1159
+ MOCK__editorStore.depotServerClient,
1160
+ 'getEntitiesByClassifier',
1161
+ ).mockResolvedValue([]);
1162
+ createSpy(
1163
+ MOCK__editorStore.depotServerClient,
1164
+ 'getEntitiesSummaryByClassifier',
1165
+ ).mockResolvedValue([]);
1166
+ createSpy(
1167
+ MOCK__editorStore.depotServerClient,
1168
+ 'getVersions',
1169
+ ).mockResolvedValue([projectData.latestVersion]);
1170
+ createSpy(
1171
+ graphManagerState.graphManager,
1172
+ 'pureCodeToLambda',
1173
+ ).mockResolvedValue(new RawLambda(lambda.parameters, lambda.body));
1174
+ createSpy(
1175
+ graphManagerState.graphManager,
1176
+ 'analyzeMappingModelCoverage',
1177
+ ).mockResolvedValue(
1178
+ graphManagerState.graphManager.buildMappingModelCoverageAnalysisResult(
1179
+ { mappedEntities: [] },
1180
+ executionState.mapping.value,
1181
+ ),
1182
+ );
1183
+
1184
+ const mockQueryBuilderState = new LegendQueryDataProductQueryBuilderState(
1185
+ MOCK__editorStore.applicationStore,
1186
+ graphManagerState,
1187
+ QueryBuilderDataBrowserWorkflow.INSTANCE,
1188
+ new QueryBuilderActionConfig_QueryApplication(MOCK__editorStore),
1189
+ dataProduct,
1190
+ artifact,
1191
+ executionState,
1192
+ MOCK__editorStore.depotServerClient,
1193
+ {
1194
+ groupId: MOCK__editorStore.groupId,
1195
+ artifactId: MOCK__editorStore.artifactId,
1196
+ versionId: MOCK__editorStore.versionId,
1197
+ },
1198
+ async () => {
1199
+ /* no-op for tests */
1200
+ },
1201
+ async () => {
1202
+ throw new Error('Not implemented in tests');
1203
+ },
1204
+ new DataProductSelectorState(
1205
+ MOCK__editorStore.depotServerClient,
1206
+ MOCK__editorStore.applicationStore,
1207
+ ),
1208
+ undefined,
1209
+ undefined,
1210
+ MOCK__editorStore.applicationStore.config.options.queryBuilderConfig,
1211
+ {
1212
+ groupId: MOCK__editorStore.groupId,
1213
+ artifactId: MOCK__editorStore.artifactId,
1214
+ versionId: MOCK__editorStore.versionId,
1215
+ dataProduct: dataProductPath,
1216
+ },
1217
+ );
1218
+ mockQueryBuilderState.initWithDataProduct(
1219
+ dataProduct,
1220
+ undefined,
1221
+ executionState,
1222
+ );
1223
+
1224
+ MOCK__editorStore.buildGraph = createMock();
1225
+ MOCK__editorStore.fetchDataProductArtifact = createMock();
1226
+ (
1227
+ MOCK__editorStore.fetchDataProductArtifact as ReturnType<typeof createMock>
1228
+ ).mockResolvedValue(artifact);
1229
+ MOCK__editorStore.buildDataProductQueryBuilderState = createMock();
1230
+ (
1231
+ MOCK__editorStore.buildDataProductQueryBuilderState as ReturnType<
1232
+ typeof createMock
1233
+ >
1234
+ ).mockResolvedValue(mockQueryBuilderState);
1235
+ graphManagerState.graphManager.initialize = createMock();
1236
+
1237
+ const sampleQueryRoute = generateDataProductSampleQueryRoute(
1238
+ MOCK__editorStore.groupId,
1239
+ MOCK__editorStore.artifactId,
1240
+ MOCK__editorStore.versionId,
1241
+ dataProductPath,
1242
+ MOCK__editorStore.templateQueryId,
1243
+ );
1244
+
1245
+ const renderResult = render(
1246
+ <ApplicationStoreProvider store={MOCK__editorStore.applicationStore}>
1247
+ <TEST__BrowserEnvironmentProvider initialEntries={[sampleQueryRoute]}>
1248
+ <LegendQueryFrameworkProvider>
1249
+ <Routes>
1250
+ <Route
1251
+ path={LEGEND_QUERY_ROUTE_PATTERN.DATA_PRODUCT_SAMPLE_QUERY}
1252
+ element={<DataProductSampleQueryCreator />}
1253
+ />
1254
+ </Routes>
1255
+ </LegendQueryFrameworkProvider>
1256
+ </TEST__BrowserEnvironmentProvider>
1257
+ </ApplicationStoreProvider>,
1258
+ );
1259
+ await waitFor(() =>
1260
+ renderResult.getByTestId(QUERY_BUILDER_TEST_ID.QUERY_BUILDER),
1261
+ );
1262
+
1263
+ return {
1264
+ renderResult,
1265
+ queryBuilderState: guaranteeNonNullable(
1266
+ MOCK__editorStore.queryBuilderState,
1267
+ `Query builder state should have been initialized`,
1268
+ ),
1269
+ };
1270
+ };
@@ -211,17 +211,19 @@ export const QueryEditorDataProductInfoModal = observer(
211
211
  </div>
212
212
  </div>
213
213
  )}
214
- <div className="dataspace-info-modal__field">
215
- <div className="dataspace-info-modal__field__label">
216
- Mapping
217
- </div>
218
- <div
219
- className="dataspace-info-modal__field__value dataspace-info-modal__field__value--linkable"
220
- onClick={() => flowResult(visitElement(mapping.path))}
221
- >
222
- {mapping.name}
214
+ {mapping && (
215
+ <div className="dataspace-info-modal__field">
216
+ <div className="dataspace-info-modal__field__label">
217
+ Mapping
218
+ </div>
219
+ <div
220
+ className="dataspace-info-modal__field__value dataspace-info-modal__field__value--linkable"
221
+ onClick={() => flowResult(visitElement(mapping.path))}
222
+ >
223
+ {mapping.name}
224
+ </div>
223
225
  </div>
224
- </div>
226
+ )}
225
227
  {selectedRuntime &&
226
228
  selectedRuntime.runtimeValue instanceof LakehouseRuntime && (
227
229
  <>
@@ -0,0 +1,114 @@
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, useLocalObservable } from 'mobx-react-lite';
18
+ import { useParams } from '@finos/legend-application/browser';
19
+ import { parseGAVCoordinates } from '@finos/legend-storage';
20
+ import {
21
+ useLegendQueryApplicationStore,
22
+ useLegendQueryBaseStore,
23
+ } from '../LegendQueryFrameworkProvider.js';
24
+ import { DataProductSampleQueryCreatorStore } from '../../stores/data-product/DataProductSampleQueryCreatorStore.js';
25
+ import { QueryEditorStoreContext } from '../QueryEditorStoreProvider.js';
26
+ import {
27
+ DATA_PRODUCT_SAMPLE_QUERY_CREATOR_ROUTE_PATTERN_TOKEN,
28
+ generateDataProductSampleQueryRoute,
29
+ type DataProductSampleQueryPathParams,
30
+ } from '../../__lib__/LegendQueryNavigation.js';
31
+ import { QueryEditor } from '../QueryEditor.js';
32
+ import { guaranteeNonNullable } from '@finos/legend-shared';
33
+ import { useApplicationStore } from '@finos/legend-application';
34
+ import { extractQueryParams } from '../utils/QueryParameterUtils.js';
35
+ import { useEffect } from 'react';
36
+
37
+ const DataProductSampleQueryCreatorStoreProvider: React.FC<{
38
+ children: React.ReactNode;
39
+ gav: string;
40
+ dataProductPath: string;
41
+ sampleQueryId: string;
42
+ params: Record<string, string> | undefined;
43
+ }> = ({ children, gav, dataProductPath, sampleQueryId, params }) => {
44
+ const { groupId, artifactId, versionId } = parseGAVCoordinates(gav);
45
+ const applicationStore = useLegendQueryApplicationStore();
46
+ const baseStore = useLegendQueryBaseStore();
47
+ const store = useLocalObservable(
48
+ () =>
49
+ new DataProductSampleQueryCreatorStore(
50
+ applicationStore,
51
+ baseStore.depotServerClient,
52
+ groupId,
53
+ artifactId,
54
+ versionId,
55
+ dataProductPath,
56
+ sampleQueryId,
57
+ params,
58
+ ),
59
+ );
60
+ return (
61
+ <QueryEditorStoreContext.Provider value={store}>
62
+ {children}
63
+ </QueryEditorStoreContext.Provider>
64
+ );
65
+ };
66
+
67
+ export const DataProductSampleQueryCreator = observer(() => {
68
+ const applicationStore = useApplicationStore();
69
+ const parameters = useParams<DataProductSampleQueryPathParams>();
70
+ const gav = guaranteeNonNullable(
71
+ parameters[DATA_PRODUCT_SAMPLE_QUERY_CREATOR_ROUTE_PATTERN_TOKEN.GAV],
72
+ );
73
+ const dataProductPath = guaranteeNonNullable(
74
+ parameters[
75
+ DATA_PRODUCT_SAMPLE_QUERY_CREATOR_ROUTE_PATTERN_TOKEN.DATA_PRODUCT_PATH
76
+ ],
77
+ );
78
+ const sampleQueryId = guaranteeNonNullable(
79
+ parameters[
80
+ DATA_PRODUCT_SAMPLE_QUERY_CREATOR_ROUTE_PATTERN_TOKEN.SAMPLE_QUERY_ID
81
+ ],
82
+ );
83
+
84
+ const queryParams =
85
+ applicationStore.navigationService.navigator.getCurrentLocationParameters();
86
+ const processed = extractQueryParams(queryParams);
87
+
88
+ useEffect(() => {
89
+ // clear params from URL after extracting
90
+ if (processed && Object.keys(processed).length) {
91
+ const { groupId, artifactId, versionId } = parseGAVCoordinates(gav);
92
+ applicationStore.navigationService.navigator.updateCurrentLocation(
93
+ generateDataProductSampleQueryRoute(
94
+ groupId,
95
+ artifactId,
96
+ versionId,
97
+ dataProductPath,
98
+ sampleQueryId,
99
+ ),
100
+ );
101
+ }
102
+ }, [applicationStore, gav, dataProductPath, sampleQueryId, processed]);
103
+
104
+ return (
105
+ <DataProductSampleQueryCreatorStoreProvider
106
+ gav={gav}
107
+ dataProductPath={dataProductPath}
108
+ sampleQueryId={sampleQueryId}
109
+ params={processed}
110
+ >
111
+ <QueryEditor />
112
+ </DataProductSampleQueryCreatorStoreProvider>
113
+ );
114
+ });
@@ -0,0 +1,219 @@
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 {
18
+ PanelHeader,
19
+ BasePopover,
20
+ ClickAwayListener,
21
+ ShareIcon,
22
+ TagIcon,
23
+ } from '@finos/legend-art';
24
+ import { observer } from 'mobx-react-lite';
25
+ import { useRef, useState } from 'react';
26
+ import {
27
+ ActionAlertActionType,
28
+ ActionAlertType,
29
+ useApplicationStore,
30
+ } from '@finos/legend-application';
31
+ import type { V1_SampleQuery } from '@finos/legend-graph';
32
+ import { flowResult } from 'mobx';
33
+ import type { LegendQueryDataProductQueryBuilderState } from '../../stores/data-product/query-builder/LegendQueryDataProductQueryBuilderState.js';
34
+ import { generateDataProductSampleQueryRoute } from '../../__lib__/LegendQueryNavigation.js';
35
+
36
+ const DataProductSampleQueryDialog = observer(
37
+ (props: {
38
+ triggerElement: HTMLElement | null;
39
+ queryBuilderState: LegendQueryDataProductQueryBuilderState;
40
+ sampleQueries: V1_SampleQuery[];
41
+ onClose: () => void;
42
+ }) => {
43
+ const { triggerElement, queryBuilderState, sampleQueries, onClose } = props;
44
+ const applicationStore = useApplicationStore();
45
+
46
+ const loadSampleQuery = async (
47
+ sampleQuery: V1_SampleQuery,
48
+ ): Promise<void> => {
49
+ const query =
50
+ await queryBuilderState.graphManagerState.graphManager.pureCodeToLambda(
51
+ sampleQuery.info.query,
52
+ );
53
+ queryBuilderState.initializeWithQuery(query);
54
+ onClose();
55
+ };
56
+
57
+ const loadQuery = async (sampleQuery: V1_SampleQuery): Promise<void> => {
58
+ if (queryBuilderState.changeDetectionState.hasChanged) {
59
+ applicationStore.alertService.setActionAlertInfo({
60
+ message:
61
+ 'Unsaved changes will be lost if you continue. Do you still want to proceed?',
62
+ type: ActionAlertType.CAUTION,
63
+ actions: [
64
+ {
65
+ label: 'Proceed',
66
+ type: ActionAlertActionType.PROCEED_WITH_CAUTION,
67
+ handler: (): void => {
68
+ flowResult(loadSampleQuery(sampleQuery));
69
+ },
70
+ },
71
+ {
72
+ label: 'Abort',
73
+ type: ActionAlertActionType.PROCEED,
74
+ default: true,
75
+ },
76
+ ],
77
+ });
78
+ } else {
79
+ flowResult(loadSampleQuery(sampleQuery));
80
+ }
81
+ };
82
+
83
+ const visitSampleQuery = (sampleQuery: V1_SampleQuery): void => {
84
+ const id = sampleQuery.info.id;
85
+ if (!id) {
86
+ applicationStore.notificationService.notifyWarning(
87
+ 'Sample query does not have an ID and cannot be visited via URL',
88
+ );
89
+ return;
90
+ }
91
+ const project = queryBuilderState.project;
92
+ applicationStore.navigationService.navigator.visitAddress(
93
+ applicationStore.navigationService.navigator.generateAddress(
94
+ generateDataProductSampleQueryRoute(
95
+ project.groupId,
96
+ project.artifactId,
97
+ project.versionId,
98
+ queryBuilderState.dataProduct.path,
99
+ id,
100
+ ),
101
+ ),
102
+ );
103
+ };
104
+
105
+ return (
106
+ <ClickAwayListener onClickAway={onClose}>
107
+ <div>
108
+ <BasePopover
109
+ open={true}
110
+ slotProps={{
111
+ paper: {
112
+ classes: {
113
+ root: '"query-builder__data-space__template-query-panel__container__root',
114
+ },
115
+ },
116
+ }}
117
+ className="query-builder__data-space__template-query-panel__container"
118
+ onClose={onClose}
119
+ anchorEl={triggerElement}
120
+ anchorOrigin={{
121
+ vertical: 'bottom',
122
+ horizontal: 'left',
123
+ }}
124
+ transformOrigin={{
125
+ vertical: 'top',
126
+ horizontal: 'center',
127
+ }}
128
+ >
129
+ <div className="query-builder__data-space__template-query-panel">
130
+ <div className="query-builder__data-space__template-query-panel__header">
131
+ Sample Queries
132
+ </div>
133
+ {sampleQueries.map((sampleQuery) => (
134
+ <div
135
+ key={sampleQuery.info.id ?? sampleQuery.title}
136
+ className="query-builder__data-space__template-query-panel__query"
137
+ >
138
+ <TagIcon className="query-builder__data-space__template-query-panel__query__icon" />
139
+ <button
140
+ className="query-builder__data-space__template-query-panel__query__entry"
141
+ title="click to load sample query"
142
+ onClick={() => {
143
+ flowResult(loadQuery(sampleQuery));
144
+ }}
145
+ >
146
+ <div className="query-builder__data-space__template-query-panel__query__entry__content">
147
+ <div className="query-builder__data-space__template-query-panel__query__entry__content__title">
148
+ {sampleQuery.title}
149
+ </div>
150
+ {sampleQuery.description && (
151
+ <div className="query-builder__data-space__template-query-panel__query__entry__content__description">
152
+ {sampleQuery.description}
153
+ </div>
154
+ )}
155
+ </div>
156
+ </button>
157
+ <button
158
+ className="query-builder__data-space__template-query-panel__query__share"
159
+ title="Visit..."
160
+ disabled={!sampleQuery.info.id}
161
+ onClick={() => visitSampleQuery(sampleQuery)}
162
+ >
163
+ <ShareIcon />
164
+ <div className="query-builder__data-space__template-query-panel__query__share__label">
165
+ Visit
166
+ </div>
167
+ </button>
168
+ </div>
169
+ ))}
170
+ </div>
171
+ </BasePopover>
172
+ </div>
173
+ </ClickAwayListener>
174
+ );
175
+ },
176
+ );
177
+
178
+ export const DataProductSampleQueryPanel = observer(
179
+ (props: { queryBuilderState: LegendQueryDataProductQueryBuilderState }) => {
180
+ const { queryBuilderState } = props;
181
+ const templateQueryButtonRef = useRef<HTMLButtonElement>(null);
182
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
183
+
184
+ const sampleQueries = queryBuilderState.isNativeMode
185
+ ? (queryBuilderState.dataProductArtifact?.nativeModelAccess
186
+ ?.sampleQueries ?? [])
187
+ : [];
188
+
189
+ if (sampleQueries.length === 0) {
190
+ return null;
191
+ }
192
+
193
+ return (
194
+ <PanelHeader className="query-builder__data-space__template-query">
195
+ <button
196
+ className="query-builder__data-space__template-query__btn"
197
+ ref={templateQueryButtonRef}
198
+ onClick={() => setIsDialogOpen(true)}
199
+ >
200
+ Sample Queries ( {sampleQueries.length} )
201
+ </button>
202
+ {isDialogOpen && (
203
+ <DataProductSampleQueryDialog
204
+ triggerElement={templateQueryButtonRef.current}
205
+ queryBuilderState={queryBuilderState}
206
+ sampleQueries={sampleQueries}
207
+ onClose={() => setIsDialogOpen(false)}
208
+ />
209
+ )}
210
+ </PanelHeader>
211
+ );
212
+ },
213
+ );
214
+
215
+ export const renderDataProductSampleQueryPanelContent = (
216
+ queryBuilderState: LegendQueryDataProductQueryBuilderState,
217
+ ): React.ReactNode => (
218
+ <DataProductSampleQueryPanel queryBuilderState={queryBuilderState} />
219
+ );