@finos/legend-cube-builder 0.0.5 → 0.0.6

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 (245) hide show
  1. package/docs/adding-an-operation.md +4 -2
  2. package/docs/demo/README.md +1 -1
  3. package/docs/hosting.md +40 -9
  4. package/lib/__lib__/LegendCubeHelpText.d.ts.map +1 -1
  5. package/lib/__lib__/LegendCubeHelpText.js +3 -1
  6. package/lib/__lib__/LegendCubeHelpText.js.map +1 -1
  7. package/lib/__lib__/LegendCubeLabels.d.ts +34 -5
  8. package/lib/__lib__/LegendCubeLabels.d.ts.map +1 -1
  9. package/lib/__lib__/LegendCubeLabels.js +45 -8
  10. package/lib/__lib__/LegendCubeLabels.js.map +1 -1
  11. package/lib/__lib__/LegendCubeNavigation.d.ts +23 -0
  12. package/lib/__lib__/LegendCubeNavigation.d.ts.map +1 -0
  13. package/lib/__lib__/LegendCubeNavigation.js +33 -0
  14. package/lib/__lib__/LegendCubeNavigation.js.map +1 -0
  15. package/lib/__lib__/LegendCubeTesting.d.ts +3 -1
  16. package/lib/__lib__/LegendCubeTesting.d.ts.map +1 -1
  17. package/lib/__lib__/LegendCubeTesting.js +2 -0
  18. package/lib/__lib__/LegendCubeTesting.js.map +1 -1
  19. package/lib/__lib__/LegendCubeTour.d.ts +62 -0
  20. package/lib/__lib__/LegendCubeTour.d.ts.map +1 -0
  21. package/lib/__lib__/LegendCubeTour.js +149 -0
  22. package/lib/__lib__/LegendCubeTour.js.map +1 -0
  23. package/lib/__test-utils__/CubeExampleTestUtils.d.ts +34 -0
  24. package/lib/__test-utils__/CubeExampleTestUtils.d.ts.map +1 -0
  25. package/lib/__test-utils__/CubeExampleTestUtils.js +72 -0
  26. package/lib/__test-utils__/CubeExampleTestUtils.js.map +1 -0
  27. package/lib/__test-utils__/CubeVariantTestUtils.d.ts +16 -0
  28. package/lib/__test-utils__/CubeVariantTestUtils.d.ts.map +1 -0
  29. package/lib/__test-utils__/CubeVariantTestUtils.js +66 -0
  30. package/lib/__test-utils__/CubeVariantTestUtils.js.map +1 -0
  31. package/lib/components/CubeAddItems.d.ts.map +1 -1
  32. package/lib/components/CubeAddItems.js +2 -1
  33. package/lib/components/CubeAddItems.js.map +1 -1
  34. package/lib/components/CubeEditor.d.ts.map +1 -1
  35. package/lib/components/CubeEditor.js +11 -5
  36. package/lib/components/CubeEditor.js.map +1 -1
  37. package/lib/components/CubeFileMenu.d.ts.map +1 -1
  38. package/lib/components/CubeFileMenu.js +2 -1
  39. package/lib/components/CubeFileMenu.js.map +1 -1
  40. package/lib/components/CubeNodeIcon.d.ts.map +1 -1
  41. package/lib/components/CubeNodeIcon.js +2 -1
  42. package/lib/components/CubeNodeIcon.js.map +1 -1
  43. package/lib/components/canvas/CubeCanvas.d.ts.map +1 -1
  44. package/lib/components/canvas/CubeCanvas.js +3 -2
  45. package/lib/components/canvas/CubeCanvas.js.map +1 -1
  46. package/lib/components/canvas/CubeCanvasNode.d.ts.map +1 -1
  47. package/lib/components/canvas/CubeCanvasNode.js +4 -1
  48. package/lib/components/canvas/CubeCanvasNode.js.map +1 -1
  49. package/lib/components/dev-settings/CubeDevSettings.d.ts +2 -2
  50. package/lib/components/dev-settings/CubeDevSettings.d.ts.map +1 -1
  51. package/lib/components/dev-settings/CubeDevSettings.js +12 -8
  52. package/lib/components/dev-settings/CubeDevSettings.js.map +1 -1
  53. package/lib/components/editors/CubeColumnPicker.d.ts.map +1 -1
  54. package/lib/components/editors/CubeColumnPicker.js +3 -1
  55. package/lib/components/editors/CubeColumnPicker.js.map +1 -1
  56. package/lib/components/editors/CubeExplodeEditor.d.ts +26 -0
  57. package/lib/components/editors/CubeExplodeEditor.d.ts.map +1 -0
  58. package/lib/components/editors/CubeExplodeEditor.js +61 -0
  59. package/lib/components/editors/CubeExplodeEditor.js.map +1 -0
  60. package/lib/components/editors/CubeExpressionFilterEditor.d.ts.map +1 -1
  61. package/lib/components/editors/CubeExpressionFilterEditor.js +8 -5
  62. package/lib/components/editors/CubeExpressionFilterEditor.js.map +1 -1
  63. package/lib/components/editors/CubeExtendEditor.d.ts +0 -2
  64. package/lib/components/editors/CubeExtendEditor.d.ts.map +1 -1
  65. package/lib/components/editors/CubeExtendEditor.js +12 -11
  66. package/lib/components/editors/CubeExtendEditor.js.map +1 -1
  67. package/lib/components/editors/CubeJsonFieldsTree.d.ts +46 -0
  68. package/lib/components/editors/CubeJsonFieldsTree.d.ts.map +1 -0
  69. package/lib/components/editors/CubeJsonFieldsTree.js +93 -0
  70. package/lib/components/editors/CubeJsonFieldsTree.js.map +1 -0
  71. package/lib/components/editors/CubeNodeEditorRegistry.d.ts.map +1 -1
  72. package/lib/components/editors/CubeNodeEditorRegistry.js +3 -1
  73. package/lib/components/editors/CubeNodeEditorRegistry.js.map +1 -1
  74. package/lib/components/grid/CubeGridRegion.d.ts.map +1 -1
  75. package/lib/components/grid/CubeGridRegion.js +5 -4
  76. package/lib/components/grid/CubeGridRegion.js.map +1 -1
  77. package/lib/components/grid/CubeGridValues.d.ts +5 -3
  78. package/lib/components/grid/CubeGridValues.d.ts.map +1 -1
  79. package/lib/components/grid/CubeGridValues.js +8 -3
  80. package/lib/components/grid/CubeGridValues.js.map +1 -1
  81. package/lib/components/grid/CubeJsonExplorerDialog.d.ts +27 -0
  82. package/lib/components/grid/CubeJsonExplorerDialog.d.ts.map +1 -0
  83. package/lib/components/grid/CubeJsonExplorerDialog.js +102 -0
  84. package/lib/components/grid/CubeJsonExplorerDialog.js.map +1 -0
  85. package/lib/components/grid/CubeJsonViewerDialog.d.ts +27 -0
  86. package/lib/components/grid/CubeJsonViewerDialog.d.ts.map +1 -0
  87. package/lib/components/grid/CubeJsonViewerDialog.js +68 -0
  88. package/lib/components/grid/CubeJsonViewerDialog.js.map +1 -0
  89. package/lib/components/grid/CubeResultGrid.d.ts +15 -3
  90. package/lib/components/grid/CubeResultGrid.d.ts.map +1 -1
  91. package/lib/components/grid/CubeResultGrid.js +95 -3
  92. package/lib/components/grid/CubeResultGrid.js.map +1 -1
  93. package/lib/components/grid/CubeServerSideGrid.d.ts.map +1 -1
  94. package/lib/components/grid/CubeServerSideGrid.js +4 -2
  95. package/lib/components/grid/CubeServerSideGrid.js.map +1 -1
  96. package/lib/components/palette/CubePalette.d.ts.map +1 -1
  97. package/lib/components/palette/CubePalette.js +4 -1
  98. package/lib/components/palette/CubePalette.js.map +1 -1
  99. package/lib/components/source-picker/CubeIngestTab.d.ts +2 -2
  100. package/lib/components/source-picker/CubeIngestTab.d.ts.map +1 -1
  101. package/lib/components/source-picker/CubeIngestTab.js +19 -13
  102. package/lib/components/source-picker/CubeIngestTab.js.map +1 -1
  103. package/lib/components/tour/CubeTour.d.ts +36 -0
  104. package/lib/components/tour/CubeTour.d.ts.map +1 -0
  105. package/lib/components/tour/CubeTour.js +183 -0
  106. package/lib/components/tour/CubeTour.js.map +1 -0
  107. package/lib/index.css +1 -1
  108. package/lib/index.d.ts +1 -0
  109. package/lib/index.d.ts.map +1 -1
  110. package/lib/index.js +1 -0
  111. package/lib/index.js.map +1 -1
  112. package/lib/stores/CubeEditorState.d.ts +17 -1
  113. package/lib/stores/CubeEditorState.d.ts.map +1 -1
  114. package/lib/stores/CubeEditorState.js +35 -5
  115. package/lib/stores/CubeEditorState.js.map +1 -1
  116. package/lib/stores/CubeExamples.d.ts.map +1 -1
  117. package/lib/stores/CubeExamples.js +101 -4
  118. package/lib/stores/CubeExamples.js.map +1 -1
  119. package/lib/stores/CubeExamplesState.d.ts.map +1 -1
  120. package/lib/stores/CubeExamplesState.js +4 -1
  121. package/lib/stores/CubeExamplesState.js.map +1 -1
  122. package/lib/stores/CubeGridQuickActions.d.ts +7 -0
  123. package/lib/stores/CubeGridQuickActions.d.ts.map +1 -1
  124. package/lib/stores/CubeGridQuickActions.js +20 -11
  125. package/lib/stores/CubeGridQuickActions.js.map +1 -1
  126. package/lib/stores/CubePureText.d.ts +20 -0
  127. package/lib/stores/CubePureText.d.ts.map +1 -0
  128. package/lib/stores/CubePureText.js +24 -0
  129. package/lib/stores/CubePureText.js.map +1 -0
  130. package/lib/stores/CubeTourState.d.ts +49 -0
  131. package/lib/stores/CubeTourState.d.ts.map +1 -0
  132. package/lib/stores/CubeTourState.js +92 -0
  133. package/lib/stores/CubeTourState.js.map +1 -0
  134. package/lib/stores/LegendCubeHost.d.ts +88 -0
  135. package/lib/stores/LegendCubeHost.d.ts.map +1 -0
  136. package/lib/stores/LegendCubeHost.js +149 -0
  137. package/lib/stores/LegendCubeHost.js.map +1 -0
  138. package/lib/stores/editors/CubeExplodeDraft.d.ts +87 -0
  139. package/lib/stores/editors/CubeExplodeDraft.d.ts.map +1 -0
  140. package/lib/stores/editors/CubeExplodeDraft.js +221 -0
  141. package/lib/stores/editors/CubeExplodeDraft.js.map +1 -0
  142. package/lib/stores/editors/CubeExpressionFilterDraft.d.ts +3 -0
  143. package/lib/stores/editors/CubeExpressionFilterDraft.d.ts.map +1 -1
  144. package/lib/stores/editors/CubeExpressionFilterDraft.js +4 -0
  145. package/lib/stores/editors/CubeExpressionFilterDraft.js.map +1 -1
  146. package/lib/stores/editors/CubeExtendDraft.d.ts +9 -0
  147. package/lib/stores/editors/CubeExtendDraft.d.ts.map +1 -1
  148. package/lib/stores/editors/CubeExtendDraft.js +26 -0
  149. package/lib/stores/editors/CubeExtendDraft.js.map +1 -1
  150. package/lib/stores/editors/CubeNodeDraftRegistry.d.ts.map +1 -1
  151. package/lib/stores/editors/CubeNodeDraftRegistry.js +6 -1
  152. package/lib/stores/editors/CubeNodeDraftRegistry.js.map +1 -1
  153. package/lib/stores/fixtures/CubeSalesDuckDbModel.d.ts +1 -0
  154. package/lib/stores/fixtures/CubeSalesDuckDbModel.d.ts.map +1 -1
  155. package/lib/stores/fixtures/CubeSalesDuckDbModel.js +8 -2
  156. package/lib/stores/fixtures/CubeSalesDuckDbModel.js.map +1 -1
  157. package/lib/stores/source-picker/CubeIngestTabState.d.ts +48 -14
  158. package/lib/stores/source-picker/CubeIngestTabState.d.ts.map +1 -1
  159. package/lib/stores/source-picker/CubeIngestTabState.js +159 -62
  160. package/lib/stores/source-picker/CubeIngestTabState.js.map +1 -1
  161. package/lib/stores/variant/CubeJson.d.ts +48 -0
  162. package/lib/stores/variant/CubeJson.d.ts.map +1 -0
  163. package/lib/stores/variant/CubeJson.js +207 -0
  164. package/lib/stores/variant/CubeJson.js.map +1 -0
  165. package/lib/stores/variant/CubeJsonExplorerState.d.ts +88 -0
  166. package/lib/stores/variant/CubeJsonExplorerState.d.ts.map +1 -0
  167. package/lib/stores/variant/CubeJsonExplorerState.js +297 -0
  168. package/lib/stores/variant/CubeJsonExplorerState.js.map +1 -0
  169. package/lib/stores/variant/CubeJsonFieldsState.d.ts +83 -0
  170. package/lib/stores/variant/CubeJsonFieldsState.d.ts.map +1 -0
  171. package/lib/stores/variant/CubeJsonFieldsState.js +207 -0
  172. package/lib/stores/variant/CubeJsonFieldsState.js.map +1 -0
  173. package/lib/stores/variant/CubeJsonOffers.d.ts +76 -0
  174. package/lib/stores/variant/CubeJsonOffers.d.ts.map +1 -0
  175. package/lib/stores/variant/CubeJsonOffers.js +250 -0
  176. package/lib/stores/variant/CubeJsonOffers.js.map +1 -0
  177. package/lib/stores/variant/CubeJsonParse.d.ts +28 -0
  178. package/lib/stores/variant/CubeJsonParse.d.ts.map +1 -0
  179. package/lib/stores/variant/CubeJsonParse.js +53 -0
  180. package/lib/stores/variant/CubeJsonParse.js.map +1 -0
  181. package/lib/stores/variant/CubeJsonSampling.d.ts +41 -0
  182. package/lib/stores/variant/CubeJsonSampling.d.ts.map +1 -0
  183. package/lib/stores/variant/CubeJsonSampling.js +59 -0
  184. package/lib/stores/variant/CubeJsonSampling.js.map +1 -0
  185. package/lib/stores/variant/CubeJsonShape.d.ts +87 -0
  186. package/lib/stores/variant/CubeJsonShape.d.ts.map +1 -0
  187. package/lib/stores/variant/CubeJsonShape.js +209 -0
  188. package/lib/stores/variant/CubeJsonShape.js.map +1 -0
  189. package/lib/stores/variant/CubeJsonViewerState.d.ts +33 -0
  190. package/lib/stores/variant/CubeJsonViewerState.d.ts.map +1 -0
  191. package/lib/stores/variant/CubeJsonViewerState.js +53 -0
  192. package/lib/stores/variant/CubeJsonViewerState.js.map +1 -0
  193. package/package.json +2 -2
  194. package/src/__lib__/LegendCubeHelpText.ts +4 -0
  195. package/src/__lib__/LegendCubeLabels.ts +60 -8
  196. package/src/__lib__/LegendCubeNavigation.ts +48 -0
  197. package/src/__lib__/LegendCubeTesting.ts +2 -0
  198. package/src/__lib__/LegendCubeTour.ts +173 -0
  199. package/src/__test-utils__/CubeExampleTestUtils.ts +119 -0
  200. package/src/__test-utils__/CubeVariantTestUtils.ts +103 -0
  201. package/src/components/CubeAddItems.tsx +2 -0
  202. package/src/components/CubeEditor.tsx +15 -0
  203. package/src/components/CubeFileMenu.tsx +2 -0
  204. package/src/components/CubeNodeIcon.tsx +2 -0
  205. package/src/components/canvas/CubeCanvas.tsx +17 -1
  206. package/src/components/canvas/CubeCanvasNode.tsx +7 -0
  207. package/src/components/dev-settings/CubeDevSettings.tsx +17 -2
  208. package/src/components/editors/CubeColumnPicker.tsx +3 -0
  209. package/src/components/editors/CubeExplodeEditor.tsx +207 -0
  210. package/src/components/editors/CubeExpressionFilterEditor.tsx +25 -8
  211. package/src/components/editors/CubeExtendEditor.tsx +32 -19
  212. package/src/components/editors/CubeJsonFieldsTree.tsx +316 -0
  213. package/src/components/editors/CubeNodeEditorRegistry.ts +3 -0
  214. package/src/components/grid/CubeGridRegion.tsx +21 -3
  215. package/src/components/grid/CubeGridValues.ts +8 -3
  216. package/src/components/grid/CubeJsonExplorerDialog.tsx +304 -0
  217. package/src/components/grid/CubeJsonViewerDialog.tsx +190 -0
  218. package/src/components/grid/CubeResultGrid.tsx +144 -2
  219. package/src/components/grid/CubeServerSideGrid.tsx +13 -0
  220. package/src/components/palette/CubePalette.tsx +9 -0
  221. package/src/components/source-picker/CubeIngestTab.tsx +62 -30
  222. package/src/components/tour/CubeTour.tsx +317 -0
  223. package/src/index.ts +7 -0
  224. package/src/stores/CubeEditorState.ts +36 -5
  225. package/src/stores/CubeExamples.ts +120 -2
  226. package/src/stores/CubeExamplesState.ts +9 -1
  227. package/src/stores/CubeGridQuickActions.ts +23 -12
  228. package/src/stores/CubePureText.ts +28 -0
  229. package/src/stores/CubeTourState.ts +116 -0
  230. package/src/stores/LegendCubeHost.ts +243 -0
  231. package/src/stores/editors/CubeExplodeDraft.ts +272 -0
  232. package/src/stores/editors/CubeExpressionFilterDraft.ts +4 -0
  233. package/src/stores/editors/CubeExtendDraft.ts +29 -0
  234. package/src/stores/editors/CubeNodeDraftRegistry.ts +7 -0
  235. package/src/stores/fixtures/CubeSalesDuckDbModel.ts +8 -2
  236. package/src/stores/source-picker/CubeIngestTabState.ts +253 -102
  237. package/src/stores/variant/CubeJson.ts +229 -0
  238. package/src/stores/variant/CubeJsonExplorerState.ts +378 -0
  239. package/src/stores/variant/CubeJsonFieldsState.ts +252 -0
  240. package/src/stores/variant/CubeJsonOffers.ts +359 -0
  241. package/src/stores/variant/CubeJsonParse.ts +75 -0
  242. package/src/stores/variant/CubeJsonSampling.ts +90 -0
  243. package/src/stores/variant/CubeJsonShape.ts +279 -0
  244. package/src/stores/variant/CubeJsonViewerState.ts +59 -0
  245. package/tsconfig.json +22 -1
