@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,316 @@
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 { CaretDownIcon, CaretRightIcon, clsx } from '@finos/legend-art';
18
+ import { type SchemaColumn, TypeFamily } from '@finos/legend-cube';
19
+ import { flowResult } from 'mobx';
20
+ import { observer } from 'mobx-react-lite';
21
+ import {
22
+ CUBE_JSON_FIELDS_TITLE,
23
+ getCubeJsonFieldsStatus,
24
+ } from '../../__lib__/LegendCubeLabels.js';
25
+ import type { CubeJsonFieldsState } from '../../stores/variant/CubeJsonFieldsState.js';
26
+ import {
27
+ type CubeJsonField,
28
+ getCubeJsonFieldDefault,
29
+ getCubeJsonOfferExpression,
30
+ } from '../../stores/variant/CubeJsonOffers.js';
31
+ import { CubeButton } from '../CubeButton.js';
32
+
33
+ /** Whether a column holds JSON, whose fields an expression editor can open */
34
+ export const isCubeJsonColumn = (column: SchemaColumn): boolean =>
35
+ column.type.family === TypeFamily.VARIANT;
36
+
37
+ /**
38
+ * The caret beside a Variant column's chip, which opens or closes its fields;
39
+ * greyed, with the reason, on a database without JSON functions
40
+ */
41
+ export const CubeJsonFieldsToggle = observer(
42
+ (props: {
43
+ fields: CubeJsonFieldsState;
44
+ column: SchemaColumn;
45
+ disabled: boolean;
46
+ }) => {
47
+ const { fields, column, disabled } = props;
48
+ const open = fields.isOpen(column.name);
49
+ const problem = fields.databaseProblem;
50
+ return (
51
+ <button
52
+ type="button"
53
+ className={clsx(
54
+ 'flex h-full items-center rounded-sm border px-0.5 disabled:text-[var(--color-text-disabled)]',
55
+ open
56
+ ? 'border-[var(--color-accent)] text-[var(--color-accent)]'
57
+ : 'border-[var(--color-border-subtle)] hover:bg-[var(--color-bg-hover)]',
58
+ )}
59
+ aria-label={`Fields of ${column.name}`}
60
+ aria-expanded={open}
61
+ title={problem ?? (open ? undefined : CUBE_JSON_FIELDS_TITLE.OPEN)}
62
+ disabled={disabled || problem !== undefined}
63
+ onClick={() => {
64
+ // a failed read shows its message under the chips
65
+ flowResult(fields.toggle(column)).catch(() => undefined);
66
+ }}
67
+ >
68
+ <span className="font-mono text-xs">{'{}'}</span>
69
+ {open ? <CaretDownIcon /> : <CaretRightIcon />}
70
+ </button>
71
+ );
72
+ },
73
+ );
74
+
75
+ /** A field of the tree: its key, kind and share, what a click writes, its other forms, then its fields */
76
+ const CubeJsonFieldsRow = observer(
77
+ (props: {
78
+ fields: CubeJsonFieldsState;
79
+ column: string;
80
+ field: CubeJsonField;
81
+ depth: number;
82
+ disabled: boolean;
83
+ onInsert: (expression: string) => void;
84
+ onAddColumn: ((name: string, code: string) => void) | undefined;
85
+ }) => {
86
+ const { fields, column, field, depth, disabled, onInsert, onAddColumn } =
87
+ props;
88
+ const label = [column, ...field.path].join('.');
89
+ const share = Math.round(field.presence * 100);
90
+ const folded = fields.isFolded(field.path);
91
+ const byDefault = getCubeJsonFieldDefault(column, field);
92
+ const defaultKey = field.offers.find(
93
+ ({ key }) => key === 'value' || key === 'json',
94
+ )?.key;
95
+ const forms = field.offers.filter((offer) => offer.key !== defaultKey);
96
+ const formsShown = fields.areFormsShown(field.path);
97
+ return (
98
+ <li>
99
+ <div
100
+ className="flex items-center gap-1 py-0.5 pr-1"
101
+ style={{ paddingLeft: `${4 + depth * 14}px` }}
102
+ >
103
+ {field.children.length ? (
104
+ <button
105
+ type="button"
106
+ className="flex w-4 shrink-0 items-center justify-center text-[var(--color-text-secondary)]"
107
+ aria-label={`${folded ? 'Unfold' : 'Fold'} ${label}`}
108
+ onClick={() => fields.toggleFolded(field.path)}
109
+ >
110
+ {folded ? <CaretRightIcon /> : <CaretDownIcon />}
111
+ </button>
112
+ ) : (
113
+ <span className="w-4 shrink-0" />
114
+ )}
115
+ <button
116
+ type="button"
117
+ className="flex min-w-0 flex-1 items-baseline gap-2 rounded-sm px-1 text-left enabled:hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
118
+ aria-label={`Write ${label}`}
119
+ title={`Write ${byDefault.expression}`}
120
+ disabled={disabled}
121
+ onClick={() => onInsert(byDefault.expression)}
122
+ >
123
+ <span className="break-all font-mono text-sm">
124
+ {field.path.at(-1) ?? column}
125
+ </span>
126
+ <span className="shrink-0 text-xs text-[var(--color-text-secondary)]">
127
+ {field.description}
128
+ </span>
129
+ {share < 100 && (
130
+ <span className="ml-auto shrink-0 text-xs text-[var(--color-text-muted)]">
131
+ {share}%
132
+ </span>
133
+ )}
134
+ </button>
135
+ {onAddColumn && byDefault.column && (
136
+ <button
137
+ type="button"
138
+ className="shrink-0 rounded-sm border border-[var(--color-border-subtle)] px-1 text-sm enabled:hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
139
+ aria-label={`Add ${label} as a column`}
140
+ title={`A new column ${byDefault.column.name}: ${byDefault.column.code}`}
141
+ disabled={disabled}
142
+ onClick={() => {
143
+ if (byDefault.column) {
144
+ onAddColumn(byDefault.column.name, byDefault.column.code);
145
+ }
146
+ }}
147
+ >
148
+ + column
149
+ </button>
150
+ )}
151
+ {forms.length > 0 && (
152
+ <button
153
+ type="button"
154
+ className={clsx(
155
+ 'shrink-0 rounded-sm border px-1 text-sm enabled:hover:bg-[var(--color-bg-hover)]',
156
+ formsShown
157
+ ? 'border-[var(--color-accent)]'
158
+ : 'border-[var(--color-border-subtle)]',
159
+ )}
160
+ aria-label={`Other forms of ${label}`}
161
+ aria-expanded={formsShown}
162
+ title={CUBE_JSON_FIELDS_TITLE.FORMS}
163
+ onClick={() => fields.toggleForms(field.path)}
164
+ >
165
+ ···
166
+ </button>
167
+ )}
168
+ </div>
169
+ {formsShown && (
170
+ <ul
171
+ aria-label={`Forms of ${label}`}
172
+ className="my-0.5 rounded-sm border border-[var(--color-border-subtle)]"
173
+ style={{ marginLeft: `${22 + depth * 14}px` }}
174
+ >
175
+ {forms.map((offer) => {
176
+ const expression = getCubeJsonOfferExpression(offer);
177
+ return (
178
+ <li key={offer.key}>
179
+ <button
180
+ type="button"
181
+ className="flex w-full items-baseline gap-2 px-2 py-0.5 text-left text-sm enabled:hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
182
+ title={
183
+ expression === undefined
184
+ ? CUBE_JSON_FIELDS_TITLE.EXPLODE
185
+ : `Write ${expression}`
186
+ }
187
+ disabled={disabled || expression === undefined}
188
+ onClick={() => {
189
+ if (expression !== undefined) {
190
+ onInsert(expression);
191
+ }
192
+ }}
193
+ >
194
+ {offer.label}
195
+ <span className="text-xs text-[var(--color-text-muted)]">
196
+ {offer.explode ? 'Explode Array' : offer.type}
197
+ </span>
198
+ </button>
199
+ </li>
200
+ );
201
+ })}
202
+ </ul>
203
+ )}
204
+ {!folded && field.children.length > 0 && (
205
+ <ul>
206
+ {field.children.map((child) => (
207
+ <CubeJsonFieldsRow
208
+ key={JSON.stringify(child.path)}
209
+ {...props}
210
+ field={child}
211
+ depth={depth + 1}
212
+ />
213
+ ))}
214
+ </ul>
215
+ )}
216
+ </li>
217
+ );
218
+ },
219
+ );
220
+
221
+ /**
222
+ * A Variant input column's fields in an expression editor (PLAN §11.20, V.9),
223
+ * under the input columns while its caret is open: what was read, a search,
224
+ * and the field tree. A field clicked writes its expression where the
225
+ * editor's chips write; "+ column", where the editor has rows, adds one; its
226
+ * other forms write theirs, one row per element being a step of its own.
227
+ */
228
+ export const CubeJsonFieldsTree = observer(
229
+ (props: {
230
+ fields: CubeJsonFieldsState;
231
+ disabled: boolean;
232
+ onInsert: (expression: string) => void;
233
+ onAddColumn?: ((name: string, code: string) => void) | undefined;
234
+ }) => {
235
+ const { fields, disabled, onInsert, onAddColumn } = props;
236
+ const { column } = fields;
237
+ if (!column) {
238
+ return null;
239
+ }
240
+ const sample =
241
+ fields.sample?.column === column.name ? fields.sample.sample : undefined;
242
+ const shown = fields.shownFields;
243
+ return (
244
+ <div
245
+ aria-label={`Fields of ${column.name}, read`}
246
+ className="flex flex-col gap-1 rounded-sm border border-[var(--color-accent)] p-1"
247
+ >
248
+ <div className="flex items-center gap-2 text-sm text-[var(--color-text-secondary)]">
249
+ <span role="status" className="min-w-0 flex-1">
250
+ {fields.isReading
251
+ ? `Reading ${column.name}…`
252
+ : sample
253
+ ? getCubeJsonFieldsStatus(sample)
254
+ : ''}
255
+ </span>
256
+ {fields.isReading ? (
257
+ <CubeButton onClick={() => fields.stop()}>Stop</CubeButton>
258
+ ) : (
259
+ sample && (
260
+ <CubeButton
261
+ title={
262
+ sample.complete
263
+ ? CUBE_JSON_FIELDS_TITLE.READ_AGAIN
264
+ : CUBE_JSON_FIELDS_TITLE.EVERY_ROW
265
+ }
266
+ onClick={() => {
267
+ flowResult(fields.read(!sample.complete)).catch(
268
+ () => undefined,
269
+ );
270
+ }}
271
+ >
272
+ {sample.complete ? 'Read again' : 'Read every row'}
273
+ </CubeButton>
274
+ )
275
+ )}
276
+ </div>
277
+ {fields.error && (
278
+ <div
279
+ role="alert"
280
+ className="break-words text-sm text-[var(--color-status-error)]"
281
+ >
282
+ {fields.error}
283
+ </div>
284
+ )}
285
+ {fields.fields && (
286
+ <>
287
+ <input
288
+ className="h-6 rounded-sm border border-[var(--color-border-default)] bg-[var(--color-bg-input)] px-1 text-sm"
289
+ placeholder="Find a field"
290
+ aria-label="Find a field"
291
+ value={fields.search}
292
+ onChange={(event) => fields.setSearch(event.target.value)}
293
+ />
294
+ {shown ? (
295
+ <ul aria-label={`Fields of ${column.name}`}>
296
+ <CubeJsonFieldsRow
297
+ fields={fields}
298
+ column={column.name}
299
+ field={shown}
300
+ depth={0}
301
+ disabled={disabled}
302
+ onInsert={onInsert}
303
+ onAddColumn={onAddColumn}
304
+ />
305
+ </ul>
306
+ ) : (
307
+ <div className="px-1 text-sm text-[var(--color-text-muted)]">
308
+ No field matches.
309
+ </div>
310
+ )}
311
+ </>
312
+ )}
313
+ </div>
314
+ );
315
+ },
316
+ );
@@ -23,6 +23,7 @@ import {
23
23
  Drop,
24
24
  ExpressionFilter,
25
25
  Pivot,
26
+ Explode,
26
27
  Extend,
27
28
  Filter,
28
29
  FunctionSource,
@@ -42,6 +43,7 @@ import type { CubeEditorState } from '../../stores/CubeEditorState.js';
42
43
  import type { CubeNodeDraft } from '../../stores/editors/CubeNodeDraft.js';
43
44
  import { CubeConcatEditor } from './CubeConcatEditor.js';
44
45
  import { CubeDataProductSourceEditor } from './CubeDataProductSourceEditor.js';
46
+ import { CubeExplodeEditor } from './CubeExplodeEditor.js';
45
47
  import { CubeExpressionFilterEditor } from './CubeExpressionFilterEditor.js';
46
48
  import { CubePivotEditor } from './CubePivotEditor.js';
47
49
  import { CubeDifferenceEditor } from './CubeDifferenceEditor.js';
@@ -100,6 +102,7 @@ export const CUBE_NODE_EDITORS: ReadonlyMap<
100
102
  [Filter.TYPE, CubeFilterEditor],
101
103
  [ExpressionFilter.TYPE, CubeExpressionFilterEditor],
102
104
  [Pivot.TYPE, CubePivotEditor],
105
+ [Explode.TYPE, CubeExplodeEditor],
103
106
  [Restrict.TYPE, CubeRestrictEditor],
104
107
  [Rename.TYPE, CubeRenameEditor],
105
108
  [Distinct.TYPE, CubeDistinctEditor],
@@ -28,6 +28,10 @@ import {
28
28
  ROW_LIMIT_WARNING_THRESHOLD,
29
29
  } from '../../__lib__/LegendCubeLabels.js';
30
30
  import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
31
+ import {
32
+ CUBE_TOUR_ANCHOR,
33
+ cubeTourAnchor,
34
+ } from '../../__lib__/LegendCubeTour.js';
31
35
  import {
32
36
  CubeDataProductRunErrorKind,
33
37
  type CubeDataProductRuntimeState,
@@ -63,7 +67,10 @@ const CubeRowLimitInput = observer(
63
67
  }
64
68
  };
65
69
  return (
66
- <div className="flex items-center gap-1">
70
+ <div
71
+ className="flex items-center gap-1"
72
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.EXECUTE)}
73
+ >
67
74
  <label className="flex items-center gap-1 text-base">
