@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
@@ -37,6 +37,7 @@ import {
37
37
  UNSAVED_CUBE_NAME,
38
38
  } from '../__lib__/LegendCubeLabels.js';
39
39
  import { LEGEND_CUBE_TEST_ID } from '../__lib__/LegendCubeTesting.js';
40
+ import { CUBE_TOUR_ANCHOR, cubeTourAnchor } from '../__lib__/LegendCubeTour.js';
40
41
  import { CubeEditorState } from '../stores/CubeEditorState.js';
41
42
  import {
42
43
  type CubeEntrySource,
@@ -53,11 +54,14 @@ import { CubeGridRegion } from './grid/CubeGridRegion.js';
53
54
  import { CubePalette } from './palette/CubePalette.js';
54
55
  import { CubeDevSettingsDialog } from './dev-settings/CubeDevSettings.js';
55
56
  import { CubeShowPureDialog } from './show-pure/CubeShowPureDialog.js';
57
+ import { CubeJsonViewerDialog } from './grid/CubeJsonViewerDialog.js';
58
+ import { CubeJsonExplorerDialog } from './grid/CubeJsonExplorerDialog.js';
56
59
  import { CubeExamplesDialog } from './examples/CubeExamplesDialog.js';
57
60
  import { CubeFileMenu } from './CubeFileMenu.js';
58
61
  import { CubeSourcePicker } from './source-picker/CubeSourcePicker.js';
59
62
  import { CubeSpecTransferDialog } from './spec-transfer/CubeSpecTransferDialog.js';
60
63
  import { CubeImportPureDialog } from './spec-transfer/CubeImportPureDialog.js';
64
+ import { CubeTour } from './tour/CubeTour.js';
61
65
 
62
66
  /** The graph never takes more of the window's height than this, so the results stay in view (spec §17.3) */
63
67
  const MAX_GRAPH_SHARE_OF_WINDOW = 0.6;
@@ -115,12 +119,14 @@ const CubeGraphHeader = observer(
115
119
  {isCubeSampleDataOffered(editorState.host) && (
116
120
  <CubeButton
117
121
  title="Open an example cube, or start one on sample data"
122
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.EXAMPLES)}
118
123
  onClick={() => editorState.examples.open()}
119
124
  >
120
125
  Examples
121
126
  </CubeButton>
122
127
  )}
123
128
  <CubeButton
