@finos/legend-application-data-cube 0.3.2 → 0.3.4

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 (103) hide show
  1. package/lib/__lib__/LegendDataCubeNavigation.d.ts +2 -0
  2. package/lib/__lib__/LegendDataCubeNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendDataCubeNavigation.js +2 -0
  4. package/lib/__lib__/LegendDataCubeNavigation.js.map +1 -1
  5. package/lib/application/LegendDataCubeApplicationConfig.d.ts +4 -0
  6. package/lib/application/LegendDataCubeApplicationConfig.d.ts.map +1 -1
  7. package/lib/application/LegendDataCubeApplicationConfig.js +5 -0
  8. package/lib/application/LegendDataCubeApplicationConfig.js.map +1 -1
  9. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts +18 -0
  10. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts.map +1 -0
  11. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js +48 -0
  12. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js.map +1 -0
  13. package/lib/components/LegendDataCubeBlockingWindow.d.ts +2 -1
  14. package/lib/components/LegendDataCubeBlockingWindow.d.ts.map +1 -1
  15. package/lib/components/LegendDataCubeBlockingWindow.js +9 -3
  16. package/lib/components/LegendDataCubeBlockingWindow.js.map +1 -1
  17. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts +42 -0
  18. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts.map +1 -0
  19. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js +104 -0
  20. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js.map +1 -0
  21. package/lib/components/builder/LegendDataCubeBuilder.d.ts +5 -0
  22. package/lib/components/builder/LegendDataCubeBuilder.d.ts.map +1 -1
  23. package/lib/components/builder/LegendDataCubeBuilder.js +26 -5
  24. package/lib/components/builder/LegendDataCubeBuilder.js.map +1 -1
  25. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.d.ts.map +1 -1
  26. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js +2 -1
  27. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js.map +1 -1
  28. package/lib/components/builder/LegendDataCubeSourceViewer.d.ts.map +1 -1
  29. package/lib/components/builder/LegendDataCubeSourceViewer.js +61 -1
  30. package/lib/components/builder/LegendDataCubeSourceViewer.js.map +1 -1
  31. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts +25 -0
  32. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts.map +1 -0
  33. package/lib/components/builder/source/LegendDataCubeSourceLoader.js +47 -0
  34. package/lib/components/builder/source/LegendDataCubeSourceLoader.js.map +1 -0
  35. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.d.ts.map +1 -1
  36. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js +1 -2
  37. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js.map +1 -1
  38. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts +22 -0
  39. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts.map +1 -0
  40. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js +28 -0
  41. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js.map +1 -0
  42. package/lib/components/builder/source/UserDefinedFunctionDataCubeSourceBuilder.d.ts.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/package.json +7 -5
  46. package/lib/stores/LegendDataCubeBaseStore.d.ts +2 -1
  47. package/lib/stores/LegendDataCubeBaseStore.d.ts.map +1 -1
  48. package/lib/stores/LegendDataCubeBaseStore.js +6 -3
  49. package/lib/stores/LegendDataCubeBaseStore.js.map +1 -1
  50. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +8 -5
  51. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
  52. package/lib/stores/LegendDataCubeDataCubeEngine.js +132 -114
  53. package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
  54. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +11 -4
  55. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
  56. package/lib/stores/LegendDataCubeDuckDBEngine.js +92 -20
  57. package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
  58. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts +7 -0
  59. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts.map +1 -1
  60. package/lib/stores/builder/LegendDataCubeBuilderStore.js +68 -17
  61. package/lib/stores/builder/LegendDataCubeBuilderStore.js.map +1 -1
  62. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts +39 -0
  63. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts.map +1 -0
  64. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js +66 -0
  65. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js.map +1 -0
  66. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts +3 -2
  67. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts.map +1 -1
  68. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js +7 -10
  69. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js.map +1 -1
  70. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts +45 -0
  71. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts.map +1 -0
  72. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js +142 -0
  73. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js.map +1 -0
  74. package/lib/stores/model/LegendQueryDataCubeSource.d.ts +2 -1
  75. package/lib/stores/model/LegendQueryDataCubeSource.d.ts.map +1 -1
  76. package/lib/stores/model/LegendQueryDataCubeSource.js +1 -0
  77. package/lib/stores/model/LegendQueryDataCubeSource.js.map +1 -1
  78. package/lib/stores/model/LocalFileDataCubeSource.d.ts +3 -8
  79. package/lib/stores/model/LocalFileDataCubeSource.d.ts.map +1 -1
  80. package/lib/stores/model/LocalFileDataCubeSource.js +5 -15
  81. package/lib/stores/model/LocalFileDataCubeSource.js.map +1 -1
  82. package/package.json +17 -15
  83. package/src/__lib__/LegendDataCubeNavigation.ts +21 -0
  84. package/src/application/LegendDataCubeApplicationConfig.ts +10 -0
  85. package/src/application/__test-utils__/LegendDataCubeApplicationTestUtils.ts +52 -0
  86. package/src/components/LegendDataCubeBlockingWindow.tsx +9 -2
  87. package/src/components/__test-utils__/LegendDataCubeStoreTestUtils.tsx +231 -0
  88. package/src/components/builder/LegendDataCubeBuilder.tsx +51 -6
  89. package/src/components/builder/LegendDataCubeBuilderStoreProvider.tsx +2 -0
  90. package/src/components/builder/LegendDataCubeSourceViewer.tsx +171 -1
  91. package/src/components/builder/source/LegendDataCubeSourceLoader.tsx +111 -0
  92. package/src/components/builder/source/LocalFileDataCubeSourceBuilder.tsx +1 -2
  93. package/src/components/builder/source/LocalFileDataCubeSourceLoader.tsx +58 -0
  94. package/src/stores/LegendDataCubeBaseStore.ts +13 -6
  95. package/src/stores/LegendDataCubeDataCubeEngine.ts +167 -131
  96. package/src/stores/LegendDataCubeDuckDBEngine.ts +110 -20
  97. package/src/stores/builder/LegendDataCubeBuilderStore.tsx +114 -23
  98. package/src/stores/builder/source/LegendDataCubeSourceLoaderState.tsx +104 -0
  99. package/src/stores/builder/source/LocalFileDataCubeSourceBuilderState.ts +9 -14
  100. package/src/stores/builder/source/LocalFileDataCubeSourceLoaderState.ts +232 -0
  101. package/src/stores/model/LegendQueryDataCubeSource.ts +2 -0
  102. package/src/stores/model/LocalFileDataCubeSource.ts +6 -15
  103. package/tsconfig.json +7 -1