@@ -0,0 +1,272 @@
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
+ type Explode,
19
+ getVariantDatabaseProblem,
20
+ type Schema,
21
+ } from '@finos/legend-cube';
22
+ import { assertErrorThrown, type GeneratorFn } from '@finos/legend-shared';
23
+ import { action, computed, flow, makeObservable, observable } from 'mobx';
24
+ import { CubeEngineError } from '../../graph-manager/CubeEngine.js';
25
+ import type { CubeEditorState } from '../CubeEditorState.js';
26
+ import { CubeQueryRunAborted } from '../CubeQueryRun.js';
27
+ import {
28
+ type CubeJsonField,
29
+ getCubeJsonElementName,
30
+ getCubeJsonFields,
31
+ getFreeCubeColumnName,
32
+ } from '../variant/CubeJsonOffers.js';
33
+ import { readCubeJsonSample } from '../variant/CubeJsonSampling.js';
34
+ import type { CubeJsonSample } from '../variant/CubeJsonShape.js';
35
+ import { isSameList } from './CubeAggregationRows.js';
36
+ import { CubeNodeDraft } from './CubeNodeDraft.js';
37
+
38
+ /** An array a sample of the column holds, to explode */
39
+ export interface CubeExplodeArray {
40
+ /** The keys from the column to it, empty for the column itself */
41
+ readonly path: readonly string[];
42
+ /** What it held, e.g. `list of object` */
43
+ readonly description: string;
44
+ /** Its share, 0 to 1, of the places it could be */
45
+ readonly presence: number;
46
+ }
47
+
48
+ /** The arrays of a column's fields, in tree order */
49
+ const arraysOf = (field: CubeJsonField): CubeExplodeArray[] => [
50
+ ...(field.offers.some((offer) => offer.explode !== undefined)
51
+ ? [
52
+ {
53
+ path: field.path,
54
+ description: field.description,
55
+ presence: field.presence,
56
+ },
57
+ ]
58
+ : []),
59
+ ...field.children.flatMap(arraysOf),
60
+ ];
61
+
62
+ /**
63
+ * The Explode Array editor's draft (PLAN §11.20): the Variant column, the
64
+ * keys to its array and the new column's name. The arrays to pick from come
65
+ * from a sample of the column's documents, read after the node's input as
66
+ * Explore JSON reads them. Until a name is typed, the name follows the
67
+ * column and keys (`<last key>_element`, free in the input); picking another
68
+ * column drops the keys and the sample, which were the other column's.
69
+ */
70
+ export class CubeExplodeDraft extends CubeNodeDraft<Explode> {
71
+ readonly editorState: CubeEditorState;
72
+ column: string;
73
+ path: readonly string[];
74
+ name: string;
75
+ /** What a sample of the column held, once read */
76
+ sample:
77
+ | { readonly column: string; readonly sample: CubeJsonSample }
78
+ | undefined;
79
+ isSampling = false;
80
+ /** Why the sample couldn't be read */
81
+ sampleError: string | undefined;
82
+ /** The name follows the column and keys until one is typed */
83
+ private namedByDefault: boolean;
84
+ private controller: AbortController | undefined;
85
+ /** Anything was changed; until then, `build()` gives the original back */
86
+ private touched = false;
87
+
88
+ constructor(original: Explode, editorState: CubeEditorState) {
89
+ super(original);
90
+ makeObservable<CubeExplodeDraft, 'touched' | 'namedByDefault'>(this, {
91
+ column: observable,
92
+ path: observable.ref,
93
+ name: observable,
94
+ sample: observable.ref,
95
+ isSampling: observable,
96
+ sampleError: observable,
97
+ namedByDefault: observable,
98
+ touched: observable,
99
+ arrays: computed,
100
+ sampleProblem: computed,
101
+ setColumn: action,
102
+ setKey: action,
103
+ addKey: action,
104
+ removeKey: action,
105
+ pickArray: action,
106
+ setName: action,
107
+ stopSample: action,
108
+ readSample: flow,
109
+ });
110
+ this.editorState = editorState;
111
+ this.column = original.column;
112
+ this.path = original.path;
113
+ this.name = original.name;
114
+ // a new node's, or one still named as Explore JSON names it
115
+ this.namedByDefault =
116
+ original.name === '' ||
117
+ original.name === this.defaultName(original.column, original.path);
118
+ }
119
+
120
+ /** The element's default name for a column and keys: `<last key>_element`, free in the input */
121
+ private defaultName(column: string, path: readonly string[]): string {
122
+ return column
123
+ ? getFreeCubeColumnName(
124
+ getCubeJsonElementName(column, path),
125
+ this.input?.schema.names() ?? [],
126
+ )
127
+ : '';
128
+ }
129
+
130
+ /**
131
+ * Why Find arrays can't read the column: the database the node's input
132
+ * runs on has no JSON functions; `undefined` when it can, or isn't known
133
+ */
134
+ get sampleProblem(): string | undefined {
135
+ const { editorState, input } = this;
136
+ const { context, query } = editorState.document;
137
+ return input === undefined || context?.runtime === undefined
138
+ ? undefined
139
+ : getVariantDatabaseProblem(
140
+ editorState.getRunDatabaseType(
141
+ query,
142
+ input.id,
143
+ context.model,
144
+ context.runtime,
145
+ ),
146
+ );
147
+ }
148
+
149
+ /** The node's input: its id and schema, once known */
150
+ private get input(): { id: string; schema: Schema } | undefined {
151
+ const [id] = this.editorState.document.query.getInputIds(this.original.id);
152
+ const schema =
153
+ id === undefined ? undefined : this.editorState.analysis.schemas.get(id);
154
+ return id === undefined || !schema ? undefined : { id, schema };
155
+ }
156
+
157
+ /** The arrays the sample found, if it was of the picked column */
158
+ get arrays(): CubeExplodeArray[] | undefined {
159
+ const { sample, column } = this;
160
+ return sample && sample.column === column
161
+ ? arraysOf(getCubeJsonFields(column, sample.sample))
162
+ : undefined;
163
+ }
164
+
165
+ private edited(): void {
166
+ this.touched = true;
167
+ if (this.namedByDefault) {
168
+ this.name = this.defaultName(this.column, this.path);
169
+ }
170
+ }
171
+
172
+ /** Picks the column; another column's keys and sample are dropped */
173
+ setColumn(column: string): void {
174
+ if (column === this.column) {
175
+ return;
176
+ }
177
+ this.stopSample();
178
+ this.column = column;
179
+ this.path = [];
180
+ this.sample = undefined;
181
+ this.sampleError = undefined;
182
+ this.edited();
183
+ }
184
+
185
+ setKey(index: number, key: string): void {
186
+ this.path = this.path.map((each, at) => (at === index ? key : each));
187
+ this.edited();
188
+ }
189
+
190
+ addKey(): void {
191
+ this.path = [...this.path, ''];
192
+ this.edited();
193
+ }
194
+
195
+ removeKey(index: number): void {
196
+ this.path = this.path.filter((_, at) => at !== index);
197
+ this.edited();
198
+ }
199
+
200
+ /** Takes the keys of an array the sample found */
201
+ pickArray(path: readonly string[]): void {
202
+ this.path = path;
203
+ this.edited();
204
+ }
205
+
206
+ /** Types the name: it no longer follows the column and keys */
207
+ setName(name: string): void {
208
+ this.name = name;
209
+ this.namedByDefault = false;
210
+ this.touched = true;
211
+ }
212
+
213
+ stopSample(): void {
214
+ this.controller?.abort();
215
+ this.controller = undefined;
216
+ this.isSampling = false;
217
+ }
218
+
219
+ /**
220
+ * Reads the picked column's first values after the node's input, as
221
+ * Explore JSON does (listed in Runs, can be stopped), for the arrays they
222
+ * hold; picking another column abandons it
223
+ */
224
+ *readSample(): GeneratorFn<void> {
225
+ const { editorState, column, input } = this;
226
+ const picked = column ? input?.schema.lookup(column) : undefined;
227
+ if (this.isSampling || !input || !picked || this.sampleProblem) {
228
+ return;
229
+ }
230
+ const controller = new AbortController();
231
+ this.controller = controller;
232
+ this.isSampling = true;
233
+ this.sampleError = undefined;
234
+ try {
235
+ const sample = (yield readCubeJsonSample(editorState, {
236
+ document: editorState.document,
237
+ afterId: input.id,
238
+ column: picked,
239
+ everyRow: false,
240
+ abortController: controller,
241
+ })) as CubeJsonSample | undefined;
242
+ if (this.controller === controller && sample) {
243
+ this.sample = { column, sample };
244
+ }
245
+ } catch (error) {
246
+ if (
247
+ this.controller !== controller ||
248
+ error instanceof CubeQueryRunAborted
249
+ ) {
250
+ return;
251
+ }
252
+ assertErrorThrown(error);
253
+ this.sampleError =
254
+ error instanceof CubeEngineError ? error.firstLine : error.message;
255
+ } finally {
256
+ if (this.controller === controller) {
257
+ this.controller = undefined;
258
+ this.isSampling = false;
259
+ }
260
+ }
261
+ }
262
+
263
+ build(): Explode {
264
+ const { original, column, path, name } = this;
265
+ return !this.touched ||
266
+ (column === original.column &&
267
+ isSameList(path, original.path) &&
268
+ name === original.name)
269
+ ? original
270
+ : original.with({ column, path, name });
271
+ }
272
+ }
@@ -50,6 +50,7 @@ import {
50
50
  type NodeId,
51
51
  } from '../../graph-manager/CubeEngine.js';