68
75
  Rows
69
76
  <input
@@ -123,6 +130,7 @@ const CubeGridModeToggle = observer(
123
130
  className="flex overflow-hidden rounded-sm border border-[var(--color-border-default)]"
124
131
  role="group"
125
132
  aria-label="Grid mode"
133
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.GRID_MODE)}
126
134
  >
127
135
  {option(
128
136
  CubeGridMode.SERVER_SIDE,
@@ -156,12 +164,17 @@ const CubeGridToolbar = observer(
156
164
  data-testid={LEGEND_CUBE_TEST_ID.GRID_TOOLBAR}
157
165
  >
158
166
  {execution.isRunning ? (
159
- <CubeButton title="Stop the run" onClick={() => execution.stop()}>
167
+ <CubeButton
168
+ title="Stop the run"
169
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.EXECUTE)}
170
+ onClick={() => execution.stop()}
171
+ >
160
172
  Stop
161
173
  </CubeButton>
162
174
  ) : (
163
175
  <CubeButton
164
176
  primary={true}
177
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.EXECUTE)}
165
178
  disabled={!execution.canExecute}
166
179
  title={
167
180
  disabledReasons.length
@@ -224,7 +237,11 @@ const CubeGridToolbar = observer(
224
237
  </span>
225
238
  )}
226
239
  {result.sql.length > 0 && (
227
- <CubeButton aria-haspopup="dialog" onClick={onShowSql}>
240
+ <CubeButton
241
+ aria-haspopup="dialog"
242
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.SHOW_SQL)}
243
+ onClick={onShowSql}
244
+ >
228
245
  Show SQL
229
246
  </CubeButton>
230
247
  )}