129
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
124
130
  title={
125
131
  readOnly
126
132
  ? READ_ONLY_CUBE_TITLE
@@ -132,6 +138,7 @@ const CubeGraphHeader = observer(
132
138
  Undo
133
139
  </CubeButton>
134
140
  <CubeButton
141
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
135
142
  title={`Redo the last change undone (${REDO_SHORTCUT_LABEL})`}
136
143
  disabled={!editorState.canRedo}
137
144
  onClick={() => editorState.redoEdited()}
@@ -139,6 +146,7 @@ const CubeGraphHeader = observer(
139
146
  Redo
140
147
  </CubeButton>
141
148
  <CubeButton
149
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
142
150
  title={
143
151
  editorState.execution.canExecute
144
152
  ? 'Show the Pure query that Execute runs'
@@ -158,6 +166,7 @@ const CubeGraphHeader = observer(
158
166
  Show Pure
159
167
  </CubeButton>
160
168
  <CubeButton
169
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
161
170
  title={
162
171
  showGraph
163
172
  ? 'Hide the graph, leaving more room for the results'
@@ -206,6 +215,8 @@ const CubeGraphHeader = observer(
206
215
  <CubeSpecTransferDialog editorState={editorState} />
207
216
  <CubeImportPureDialog editorState={editorState} />
208
217
  <CubeShowPureDialog editorState={editorState} />
218
+ <CubeJsonViewerDialog editorState={editorState} />
219
+ <CubeJsonExplorerDialog editorState={editorState} />
209
220
  <CubeDevSettingsDialog editorState={editorState} />
210
221
  </div>
211
222
  );
@@ -260,9 +271,12 @@ export const CubeEditor = observer(
260
271
  // hiding the graph collapses its panel: the header and the results stay
261
272
  // where they are, so the grid keeps its state
262
273
  const graphPanel = getCollapsiblePanelGroupProps(!showGraph);
274
+ // the page, whose controls the tour points at
275
+ const [page, setPage] = useState<HTMLDivElement | null>(null);
263
276
 
264
277
  return (
265
278
  <div
279
+ ref={setPage}
266
280
  className="legend-cube flex bg-[var(--color-bg-app)] text-[var(--color-text-primary)]"
267
281
  data-testid={LEGEND_CUBE_TEST_ID.EDITOR}
268
282
  >
@@ -288,6 +302,7 @@ export const CubeEditor = observer(
288
302
  </ResizablePanel>
289
303
  </ResizablePanelGroup>
290
304
  </div>
305
+ <CubeTour editorState={editorState} root={page} />
291
306
  </div>
292
307
  );
293
308
  },
@@ -23,6 +23,7 @@ import {
23
23
  } from '@finos/legend-art';
24
24
  import { observer } from 'mobx-react-lite';
25
25
  import { READ_ONLY_CUBE_TITLE } from '../__lib__/LegendCubeLabels.js';
26
+ import { CUBE_TOUR_ANCHOR, cubeTourAnchor } from '../__lib__/LegendCubeTour.js';
26
27
  import type { CubeEditorState } from '../stores/CubeEditorState.js';
27
28
 
28
29
  interface CubeFileMenuItem {
@@ -83,6 +84,7 @@ export const CubeFileMenu = observer(
83
84
  type="button"
84
85
  className="flex h-6 shrink-0 items-center gap-1 rounded-sm border border-[var(--color-border-default)] bg-[var(--color-bg-elevated)] px-2 text-base text-[var(--color-text-primary)] enabled:cursor-pointer enabled:hover:bg-[var(--color-bg-hover)]"
85
86
  title="New, Import, Import Pure and Export"
87
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.CUBE_MENU)}
86
88
  aria-haspopup="menu"
87
89
  aria-expanded={isOpen}
88
90
  onClick={openMenu}
@@ -19,6 +19,7 @@ import {
19
19
  AlignMiddleIcon,
20
20
  AlignTopIcon,
21
21
  ArrowsJoinIcon,
22
+ ArrowsSplitIcon,
22
23
  CalculatorIcon,
23
24
  CodeIcon,
24
25
  CompareIcon,
@@ -53,6 +54,7 @@ const NODE_ICONS: Readonly<
53
54
  concat: LayerGroupIcon,
54
55
  difference: CompareIcon,
55
56
  extend: CalculatorIcon,
57
+ explode: ArrowsSplitIcon,
56
58
  limit: AlignTopIcon,
57
59
  drop: AlignBottomIcon,
58
60
  slice: AlignMiddleIcon,
@@ -36,6 +36,11 @@ import { observer } from 'mobx-react-lite';
36
36
  import { useEffect, useMemo, useRef } from 'react';
37
37
  import { useDrop } from 'react-dnd';
38
38
  import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
39
+ import {
40
+ CUBE_TOUR_ANCHOR,
41
+ CUBE_TOUR_LABEL,
42
+ cubeTourAnchor,
43
+ } from '../../__lib__/LegendCubeTour.js';
39
44
  import { canAddCubeNode } from '../../stores/CubeAddPlacement.js';
40
45
  import type { CubeEditorState } from '../../stores/CubeEditorState.js';
41
46
  import { isCubeSampleDataOffered } from '../../stores/CubeHost.js';
@@ -284,6 +289,7 @@ export const CubeCanvas = observer(
284
289
  ref={ref}
285
290
  className="relative h-full w-full"
286
291
  data-testid={LEGEND_CUBE_TEST_ID.CANVAS}
292
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.CANVAS)}
287
293
  >
288
294
  <ContextMenu
289
295
  className="h-full w-full"
@@ -293,7 +299,7 @@ export const CubeCanvas = observer(
293
299
  menuProps={{ elevation: 7 }}
294
300
  >
295
301
  {editorState.document.query.isEmpty ? (
296
- <div className="flex h-full items-center justify-center p-4 text-base text-[var(--color-text-secondary)]">
302
+ <div className="flex h-full flex-col items-center justify-center gap-2 p-4 text-base text-[var(--color-text-secondary)]">
297
303
  <span>
298
304
  <button
299
305
  className="text-[var(--color-accent)] underline disabled:text-[var(--color-text-disabled)] disabled:no-underline"
@@ -321,6 +327,16 @@ export const CubeCanvas = observer(
321
327
  ' to start a new one.'
322
328
  )}
323
329
  </span>
330
+ <span>
331
+ {CUBE_TOUR_LABEL.OFFER}{' '}
332
+ <button
333
+ className="text-[var(--color-accent)] underline"
334
+ onClick={() => editorState.tour.start()}
335
+ >
336
+ {CUBE_TOUR_LABEL.START}
337
+ </button>
338
+ .
339
+ </span>
324
340
  </div>
325
341
  ) : (
326
342
  <CubeCanvasContext.Provider value={editorState}>
@@ -21,6 +21,10 @@ import { useRef } from 'react';
21
21
  import { RUNNING_NODE_LABEL } from '../../__lib__/LegendCubeHelpText.js';
22
22
  import { useDrag, useDrop } from 'react-dnd';
23
23
  import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
24
+ import {
25
+ CUBE_TOUR_ANCHOR,
26
+ cubeTourAnchor,
27
+ } from '../../__lib__/LegendCubeTour.js';
24
28
  import { CubeNodeIcon } from '../CubeNodeIcon.js';
25
29
  import {
26
30
  CUBE_OUTPUT_HANDLE_ID,
@@ -144,6 +148,9 @@ export const CubeCanvasNode = observer(
144
148
  aria-description={status.tooltip}
145
149
  aria-current={status.isCapture}
146
150
  data-testid={LEGEND_CUBE_TEST_ID.CANVAS_NODE}
151
+ {...(status.isCapture
152
+ ? cubeTourAnchor(CUBE_TOUR_ANCHOR.RUNNING_STEP)
153
+ : {})}
147
154
  >
148
155
  <CubeNodeIcon
149
156
  icon={editorState.registry.get(node.type)?.icon}
@@ -30,6 +30,11 @@ import {
30
30
  useDropdownMenu,
31
31
  } from '@finos/legend-art';
32
32
  import { observer } from 'mobx-react-lite';
33
+ import {
34
+ CUBE_TOUR_ANCHOR,
35
+ CUBE_TOUR_LABEL,
36
+ cubeTourAnchor,
37
+ } from '../../__lib__/LegendCubeTour.js';
33
38
  import type { CubeEditorState } from '../../stores/CubeEditorState.js';
34
39
 
35
40
  /** Why Dev Settings can't change anything */
@@ -37,8 +42,8 @@ const DEV_SETTINGS_UNAVAILABLE =
37
42
  "This Legend deployment doesn't let Legend Cube's engine clients be changed.";
38
43
 
39
44
  /**
40
- * The page's menu, at the top left, as Legend Query's editor has one: Dev
41
- * Settings (PLAN §11.16)
45
+ * The page's menu, at the top left, as Legend Query's editor has one: the
46
+ * tour (PLAN §11.21) and Dev Settings (PLAN §11.16)
42
47
  */
43
48
  export const CubePageMenu = observer(
44
49
  (props: { editorState: CubeEditorState }) => {
@@ -50,6 +55,7 @@ export const CubePageMenu = observer(
50
55
  type="button"
51
56
  className="flex h-6 w-6 shrink-0 items-center justify-center text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"
52
57
  title="Menu"
58
+ {...cubeTourAnchor(CUBE_TOUR_ANCHOR.PAGE_MENU)}
53
59
  aria-haspopup="menu"
54
60
  aria-expanded={isOpen}
55
61
  onClick={openMenu}
@@ -65,6 +71,15 @@ export const CubePageMenu = observer(
65
71
  },
66
72
  }}
67
73
  >
74
+ <DropdownMenuItem
75
+ className="px-2 text-base"
76
+ onClick={() => {
77
+ closeMenu();
78
+ editorState.tour.start();
79
+ }}
80
+ >
81
+ {CUBE_TOUR_LABEL.START}
82
+ </DropdownMenuItem>
68
83
  <DropdownMenuItem
69
84
  className="px-2 text-base"
70
85
  onClick={() => {
@@ -20,6 +20,7 @@ import {
20
20
  HashtagIcon,
21
21
  ListIcon,
22
22
  QuestionIcon,
23
+ SitemapIcon,
23
24
  StringTypeIcon,
24
25
  ToggleIcon,
25
26
  } from '@finos/legend-art';
@@ -47,6 +48,8 @@ const FAMILY_ICONS: Readonly<
47
48
  [TypeFamily.DATETIME]: CalendarIcon,
48
49
  [TypeFamily.DATE]: CalendarIcon,
49
50
  [TypeFamily.ENUM]: ListIcon,
51
+ // a JSON document: its fields as a tree (PLAN §11.20)
52
+ [TypeFamily.VARIANT]: SitemapIcon,
50
53
  };
51
54
 
52
55
  /** The icon of a column type's family: text, number, date, ...; a question mark for the rest */
@@ -0,0 +1,207 @@
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 { clsx, TimesIcon } from '@finos/legend-art';
18
+ import { type Schema, TypeFamily } from '@finos/legend-cube';
19
+ import { guaranteeType } from '@finos/legend-shared';
20
+ import { flowResult } from 'mobx';
21
+ import { observer } from 'mobx-react-lite';
22
+ import {
23
+ COLUMN_NAME_RULES_HINT,
24
+ EXPLODE_COLUMN_DISABLED_REASON,
25
+ EXPLODE_EDITOR_NOTES,
26
+ EXPLODE_NO_ARRAYS_NOTICE,
27
+ } from '../../__lib__/LegendCubeLabels.js';
28
+ import { CubeExplodeDraft } from '../../stores/editors/CubeExplodeDraft.js';
29
+ import { CubeButton } from '../CubeButton.js';
30
+ import { CubeColumnPicker } from './CubeColumnPicker.js';
31
+ import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
32
+
33
+ const FIELD =
34
+ 'h-6 min-w-0 flex-1 rounded-sm border border-[var(--color-border-default)] bg-[var(--color-bg-input)] px-1 text-base';
35
+
36
+ /**
37
+ * The Explode Array editor (PLAN §11.20): the Variant column; the keys to its
38
+ * array, typed or picked from the arrays a sample of the column holds; and
39
+ * the new column's name, which follows the keys until typed. The node's
40
+ * problems show under it, as for any node.
41
+ */
42
+ export const CubeExplodeEditor = observer((props: CubeNodeEditorProps) => {
43
+ const { readOnly } = props;
44
+ const draft = guaranteeType(props.draft, CubeExplodeDraft);
45
+ const [schema] = props.inputSchemas as [Schema];
46
+ const { arrays } = draft;
47
+ const findArrays = (): void => {
48
+ flowResult(draft.readSample()).catch(
49
+ draft.editorState.host.applicationStore.alertUnhandledError,
50
+ );
51
+ };
52
+ return (
53
+ <div className="flex flex-col gap-2 text-base">
54
+ <div className="flex items-center gap-2">
55
+ <span className="w-16 shrink-0">Column</span>
56
+ <CubeColumnPicker
57
+ label="Explode column"
58
+ schema={schema}
59
+ value={draft.column}
60
+ disabled={readOnly}
61
+ onChange={(name) => draft.setColumn(name)}
62
+ isColumnDisabled={(column) =>
63
+ column.type.family === TypeFamily.VARIANT
64
+ ? undefined
65
+ : EXPLODE_COLUMN_DISABLED_REASON.NOT_JSON
66
+ }
67
+ />
68
+ </div>
69
+ <div className="flex flex-col gap-1">
70
+ <div className="flex items-center gap-2">
71
+ <span className="w-16 shrink-0">Array</span>
72
+ <span className="min-w-0 flex-1 break-all font-mono text-sm text-[var(--color-text-secondary)]">
73
+ {[draft.column || '…', ...draft.path].join(' › ')}
74
+ </span>
75
+ <CubeButton
76
+ title={
77
+ draft.sampleProblem ??
78
+ "Read the column's first values, and list the arrays they hold"
79
+ }
80
+ disabled={
81
+ readOnly ||
82
+ !draft.column ||
83
+ draft.isSampling ||
84
+ draft.sampleProblem !== undefined
85
+ }
86
+ onClick={findArrays}
87
+ >
88
+ {draft.isSampling ? 'Reading…' : 'Find arrays'}
89
+ </CubeButton>
90
+ </div>
91
+ {draft.sampleError && (
92
+ <div className="break-words text-sm text-[var(--color-status-error)]">
93
+ {draft.sampleError}
94
+ </div>
95
+ )}
96
+ {arrays && arrays.length === 0 && (
97
+ <div className="text-sm text-[var(--color-text-secondary)]">
98
+ {EXPLODE_NO_ARRAYS_NOTICE}
99
+ </div>
100
+ )}
101
+ {arrays && arrays.length > 0 && (
102
+ <ul
103
+ aria-label="Arrays found"
104
+ className="rounded-sm border border-[var(--color-border-subtle)]"
105
+ >
106
+ {arrays.map((array) => {
107
+ const picked =
108
+ array.path.length === draft.path.length &&
109
+ array.path.every((key, index) => key === draft.path[index]);
110
+ const label = [draft.column, ...array.path].join(' › ');
111
+ const share = Math.round(array.presence * 100);
112
+ return (
113
+ <li key={JSON.stringify(array.path)}>
114
+ <button
115
+ type="button"
116
+ aria-label={`Array ${label}`}
117
+ aria-pressed={picked}
118
+ disabled={readOnly}
119
+ className={clsx(
120
+ 'flex w-full items-baseline gap-2 px-2 py-0.5 text-left enabled:hover:bg-[var(--color-bg-hover)]',
121
+ picked && 'bg-[var(--color-bg-selected)]',
122
+ )}
123
+ onClick={() => draft.pickArray(array.path)}
124
+ >
125
+ <span className="min-w-0 break-all font-mono text-sm">
126
+ {label}
127
+ </span>
128
+ <span className="shrink-0 text-xs text-[var(--color-text-muted)]">
129
+ {array.description}
130
+ {share < 100 ? ` · ${share}%` : ''}
131
+ </span>
132
+ </button>
133
+ </li>
134
+ );
135
+ })}
136
+ </ul>
137
+ )}
138
+ <ul aria-label="Path keys" className="flex flex-col gap-1">
139
+ {draft.path.map((key, index) => {
140
+ const position = index + 1;
141
+ return (
142
+ // keys are edited in place: their position is their identity
143
+ // eslint-disable-next-line react/no-array-index-key
144
+ <li key={index} className="flex items-center gap-1">
145
+ <span className="w-16 shrink-0 text-sm text-[var(--color-text-secondary)]">
146
+ {`Key ${position}`}
147
+ </span>
148
+ <input
149
+ aria-label={`Path key ${position}`}
150
+ aria-invalid={key === ''}
151
+ className={clsx(
152
+ FIELD,
153
+ key === '' && 'border-[var(--color-status-error)]',
154
+ )}
155
+ type="text"
156
+ spellCheck={false}
157
+ value={key}
158
+ disabled={readOnly}
159
+ onChange={(event) => draft.setKey(index, event.target.value)}
160
+ />
161
+ <button
162
+ type="button"
163
+ className="flex h-6 w-6 items-center justify-center text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] disabled:text-[var(--color-text-disabled)]"
164
+ aria-label={`Remove path key ${position}`}
165
+ title="Remove this key"
166
+ disabled={readOnly}
167
+ onClick={() => draft.removeKey(index)}
168
+ >
169
+ <TimesIcon />
170
+ </button>
171
+ </li>
172
+ );
173
+ })}
174
+ </ul>
175
+ <div>
176
+ <CubeButton
177
+ title="Add a key: the field of the object the keys before it reach"
178
+ disabled={readOnly || !draft.column}
179
+ onClick={() => draft.addKey()}
180
+ >
181
+ Add key
182
+ </CubeButton>
183
+ </div>
184
+ </div>
185
+ <div className="flex items-center gap-2">
186
+ <span className="w-16 shrink-0">Into</span>
187
+ <input
188
+ aria-label="New column name"
189
+ className={FIELD}
190
+ type="text"
191
+ spellCheck={false}
192
+ value={draft.name}
193
+ disabled={readOnly}
194
+ onChange={(event) => draft.setName(event.target.value)}
195
+ />
196
+ </div>
197
+ <div className="text-sm text-[var(--color-text-secondary)]">
198
+ {COLUMN_NAME_RULES_HINT}
199
+ </div>
200
+ <ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
201
+ {EXPLODE_EDITOR_NOTES.map((note) => (
202
+ <li key={note}>{note}</li>
203
+ ))}
204
+ </ul>
205
+ </div>
206
+ );
207
+ });
@@ -33,7 +33,12 @@ import {
33
33
  } from '../../stores/editors/CubeExpressionFilterDraft.js';
34
34
  import { CubeButton } from '../CubeButton.js';
35
35
  import { CubeColumnTypeIcon } from './CubeColumnPicker.js';
36
- import { columnAccessOf } from './CubeExtendEditor.js';
36
+ import {
37
+ CubeJsonFieldsToggle,
38
+ CubeJsonFieldsTree,
39
+ isCubeJsonColumn,
40
+ } from './CubeJsonFieldsTree.js';
41
+ import { columnAccessOf } from '../../stores/CubePureText.js';
37
42
  import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
38
43
 
39
44
  /** The code editor's language: legend-code-editor's `CODE_EDITOR_LANGUAGE.PURE` */
@@ -108,6 +113,11 @@ export const CubeExpressionFilterEditor = observer(
108
113
  const [schema] = inputSchemas as [Schema];
109
114
  const [expanded, setExpanded] = useState(false);
110
115
  const disabled = readOnly || draft.validating;
116
+ /** Writes text at the end of the condition, as the chips do */
117
+ const write = (text: string): void => {
118
+ const separator = /\s$/u.test(draft.code) ? '' : ' ';
119
+ draft.setCode(`${draft.code}${separator}${text}`);
120
+ };
111
121
  return (
112
122
  <div className="flex flex-col gap-2 text-base">
113
123
  <div className="flex items-center gap-1">
@@ -164,24 +174,31 @@ export const CubeExpressionFilterEditor = observer(
164
174
  <span>Input columns</span>
165
175
  <ul aria-label="Input columns" className="flex flex-wrap gap-1">
166
176
  {schema.columns.map((column) => (
167
- <li key={column.name}>
177
+ <li key={column.name} className="flex items-stretch gap-0.5">
168
178
  <button
169
179
  className="flex items-center gap-1 rounded-sm border border-[var(--color-border-subtle)] px-1 text-sm hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
170
180
  title={`Write ${columnAccessOf(column.name)} at the end of the condition (${getExactColumnTypeLabel(column)})`}
171
181
  disabled={disabled}
172
- onClick={() => {
173
- const separator = /\s$/u.test(draft.code) ? '' : ' ';
174
- draft.setCode(
175
- `${draft.code}${separator}${columnAccessOf(column.name)}`,
176
- );
177
- }}
182
+ onClick={() => write(columnAccessOf(column.name))}
178
183
  >
179
184
  <CubeColumnTypeIcon type={column.type} />
180
185
  {column.name}
181
186
  </button>
187
+ {isCubeJsonColumn(column) && (
188
+ <CubeJsonFieldsToggle
189
+ fields={draft.jsonFields}
190
+ column={column}
191
+ disabled={readOnly}
192
+ />
193
+ )}
182
194
  </li>
183
195
  ))}
184
196
  </ul>
197
+ <CubeJsonFieldsTree
198
+ fields={draft.jsonFields}
199
+ disabled={disabled}
200
+ onInsert={write}
201
+ />
185
202
  <ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
186
203
  {EXPRESSION_FILTER_EDITOR_NOTES.map((note) => (
187
204
  <li key={note}>{note}</li>
@@ -38,19 +38,19 @@ import {
38
38
  type CubeExtendRow,
39
39
  CubeExtendDraft,
40
40
  } from '../../stores/editors/CubeExtendDraft.js';
41
+ import { columnAccessOf } from '../../stores/CubePureText.js';
41
42
  import { CubeButton } from '../CubeButton.js';
42
43
  import { CubeColumnTypeIcon } from './CubeColumnPicker.js';
44
+ import {
45
+ CubeJsonFieldsToggle,
46
+ CubeJsonFieldsTree,
47
+ isCubeJsonColumn,
48
+ } from './CubeJsonFieldsTree.js';
43
49
  import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
44
50
 
45
51
  /** The code editor's language: legend-code-editor's `CODE_EDITOR_LANGUAGE.PURE` */
46
52
  const PURE = 'pure';
47
53
 
48
- /** How an expression reads a column: `$x.NAME`, or `$x.'a name'` for one that isn't an identifier */
49
- export const columnAccessOf = (name: string): string =>
50
- /^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)
51
- ? `$x.${name}`
52
- : `$x.'${name.replace(/\\/gu, '\\\\').replace(/'/gu, "\\'")}'`;
53
-
54
54
  /** The error the code editor marks, from a row's problem located in its code */
55
55
  const markerOf = (row: CubeExtendRow): CompilationError | undefined => {
56
56
  const { problem } = row;
@@ -94,8 +94,8 @@ const CubeExtendRowStatus = observer((props: { row: CubeExtendRow }) => {
94
94
  return row.type ? (
95
95
  <div className="flex items-center gap-1 text-sm text-[var(--color-text-secondary)]">
96
96
  <CubeColumnTypeIcon type={row.type} />
97
- {/* every new column can be empty (PLAN §11.7) */}
98
- {`${row.type.displayName}, can be empty`}
97
+ {/* every new column is optional (PLAN §11.7) */}
98
+ {`${row.type.displayName}, optional`}
99
99
  </div>
100
100
  ) : null;
101
101
  });
@@ -200,6 +200,14 @@ export const CubeExtendEditor = observer((props: CubeNodeEditorProps) => {
200
200
  const [focusedKey, setFocusedKey] = useState<number | undefined>();
201
201
  const target =
202
202
  draft.rows.find((row) => row.key === focusedKey) ?? draft.rows.at(-1);
203
+ const chipsDisabled = readOnly || draft.validating || !target;
204
+ /** Writes text at the end of the expression last used, as the chips do */
205
+ const write = (text: string): void => {
206
+ if (target) {
207
+ const separator = /\s$/u.test(target.code) ? '' : ' ';
208
+ draft.setCode(target.key, `${target.code}${separator}${text}`);
209
+ }
210
+ };
203
211
  return (
204
212
  <div className="flex flex-col gap-2 text-base">
205
213
  <span>New columns</span>
@@ -240,27 +248,32 @@ export const CubeExtendEditor = observer((props: CubeNodeEditorProps) => {
240
248
  <span>Input columns</span>
241
249
  <ul aria-label="Input columns" className="flex flex-wrap gap-1">
242
250
  {schema.columns.map((column) => (
243
- <li key={column.name}>
251
+ <li key={column.name} className="flex items-stretch gap-0.5">
244
252
  <button
245
253
  className="flex items-center gap-1 rounded-sm border border-[var(--color-border-subtle)] px-1 text-sm hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
246
254
  title={`Write ${columnAccessOf(column.name)} at the end of the expression last used (${getExactColumnTypeLabel(column)})`}
247
- disabled={readOnly || draft.validating || !target}
248
- onClick={() => {
249
- if (target) {
250
- const separator = /\s$/u.test(target.code) ? '' : ' ';
251
- draft.setCode(
252
- target.key,
253
- `${target.code}${separator}${columnAccessOf(column.name)}`,
254
- );
255
- }
256
- }}
255
+ disabled={chipsDisabled}
256
+ onClick={() => write(columnAccessOf(column.name))}
257
257
  >
258
258
  <CubeColumnTypeIcon type={column.type} />
259
259
  {column.name}
260
260
  </button>
261
+ {isCubeJsonColumn(column) && (
262
+ <CubeJsonFieldsToggle
263
+ fields={draft.jsonFields}
264
+ column={column}
265
+ disabled={readOnly}
266
+ />
267
+ )}
261
268
  </li>
262
269
  ))}
263
270
  </ul>
271
+ <CubeJsonFieldsTree
272
+ fields={draft.jsonFields}
273
+ disabled={chipsDisabled}
274
+ onInsert={write}
275
+ onAddColumn={(name, code) => draft.addColumn(name, code)}
276
+ />
264
277
  <ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
265
278
  {[...EXTEND_EDITOR_NOTES, COLUMN_NAME_RULES_HINT].map((note) => (
266
279
  <li key={note}>{note}</li>