@@ -27,6 +27,10 @@ import { useRef } from 'react';
27
27
  export const LegendDataCubeBlockingWindow = observer(
28
28
  (props: { windowState: LegendDataCubeBlockingWindowState }) => {
29
29
  const { windowState } = props;
30
+ const onClose = () => {
31
+ windowState.onClose?.();
32
+ windowState.close();
33
+ };
30
34
  const ref = useRef<HTMLDivElement>(null);
31
35
 
32
36
  // set the width and height of the dialog to make sure content overflow works properly
@@ -45,7 +49,7 @@ export const LegendDataCubeBlockingWindow = observer(
45
49
  return (
46
50
  <Dialog
47
51
  open={windowState.isOpen}
48
- onClose={() => windowState.close()}
52
+ onClose={onClose}
49
53
  slotProps={{
50
54
  transition: {
51
55
  onEnter: handleEnter,
@@ -72,7 +76,7 @@ export const LegendDataCubeBlockingWindow = observer(
72
76
  <div className="px-2">{windowState.configuration.title}</div>
73
77
  <button
74
78
  className="flex h-[23px] w-6 items-center justify-center hover:bg-red-500 hover:text-white"
75
- onClick={() => windowState.close()}
79
+ onClick={onClose}
76
80
  >
77
81
  <DataCubeIcon.X />
78
82
  </button>
@@ -91,11 +95,13 @@ export const LegendDataCubeBlockingWindow = observer(
91
95
  export class LegendDataCubeBlockingWindowState {
92
96
  isOpen = false;
93
97
  readonly configuration: LayoutConfiguration;
98
+ readonly onClose?: (() => void) | undefined;
94
99
 
95
100
  constructor(
96
101
  title: string,
97
102
  contentRenderer: (config: LayoutConfiguration) => React.ReactNode,
98
103
  windowConfiguration?: WindowConfiguration | undefined,
104
+ closeModal?: (() => void) | undefined,
99
105
  ) {
100
106
  makeObservable(this, {
101
107
  isOpen: observable,
@@ -108,6 +114,7 @@ export class LegendDataCubeBlockingWindowState {
108
114
  if (windowConfiguration) {
109
115
  this.configuration.window = windowConfiguration;
110
116
  }
117
+ this.onClose = closeModal;
111
118
  }
112
119
 
113
120
  open() {
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Copyright (c) 2020-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 { type RenderResult, render, waitFor } from '@testing-library/react';
18
+ import {
19
+ type AbstractPlugin,
20
+ type AbstractPreset,
21
+ type PlainObject,
22
+ } from '@finos/legend-shared';
23
+ import { createMock, createSpy } from '@finos/legend-shared/test';
24
+ import {
25
+ ApplicationStore,
26
+ ApplicationStoreProvider,
27
+ } from '@finos/legend-application';
28
+ import { LegendDataCubePluginManager } from '../../application/LegendDataCubePluginManager.js';
29
+ import { Core_LegendDataCubeApplicationPlugin } from '../../application/Core_LegendDataCubeApplicationPlugin.js';
30
+ import { TEST__getTestLegendDataCubeApplicationConfig } from '../../application/__test-utils__/LegendDataCubeApplicationTestUtils.js';
31
+ import {
32
+ type LegendDataCubeBuilderState,
33
+ LegendDataCubeBuilderStore,
34
+ } from '../../stores/builder/LegendDataCubeBuilderStore.js';
35
+ import {
36
+ type LegendDataCubeApplicationStore,
37
+ LegendDataCubeBaseStore,
38
+ } from '../../stores/LegendDataCubeBaseStore.js';
39
+ import { TEST__BrowserEnvironmentProvider } from '@finos/legend-application/test';
40
+ import { LegendDataCubeFrameworkProvider } from '../LegendDataCubeFrameworkProvider.js';
41
+ import { Route, Routes } from '@finos/legend-application/browser';
42
+ import { LEGEND_DATA_CUBE_ROUTE_PATTERN } from '../../__lib__/LegendDataCubeNavigation.js';
43
+ import { LegendDataCubeBuilder } from '../builder/LegendDataCubeBuilder.js';
44
+ import { LEGEND_DATACUBE_TEST_ID } from '@finos/legend-data-cube';
45
+ import { Core_LegendDataCube_LegendApplicationPlugin } from '../../application/Core_LegendDataCube_LegendApplicationPlugin.js';
46
+ import {
47
+ type PersistentDataCube,
48
+ type V1_LambdaReturnTypeInput,
49
+ type V1_Query,
50
+ type V1_RawLambda,
51
+ type V1_ValueSpecification,
52
+ V1_entitiesToPureModelContextData,
53
+ V1_ExecuteInput,
54
+ V1_PureModelContextData,
55
+ V1_serializePureModelContext,
56
+ } from '@finos/legend-graph';
57
+ import { DSL_DataSpace_GraphManagerPreset } from '@finos/legend-extension-dsl-data-space/graph';
58
+ import {
59
+ ENGINE_TEST_SUPPORT__execute,
60
+ ENGINE_TEST_SUPPORT__getLambdaRelationType,
61
+ ENGINE_TEST_SUPPORT__grammarToJSON_lambda,
62
+ ENGINE_TEST_SUPPORT__grammarToJSON_valueSpecification,
63
+ ENGINE_TEST_SUPPORT__JSONToGrammar_valueSpecification,
64
+ } from '@finos/legend-graph/test';
65
+ import type { Entity } from '@finos/legend-storage';
66
+
67
+ export const TEST__provideMockedLegendDataCubeBaseStore =
68
+ async (customization?: {
69
+ mock?: LegendDataCubeBaseStore | undefined;
70
+ applicationStore?: LegendDataCubeApplicationStore | undefined;
71
+ pluginManager?: LegendDataCubePluginManager | undefined;
72
+ extraPlugins?: AbstractPlugin[] | undefined;
73
+ extraPresets?: AbstractPreset[] | undefined;
74
+ }): Promise<LegendDataCubeBaseStore> => {
75
+ const pluginManager =
76
+ customization?.pluginManager ?? LegendDataCubePluginManager.create();
77
+ pluginManager
78
+ .usePlugins([
79
+ new Core_LegendDataCube_LegendApplicationPlugin(),
80
+ new Core_LegendDataCubeApplicationPlugin(),
81
+ ...(customization?.extraPlugins ?? []),
82
+ ])
83
+ .usePresets([
84
+ new DSL_DataSpace_GraphManagerPreset(),
85
+ ...(customization?.extraPresets ?? []),
86
+ ])
87
+ .install();
88
+ const applicationStore =
89
+ customization?.applicationStore ??
90
+ new ApplicationStore(
91
+ TEST__getTestLegendDataCubeApplicationConfig(),
92
+ pluginManager,
93
+ );
94
+ const value =
95
+ customization?.mock ?? new LegendDataCubeBaseStore(applicationStore);
96
+ await value.initialize();
97
+ const MOCK__LegendDataCubeBaseStoreProvider = require('../LegendDataCubeFrameworkProvider.js'); // eslint-disable-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
98
+ MOCK__LegendDataCubeBaseStoreProvider.useLegendDataCubeBaseStore =
99
+ createMock();
100
+ MOCK__LegendDataCubeBaseStoreProvider.useLegendDataCubeBaseStore.mockReturnValue(
101
+ value,
102
+ );
103
+ return value;
104
+ };
105
+
106
+ export const TEST__provideMockedLegendDataCubeBuilderStore =
107
+ async (customization?: {
108
+ mock?: LegendDataCubeBuilderStore;
109
+ mockBaseStore?: LegendDataCubeBaseStore;
110
+ applicationStore?: LegendDataCubeApplicationStore;
111
+ pluginManager?: LegendDataCubePluginManager;
112
+ extraPlugins?: AbstractPlugin[];
113
+ extraPresets?: AbstractPreset[];
114
+ }): Promise<LegendDataCubeBuilderStore> => {
115
+ const value =
116
+ customization?.mock ??
117
+ new LegendDataCubeBuilderStore(
118
+ await TEST__provideMockedLegendDataCubeBaseStore({
119
+ mock: customization?.mockBaseStore,
120
+ applicationStore: customization?.applicationStore,
121
+ pluginManager: customization?.pluginManager,
122
+ extraPlugins: customization?.extraPlugins,
123
+ extraPresets: customization?.extraPresets,
124
+ }),
125
+ );
126
+ const MOCK__LegendDataCubeBuilderStoreProvider = require('../builder/LegendDataCubeBuilderStoreProvider.js'); // eslint-disable-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
127
+ MOCK__LegendDataCubeBuilderStoreProvider.useLegendDataCubeBuilderStore =
128
+ createMock();
129
+ MOCK__LegendDataCubeBuilderStoreProvider.useLegendDataCubeBuilderStore.mockReturnValue(
130
+ value,
131
+ );
132
+ return value;
133
+ };
134
+
135
+ export const TEST__setUpDataCubeBuilder = async (
136
+ MOCK__builderStore: LegendDataCubeBuilderStore,
137
+ mockDataCube?: PersistentDataCube,
138
+ mockQuery?: V1_Query,
139
+ mockEntities?: PlainObject<Entity>[],
140
+ ): Promise<{
141
+ renderResult: RenderResult;
142
+ legendDataCubeBuilderState: LegendDataCubeBuilderState | undefined;
143
+ }> => {
144
+ if (mockDataCube) {
145
+ createSpy(MOCK__builderStore.graphManager, 'getDataCube').mockResolvedValue(
146
+ mockDataCube,
147
+ );
148
+ }
149
+ if (mockQuery) {
150
+ createSpy(
151
+ MOCK__builderStore.graphManager.engine,
152
+ 'getQuery',
153
+ ).mockResolvedValue(mockQuery);
154
+ }
155
+ if (mockEntities) {
156
+ const pmcd = new V1_PureModelContextData();
157
+ await V1_entitiesToPureModelContextData(
158
+ mockEntities as unknown as Entity[],
159
+ pmcd,
160
+ MOCK__builderStore.application.pluginManager.getPureProtocolProcessorPlugins(),
161
+ undefined,
162
+ undefined,
163
+ );
164
+ createSpy(
165
+ MOCK__builderStore.depotServerClient,
166
+ 'getVersionEntities',
167
+ ).mockResolvedValue(mockEntities);
168
+ createSpy(
169
+ MOCK__builderStore.engineServerClient,
170
+ 'lambdaRelationType',
171
+ ).mockImplementation(
172
+ async (input: PlainObject<V1_LambdaReturnTypeInput>) => {
173
+ return ENGINE_TEST_SUPPORT__getLambdaRelationType(
174
+ input.lambda as PlainObject<V1_RawLambda>,
175
+ V1_serializePureModelContext(pmcd),
176
+ );
177
+ },
178
+ );
179
+ createSpy(
180
+ MOCK__builderStore.engineServerClient,
181
+ 'runQuery',
182
+ ).mockImplementation(async (input: PlainObject<V1_ExecuteInput>) => {
183
+ const executeInput = V1_ExecuteInput.serialization.fromJson(input);
184
+ executeInput.model = pmcd;
185
+ return ENGINE_TEST_SUPPORT__execute(executeInput);
186
+ });
187
+ }
188
+ createSpy(
189
+ MOCK__builderStore.engineServerClient,
190
+ 'grammarToJSON_lambda',
191
+ ).mockImplementation(async (input: string) =>
192
+ ENGINE_TEST_SUPPORT__grammarToJSON_lambda(input),
193
+ );
194
+ createSpy(
195
+ MOCK__builderStore.engineServerClient,
196
+ 'grammarToJSON_valueSpecification',
197
+ ).mockImplementation(async (input: string) =>
198
+ ENGINE_TEST_SUPPORT__grammarToJSON_valueSpecification(input),
199
+ );
200
+ createSpy(
201
+ MOCK__builderStore.engineServerClient,
202
+ 'JSONToGrammar_valueSpecification',
203
+ ).mockImplementation(async (input: PlainObject<V1_ValueSpecification>) =>
204
+ ENGINE_TEST_SUPPORT__JSONToGrammar_valueSpecification(input),
205
+ );
206
+
207
+ const renderResult = render(
208
+ <ApplicationStoreProvider store={MOCK__builderStore.application}>
209
+ <TEST__BrowserEnvironmentProvider
210
+ initialEntries={[mockDataCube?.id ? `/${mockDataCube.id}` : '/']}
211
+ >
212
+ <LegendDataCubeFrameworkProvider>
213
+ <Routes>
214
+ <Route
215
+ path={LEGEND_DATA_CUBE_ROUTE_PATTERN.BUILDER}
216
+ element={<LegendDataCubeBuilder />}
217
+ />
218
+ </Routes>
219
+ </LegendDataCubeFrameworkProvider>
220
+ </TEST__BrowserEnvironmentProvider>
221
+ </ApplicationStoreProvider>,
222
+ );
223
+ await waitFor(() =>
224
+ renderResult.getByTestId(LEGEND_DATACUBE_TEST_ID.PLACEHOLDER),
225
+ );
226
+
227
+ return {
228
+ renderResult,
229
+ legendDataCubeBuilderState: MOCK__builderStore.builder,
230
+ };
231
+ };
@@ -38,7 +38,7 @@ import {
38
38
  import { useEffect } from 'react';
39
39
  import { LegendDataCubeSettingStorageKey } from '../../__lib__/LegendDataCubeSetting.js';
40
40
  import type { LegendDataCubeBuilderStore } from '../../stores/builder/LegendDataCubeBuilderStore.js';
41
- import { LocalFileDataCubeSource } from '../../stores/model/LocalFileDataCubeSource.js';
41
+ import { ReleaseViewer } from '@finos/legend-application';
42
42
 
43
43
  const LegendDataCubeBuilderHeader = observer(() => {
44
44
  const store = useLegendDataCubeBuilderStore();
@@ -58,11 +58,7 @@ const LegendDataCubeBuilderHeader = observer(() => {
58
58
  <FormButton
59
59
  compact={true}
60
60
  className="ml-1.5"
61
- disabled={
62
- !store.builder?.dataCube ||
63
- /* TODO: @gs-gunjan we should allow saving DataCube using CSV file source */
64
- store.builder.source instanceof LocalFileDataCubeSource
65
- }
61
+ disabled={!store.builder?.dataCube}
66
62
  onClick={() => store.saverDisplay.open()}
67
63
  >
68
64
  Save DataCube
@@ -71,8 +67,37 @@ const LegendDataCubeBuilderHeader = observer(() => {
71
67
  );
72
68
  });
73
69
 
70
+ export const LegendDataCubeReleaseLogManager = observer(
71
+ (props: { showOnlyLatestNotes: boolean }) => {
72
+ const { showOnlyLatestNotes } = props;
73
+ const store = useLegendDataCubeBuilderStore();
74
+ const applicationStore = store.application;
75
+ const releaseService = applicationStore.releaseNotesService;
76
+ const releaseNotes =
77
+ (showOnlyLatestNotes
78
+ ? releaseService.showableVersions()
79
+ : releaseService.releaseNotes) ?? [];
80
+
81
+ applicationStore.releaseNotesService.updateViewedVersion();
82
+
83
+ return (
84
+ <div className="legend-datacube-release-notes h-full items-center p-3">
85
+ <div className="my-0.5 flex font-mono">
86
+ New features, enhancements and bug fixes that were released
87
+ </div>
88
+ <div className="p-2">
89
+ {releaseNotes.map((e) => (
90
+ <ReleaseViewer key={e.version} releaseNotes={e} />
91
+ ))}
92
+ </div>
93
+ </div>
94
+ );
95
+ },
96
+ );
97
+
74
98
  export const LegendDataCubeAbout = observer(() => {
75
99
  const store = useLegendDataCubeBuilderStore();
100
+ const releaseService = store.application.releaseNotesService;
76
101
  const config = store.application.config;
77
102
 
78
103
  return (
@@ -93,6 +118,14 @@ export const LegendDataCubeAbout = observer(() => {
93
118
  <div>Build Time:</div>
94
119
  <div className="ml-1 font-bold">{config.appVersionBuildTime}</div>
95
120
  </div>
121
+ {releaseService.isConfigured && (
122
+ <div
123
+ onClick={() => store.releaseLogDisplay.open()}
124
+ className="my-0.5 flex cursor-pointer font-bold text-sky-500 underline"
125
+ >
126
+ <div>Details of Released Versions</div>
127
+ </div>
128
+ )}
96
129
  <div className="mt-3 rounded-sm bg-white px-4 py-2">
97
130
  <div className="my-0.5 flex font-mono">
98
131
  <div>Engine Server:</div>
@@ -255,6 +288,18 @@ export const LegendDataCubeBuilder = withLegendDataCubeBuilderStore(
255
288
  .catch((error) => store.alertService.alertUnhandledError(error));
256
289
  }, [store, dataCubeId]);
257
290
 
291
+ useEffect(() => {
292
+ const releaseService = application.releaseNotesService;
293
+ const releaseNotes = releaseService.showableVersions();
294
+ const isOpen = releaseService.showCurrentReleaseModal;
295
+
296
+ if (releaseService.isConfigured && isOpen && releaseNotes?.length) {
297
+ store.releaseNotesDisplay.open();
298
+ } else {
299
+ releaseService.updateViewedVersion();
300
+ }
301
+ }, [application, store]);
302
+
258
303
  if (!store.initializeState.hasSucceeded) {
259
304
  return (
260
305
  <DataCubePlaceholder
@@ -20,6 +20,7 @@ import { createContext, useContext } from 'react';
20
20
  import { LegendDataCubeBuilderStore } from '../../stores/builder/LegendDataCubeBuilderStore.js';
21
21
  import { useLegendDataCubeBaseStore } from '../LegendDataCubeFrameworkProvider.js';
22
22
  import { LegendDataCubeBlockingWindow } from '../LegendDataCubeBlockingWindow.js';
23
+ import { LegendDataCubeSourceLoaderBlockingWindow } from './source/LegendDataCubeSourceLoader.js';
23
24
 
24
25
  const LegendDataCubeBuilderStoreContext = createContext<
25
26
  LegendDataCubeBuilderStore | undefined
@@ -39,6 +40,7 @@ const LegendDataCubeBuilderStoreProvider = (props: {
39
40
  <LegendDataCubeBlockingWindow
40
41
  windowState={store.deleteConfirmationDisplay}
41
42
  />
43
+ <LegendDataCubeSourceLoaderBlockingWindow />
42
44
  </LegendDataCubeBuilderStoreContext.Provider>
43
45
  );
44
46
  };
@@ -18,8 +18,175 @@ import { observer } from 'mobx-react-lite';
18
18
  import { useLegendDataCubeBuilderStore } from './LegendDataCubeBuilderStoreProvider.js';
19
19
  import { LegendQueryDataCubeSource } from '../../stores/model/LegendQueryDataCubeSource.js';
20
20
  import { useLegendDataCubeApplicationStore } from '../LegendDataCubeFrameworkProvider.js';
21
- import { EXTERNAL_APPLICATION_NAVIGATION__generateQueryViewUrl } from '../../__lib__/LegendDataCubeNavigation.js';
21
+ import {
22
+ EXTERNAL_APPLICATION_NAVIGATION__generateQueryViewUrl,
23
+ EXTERNAL_APPLICATION_NAVIGATION__generateStudioSDLCViewUrl,
24
+ EXTERNAL_APPLICATION_NAVIGATION__generateStudioViewUrl,
25
+ } from '../../__lib__/LegendDataCubeNavigation.js';
22
26
  import { DataCubeIcon } from '@finos/legend-art';
27
+ import { UserDefinedFunctionDataCubeSource } from '@finos/legend-data-cube';
28
+ import { useCallback, useEffect, useState } from 'react';
29
+ import {
30
+ type DepotServerClient,
31
+ DepotScope,
32
+ StoreProjectData,
33
+ } from '@finos/legend-server-depot';
34
+ import { returnUndefOnError } from '@finos/legend-shared';
35
+ import {
36
+ V1_deserializePureModelContext,
37
+ V1_LegendSDLC,
38
+ V1_PureModelContextPointer,
39
+ type V1_PureModelContext,
40
+ } from '@finos/legend-graph';
41
+
42
+ const handleFetchProject = (
43
+ depotServerClient: DepotServerClient,
44
+ model: V1_PureModelContext,
45
+ handleProjectChange: (val: StoreProjectData | undefined) => void,
46
+ ) => {
47
+ if (
48
+ model instanceof V1_PureModelContextPointer &&
49
+ model.sdlcInfo instanceof V1_LegendSDLC
50
+ ) {
51
+ depotServerClient
52
+ .getProject(model.sdlcInfo.groupId, model.sdlcInfo.artifactId)
53
+ .then((e) => {
54
+ handleProjectChange(
55
+ returnUndefOnError(() => StoreProjectData.serialization.fromJson(e)),
56
+ );
57
+ })
58
+ .catch((e) => {
59
+ // ignore
60
+ });
61
+ }
62
+ };
63
+
64
+ const generateStudioViewLink = (
65
+ studio: string,
66
+ project: StoreProjectData,
67
+ version: string,
68
+ element: string | undefined,
69
+ ): string => {
70
+ if (version.endsWith(DepotScope.SNAPSHOT)) {
71
+ return EXTERNAL_APPLICATION_NAVIGATION__generateStudioViewUrl(
72
+ studio,
73
+ project.groupId,
74
+ project.artifactId,
75
+ version,
76
+ element,
77
+ );
78
+ } else {
79
+ return EXTERNAL_APPLICATION_NAVIGATION__generateStudioSDLCViewUrl(
80
+ studio,
81
+ project.projectId,
82
+ version,
83
+ element,
84
+ );
85
+ }
86
+ };
87
+
88
+ const UserDefinedFunctionSourceViewer = observer(
89
+ (props: { source: UserDefinedFunctionDataCubeSource }) => {
90
+ const { source } = props;
91
+ const sourceModel = returnUndefOnError(() =>
92
+ V1_deserializePureModelContext(source.model),
93
+ );
94
+ const version =
95
+ sourceModel instanceof V1_PureModelContextPointer &&
96
+ sourceModel.sdlcInfo instanceof V1_LegendSDLC
97
+ ? sourceModel.sdlcInfo.version
98
+ : undefined;
99
+ const store = useLegendDataCubeBuilderStore();
100
+ const application = useLegendDataCubeApplicationStore();
101
+ const [project, setProject] = useState<StoreProjectData | undefined>(
102
+ undefined,
103
+ );
104
+ const link =
105
+ project?.projectId && application.config.studioApplicationUrl && version
106
+ ? generateStudioViewLink(
107
+ application.config.studioApplicationUrl,
108
+ project,
109
+ version,
110
+ source.functionPath,
111
+ )
112
+ : undefined;
113
+ const _handleFetchProject = useCallback(() => {
114
+ if (sourceModel) {
115
+ handleFetchProject(store.depotServerClient, sourceModel, setProject);
116
+ }
117
+ }, [sourceModel, store.depotServerClient]);
118
+
119
+ useEffect(() => {
120
+ _handleFetchProject();
121
+ }, [_handleFetchProject]);
122
+
123
+ return (
124
+ <div className="h-full w-full px-2 pt-2">
125
+ <div className="h-[calc(100%_-_8px)] w-full border border-neutral-300 bg-white">
126
+ <div className="h-full w-full select-none p-2">
127
+ <div className="flex h-6">
128
+ <div className="flex h-6 items-center text-xl font-medium">
129
+ <DataCubeIcon.Table />
130
+ </div>
131
+ <div className="ml-1 flex h-6 items-center text-xl font-medium">
132
+ User Defined Function
133
+ </div>
134
+ </div>
135
+ {link && (
136
+ <div className="mt-2 flex h-6 w-full">
137
+ <div className="flex h-full w-[calc(100%_-_20px)] items-center border border-r-0 border-neutral-400 px-1.5 font-bold text-sky-500 underline">
138
+ <a
139
+ href={link}
140
+ target="_blank"
141
+ rel="noopener noreferrer"
142
+ className="overflow-hidden overflow-ellipsis whitespace-nowrap"
143
+ >
144
+ {source.functionPath}
145
+ </a>
146
+ </div>
147
+ <button
148
+ className="flex aspect-square h-full w-6 items-center justify-center border border-neutral-400 bg-neutral-300 hover:brightness-95"
149
+ onClick={() => {
150
+ store.application.clipboardService
151
+ .copyTextToClipboard(link)
152
+ .catch((error) =>
153
+ store.alertService.alertUnhandledError(error),
154
+ );
155
+ }}
156
+ title="Copy Link"
157
+ >
158
+ <DataCubeIcon.Clipboard />
159
+ </button>
160
+ </div>
161
+ )}
162
+ {!link && (
163
+ <div className="mt-2 flex h-6 w-full">
164
+ <div className="flex h-full w-[calc(100%_-_20px)] items-center border border-r-0 border-neutral-400 bg-neutral-200 px-1.5">
165
+ <div className="overflow-hidden overflow-ellipsis whitespace-nowrap">
166
+ {source.functionPath}
167
+ </div>
168
+ </div>
169
+ <button
170
+ className="flex aspect-square h-full w-6 items-center justify-center border border-neutral-400 bg-neutral-300 hover:brightness-95"
171
+ onClick={() => {
172
+ application.clipboardService
173
+ .copyTextToClipboard(source.functionPath)
174
+ .catch((error) =>
175
+ store.alertService.alertUnhandledError(error),
176
+ );
177
+ }}
178
+ title="Copy ID"
179
+ >
180
+ <DataCubeIcon.Clipboard />
181
+ </button>
182
+ </div>
183
+ )}
184
+ </div>
185
+ </div>
186
+ </div>
187
+ );
188
+ },
189
+ );
23
190
 
24
191
  export const LegendDataCubeSourceViewer = observer(() => {
25
192
  const store = useLegendDataCubeBuilderStore();
@@ -36,6 +203,7 @@ export const LegendDataCubeSourceViewer = observer(() => {
36
203
  source.info.id,
37
204
  )
38
205
  : undefined;
206
+
39
207
  return (
40
208
  <div className="h-full w-full px-2 pt-2">
41
209
  <div className="h-[calc(100%_-_8px)] w-full border border-neutral-300 bg-white">
@@ -101,6 +269,8 @@ export const LegendDataCubeSourceViewer = observer(() => {
101
269
  </div>
102
270
  </div>
103
271
  );
272
+ } else if (source instanceof UserDefinedFunctionDataCubeSource) {
273
+ return <UserDefinedFunctionSourceViewer source={source} />;
104
274
  }
105
275
  return (
106
276
  <div className="h-full w-full px-2 pt-2">{`Can't display source`}</div>