52
52
  import type { CubeEditorState } from '../CubeEditorState.js';
53
+ import { CubeJsonFieldsState } from '../variant/CubeJsonFieldsState.js';
53
54
  import {
54
55
  buildCubeConditionCheckLambdas,
55
56
  readCubeConditionCheck,
@@ -118,6 +119,8 @@ const asEngineError = (error: unknown): CubeEngineError =>
118
119
  */
119
120
  export class CubeExpressionFilterDraft extends CubeNodeDraft<ExpressionFilter> {
120
121
  readonly editorState: CubeEditorState;
122
+ /** A Variant input column's fields, opened from its chip (PLAN §11.20, V.9) */
123
+ readonly jsonFields: CubeJsonFieldsState;
121
124
  code: string;
122
125
  /** What the engine made of the code the last time it parsed it */
123
126
  checked: CubeConditionCheckedCode | undefined;
@@ -149,6 +152,7 @@ export class CubeExpressionFilterDraft extends CubeNodeDraft<ExpressionFilter> {
149
152
  follow: action,
150
153
  });
151
154
  this.editorState = editorState;
155
+ this.jsonFields = new CubeJsonFieldsState(editorState, original.id);
152
156
  const { code, lambda } = original;
153
157
  this.code = code || NEW_CONDITION_CODE;
154
158
  this.checked = lambda ? { code, lambda, located: lambda } : undefined;
@@ -51,6 +51,8 @@ import {
51
51
  type NodeId,
52
52
  } from '../../graph-manager/CubeEngine.js';
53
53
  import type { CubeEditorState } from '../CubeEditorState.js';
54
+ import { CubeJsonFieldsState } from '../variant/CubeJsonFieldsState.js';
55
+ import { getFreeCubeColumnName } from '../variant/CubeJsonOffers.js';
54
56
  import {
55
57
  buildCubeExtendTypingLambdas,
56
58
  getCubeExtendUpstream,
@@ -110,6 +112,8 @@ const problemOf = (error: CubeEngineError): CubeExtendRowProblem => ({
110
112
  */
111
113
  export class CubeExtendDraft extends CubeNodeDraft<Extend> {
112
114
  readonly editorState: CubeEditorState;
115
+ /** A Variant input column's fields, opened from its chip (PLAN §11.20, V.9) */
116
+ readonly jsonFields: CubeJsonFieldsState;
113
117
  rows: readonly CubeExtendRow[];
114
118
  /** The typing the last Validate gave, for the rows as they were then */
115
119
  typing: ExtendTyping | undefined;
@@ -131,6 +135,7 @@ export class CubeExtendDraft extends CubeNodeDraft<Extend> {
131
135
  isValidated: computed,
132
136
  applyDisabledReason: computed,
133
137
  addRow: action,
138
+ addColumn: action,
134
139
  removeRow: action,
135
140
  moveRow: action,
136
141
  setName: action,
@@ -139,6 +144,7 @@ export class CubeExtendDraft extends CubeNodeDraft<Extend> {
139
144
  follow: action,
140
145
  });
141
146
  this.editorState = editorState;
147
+ this.jsonFields = new CubeJsonFieldsState(editorState, original.id);
142
148
  const types = original.typing.kind === 'typed' ? original.typing.types : [];
143
149
  this.rows = original.columns.map(({ name, code, lambda }, index) => ({
144
150
  key: nextRowKey++,
@@ -214,6 +220,29 @@ export class CubeExtendDraft extends CubeNodeDraft<Extend> {
214
220
  this.change([...this.rows, this.newRow()]);
215
221
  }
216
222
 
223
+ /**
224
+ * Adds a row of this code, named as given or the next free name, compared
225
+ * as the input's and the other rows' names fold (a JSON field's "+ column",
226
+ * V.9); it replaces a new row still empty
227
+ */
228
+ addColumn(name: string, code: string): void {
229
+ const blank = this.rows.find(
230
+ (row) => row.code.trim() === NEW_EXPRESSION_CODE.trim() && !row.checked,
231
+ );
232
+ const others = this.rows.filter((row) => row !== blank);
233
+ this.change([
234
+ ...others,
235
+ {
236
+ key: nextRowKey++,
237
+ name: getFreeCubeColumnName(name, [
238
+ ...(this.inputSchema?.names() ?? []),
239
+ ...others.map((row) => row.name),
240
+ ]),
241
+ code,
242
+ },
243
+ ]);
244
+ }
245
+
217
246
  removeRow(key: number): void {
218
247
  this.change(this.rows.filter((row) => row.key !== key));
219
248
  }
@@ -19,6 +19,7 @@ import {
19
19
  Difference,
20
20
  Distinct,
21
21
  Drop,
22
+ Explode,
22
23
  ExpressionFilter,
23
24
  Pivot,
24
25
  Extend,
@@ -38,6 +39,7 @@ import { guaranteeType } from '@finos/legend-shared';
38
39
  import type { CubeEditorState } from '../CubeEditorState.js';
39
40
  import { CubeConcatDraft } from './CubeConcatDraft.js';
40
41
  import { CubeDifferenceDraft } from './CubeDifferenceDraft.js';
42
+ import { CubeExplodeDraft } from './CubeExplodeDraft.js';
41
43
  import { CubeExpressionFilterDraft } from './CubeExpressionFilterDraft.js';
42
44
  import { CubePivotDraft } from './CubePivotDraft.js';
43
45
  import { CubeExtendDraft } from './CubeExtendDraft.js';
@@ -113,6 +115,11 @@ export const CUBE_NODE_DRAFT_FACTORIES: ReadonlyMap<
113
115
  (node, editorState) =>
114
116
  new CubePivotDraft(guaranteeType(node, Pivot), editorState),
115
117
  ],
118
+ [
119
+ Explode.TYPE,
120
+ (node, editorState) =>
121
+ new CubeExplodeDraft(guaranteeType(node, Explode), editorState),
122
+ ],
116
123
  ]);
117
124
 
118
125
  /**
@@ -24,8 +24,9 @@ import {
24
24
  // The Sales sample (the review of the demo, 2026-10-13): a small table on an
25
25
  // in-memory DuckDB, which the engine builds from setup SQL on each
26
26
  // connection, as the H2 samples are. The H2 samples can't run the engine's
27
- // pivot(); this one can, so Pivot works on a sample out of the box. Its cubes
28
- // are direct-connection cubes: the connection is their model.
27
+ // pivot(); this one can, so Pivot works on a sample out of the box. Its orders
28
+ // hold JSON documents as text, for Parse as JSON (PLAN §11.20). Its cubes are
29
+ // direct-connection cubes: the connection is their model.
29
30
 
30
31
  /** The setup SQL that makes the table, one statement an entry */
31
32
  export const CUBE_SALES_SETUP_SQLS: readonly string[] = Object.freeze([
@@ -33,6 +34,10 @@ export const CUBE_SALES_SETUP_SQLS: readonly string[] = Object.freeze([
33
34
  'create schema demo',
34
35
  'create table demo.sales (id INTEGER PRIMARY KEY, country VARCHAR(10), product VARCHAR(10), year INTEGER, amount INTEGER, price DOUBLE)',
35
36
  "insert into demo.sales values (1, 'France', 'Tea', 2022, 120, 2.5), (2, 'France', 'Coffee', 2023, 80, 3.1), (3, 'France', 'Tea', 2024, 150, 2.7), (4, 'Germany', 'Tea', 2022, 90, 2.4), (5, 'Germany', 'Coffee', 2022, 60, 3.0), (6, 'Germany', 'Coffee', 2024, 110, 3.3), (7, 'Japan', 'Tea', 2023, 200, 2.9), (8, 'Japan', 'Tea', 2024, 40, 2.9), (9, 'France', 'Coffee', 2024, 30, 3.2), (10, 'Spain', 'Coffee', 2022, 70, 3.0)",
37
+ // orders as JSON documents in a text column (PLAN §11.20): Parse as JSON
38
+ // reads them as a Variant column, which Explore JSON and Explode Array use
39
+ 'create table demo.orders (id INTEGER PRIMARY KEY, document VARCHAR)',
40
+ `insert into demo.orders values (1, '{"customer":{"name":"Ann","tier":"gold","country":"France"},"items":[{"product":"Tea","qty":2,"price":2.5},{"product":"Coffee","qty":1,"price":3.1}],"paid":true,"placed":"2024-01-31","tags":["web","promo"]}'), (2, '{"customer":{"name":"Bob","tier":"silver","country":"Germany"},"items":[{"product":"Coffee","qty":3,"price":3.0}],"paid":false,"placed":"2024-02-02","tags":["store"]}'), (3, '{"customer":{"name":"Chloé","country":"France"},"items":[{"product":"Tea","qty":1,"price":2.7},{"product":"Tea","qty":4,"price":2.7},{"product":"Coffee","qty":1,"price":3.2}],"paid":true,"placed":"2024-02-14","tags":["web"]}'), (4, '{"customer":{"name":"Daichi","tier":"gold","country":"Japan"},"items":[{"product":"Tea","qty":5,"price":2.9}],"paid":true,"placed":"2024-03-01","tags":[]}'), (5, '{"customer":{"name":"Eva","tier":"bronze","country":"Spain"},"items":[],"paid":false,"placed":"2024-03-09","tags":["web","promo"]}'), (6, '{"customer":{"name":"Felix","tier":"silver","country":"Germany"},"items":[{"product":"Tea","qty":2,"price":2.4},{"product":"Coffee","qty":2,"price":3.3}],"paid":true,"placed":"2024-04-20","tags":["store","promo"]}'), (7, null), (8, '{"customer":{"name":"Gabi","tier":"gold","country":"Spain"},"items":[{"product":"Coffee","qty":1,"price":3.0}],"paid":true,"placed":"2024-05-05","tags":["web"]}')`,
36
41
  ]);
37
42
 
38
43
  /** The model of a Sales cube: an in-memory DuckDB connection with its setup SQL */
@@ -52,3 +57,4 @@ export const CUBE_SALES_DATABASE = CUBE_DIRECT_DATABASE_PATH;
52
57
  /** As a direct connection's coordinates store a name: quoted */
53
58
  export const CUBE_SALES_SCHEMA = '"demo"';
54
59
  export const CUBE_SALES_TABLE = '"sales"';
60
+ export const CUBE_SALES_ORDERS_TABLE = '"orders"';