@malloydata/malloy-explorer 0.0.278-dev250515234639 → 0.0.282-dev250527225235

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.
@@ -3342,29 +3342,105 @@ function useQueryBuilder(source, query) {
3342
3342
  }
3343
3343
  }, [source, query]);
3344
3344
  }
3345
+ const MalloyQueryFocusContext = /* @__PURE__ */ React__namespace.createContext({
3346
+ focusMainView: () => {
3347
+ },
3348
+ isMainViewFocused: true,
3349
+ focusNestView: () => {
3350
+ },
3351
+ isNestViewFocused: () => false,
3352
+ focusedNestView: null
3353
+ });
3354
+ function MalloyQueryFocusProvider({
3355
+ rootQuery,
3356
+ focusedNestViewPath,
3357
+ onFocusedNestViewPathChange,
3358
+ children
3359
+ }) {
3360
+ const focusedNestView = React__namespace.useMemo(() => {
3361
+ if (focusedNestViewPath.length === 0) {
3362
+ return null;
3363
+ }
3364
+ if (rootQuery) {
3365
+ const queryDef = rootQuery.definition;
3366
+ if (queryDef instanceof QB.ASTArrowQueryDefinition) {
3367
+ return findNestView(queryDef.view, [...focusedNestViewPath].reverse());
3368
+ }
3369
+ }
3370
+ return null;
3371
+ }, [rootQuery, focusedNestViewPath]);
3372
+ const focusMainView = React__namespace.useCallback(() => {
3373
+ onFocusedNestViewPathChange([]);
3374
+ }, [onFocusedNestViewPathChange]);
3375
+ const focusNestView = React__namespace.useCallback((path) => {
3376
+ onFocusedNestViewPathChange([...path]);
3377
+ }, [onFocusedNestViewPathChange]);
3378
+ const isNestViewFocused = React__namespace.useCallback((path) => {
3379
+ return JSON.stringify(path) === JSON.stringify(focusedNestViewPath);
3380
+ }, [focusedNestViewPath]);
3381
+ const isMainViewFocused = focusedNestViewPath.length === 0;
3382
+ return /* @__PURE__ */ jsxRuntime.jsx(MalloyQueryFocusContext.Provider, {
3383
+ value: {
3384
+ focusMainView,
3385
+ isMainViewFocused,
3386
+ focusNestView,
3387
+ isNestViewFocused,
3388
+ focusedNestView
3389
+ },
3390
+ children
3391
+ });
3392
+ }
3393
+ function useQueryFocus() {
3394
+ return React__namespace.useContext(MalloyQueryFocusContext);
3395
+ }
3396
+ const findNestView = (currentView, remainingPath) => {
3397
+ if (remainingPath.length === 0) {
3398
+ return null;
3399
+ }
3400
+ if (currentView instanceof QB.ASTArrowViewDefinition) {
3401
+ return findNestView(currentView.view, remainingPath);
3402
+ }
3403
+ if (currentView instanceof QB.ASTSegmentViewDefinition) {
3404
+ const currentNestName = remainingPath.pop();
3405
+ const currentNestOperation = currentView.operations.items.find((operation) => operation instanceof QB.ASTNestViewOperation && operation.name === currentNestName);
3406
+ if (currentNestOperation === void 0) {
3407
+ remainingPath.push(currentNestName);
3408
+ return null;
3409
+ } else if (remainingPath.length === 0) {
3410
+ return currentNestOperation.view;
3411
+ } else {
3412
+ return findNestView(currentNestOperation.view.definition, remainingPath);
3413
+ }
3414
+ }
3415
+ if (currentView instanceof QB.ASTRefinementViewDefinition) {
3416
+ return findNestView(currentView.refinement, remainingPath);
3417
+ }
3418
+ return null;
3419
+ };
3345
3420
  function MalloyExplorerProvider({
3346
3421
  source,
3347
3422
  query,
3348
- setQuery,
3423
+ onQueryChange,
3424
+ focusedNestViewPath,
3425
+ onFocusedNestViewPathChange,
3349
3426
  children,
3350
3427
  topValues
3351
3428
  }) {
3352
3429
  const rootQuery = useQueryBuilder(source, query);
3353
- const [currentNestView, setCurrentNestView] = React__namespace.useState(null);
3354
- const [currentNestQueryPanel, setCurrentNestQueryPanel] = React__namespace.useState(null);
3355
3430
  return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, {
3356
- children: /* @__PURE__ */ jsxRuntime.jsx(QueryEditorContext.Provider, {
3357
- value: {
3358
- source,
3359
- rootQuery,
3360
- setQuery,
3361
- topValues,
3362
- currentNestQueryPanel,
3363
- onCurrentNestQueryPanelChange: setCurrentNestQueryPanel,
3364
- currentNestView,
3365
- onCurrentNestViewChange: setCurrentNestView
3366
- },
3367
- children
3431
+ children: /* @__PURE__ */ jsxRuntime.jsx(MalloyQueryFocusProvider, {
3432
+ rootQuery,
3433
+ focusedNestViewPath,
3434
+ onFocusedNestViewPathChange,
3435
+ children: /* @__PURE__ */ jsxRuntime.jsx(QueryEditorContext.Provider, {
3436
+ value: {
3437
+ source,
3438
+ rootQuery,
3439
+ setQuery: onQueryChange,
3440
+ topValues
3441
+ },
3442
+ children
3443
+ })
3368
3444
  })
3369
3445
  });
3370
3446
  }
@@ -25881,13 +25957,14 @@ function QueryActionBar({
25881
25957
  const {
25882
25958
  rootQuery,
25883
25959
  setQuery,
25884
- source,
25885
- onCurrentNestQueryPanelChange,
25886
- onCurrentNestViewChange
25960
+ source
25887
25961
  } = React.useContext(QueryEditorContext);
25888
25962
  const {
25889
25963
  onCollapse
25890
25964
  } = React.useContext(ResizableCollapsiblePanelContext);
25965
+ const {
25966
+ focusMainView
25967
+ } = useQueryFocus();
25891
25968
  const isQueryEmpty = !rootQuery || rootQuery.isEmpty();
25892
25969
  const isRunEnabled = rootQuery == null ? void 0 : rootQuery.isRunnable();
25893
25970
  const onRunQuery = () => {
@@ -25895,10 +25972,6 @@ function QueryActionBar({
25895
25972
  runQuery(source, rootQuery.build());
25896
25973
  }
25897
25974
  };
25898
- const focusMainQueryPanel = () => {
25899
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
25900
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
25901
- };
25902
25975
  return /* @__PURE__ */ jsxRuntime.jsxs("div", {
25903
25976
  ...{
25904
25977
  className: "mly78zum5 mly1qughib mly6s0dn4 mlye8ttls"
@@ -25919,7 +25992,7 @@ function QueryActionBar({
25919
25992
  },
25920
25993
  children: [/* @__PURE__ */ jsxRuntime.jsx(Button, {
25921
25994
  onClick: () => {
25922
- focusMainQueryPanel();
25995
+ focusMainView();
25923
25996
  setQuery == null ? void 0 : setQuery(void 0);
25924
25997
  },
25925
25998
  isDisabled: !rootQuery || (rootQuery == null ? void 0 : rootQuery.isEmpty()),
@@ -29950,6 +30023,11 @@ function getViewDefinition(parent) {
29950
30023
  return parent instanceof QB.ASTArrowQueryDefinition ? parent.view : parent.definition;
29951
30024
  }
29952
30025
  function getInputSchemaFromViewParent(parent) {
30026
+ if (!parent) {
30027
+ return {
30028
+ fields: []
30029
+ };
30030
+ }
29953
30031
  const definition = getViewDefinition(parent);
29954
30032
  return definition.getInputSchema();
29955
30033
  }
@@ -32400,12 +32478,54 @@ const styles$g = {
32400
32478
  $$css: true
32401
32479
  }
32402
32480
  };
32481
+ function toFullName(path, name) {
32482
+ return [...path || [], name].join(".");
32483
+ }
32403
32484
  function segmentHasLimit(segment) {
32404
32485
  return segment.operations.items.find((operation) => operation instanceof QB.ASTLimitViewOperation) !== void 0;
32405
32486
  }
32406
32487
  function segmentHasOrderBy(segment, name) {
32407
32488
  return segment.operations.items.find((operation) => operation instanceof QB.ASTOrderByViewOperation && operation.name === name) !== void 0;
32408
32489
  }
32490
+ function getOutputNameToInputNameMap(segment) {
32491
+ const nameMap = /* @__PURE__ */ new Map();
32492
+ for (const operation of segment.operations.items) {
32493
+ if (operation instanceof QB.ASTGroupByViewOperation || operation instanceof QB.ASTAggregateViewOperation) {
32494
+ const reference = operation.field.getReference();
32495
+ if (reference) {
32496
+ nameMap.set(operation.name, toFullName(reference.path, reference.name));
32497
+ }
32498
+ }
32499
+ }
32500
+ return nameMap;
32501
+ }
32502
+ function segmentHasOrderBySourceField(segment, path, name) {
32503
+ const nameMap = getOutputNameToInputNameMap(segment);
32504
+ const fullInputName = toFullName(path, name);
32505
+ return !!segment.operations.items.find((operation) => {
32506
+ if (operation instanceof QB.ASTOrderByViewOperation && nameMap.has(operation.name)) {
32507
+ return fullInputName === nameMap.get(operation.name);
32508
+ }
32509
+ return false;
32510
+ });
32511
+ }
32512
+ function areReferencesEqual(path1, name1, path2, name2) {
32513
+ return name1 === name2 && (path1 || []).join(".") === (path2 || []).join(".");
32514
+ }
32515
+ function segmentHasFieldInOutputSpace(segment, path, name) {
32516
+ const match = segment.operations.items.find((operation) => {
32517
+ if (operation instanceof QB.ASTGroupByViewOperation || operation instanceof QB.ASTAggregateViewOperation) {
32518
+ const reference = operation.field.getReference();
32519
+ if (reference) {
32520
+ return areReferencesEqual(path, name, reference.path, reference.name);
32521
+ } else {
32522
+ return false;
32523
+ }
32524
+ }
32525
+ return false;
32526
+ });
32527
+ return !!match;
32528
+ }
32409
32529
  function segmentNestNo(segment, name) {
32410
32530
  return segment.operations.items.reduce((acc, operation) => {
32411
32531
  if (operation instanceof QB.ASTNestViewOperation) {
@@ -32460,6 +32580,19 @@ function addNest(view, field) {
32460
32580
  }
32461
32581
  segment.addNest(field.name, rename);
32462
32582
  }
32583
+ function addOrderByFromSource(view, path, name, direction = "desc") {
32584
+ const fullInputName = toFullName(path, name);
32585
+ let orderByName = name;
32586
+ const segment = view.getOrAddDefaultSegment();
32587
+ const nameMap = getOutputNameToInputNameMap(segment);
32588
+ for (const entry of nameMap.entries()) {
32589
+ if (entry[1] === fullInputName) {
32590
+ orderByName = entry[0];
32591
+ break;
32592
+ }
32593
+ }
32594
+ segment.addOrderBy(orderByName, direction);
32595
+ }
32463
32596
  function addOrderBy(view, field, direction = "desc") {
32464
32597
  const segment = view.getOrAddDefaultSegment();
32465
32598
  segment.addOrderBy(field.name, direction);
@@ -32473,6 +32606,9 @@ function addFilter(view, field, path, filter) {
32473
32606
  }
32474
32607
  }
32475
32608
  function getSegmentIfPresent(parent) {
32609
+ if (!parent) {
32610
+ return void 0;
32611
+ }
32476
32612
  const definition = getViewDefinition(parent);
32477
32613
  if (definition instanceof QB.ASTSegmentViewDefinition) {
32478
32614
  return definition;
@@ -33101,11 +33237,12 @@ function SortableOperation({
33101
33237
  operation,
33102
33238
  color
33103
33239
  }) {
33240
+ var _a2;
33104
33241
  const {
33105
33242
  setQuery
33106
33243
  } = React.useContext(QueryEditorContext);
33107
33244
  const fieldInfo = operation.getFieldInfo();
33108
- const path = operation.field.getReference().path ?? NULL_PATH;
33245
+ const path = ((_a2 = operation.field.getReference()) == null ? void 0 : _a2.path) ?? NULL_PATH;
33109
33246
  const {
33110
33247
  attributes,
33111
33248
  listeners,
@@ -33306,28 +33443,62 @@ const parsedToLabels = (parsed, filterString) => {
33306
33443
  value = stringClause.escaped_values.join(", ");
33307
33444
  break;
33308
33445
  case "=":
33309
- op = "is";
33310
- value = stringClause.values.join(", ");
33446
+ {
33447
+ const {
33448
+ not,
33449
+ values
33450
+ } = stringClause;
33451
+ op = not ? "is not" : "is";
33452
+ value = values.join(", ");
33453
+ }
33311
33454
  break;
33312
33455
  case "contains":
33313
- op = "contains";
33314
- value = stringClause.values.join(", ");
33456
+ {
33457
+ const {
33458
+ not,
33459
+ values
33460
+ } = stringClause;
33461
+ op = not ? "does not contain" : "contains";
33462
+ value = values.join(", ");
33463
+ }
33315
33464
  break;
33316
33465
  case "starts":
33317
- op = "starts with";
33318
- value = stringClause.values.join(", ");
33466
+ {
33467
+ const {
33468
+ not,
33469
+ values
33470
+ } = stringClause;
33471
+ op = not ? "does not start with" : "starts with";
33472
+ value = values.join(", ");
33473
+ }
33319
33474
  break;
33320
33475
  case "ends":
33321
- op = "is like";
33322
- value = stringClause.values.join(", ");
33476
+ {
33477
+ const {
33478
+ not,
33479
+ values
33480
+ } = stringClause;
33481
+ op = not ? "does not end with" : "ends with";
33482
+ value = values.join(", ");
33483
+ }
33323
33484
  break;
33324
33485
  case "empty":
33325
- op = "is empty";
33326
- value = "";
33486
+ {
33487
+ const {
33488
+ not
33489
+ } = stringClause;
33490
+ op = not ? "is not empty" : "is empty";
33491
+ value = "";
33492
+ }
33327
33493
  break;
33328
33494
  case "null":
33329
- op = stringClause.not ? "is not" : "is";
33330
- value = "null";
33495
+ {
33496
+ const {
33497
+ not
33498
+ } = stringClause;
33499
+ op = not ? "is not" : "is";
33500
+ value = "null";
33501
+ }
33331
33502
  break;
33332
33503
  }
33333
33504
  }
@@ -33491,22 +33662,27 @@ function SingleFilterOperation({
33491
33662
  rootQuery,
33492
33663
  filterOperation
33493
33664
  }) {
33665
+ const {
33666
+ setQuery
33667
+ } = React.useContext(QueryEditorContext);
33668
+ const setFilter = React.useCallback((filter2) => {
33669
+ if (filterOperation.filter instanceof QB.ASTFilterWithFilterString) {
33670
+ filterOperation.filter.setFilter(filter2);
33671
+ }
33672
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33673
+ }, [filterOperation.filter, rootQuery, setQuery]);
33674
+ if (!(filterOperation.filter instanceof QB.ASTFilterWithFilterString)) {
33675
+ return null;
33676
+ }
33494
33677
  const {
33495
33678
  fieldReference,
33496
33679
  filterString
33497
33680
  } = filterOperation.filter;
33498
33681
  const filter = filterOperation.filter.getFilter();
33499
33682
  const fieldInfo = fieldReference.getFieldInfo();
33500
- const {
33501
- setQuery
33502
- } = React.useContext(QueryEditorContext);
33503
33683
  if (fieldInfo.kind !== "dimension" && fieldInfo.kind !== "measure") {
33504
33684
  throw new Error(`Invalid filter field kind: ${fieldInfo.kind}`);
33505
33685
  }
33506
- const setFilter = React.useCallback((filter2) => {
33507
- filterOperation.filter.setFilter(filter2);
33508
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33509
- }, [filterOperation.filter, rootQuery, setQuery]);
33510
33686
  const {
33511
33687
  op,
33512
33688
  value
@@ -34117,6 +34293,35 @@ const styles$d = {
34117
34293
  $$css: true
34118
34294
  }
34119
34295
  };
34296
+ const NestViewPathContext = /* @__PURE__ */ React.createContext([]);
34297
+ function FocusableView({
34298
+ children,
34299
+ nest
34300
+ }) {
34301
+ const {
34302
+ focusNestView,
34303
+ focusMainView
34304
+ } = useQueryFocus();
34305
+ const parentNestViewPath = React.useContext(NestViewPathContext);
34306
+ return /* @__PURE__ */ jsxRuntime.jsx("div", {
34307
+ onPointerDown: (e) => {
34308
+ e.stopPropagation();
34309
+ if (nest) {
34310
+ focusNestView([...parentNestViewPath, nest.name]);
34311
+ } else {
34312
+ focusMainView();
34313
+ }
34314
+ },
34315
+ children: nest ? /* @__PURE__ */ jsxRuntime.jsx(NestViewPathContext.Provider, {
34316
+ value: [...parentNestViewPath, nest.name],
34317
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", {
34318
+ children
34319
+ })
34320
+ }) : /* @__PURE__ */ jsxRuntime.jsx("div", {
34321
+ children
34322
+ })
34323
+ });
34324
+ }
34120
34325
  function NestOperations({
34121
34326
  rootQuery,
34122
34327
  view,
@@ -34140,31 +34345,14 @@ function NestOperation({
34140
34345
  nest
34141
34346
  }) {
34142
34347
  const {
34143
- setQuery,
34144
- currentNestQueryPanel,
34145
- onCurrentNestQueryPanelChange,
34146
- onCurrentNestViewChange
34348
+ setQuery
34147
34349
  } = React.useContext(QueryEditorContext);
34148
34350
  const [renameOpen, setRenameOpen] = React.useState(false);
34149
- const panelRef = React__namespace.useRef(null);
34150
- const isCurrentNestQueryPanelFocused = currentNestQueryPanel !== null && panelRef.current == currentNestQueryPanel;
34151
- React__namespace.useEffect(() => {
34152
- if (isCurrentNestQueryPanelFocused) {
34153
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34154
- }
34155
- }, [nest, isCurrentNestQueryPanelFocused, onCurrentNestViewChange]);
34156
- const focusCurrentNestQueryPanel = () => {
34157
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(panelRef.current);
34158
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34159
- };
34160
- const focusParentQueryPanel = () => {
34161
- const currentPanel = panelRef.current;
34162
- const parent = findParentNestQueryPanel(currentPanel);
34163
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(parent);
34164
- if (parent === null) {
34165
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34166
- }
34167
- };
34351
+ const parentNestViewPath = React.useContext(NestViewPathContext);
34352
+ const {
34353
+ focusNestView,
34354
+ isNestViewFocused
34355
+ } = useQueryFocus();
34168
34356
  const getControls = (nest2) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
34169
34357
  children: [/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, {
34170
34358
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, {
@@ -34177,7 +34365,7 @@ function NestOperation({
34177
34365
  icon: "clear",
34178
34366
  label: "Delete Query",
34179
34367
  onClick: () => {
34180
- focusParentQueryPanel();
34368
+ focusNestView([...parentNestViewPath]);
34181
34369
  nest2.delete();
34182
34370
  setQuery == null ? void 0 : setQuery(rootQuery.build());
34183
34371
  }
@@ -34192,21 +34380,19 @@ function NestOperation({
34192
34380
  view: nest2.view
34193
34381
  })]
34194
34382
  });
34195
- return /* @__PURE__ */ jsxRuntime.jsx("div", {
34196
- ...{
34197
- className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34198
- },
34383
+ return /* @__PURE__ */ jsxRuntime.jsx(FocusableView, {
34384
+ nest,
34199
34385
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
34200
- ref: panelRef,
34201
- onPointerDownCapture: focusCurrentNestQueryPanel,
34202
- "data-nest-panel": true,
34386
+ ...{
34387
+ className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34388
+ },
34203
34389
  children: [/* @__PURE__ */ jsxRuntime.jsx(CollapsiblePanel, {
34204
34390
  title: nest.name,
34205
34391
  icon: viewToVisualizationIcon(nest.view),
34206
34392
  defaultOpen: true,
34207
34393
  controls: getControls(nest),
34208
34394
  collapsedControls: getControls(nest),
34209
- isFocused: isCurrentNestQueryPanelFocused,
34395
+ isFocused: isNestViewFocused([...parentNestViewPath, nest.name]),
34210
34396
  children: /* @__PURE__ */ jsxRuntime.jsx(View, {
34211
34397
  rootQuery,
34212
34398
  view: nest.view
@@ -34218,14 +34404,8 @@ function NestOperation({
34218
34404
  open: renameOpen,
34219
34405
  setOpen: setRenameOpen
34220
34406
  })]
34221
- })
34222
- }, nest.name);
34223
- }
34224
- function findParentNestQueryPanel(element2) {
34225
- if (!element2 || !element2.parentElement) return null;
34226
- const parentElement = element2.parentElement;
34227
- if (parentElement.dataset.nestPanel !== void 0) return parentElement;
34228
- return findParentNestQueryPanel(parentElement);
34407
+ }, nest.name)
34408
+ });
34229
34409
  }
34230
34410
  function Operations({
34231
34411
  rootQuery,
@@ -34255,7 +34435,8 @@ function Operations({
34255
34435
  orderBys.push(operation);
34256
34436
  } else if (operation instanceof QB.ASTNestViewOperation) {
34257
34437
  nests.push(operation);
34258
- } else {
34438
+ } else if (operation instanceof QB.ASTDrillViewOperation) ;
34439
+ else {
34259
34440
  limit = operation;
34260
34441
  }
34261
34442
  });
@@ -34394,19 +34575,13 @@ function Query({
34394
34575
  setQuery
34395
34576
  }) {
34396
34577
  const {
34397
- currentNestQueryPanel,
34398
- onCurrentNestQueryPanelChange,
34399
- onCurrentNestViewChange
34400
- } = React__namespace.useContext(QueryEditorContext);
34401
- const focusMainQueryPanel = () => {
34402
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
34403
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34404
- };
34405
- return /* @__PURE__ */ jsxRuntime.jsx("div", {
34406
- onPointerDownCapture: focusMainQueryPanel,
34578
+ focusMainView,
34579
+ isMainViewFocused
34580
+ } = useQueryFocus();
34581
+ return /* @__PURE__ */ jsxRuntime.jsx(FocusableView, {
34407
34582
  children: /* @__PURE__ */ jsxRuntime.jsxs(CollapsiblePanel, {
34408
34583
  title: "Main query",
34409
- isFocused: !currentNestQueryPanel,
34584
+ isFocused: isMainViewFocused,
34410
34585
  controls: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
34411
34586
  children: [/* @__PURE__ */ jsxRuntime.jsx(DropdownMenu, {
34412
34587
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, {
@@ -34420,7 +34595,7 @@ function Query({
34420
34595
  icon: "clear",
34421
34596
  label: "Clear query",
34422
34597
  onClick: () => {
34423
- focusMainQueryPanel();
34598
+ focusMainView();
34424
34599
  setQuery == null ? void 0 : setQuery(void 0);
34425
34600
  },
34426
34601
  disabled: rootQuery.isEmpty()
@@ -35914,62 +36089,83 @@ const FIELD_KIND_TO_TITLE = {
35914
36089
  dimension: "Dimensions"
35915
36090
  };
35916
36091
  function useOperations(view, field, path) {
35917
- const dimensionFields = React.useMemo(() => {
36092
+ const fullName = toFullName(path, field.name);
36093
+ const flattenedFields = React.useMemo(() => {
35918
36094
  const {
35919
36095
  fields
35920
36096
  } = getInputSchemaFromViewParent(view);
35921
- return new Set(flattenFieldsTree(fields).filter(({
35922
- field: field2
35923
- }) => field2.kind === "dimension").map(({
35924
- field: field2
35925
- }) => field2.name));
35926
- }, [view]);
35927
- const measureFields = React.useMemo(() => {
35928
- const {
35929
- fields
35930
- } = getInputSchemaFromViewParent(view);
35931
- return new Set(flattenFieldsTree(fields).filter(({
35932
- field: field2
35933
- }) => field2.kind === "measure").map(({
35934
- field: field2
35935
- }) => field2.name));
35936
- }, [view]);
35937
- const isGroupByAllowed = React.useMemo(() => {
35938
- if (!view) {
35939
- return false;
35940
- }
36097
+ const inputPath = path.join(".");
36098
+ return flattenFieldsTree(fields).filter((fieldItem) => {
36099
+ return fieldItem.path.join(".") === inputPath;
36100
+ });
36101
+ }, [path, view]);
36102
+ const matchingFieldItem = flattenedFields.find((fieldItem) => field.name === fieldItem.field.name);
36103
+ const groupByDisabledReason = React.useMemo(() => {
35941
36104
  const segment = getSegmentIfPresent(view);
35942
- return dimensionFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35943
- }, [view, field, path, dimensionFields]);
35944
- const isAggregateAllowed = React.useMemo(() => {
35945
- if (!view) {
35946
- return false;
36105
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "dimension") {
36106
+ return "Grouping is only available on a dimenion.";
36107
+ }
36108
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36109
+ return "Cannot group by a field already in the view.";
36110
+ }
36111
+ if (!isNotAnnotatedFilteredField(field)) {
36112
+ return "This field is annotated with #NO_UI.";
36113
+ }
36114
+ return "";
36115
+ }, [view, matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, field, path]);
36116
+ const aggregateDisabledReason = React.useMemo(() => {
36117
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "measure") {
36118
+ return "Aggregation only supports measure fields.";
35947
36119
  }
35948
36120
  const segment = getSegmentIfPresent(view);
35949
- return measureFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35950
- }, [view, field, path, measureFields]);
35951
- const isFilterAllowed = React.useMemo(() => {
35952
- if (!view) {
35953
- return false;
36121
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36122
+ return "This field is already used in the query.";
35954
36123
  }
35955
- const fieldName = field.name;
35956
- const inputSchemaFields = getInputSchemaFromViewParent(view).fields;
35957
- return inputSchemaFields.filter((field2) => field2.kind === "dimension" || field2.kind === "measure").filter((field2) => FILTERABLE_TYPES.includes(field2.type.kind)).some((field2) => field2.name === fieldName);
35958
- }, [view, field]);
35959
- const isOrderByAllowed = React.useMemo(() => {
35960
- if (!view) {
35961
- return false;
36124
+ if (!isNotAnnotatedFilteredField(field)) {
36125
+ return "This field is annotated with #NO_UI.";
36126
+ }
36127
+ return "";
36128
+ }, [matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, view, field, path]);
36129
+ const filterDisabledReason = React.useMemo(() => {
36130
+ if (!matchingFieldItem) {
36131
+ return `Unexpected Error: Could not find a field ${fullName}.`;
36132
+ }
36133
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36134
+ return `Filtering is only available for a dimension or measure.`;
36135
+ }
36136
+ if (!FILTERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36137
+ return "Filtering only supports string, boolean, number, date and time fields.";
36138
+ }
36139
+ return "";
36140
+ }, [fullName, matchingFieldItem]);
36141
+ const orderByDisabledReason = React.useMemo(() => {
36142
+ if (!matchingFieldItem) {
36143
+ return `Unexpected Error: Could not find a field ${fullName}.`;
35962
36144
  }
35963
- const fieldName = field.name;
35964
- const outputSchemaFields = view.getOutputSchema().fields;
35965
36145
  const segment = getSegmentIfPresent(view);
35966
- return outputSchemaFields.filter((field2) => field2.kind === "dimension").filter((field2) => ORDERABLE_TYPES.includes(field2.type.kind)).filter((field2) => !segment || !segmentHasOrderBy(segment, field2.name)).some((field2) => field2.name === fieldName);
35967
- }, [view, field]);
36146
+ if (segment && segmentHasOrderBySourceField(segment, path, field.name)) {
36147
+ return "Query is already ordered by this field.";
36148
+ }
36149
+ if (!segment || !segmentHasFieldInOutputSpace(segment, path, field.name)) {
36150
+ return "Order by is only available for fields in the output.";
36151
+ }
36152
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36153
+ return "Order By is only available for dimension or measure fields.";
36154
+ }
36155
+ if (!ORDERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36156
+ return "Order By only supports string, boolean, number, date and time fields.";
36157
+ }
36158
+ return "";
36159
+ }, [matchingFieldItem, view, path, field.name, fullName]);
35968
36160
  return {
35969
- isGroupByAllowed,
35970
- isAggregateAllowed,
35971
- isFilterAllowed,
35972
- isOrderByAllowed
36161
+ isGroupByAllowed: !groupByDisabledReason,
36162
+ groupByDisabledReason,
36163
+ isAggregateAllowed: !aggregateDisabledReason,
36164
+ aggregateDisabledReason,
36165
+ isFilterAllowed: !filterDisabledReason,
36166
+ filterDisabledReason,
36167
+ isOrderByAllowed: !orderByDisabledReason,
36168
+ orderByDisabledReason
35973
36169
  };
35974
36170
  }
35975
36171
  const FILTERABLE_TYPES = ["string_type", "boolean_type", "number_type", "date_type", "timestamp_type"];
@@ -35981,27 +36177,29 @@ function FieldTokenWithActions({
35981
36177
  }) {
35982
36178
  const {
35983
36179
  rootQuery,
35984
- setQuery,
35985
- currentNestView
36180
+ setQuery
35986
36181
  } = React.useContext(QueryEditorContext);
35987
- const view = currentNestView ?? viewDef;
35988
36182
  const {
35989
- isGroupByAllowed,
35990
- isAggregateAllowed,
35991
- isFilterAllowed,
35992
- isOrderByAllowed
36183
+ focusedNestView
36184
+ } = useQueryFocus();
36185
+ const view = focusedNestView ?? viewDef;
36186
+ const {
36187
+ groupByDisabledReason,
36188
+ aggregateDisabledReason,
36189
+ filterDisabledReason,
36190
+ orderByDisabledReason
35993
36191
  } = useOperations(view, field, path);
35994
36192
  const [isFilterPopoverOpen, setIsFilterPopoverOpen] = React.useState();
35995
36193
  const [isTooltipOpen, setIsTooltipOpen] = React.useState(false);
35996
36194
  const handleAddOperationAction = (operation, filter) => {
35997
36195
  if (field.kind === "dimension" || field.kind === "measure") {
35998
- if (operation === "groupBy" && isGroupByAllowed) {
36196
+ if (operation === "groupBy" && !groupByDisabledReason) {
35999
36197
  addGroupBy(view, field, path);
36000
- } else if (operation === "aggregate" && isAggregateAllowed) {
36198
+ } else if (operation === "aggregate" && !aggregateDisabledReason) {
36001
36199
  addAggregate(view, field, path);
36002
- } else if (operation === "orderBy" && isOrderByAllowed) {
36003
- addOrderBy(view, field);
36004
- } else if (operation === "filter" && isFilterAllowed && filter) {
36200
+ } else if (operation === "orderBy" && !orderByDisabledReason) {
36201
+ addOrderByFromSource(view, path, field.name);
36202
+ } else if (operation === "filter" && !filterDisabledReason && filter) {
36005
36203
  addFilter(view, field, path, filter);
36006
36204
  }
36007
36205
  setQuery == null ? void 0 : setQuery(rootQuery == null ? void 0 : rootQuery.build());
@@ -36026,7 +36224,7 @@ function FieldTokenWithActions({
36026
36224
  icon: "insert",
36027
36225
  disabled: !(rootQuery == null ? void 0 : rootQuery.isEmpty()),
36028
36226
  onClick: handleSetView,
36029
- tooltip: "Add view",
36227
+ tooltip: !(rootQuery == null ? void 0 : rootQuery.isEmpty()) ? "Can only add a view to an empty query." : "Add view",
36030
36228
  onTooltipOpenChange: setIsTooltipOpen
36031
36229
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36032
36230
  icon: "nest",
@@ -36037,8 +36235,8 @@ function FieldTokenWithActions({
36037
36235
  }) : field.kind === "measure" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
36038
36236
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36039
36237
  icon: "aggregate",
36040
- tooltip: "Add as aggregate",
36041
- disabled: !isAggregateAllowed,
36238
+ tooltip: aggregateDisabledReason || "Add as aggregate",
36239
+ disabled: !!aggregateDisabledReason,
36042
36240
  onClick: () => handleAddOperationAction("aggregate"),
36043
36241
  onTooltipOpenChange: setIsTooltipOpen
36044
36242
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -36047,23 +36245,23 @@ function FieldTokenWithActions({
36047
36245
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36048
36246
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36049
36247
  icon: "filter",
36050
- tooltip: "Add as filter",
36051
- disabled: !isFilterAllowed,
36248
+ tooltip: filterDisabledReason || "Add as filter",
36249
+ disabled: !!filterDisabledReason,
36052
36250
  onTooltipOpenChange: setIsTooltipOpen
36053
36251
  }),
36054
36252
  onOpenChange: setIsFilterPopoverOpen
36055
36253
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36056
36254
  icon: "orderBy",
36057
- tooltip: "Add as order by",
36058
- disabled: !isOrderByAllowed,
36255
+ tooltip: orderByDisabledReason || "Add as order by",
36256
+ disabled: !!orderByDisabledReason,
36059
36257
  onClick: () => handleAddOperationAction("orderBy"),
36060
36258
  onTooltipOpenChange: setIsTooltipOpen
36061
36259
  })]
36062
36260
  }) : field.kind === "dimension" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
36063
36261
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36064
36262
  icon: "groupBy",
36065
- tooltip: "Add as group by",
36066
- disabled: !isGroupByAllowed,
36263
+ tooltip: groupByDisabledReason || "Add as group by",
36264
+ disabled: !!groupByDisabledReason,
36067
36265
  onClick: () => handleAddOperationAction("groupBy"),
36068
36266
  onTooltipOpenChange: setIsTooltipOpen
36069
36267
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -36072,20 +36270,26 @@ function FieldTokenWithActions({
36072
36270
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36073
36271
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36074
36272
  icon: "filter",
36075
- tooltip: "Add as filter",
36076
- disabled: !isFilterAllowed,
36273
+ tooltip: filterDisabledReason || "Add as filter",
36274
+ disabled: !!filterDisabledReason,
36077
36275
  onTooltipOpenChange: setIsTooltipOpen
36078
36276
  }),
36079
36277
  onOpenChange: setIsFilterPopoverOpen
36080
36278
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36081
36279
  icon: "orderBy",
36082
- tooltip: "Add as order by",
36083
- disabled: !isOrderByAllowed,
36280
+ tooltip: orderByDisabledReason || "Add as order by",
36281
+ disabled: !!orderByDisabledReason,
36084
36282
  onClick: () => handleAddOperationAction("orderBy"),
36085
36283
  onTooltipOpenChange: setIsTooltipOpen
36086
36284
  })]
36087
36285
  }) : null,
36088
- onClick: field.kind === "dimension" && isGroupByAllowed ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && isAggregateAllowed ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => handleAddView() : void 0,
36286
+ onClick: field.kind === "dimension" && !groupByDisabledReason ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && !aggregateDisabledReason ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => {
36287
+ if (rootQuery == null ? void 0 : rootQuery.isEmpty()) {
36288
+ handleSetView();
36289
+ } else {
36290
+ handleAddView();
36291
+ }
36292
+ } : void 0,
36089
36293
  hoverActionsVisible: isFilterPopoverOpen || isTooltipOpen,
36090
36294
  tooltip: /* @__PURE__ */ jsxRuntime.jsx(FieldHoverCard, {
36091
36295
  field,