@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-cube-builder",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Legend Cube builder: the canvas UI and the Legend engine adapter of the Legend Cube query builder",
5
5
  "keywords": [
6
6
  "legend",
@@ -47,7 +47,7 @@
47
47
  "@dagrejs/dagre": "1.1.4",
48
48
  "@finos/legend-application": "16.0.129",
49
49
  "@finos/legend-art": "7.1.169",
50
- "@finos/legend-cube": "0.0.5",
50
+ "@finos/legend-cube": "0.0.6",
51
51
  "@finos/legend-graph": "32.8.0",
52
52
  "@finos/legend-lego": "2.0.231",
53
53
  "@finos/legend-server-depot": "6.1.24",
@@ -22,6 +22,7 @@ import {
22
22
  Distinct,
23
23
  Drop,
24
24
  ExpressionFilter,
25
+ Explode,
25
26
  Extend,
26
27
  Filter,
27
28
  FunctionSource,
@@ -67,6 +68,9 @@ export const CUBE_NODE_HELP_TEXT: Readonly<Record<string, string>> = {
67
68
  'Reduces the number of rows in the previous data set, removing the specified number of rows from the beginning of the data set.',
68
69
  [Extend.TYPE]:
69
70
  'Extends outgoing data set with new columns produced by expressions.',
71
+ /** Added by Cube: the original has no JSON operations (PLAN §11.20) */
72
+ [Explode.TYPE]:
73
+ 'Makes a row for each element of an array in a JSON (Variant) column of the previous data set, keeping its columns and adding the element as a new column. A row whose array is empty, or missing, is kept once, with an empty element.',
70
74
  [Filter.TYPE]:
71
75
  'Reduces the number of rows in the previous data set, keeping only rows matching the specified criteria.',
72
76
  /** Added by Cube: the original has no filter by expression (PLAN §11.11) */
@@ -141,13 +141,13 @@ const TYPE_FAMILY_LABELS: Partial<Record<TypeFamily, string>> = {
141
141
 
142
142
  /**
143
143
  * A column's type as the editors show it: the kind of value, and whether it
144
- * can be empty, e.g. `Text, can be empty`; an enumeration, or a type Cube
145
- * doesn't know, by its name. The exact type (`getExactColumnTypeLabel`) is
146
- * on hover.
144
+ * is optional (can be empty), e.g. `Text, optional`; an enumeration, or a
145
+ * type Cube doesn't know, by its name. The exact type
146
+ * (`getExactColumnTypeLabel`) is on hover.
147
147
  */
148
148
  export const getColumnTypeLabel = (column: SchemaColumn): string =>
149
149
  `${TYPE_FAMILY_LABELS[column.type.family] ?? column.type.displayName}${
150
- column.nullable ? ', can be empty' : ''
150
+ column.nullable ? ', optional' : ''
151
151
  }`;
152
152
 
153
153
  const listColumns = (columns: readonly SchemaColumn[]): string =>
@@ -188,6 +188,12 @@ export const CUBE_SORT_COLUMN_DISABLED_REASON = {
188
188
  };
189
189
 
190
190
  /** Why the grid's quick actions (spec §12.4) can't be used */
191
+ /** Why a Variant cell's Open JSON can't be used (PLAN §11.20) */
192
+ export const CUBE_JSON_DISABLED_REASON = {
193
+ NOT_A_ROW: "A group row's cell holds no row's document.",
194
+ EMPTY: 'This cell is empty.',
195
+ };
196
+
191
197
  export const CUBE_QUICK_ACTION_DISABLED_REASON = {
192
198
  STALE_ROWS:
193
199
  'Execute again: these rows are from an earlier version of the query.',
@@ -343,6 +349,50 @@ export const PIVOT_EDITOR_NOTES = [
343
349
  "Pivot runs on Snowflake, Databricks, SingleStore and DuckDB; other databases, such as H2 and Postgres, can't run it.",
344
350
  ] as const;
345
351
 
352
+ /** Why the Explode Array editor won't pick a column (PLAN §11.20) */
353
+ export const EXPLODE_COLUMN_DISABLED_REASON = {
354
+ /** a column of another type than Variant */
355
+ NOT_JSON: 'not JSON',
356
+ } as const;
357
+
358
+ /** Under the Explode Array editor (PLAN §11.20) */
359
+ export const EXPLODE_EDITOR_NOTES = [
360
+ 'One row per element of the array, the other columns repeated on each. A row whose array is empty, or that has none, is kept once, with an empty element.',
361
+ "Find arrays reads the column's first values, as Explore JSON does, and lists the arrays they hold. Keys can be typed too: none when the column is the array.",
362
+ 'The element is JSON: Explore JSON, in its column menu, makes columns of its fields. Explode Array runs on Snowflake, Databricks, Postgres and DuckDB.',
363
+ ] as const;
364
+
365
+ /** Explore JSON's one row per element offer, as its tooltip explains it (PLAN §11.20) */
366
+ export const CUBE_JSON_EXPLODE_OFFER_TITLE =
367
+ 'Adds an Explode Array node: a row for each element, the element as a new JSON column. One array at a time.';
368
+
369
+ /** The tooltips of a Variant column's fields in an expression editor (PLAN §11.20, V.9) */
370
+ export const CUBE_JSON_FIELDS_TITLE = {
371
+ OPEN: 'Show its fields, read from its first values just before this step: listed in Runs; a field clicked is written into the expression',
372
+ FORMS:
373
+ 'Other forms of the field: counts, lists, the first element, totals, as JSON',
374
+ EXPLODE:
375
+ 'One row per element is a step of its own: add Explode Array from the palette, or from Explore JSON in the grid',
376
+ READ_AGAIN: 'Read the column again',
377
+ EVERY_ROW:
378
+ 'Read every value of the column, for the shape of the whole column: listed in Runs, and it can be stopped',
379
+ } as const;
380
+
381
+ /** What a Variant column's fields were read from, in an expression editor (PLAN §11.20, V.9) */
382
+ export const getCubeJsonFieldsStatus = (sample: {
383
+ readonly rows: number;
384
+ readonly complete: boolean;
385
+ }): string => {
386
+ const count = sample.rows.toLocaleString('en-US');
387
+ return sample.complete
388
+ ? `All ${count} values read, just before this step`
389
+ : `The first ${count} values read, just before this step`;
390
+ };
391
+
392
+ /** Says that Find arrays found none in the values read (PLAN §11.20) */
393
+ export const EXPLODE_NO_ARRAYS_NOTICE =
394
+ 'No array in the values read: type the keys to one.';
395
+
346
396
  /** Says that Load values found no value to list (PLAN §11.12) */
347
397
  export const PIVOT_NO_VALUES_NOTICE =
348
398
  'The pivot column has no value to list: its rows are all empty, or there are none.';
@@ -364,7 +414,7 @@ export const PARTITION_EDITOR_NOTES = [
364
414
  "Rank, Dense Rank and Row Number need a sort column. Tied rows share a rank: Rank then skips numbers and Dense Rank doesn't. Row Number numbers every row, and rows tied on this node's sort columns get their numbers in no set order: add a sort column that tells them apart, such as an id, for a stable top N.",
365
415
  'NTile, Percent Rank and Cumulative Distribution need a sort column too. NTile splits each partition into buckets of near-equal size, numbered from 1 in the sort order; Percent Rank and Cumulative Distribution give each row its place in its partition, from 0 to 1.',
366
416
  "Lag and Lead read a column from the row so many rows before or after in the sort order, and are empty at the partition's edges. First and Last read it from the partition's first and last rows, the same on every row. They need a sort column, and rows tied on it may give any of them.",
367
- "On ClickHouse, First and Last skip empty values, and Lag and Lead show a column's default, such as 0, at the partition's edges instead of empty, unless the column can be empty there.",
417
+ "On ClickHouse, First and Last skip empty values, and Lag and Lead show a column's default, such as 0, at the partition's edges instead of empty, unless the column is optional there.",
368
418
  "Their sort columns also make this node's other functions run. For totals over the whole partition, add another Apply Window Functions without sort columns.",
369
419
  'Empty sort values come last when ascending and first when descending on H2; other databases may differ.',
370
420
  "A Sort before this node doesn't order the window: its sort columns do. The rows keep the order they came in.",
@@ -391,8 +441,9 @@ export const DIFFERENCE_EDITOR_NOTES = [
391
441
  /** What the Extend editor says about its expressions (PLAN §11.7) */
392
442
  export const EXTEND_EDITOR_NOTES = [
393
443
  'Write each expression as a lambda of one row, such as x | $x.PRICE * $x.QTY: $x.PRICE reads the column PRICE. A column can use the new columns above it.',
394
- 'A column that can be empty needs ->toOne() before arithmetic, such as $x.QTY->toOne() * 2; an empty value still gives an empty result. Every new column can be empty.',
444
+ 'An optional column needs ->toOne() before arithmetic, such as $x.QTY->toOne() * 2; an empty value still gives an empty result. Every new column is optional.',
395
445
  "Validate (F10) checks the expressions with the engine, types them, and plans them for this cube's database: some functions type, but not every database runs them.",
446
+ 'A JSON (Variant) column’s {} opens its fields, read from its first values just before this step: a field clicked is written into the expression last used, and + column adds it as a new column.',
396
447
  ];
397
448
 
398
449
  /** Why the Extend editor's Apply waits (PLAN §11.7) */
@@ -434,14 +485,15 @@ export const EXPRESSION_FILTER_PARENTHESES_HINT =
434
485
  export const EXPRESSION_FILTER_EDITOR_NOTES = [
435
486
  "A condition of one row, written as in Query's text mode: x | $x.SHIPPED_DATE > $x.REQUIRED_DATE. The rows for which it is true are kept, in their order.",
436
487
  'Put each comparison in parentheses when you combine them: ($x.A > 1) && ($x.B > 3). In Pure, &&, || and ! bind as tightly as >, <, >= and <=; == binds tighter.',
437
- 'A column that can be empty, compared directly ($x.A > 6), fails the condition when it is empty and passes its negation. After ->toOne(), an empty value fails both.',
488
+ 'An optional column, compared directly ($x.A > 6), fails the condition when it is empty and passes its negation. After ->toOne(), an empty value fails both.',
438
489
  "Validate (F10) checks the condition with the engine and plans it for the cube's database.",
439
490
  "startsWith, endsWith and contains can't take a value with a backslash (\\) yet, nor, on DuckDB, one with % or _.",
491
+ 'A JSON (Variant) column’s {} opens its fields, read from its first values just before this step: a field clicked is written into the condition.',
440
492
  ] as const;
441
493
 
442
494
  /** The hint after an engine error on a column that can be empty, in arithmetic (PLAN §11.7) */
443
495
  export const EXTEND_TO_ONE_HINT =
444
- 'A column that can be empty needs ->toOne() first, such as $x.QTY->toOne() + 1.';
496
+ 'An optional column needs ->toOne() first, such as $x.QTY->toOne() + 1.';
445
497
 
446
498
  /** The problem a database refusing an Extend's or a Filter by Expression's plan gives (PLAN §11.7, §11.11) */
447
499
  export const getExtendPlanProblem = (
@@ -0,0 +1,48 @@
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
+ /**
18
+ * The anchor Legend Marketplace's data product viewer gives an access point
19
+ * group, which it reads from the URL hash on load: as Legend Query writes it
20
+ * (`EXTERNAL_APPLICATION_NAVIGATION__generateMarketplaceDataProductUrl`)
21
+ *
22
+ * @external_application_navigation This depends on Legend Marketplace routing and is hardcoded so it's potentially brittle
23
+ */
24
+ const EXTERNAL_APPLICATION_NAVIGATION__generateCubeAccessPointGroupAnchor = (
25
+ accessPointGroupId: string,
26
+ ): string =>
27
+ encodeURIComponent(
28
+ `apg-${accessPointGroupId}`.trim().toLowerCase().replace(/\s+/gu, '-'),
29
+ );
30
+
31
+ /**
32
+ * A deployed data product's page in Legend Marketplace, at its access point
33
+ * group when one is given, as Legend Query links to it (PLAN §11.18)
34
+ *
35
+ * @external_application_navigation This depends on Legend Marketplace routing and is hardcoded so it's potentially brittle
36
+ */
37
+ export const EXTERNAL_APPLICATION_NAVIGATION__generateCubeMarketplaceDataProductUrl =
38
+ (
39
+ marketplaceApplicationUrl: string,
40
+ dataProductId: string,
41
+ deploymentId: string,
42
+ accessPointGroupId?: string | undefined,
43
+ ): string =>
44
+ `${marketplaceApplicationUrl}/dataProduct/deployed/${dataProductId}/${deploymentId}${
45
+ accessPointGroupId
46
+ ? `#${EXTERNAL_APPLICATION_NAVIGATION__generateCubeAccessPointGroupAnchor(accessPointGroupId)}`
47
+ : ''
48
+ }`;
@@ -38,4 +38,6 @@ export enum LEGEND_CUBE_TEST_ID {
38
38
  GRID_TOOLBAR = 'legend-cube__grid-toolbar',
39
39
  EXECUTION_ERROR = 'legend-cube__execution-error',
40
40
  SQL_PANEL = 'legend-cube__sql-panel',
41
+ JSON_VIEWER = 'legend-cube__json-viewer',
42
+ JSON_EXPLORER = 'legend-cube__json-explorer',
41
43
  }
@@ -0,0 +1,173 @@
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
+ EXECUTE_SHORTCUT_LABEL,
19
+ REDO_SHORTCUT_LABEL,
20
+ UNDO_SHORTCUT_LABEL,
21
+ } from './LegendCubeCommand.js';
22
+
23
+ // The editor's guided tour (PLAN §11.21): its steps, in order, each pointing
24
+ // at the controls marked with its anchor. A control joins a step by carrying
25
+ // `cubeTourAnchor(...)`; a step whose controls aren't on the page (the graph
26
+ // hidden, no run yet, a host without examples) is skipped. A new step adds
27
+ // its anchor here, its text below, and the anchor on its controls.
28
+
29
+ /** The attribute that marks a control as a tour step's */
30
+ export const CUBE_TOUR_ANCHOR_ATTRIBUTE = 'data-cube-tour';
31
+
32
+ export enum CUBE_TOUR_ANCHOR {
33
+ SOURCES = 'sources',
34
+ STEPS = 'steps',
35
+ CANVAS = 'canvas',
36
+ RUNNING_STEP = 'running-step',
37
+ EXECUTE = 'execute',
38
+ GRID_MODE = 'grid-mode',
39
+ RESULTS = 'results',
40
+ SHOW_SQL = 'show-sql',
41
+ RUNS = 'runs',
42
+ CUBE_MENU = 'cube-menu',
43
+ ADD_ITEMS = 'add-items',
44
+ EXAMPLES = 'examples',
45
+ VIEW = 'view',
46
+ PAGE_MENU = 'page-menu',
47
+ }
48
+
49
+ /** The props that mark a control as a tour step's */
50
+ export const cubeTourAnchor = (
51
+ anchor: CUBE_TOUR_ANCHOR,
52
+ ): { [CUBE_TOUR_ANCHOR_ATTRIBUTE]: string } => ({
53
+ [CUBE_TOUR_ANCHOR_ATTRIBUTE]: anchor,
54
+ });
55
+
56
+ /** Where a step's card sits beside its controls */
57
+ export type CubeTourPlacement = 'top' | 'bottom' | 'left' | 'right';
58
+
59
+ export interface CubeTourStep {
60
+ readonly title: string;
61
+ readonly text: string;
62
+ /** The controls it points at; none for a card in the middle of the page */
63
+ readonly anchor?: CUBE_TOUR_ANCHOR | undefined;
64
+ readonly placement?: CubeTourPlacement | undefined;
65
+ }
66
+
67
+ /** The example the tour opens on an empty cube, where examples are offered */
68
+ export const CUBE_TOUR_EXAMPLE_ID = 'northwind-top-customers';
69
+
70
+ export const CUBE_EDITOR_TOUR: readonly CubeTourStep[] = Object.freeze([
71
+ {
72
+ title: 'Welcome to Legend Cube',
73
+ text: 'A cube is a query built as a chain of steps: a source of rows, then steps that sort, group, filter or join them. This tour shows where everything is: Next (or the → key) moves on, and Esc ends it.',
74
+ },
75
+ {
76
+ anchor: CUBE_TOUR_ANCHOR.SOURCES,
77
+ placement: 'right',
78
+ title: 'Sources',
79
+ text: 'Where the rows come from: a database table, a function, a data product or a service. Click one, or drop it on the canvas.',
80
+ },
81
+ {
82
+ anchor: CUBE_TOUR_ANCHOR.STEPS,
83
+ placement: 'right',
84
+ title: 'Steps',
85
+ text: 'Each step works on the rows of the step before it. Click one to add it after the selected step, or drop it on the canvas. A greyed-out step says why when you hover over it.',
86
+ },
87
+ {
88
+ anchor: CUBE_TOUR_ANCHOR.CANVAS,
89
+ placement: 'bottom',
90
+ title: 'The graph',
91
+ text: 'Your query, from left to right. Click a step to edit it: its editor opens beside it, with a ? that says what the step does.',
92
+ },
93
+ {
94
+ anchor: CUBE_TOUR_ANCHOR.RUNNING_STEP,
95
+ placement: 'bottom',
96
+ title: 'The step that runs',
97
+ text: 'Execute runs the query up to the step marked Runs. To run up to another step, open its editor and click Run this step.',
98
+ },
99
+ {
100
+ anchor: CUBE_TOUR_ANCHOR.EXECUTE,
101
+ placement: 'top',
102
+ title: 'Execute',
103
+ text: `Runs the query and shows its rows below (${EXECUTE_SHORTCUT_LABEL}). Rows caps how many rows come back.`,
104
+ },
105
+ {
106
+ anchor: CUBE_TOUR_ANCHOR.GRID_MODE,
107
+ placement: 'top',
108
+ title: 'Server-side or Local',
109
+ text: 'Server-side: each sort and drill-down in the grid runs on the engine, over every row of the query. Local: the grid groups, pivots and filters the rows already fetched, in your browser.',
110
+ },
111
+ {
112
+ anchor: CUBE_TOUR_ANCHOR.RESULTS,
113
+ placement: 'top',
114
+ title: 'The results',
115
+ text: 'The rows of the last run. Right-click a cell to sort, group or filter by its column as a new step, drill down, format a number column, or export the rows.',
116
+ },
117
+ {
118
+ anchor: CUBE_TOUR_ANCHOR.SHOW_SQL,
119
+ placement: 'top',
120
+ title: 'Show SQL',
121
+ text: 'The SQL the last run sent to the database.',
122
+ },
123
+ {
124
+ anchor: CUBE_TOUR_ANCHOR.RUNS,
125
+ placement: 'top',
126
+ title: 'Runs',
127
+ text: 'The latest runs, fetches and exports, each with its SQL and how long it took.',
128
+ },
129
+ {
130
+ anchor: CUBE_TOUR_ANCHOR.CUBE_MENU,
131
+ placement: 'bottom',
132
+ title: 'The Cube menu',
133
+ text: 'Start a new cube, import or export one, or read a Pure query in as steps with Import Pure.',
134
+ },
135
+ {
136
+ anchor: CUBE_TOUR_ANCHOR.ADD_ITEMS,
137
+ placement: 'bottom',
138
+ title: 'Add Items',
139
+ text: "The palette's sources and steps, as a menu.",
140
+ },
141
+ {
142
+ anchor: CUBE_TOUR_ANCHOR.EXAMPLES,
143
+ placement: 'bottom',
144
+ title: 'Examples',
145
+ text: 'Ready-made cubes on sample data, to try things out.',
146
+ },
147
+ {
148
+ anchor: CUBE_TOUR_ANCHOR.VIEW,
149
+ placement: 'bottom',
150
+ title: 'Undo, Show Pure, Hide graph',
151
+ text: `Undo (${UNDO_SHORTCUT_LABEL}) and Redo (${REDO_SHORTCUT_LABEL}) every change. Show Pure shows the Pure query Execute runs. Hide graph leaves more room for the results.`,
152
+ },
153
+ {
154
+ anchor: CUBE_TOUR_ANCHOR.PAGE_MENU,
155
+ placement: 'right',
156
+ title: "That's the tour",
157
+ text: 'Take it again from this menu whenever you like.',
158
+ },
159
+ ]);
160
+
161
+ export const CUBE_TOUR_LABEL = {
162
+ START: 'Take the tour',
163
+ /** The empty canvas's offer */
164
+ OFFER: 'New to Legend Cube?',
165
+ NEXT: 'Next',
166
+ BACK: 'Back',
167
+ DONE: 'Done',
168
+ END: 'End the tour',
169
+ progress: (step: number, steps: number): string => `${step} of ${steps}`,
170
+ /** Added to the first step when the tour opened its example */
171
+ EXAMPLE_OPENED:
172
+ 'The tour opened the example "Top customers by orders" to show you around; Undo puts the empty cube back.',
173
+ };
@@ -0,0 +1,119 @@
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
+ buildSchemasAndValidity,
19
+ createNodeRegistry,
20
+ Extend,
21
+ type ModelContext,
22
+ Query,
23
+ RelationalTableSource,
24
+ Schema,
25
+ } from '@finos/legend-cube';
26
+ import type { CubeEngine } from '../graph-manager/CubeEngine.js';
27
+ import {
28
+ buildCubeExtendTypingLambdas,
29
+ readCubeExtendTyping,
30
+ } from '../stores/CubeExtendTyping.js';
31
+ import type { CubeExample } from '../stores/CubeExamples.js';
32
+
33
+ /**
34
+ * The query with each Extend typed by the engine, in node order, as the
35
+ * editor types them in the background
36
+ */
37
+ export const TEST__typeExtendsOnEngine = async (
38
+ engine: CubeEngine,
39
+ model: ModelContext,
40
+ input: Query,
41
+ ): Promise<Query> => {
42
+ const registry = createNodeRegistry();
43
+ let query = input;
44
+ for (const node of query.nodes) {
45
+ if (node instanceof Extend) {
46
+ const columns = node.columns.map(({ name, lambda }) => ({
47
+ name,
48
+ lambda: lambda as NonNullable<typeof lambda>,
49
+ }));
50
+ const [inputId] = query.getInputIds(node.id);
51
+ const answers = await engine.typeLambdas(
52
+ model,
53
+ buildCubeExtendTypingLambdas(query, node, columns, registry, false),
54
+ );
55
+ const { typing } = readCubeExtendTyping(
56
+ answers,
57
+ node,
58
+ buildSchemasAndValidity(query, registry.queryRules).schemas.get(
59
+ inputId ?? '',
60
+ ) as Schema,
61
+ columns,
62
+ undefined,
63
+ );
64
+ query = query.replace(
65
+ new Extend(node.id, node.columns, typing, node.rest),
66
+ );
67
+ }
68
+ }
69
+ return query;
70
+ };
71
+
72
+ /**
73
+ * An example's cube as opening it types it, on the engine: its tables, then
74
+ * each Extend, in node order, as the editor types it in the background (an
75
+ * Extend parsing JSON, PLAN §11.20)
76
+ */
77
+ export const TEST__openExampleOnEngine = async (
78
+ engine: CubeEngine,
79
+ example: CubeExample,
80
+ ): Promise<{ model: ModelContext; runtime: string; query: Query }> => {
81
+ const document = example.createDocument();
82
+ const model = document.context?.model;
83
+ const runtime = document.context?.runtime;
84
+ if (!model || !runtime) {
85
+ throw new Error(`Example ${example.id} has no model or runtime`);
86
+ }
87
+ const sources = document.query.nodes.filter(
88
+ (node): node is RelationalTableSource =>
89
+ node instanceof RelationalTableSource,
90
+ );
91
+ const typed = await engine.resolveSchemas(
92
+ model,
93
+ new Map(
94
+ sources.map((source) => [
95
+ source.id,
96
+ [source.database, source.schema, source.table] as const,
97
+ ]),
98
+ ),
99
+ );
100
+ const query = new Query(
101
+ document.query.nodes.map((node) => {
102
+ if (!(node instanceof RelationalTableSource)) {
103
+ return node;
104
+ }
105
+ const schema = typed.get(node.id);
106
+ if (!(schema instanceof Schema)) {
107
+ throw schema ?? new Error(`No schema for ${node.id}`);
108
+ }
109
+ return node.withResolution({ kind: 'resolved', schema });
110
+ }),
111
+ document.query.connections,
112
+ document.query.selected,
113
+ );
114
+ return {
115
+ model,
116
+ runtime,
117
+ query: await TEST__typeExtendsOnEngine(engine, model, query),
118
+ };
119
+ };
@@ -0,0 +1,103 @@
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
+ import { expect } from '@jest/globals';
17
+ import {
18
+ CubeDocument,
19
+ PrimitiveType,
20
+ Query,
21
+ SchemaColumn,
22
+ } from '@finos/legend-cube';
23
+ import { flowResult } from 'mobx';
24
+ import type { CubeResult } from '../graph-manager/CubeEngine.js';
25
+ import { CubeEditorState } from '../stores/CubeEditorState.js';
26
+ import { CUBE_NORTHWIND_MODEL } from '../stores/fixtures/CubeNorthwindModel.js';
27
+ import { TEST__createCubeHost } from './CubeTestApplication.js';
28
+ import {
29
+ NORTHWIND_RUNTIME,
30
+ northwindTable,
31
+ } from './CubeNorthwindTestQueries.js';
32
+ import {
33
+ createFakeNorthwindOutline,
34
+ type FakeCubeEngine,
35
+ } from './FakeCubeEngine.js';
36
+
37
+ // A cube whose rows hold a Variant column, PAYLOAD, for Explore JSON's tests
38
+ // (PLAN §11.20): Northwind's ORDERS as a table of a model whose runtime runs on
39
+ // the database type given
40
+
41
+ export const TEST__PAYLOAD = new SchemaColumn(
42
+ 'PAYLOAD',
43
+ PrimitiveType.get('Variant'),
44
+ true,
45
+ );
46
+
47
+ /** ORDERS' columns in these tests: an id, PAYLOAD, and a number */
48
+ export const TEST__PAYLOAD_COLUMNS = [
49
+ new SchemaColumn(
50
+ 'ORDER_ID',
51
+ PrimitiveType.get('meta::pure::precisePrimitives::Int'),
52
+ false,
53
+ ),
54
+ TEST__PAYLOAD,
55
+ // a name an offer would take, in another case
56
+ new SchemaColumn('PAYLOAD_TAGS_COUNT', PrimitiveType.get('Integer'), true),
57
+ ];
58
+
59
+ export const TEST__PAYLOAD_DOCUMENTS = [
60
+ '{"user":{"name":"ann","tier":"gold"},"tags":["a","b"]}',
61
+ '{"user":{"name":"bob"},"tags":[]}',
62
+ ];
63
+
64
+ /** The engine's answer to a read of PAYLOAD: these documents, one per row */
65
+ export const TEST__payloadSample = (
66
+ documents: readonly string[] = TEST__PAYLOAD_DOCUMENTS,
67
+ ): CubeResult => ({
68
+ columns: ['PAYLOAD'],
69
+ rows: documents.map((document) => [document]),
70
+ sql: [],
71
+ durationMs: 1,
72
+ });
73
+
74
+ /** The table alone, run once on a database of this type; later runs read the sample */
75
+ export const TEST__setUpPayloadRun = async (
76
+ databaseType = 'DuckDB',
77
+ sample: CubeResult = TEST__payloadSample(),
78
+ ): Promise<{ state: CubeEditorState; fake: FakeCubeEngine }> => {
79
+ const { host, fake } = TEST__createCubeHost({
80
+ result: {
81
+ columns: TEST__PAYLOAD_COLUMNS.map((column) => column.name),
82
+ rows: [['1', TEST__PAYLOAD_DOCUMENTS[0] ?? null, '2']],
83
+ sql: [],
84
+ durationMs: 1,
85
+ },
86
+ outline: createFakeNorthwindOutline(databaseType),
87
+ });
88
+ const state = new CubeEditorState(
89
+ host,
90
+ new CubeDocument({
91
+ context: { model: CUBE_NORTHWIND_MODEL, runtime: NORTHWIND_RUNTIME },
92
+ query: new Query(
93
+ [northwindTable('relational101', 'ORDERS', TEST__PAYLOAD_COLUMNS)],
94
+ [],
95
+ 'relational101',
96
+ ),
97
+ }),
98
+ );
99
+ await flowResult(state.execution.execute());
100
+ expect(state.execution.result).toBeDefined();
101
+ fake.execute.mockResolvedValue(sample);
102
+ return { state, fake };
103
+ };
@@ -32,6 +32,7 @@ import {
32
32
  OTHER_SOURCE_KIND_TITLE,
33
33
  READ_ONLY_CUBE_TITLE,
34
34
  } from '../__lib__/LegendCubeLabels.js';
35
+ import { CUBE_TOUR_ANCHOR, cubeTourAnchor } from '../__lib__/LegendCubeTour.js';
35
36
  import {
36
37
  addCubeNode,
37
38
  canAddCubeNode,
@@ -158,6 +159,7 @@ export const CubeAddItemsMenu = observer(
158
159
  ? READ_ONLY_CUBE_TITLE
159
160
  : 'Add a source, or a step after the selected node'
160
161
  }
162
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.ADD_ITEMS)}
161
163
  aria-haspopup="menu"
162
164
  aria-expanded={isOpen}
163
165
  onClick={openMenu}