@@ -233,6 +250,7 @@ const CubeGridToolbar = observer(
233
250
  <span className="flex-1" />
234
251
  <CubeButton
235
252
  aria-expanded={showRuns}
253
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.RUNS)}
236
254
  title="The latest runs, fetches and exports, with their SQL"
237
255
  onClick={onToggleRuns}
238
256
  >
@@ -23,6 +23,7 @@ import {
23
23
  TypeFamily,
24
24
  } from '@finos/legend-cube';
25
25
  import type { CubeResultValue } from '../../graph-manager/CubeEngine.js';
26
+ import { formatCubeJsonLine } from '../../stores/variant/CubeJson.js';
26
27
 
27
28
  // How the grid shows and sorts values (PLAN §9, Settled before M1.8).
28
29
  // Integer and Decimal values arrive as their exact text and are never turned
@@ -153,9 +154,11 @@ export const groupThousands = (text: string): string => {
153
154
  * How the grid shows a value of a column of the family (PLAN §9, user,
154
155
  * 2026-10-10): Decimal and Number values keep every digit,
155
156
  * their thousands grouped; Float values are rounded to
156
- * `CUBE_GRID_FLOAT_DECIMAL_PLACES` places, grouped too; Integer values, and
157
- * everything else, show as they are, so ids and years get no commas. The
158
- * value itself is unchanged, so copying a cell copies its exact value.
157
+ * `CUBE_GRID_FLOAT_DECIMAL_PLACES` places, grouped too; a Variant's JSON on
158
+ * one line, `{name: ann, tier: gold}` (`formatCubeJsonLine`, PLAN §11.20);
159
+ * Integer values, and everything else, show as they are, so ids and years get
160
+ * no commas. The value itself is unchanged, so copying a cell copies its
161
+ * exact value.
159
162
  */
160
163
  export const formatCellValue = (
161
164
  value: Exclude<CubeResultValue, null>,
@@ -181,6 +184,8 @@ export const formatCellValue = (
181
184
  Number(rounded) === 0 ? rounded.replace(/^-/u, '') : rounded,
182
185
  );
183
186
  }
187
+ case TypeFamily.VARIANT:
188
+ return formatCubeJsonLine(text);
184
189
  default:
185
190
  return text;
186
191
  }