@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.
package/dist/esm/index.js CHANGED
@@ -17,7 +17,7 @@ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
17
17
  import * as React from "react";
18
18
  import React__default, { useLayoutEffect, useEffect, useMemo, useState, useRef, createElement, useContext, useCallback, createContext, memo, useReducer } from "react";
19
19
  import * as QB from "@malloydata/malloy-query-builder";
20
- import { ASTArrowQueryDefinition, ASTSegmentViewDefinition, ASTRefinementViewDefinition, ASTOrderByViewOperation, ASTLimitViewOperation, ASTNestViewOperation, ASTAggregateViewOperation, ASTGroupByViewOperation, ASTTimeTruncationExpression, ASTWhereViewOperation, ASTHavingViewOperation, ASTArrowViewDefinition } from "@malloydata/malloy-query-builder";
20
+ import { ASTArrowQueryDefinition, ASTArrowViewDefinition, ASTSegmentViewDefinition, ASTNestViewOperation, ASTRefinementViewDefinition, ASTOrderByViewOperation, ASTGroupByViewOperation, ASTAggregateViewOperation, ASTLimitViewOperation, ASTTimeTruncationExpression, ASTFilterWithFilterString, ASTWhereViewOperation, ASTHavingViewOperation, ASTDrillViewOperation } from "@malloydata/malloy-query-builder";
21
21
  import { Tag } from "@malloydata/malloy-tag";
22
22
  import { TemporalFilterExpression, BooleanFilterExpression, NumberFilterExpression, StringFilterExpression } from "@malloydata/malloy-filter";
23
23
  import "@malloydata/render/webcomponent";
@@ -3324,29 +3324,105 @@ function useQueryBuilder(source, query) {
3324
3324
  }
3325
3325
  }, [source, query]);
3326
3326
  }
3327
+ const MalloyQueryFocusContext = /* @__PURE__ */ React.createContext({
3328
+ focusMainView: () => {
3329
+ },
3330
+ isMainViewFocused: true,
3331
+ focusNestView: () => {
3332
+ },
3333
+ isNestViewFocused: () => false,
3334
+ focusedNestView: null
3335
+ });
3336
+ function MalloyQueryFocusProvider({
3337
+ rootQuery,
3338
+ focusedNestViewPath,
3339
+ onFocusedNestViewPathChange,
3340
+ children
3341
+ }) {
3342
+ const focusedNestView = React.useMemo(() => {
3343
+ if (focusedNestViewPath.length === 0) {
3344
+ return null;
3345
+ }
3346
+ if (rootQuery) {
3347
+ const queryDef = rootQuery.definition;
3348
+ if (queryDef instanceof ASTArrowQueryDefinition) {
3349
+ return findNestView(queryDef.view, [...focusedNestViewPath].reverse());
3350
+ }
3351
+ }
3352
+ return null;
3353
+ }, [rootQuery, focusedNestViewPath]);
3354
+ const focusMainView = React.useCallback(() => {
3355
+ onFocusedNestViewPathChange([]);
3356
+ }, [onFocusedNestViewPathChange]);
3357
+ const focusNestView = React.useCallback((path) => {
3358
+ onFocusedNestViewPathChange([...path]);
3359
+ }, [onFocusedNestViewPathChange]);
3360
+ const isNestViewFocused = React.useCallback((path) => {
3361
+ return JSON.stringify(path) === JSON.stringify(focusedNestViewPath);
3362
+ }, [focusedNestViewPath]);
3363
+ const isMainViewFocused = focusedNestViewPath.length === 0;
3364
+ return /* @__PURE__ */ jsx(MalloyQueryFocusContext.Provider, {
3365
+ value: {
3366
+ focusMainView,
3367
+ isMainViewFocused,
3368
+ focusNestView,
3369
+ isNestViewFocused,
3370
+ focusedNestView
3371
+ },
3372
+ children
3373
+ });
3374
+ }
3375
+ function useQueryFocus() {
3376
+ return React.useContext(MalloyQueryFocusContext);
3377
+ }
3378
+ const findNestView = (currentView, remainingPath) => {
3379
+ if (remainingPath.length === 0) {
3380
+ return null;
3381
+ }
3382
+ if (currentView instanceof ASTArrowViewDefinition) {
3383
+ return findNestView(currentView.view, remainingPath);
3384
+ }
3385
+ if (currentView instanceof ASTSegmentViewDefinition) {
3386
+ const currentNestName = remainingPath.pop();
3387
+ const currentNestOperation = currentView.operations.items.find((operation) => operation instanceof ASTNestViewOperation && operation.name === currentNestName);
3388
+ if (currentNestOperation === void 0) {
3389
+ remainingPath.push(currentNestName);
3390
+ return null;
3391
+ } else if (remainingPath.length === 0) {
3392
+ return currentNestOperation.view;
3393
+ } else {
3394
+ return findNestView(currentNestOperation.view.definition, remainingPath);
3395
+ }
3396
+ }
3397
+ if (currentView instanceof ASTRefinementViewDefinition) {
3398
+ return findNestView(currentView.refinement, remainingPath);
3399
+ }
3400
+ return null;
3401
+ };
3327
3402
  function MalloyExplorerProvider({
3328
3403
  source,
3329
3404
  query,
3330
- setQuery,
3405
+ onQueryChange,
3406
+ focusedNestViewPath,
3407
+ onFocusedNestViewPathChange,
3331
3408
  children,
3332
3409
  topValues
3333
3410
  }) {
3334
3411
  const rootQuery = useQueryBuilder(source, query);
3335
- const [currentNestView, setCurrentNestView] = React.useState(null);
3336
- const [currentNestQueryPanel, setCurrentNestQueryPanel] = React.useState(null);
3337
3412
  return /* @__PURE__ */ jsx(TooltipProvider, {
3338
- children: /* @__PURE__ */ jsx(QueryEditorContext.Provider, {
3339
- value: {
3340
- source,
3341
- rootQuery,
3342
- setQuery,
3343
- topValues,
3344
- currentNestQueryPanel,
3345
- onCurrentNestQueryPanelChange: setCurrentNestQueryPanel,
3346
- currentNestView,
3347
- onCurrentNestViewChange: setCurrentNestView
3348
- },
3349
- children
3413
+ children: /* @__PURE__ */ jsx(MalloyQueryFocusProvider, {
3414
+ rootQuery,
3415
+ focusedNestViewPath,
3416
+ onFocusedNestViewPathChange,
3417
+ children: /* @__PURE__ */ jsx(QueryEditorContext.Provider, {
3418
+ value: {
3419
+ source,
3420
+ rootQuery,
3421
+ setQuery: onQueryChange,
3422
+ topValues
3423
+ },
3424
+ children
3425
+ })
3350
3426
  })
3351
3427
  });
3352
3428
  }
@@ -25863,13 +25939,14 @@ function QueryActionBar({
25863
25939
  const {
25864
25940
  rootQuery,
25865
25941
  setQuery,
25866
- source,
25867
- onCurrentNestQueryPanelChange,
25868
- onCurrentNestViewChange
25942
+ source
25869
25943
  } = useContext(QueryEditorContext);
25870
25944
  const {
25871
25945
  onCollapse
25872
25946
  } = useContext(ResizableCollapsiblePanelContext);
25947
+ const {
25948
+ focusMainView
25949
+ } = useQueryFocus();
25873
25950
  const isQueryEmpty = !rootQuery || rootQuery.isEmpty();
25874
25951
  const isRunEnabled = rootQuery == null ? void 0 : rootQuery.isRunnable();
25875
25952
  const onRunQuery = () => {
@@ -25877,10 +25954,6 @@ function QueryActionBar({
25877
25954
  runQuery(source, rootQuery.build());
25878
25955
  }
25879
25956
  };
25880
- const focusMainQueryPanel = () => {
25881
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
25882
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
25883
- };
25884
25957
  return /* @__PURE__ */ jsxs("div", {
25885
25958
  ...{
25886
25959
  className: "mly78zum5 mly1qughib mly6s0dn4 mlye8ttls"
@@ -25901,7 +25974,7 @@ function QueryActionBar({
25901
25974
  },
25902
25975
  children: [/* @__PURE__ */ jsx(Button, {
25903
25976
  onClick: () => {
25904
- focusMainQueryPanel();
25977
+ focusMainView();
25905
25978
  setQuery == null ? void 0 : setQuery(void 0);
25906
25979
  },
25907
25980
  isDisabled: !rootQuery || (rootQuery == null ? void 0 : rootQuery.isEmpty()),
@@ -29932,6 +30005,11 @@ function getViewDefinition(parent) {
29932
30005
  return parent instanceof ASTArrowQueryDefinition ? parent.view : parent.definition;
29933
30006
  }
29934
30007
  function getInputSchemaFromViewParent(parent) {
30008
+ if (!parent) {
30009
+ return {
30010
+ fields: []
30011
+ };
30012
+ }
29935
30013
  const definition = getViewDefinition(parent);
29936
30014
  return definition.getInputSchema();
29937
30015
  }
@@ -32382,12 +32460,54 @@ const styles$g = {
32382
32460
  $$css: true
32383
32461
  }
32384
32462
  };
32463
+ function toFullName(path, name) {
32464
+ return [...path || [], name].join(".");
32465
+ }
32385
32466
  function segmentHasLimit(segment) {
32386
32467
  return segment.operations.items.find((operation) => operation instanceof ASTLimitViewOperation) !== void 0;
32387
32468
  }
32388
32469
  function segmentHasOrderBy(segment, name) {
32389
32470
  return segment.operations.items.find((operation) => operation instanceof ASTOrderByViewOperation && operation.name === name) !== void 0;
32390
32471
  }
32472
+ function getOutputNameToInputNameMap(segment) {
32473
+ const nameMap = /* @__PURE__ */ new Map();
32474
+ for (const operation of segment.operations.items) {
32475
+ if (operation instanceof ASTGroupByViewOperation || operation instanceof ASTAggregateViewOperation) {
32476
+ const reference = operation.field.getReference();
32477
+ if (reference) {
32478
+ nameMap.set(operation.name, toFullName(reference.path, reference.name));
32479
+ }
32480
+ }
32481
+ }
32482
+ return nameMap;
32483
+ }
32484
+ function segmentHasOrderBySourceField(segment, path, name) {
32485
+ const nameMap = getOutputNameToInputNameMap(segment);
32486
+ const fullInputName = toFullName(path, name);
32487
+ return !!segment.operations.items.find((operation) => {
32488
+ if (operation instanceof ASTOrderByViewOperation && nameMap.has(operation.name)) {
32489
+ return fullInputName === nameMap.get(operation.name);
32490
+ }
32491
+ return false;
32492
+ });
32493
+ }
32494
+ function areReferencesEqual(path1, name1, path2, name2) {
32495
+ return name1 === name2 && (path1 || []).join(".") === (path2 || []).join(".");
32496
+ }
32497
+ function segmentHasFieldInOutputSpace(segment, path, name) {
32498
+ const match = segment.operations.items.find((operation) => {
32499
+ if (operation instanceof ASTGroupByViewOperation || operation instanceof ASTAggregateViewOperation) {
32500
+ const reference = operation.field.getReference();
32501
+ if (reference) {
32502
+ return areReferencesEqual(path, name, reference.path, reference.name);
32503
+ } else {
32504
+ return false;
32505
+ }
32506
+ }
32507
+ return false;
32508
+ });
32509
+ return !!match;
32510
+ }
32391
32511
  function segmentNestNo(segment, name) {
32392
32512
  return segment.operations.items.reduce((acc, operation) => {
32393
32513
  if (operation instanceof ASTNestViewOperation) {
@@ -32442,6 +32562,19 @@ function addNest(view, field) {
32442
32562
  }
32443
32563
  segment.addNest(field.name, rename);
32444
32564
  }
32565
+ function addOrderByFromSource(view, path, name, direction = "desc") {
32566
+ const fullInputName = toFullName(path, name);
32567
+ let orderByName = name;
32568
+ const segment = view.getOrAddDefaultSegment();
32569
+ const nameMap = getOutputNameToInputNameMap(segment);
32570
+ for (const entry of nameMap.entries()) {
32571
+ if (entry[1] === fullInputName) {
32572
+ orderByName = entry[0];
32573
+ break;
32574
+ }
32575
+ }
32576
+ segment.addOrderBy(orderByName, direction);
32577
+ }
32445
32578
  function addOrderBy(view, field, direction = "desc") {
32446
32579
  const segment = view.getOrAddDefaultSegment();
32447
32580
  segment.addOrderBy(field.name, direction);
@@ -32455,6 +32588,9 @@ function addFilter(view, field, path, filter) {
32455
32588
  }
32456
32589
  }
32457
32590
  function getSegmentIfPresent(parent) {
32591
+ if (!parent) {
32592
+ return void 0;
32593
+ }
32458
32594
  const definition = getViewDefinition(parent);
32459
32595
  if (definition instanceof ASTSegmentViewDefinition) {
32460
32596
  return definition;
@@ -33083,11 +33219,12 @@ function SortableOperation({
33083
33219
  operation,
33084
33220
  color
33085
33221
  }) {
33222
+ var _a2;
33086
33223
  const {
33087
33224
  setQuery
33088
33225
  } = useContext(QueryEditorContext);
33089
33226
  const fieldInfo = operation.getFieldInfo();
33090
- const path = operation.field.getReference().path ?? NULL_PATH;
33227
+ const path = ((_a2 = operation.field.getReference()) == null ? void 0 : _a2.path) ?? NULL_PATH;
33091
33228
  const {
33092
33229
  attributes,
33093
33230
  listeners,
@@ -33288,28 +33425,62 @@ const parsedToLabels = (parsed, filterString) => {
33288
33425
  value = stringClause.escaped_values.join(", ");
33289
33426
  break;
33290
33427
  case "=":
33291
- op = "is";
33292
- value = stringClause.values.join(", ");
33428
+ {
33429
+ const {
33430
+ not,
33431
+ values
33432
+ } = stringClause;
33433
+ op = not ? "is not" : "is";
33434
+ value = values.join(", ");
33435
+ }
33293
33436
  break;
33294
33437
  case "contains":
33295
- op = "contains";
33296
- value = stringClause.values.join(", ");
33438
+ {
33439
+ const {
33440
+ not,
33441
+ values
33442
+ } = stringClause;
33443
+ op = not ? "does not contain" : "contains";
33444
+ value = values.join(", ");
33445
+ }
33297
33446
  break;
33298
33447
  case "starts":
33299
- op = "starts with";
33300
- value = stringClause.values.join(", ");
33448
+ {
33449
+ const {
33450
+ not,
33451
+ values
33452
+ } = stringClause;
33453
+ op = not ? "does not start with" : "starts with";
33454
+ value = values.join(", ");
33455
+ }
33301
33456
  break;
33302
33457
  case "ends":
33303
- op = "is like";
33304
- value = stringClause.values.join(", ");
33458
+ {
33459
+ const {
33460
+ not,
33461
+ values
33462
+ } = stringClause;
33463
+ op = not ? "does not end with" : "ends with";
33464
+ value = values.join(", ");
33465
+ }
33305
33466
  break;
33306
33467
  case "empty":
33307
- op = "is empty";
33308
- value = "";
33468
+ {
33469
+ const {
33470
+ not
33471
+ } = stringClause;
33472
+ op = not ? "is not empty" : "is empty";
33473
+ value = "";
33474
+ }
33309
33475
  break;
33310
33476
  case "null":
33311
- op = stringClause.not ? "is not" : "is";
33312
- value = "null";
33477
+ {
33478
+ const {
33479
+ not
33480
+ } = stringClause;
33481
+ op = not ? "is not" : "is";
33482
+ value = "null";
33483
+ }
33313
33484
  break;
33314
33485
  }
33315
33486
  }
@@ -33473,22 +33644,27 @@ function SingleFilterOperation({
33473
33644
  rootQuery,
33474
33645
  filterOperation
33475
33646
  }) {
33647
+ const {
33648
+ setQuery
33649
+ } = useContext(QueryEditorContext);
33650
+ const setFilter = useCallback((filter2) => {
33651
+ if (filterOperation.filter instanceof ASTFilterWithFilterString) {
33652
+ filterOperation.filter.setFilter(filter2);
33653
+ }
33654
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33655
+ }, [filterOperation.filter, rootQuery, setQuery]);
33656
+ if (!(filterOperation.filter instanceof ASTFilterWithFilterString)) {
33657
+ return null;
33658
+ }
33476
33659
  const {
33477
33660
  fieldReference,
33478
33661
  filterString
33479
33662
  } = filterOperation.filter;
33480
33663
  const filter = filterOperation.filter.getFilter();
33481
33664
  const fieldInfo = fieldReference.getFieldInfo();
33482
- const {
33483
- setQuery
33484
- } = useContext(QueryEditorContext);
33485
33665
  if (fieldInfo.kind !== "dimension" && fieldInfo.kind !== "measure") {
33486
33666
  throw new Error(`Invalid filter field kind: ${fieldInfo.kind}`);
33487
33667
  }
33488
- const setFilter = useCallback((filter2) => {
33489
- filterOperation.filter.setFilter(filter2);
33490
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33491
- }, [filterOperation.filter, rootQuery, setQuery]);
33492
33668
  const {
33493
33669
  op,
33494
33670
  value
@@ -34099,6 +34275,35 @@ const styles$d = {
34099
34275
  $$css: true
34100
34276
  }
34101
34277
  };
34278
+ const NestViewPathContext = /* @__PURE__ */ React__default.createContext([]);
34279
+ function FocusableView({
34280
+ children,
34281
+ nest
34282
+ }) {
34283
+ const {
34284
+ focusNestView,
34285
+ focusMainView
34286
+ } = useQueryFocus();
34287
+ const parentNestViewPath = useContext(NestViewPathContext);
34288
+ return /* @__PURE__ */ jsx("div", {
34289
+ onPointerDown: (e) => {
34290
+ e.stopPropagation();
34291
+ if (nest) {
34292
+ focusNestView([...parentNestViewPath, nest.name]);
34293
+ } else {
34294
+ focusMainView();
34295
+ }
34296
+ },
34297
+ children: nest ? /* @__PURE__ */ jsx(NestViewPathContext.Provider, {
34298
+ value: [...parentNestViewPath, nest.name],
34299
+ children: /* @__PURE__ */ jsx("div", {
34300
+ children
34301
+ })
34302
+ }) : /* @__PURE__ */ jsx("div", {
34303
+ children
34304
+ })
34305
+ });
34306
+ }
34102
34307
  function NestOperations({
34103
34308
  rootQuery,
34104
34309
  view,
@@ -34122,31 +34327,14 @@ function NestOperation({
34122
34327
  nest
34123
34328
  }) {
34124
34329
  const {
34125
- setQuery,
34126
- currentNestQueryPanel,
34127
- onCurrentNestQueryPanelChange,
34128
- onCurrentNestViewChange
34330
+ setQuery
34129
34331
  } = useContext(QueryEditorContext);
34130
34332
  const [renameOpen, setRenameOpen] = useState(false);
34131
- const panelRef = React.useRef(null);
34132
- const isCurrentNestQueryPanelFocused = currentNestQueryPanel !== null && panelRef.current == currentNestQueryPanel;
34133
- React.useEffect(() => {
34134
- if (isCurrentNestQueryPanelFocused) {
34135
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34136
- }
34137
- }, [nest, isCurrentNestQueryPanelFocused, onCurrentNestViewChange]);
34138
- const focusCurrentNestQueryPanel = () => {
34139
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(panelRef.current);
34140
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34141
- };
34142
- const focusParentQueryPanel = () => {
34143
- const currentPanel = panelRef.current;
34144
- const parent = findParentNestQueryPanel(currentPanel);
34145
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(parent);
34146
- if (parent === null) {
34147
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34148
- }
34149
- };
34333
+ const parentNestViewPath = useContext(NestViewPathContext);
34334
+ const {
34335
+ focusNestView,
34336
+ isNestViewFocused
34337
+ } = useQueryFocus();
34150
34338
  const getControls = (nest2) => /* @__PURE__ */ jsxs(Fragment, {
34151
34339
  children: [/* @__PURE__ */ jsxs(DropdownMenu, {
34152
34340
  trigger: /* @__PURE__ */ jsx(Button, {
@@ -34159,7 +34347,7 @@ function NestOperation({
34159
34347
  icon: "clear",
34160
34348
  label: "Delete Query",
34161
34349
  onClick: () => {
34162
- focusParentQueryPanel();
34350
+ focusNestView([...parentNestViewPath]);
34163
34351
  nest2.delete();
34164
34352
  setQuery == null ? void 0 : setQuery(rootQuery.build());
34165
34353
  }
@@ -34174,21 +34362,19 @@ function NestOperation({
34174
34362
  view: nest2.view
34175
34363
  })]
34176
34364
  });
34177
- return /* @__PURE__ */ jsx("div", {
34178
- ...{
34179
- className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34180
- },
34365
+ return /* @__PURE__ */ jsx(FocusableView, {
34366
+ nest,
34181
34367
  children: /* @__PURE__ */ jsxs("div", {
34182
- ref: panelRef,
34183
- onPointerDownCapture: focusCurrentNestQueryPanel,
34184
- "data-nest-panel": true,
34368
+ ...{
34369
+ className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34370
+ },
34185
34371
  children: [/* @__PURE__ */ jsx(CollapsiblePanel, {
34186
34372
  title: nest.name,
34187
34373
  icon: viewToVisualizationIcon(nest.view),
34188
34374
  defaultOpen: true,
34189
34375
  controls: getControls(nest),
34190
34376
  collapsedControls: getControls(nest),
34191
- isFocused: isCurrentNestQueryPanelFocused,
34377
+ isFocused: isNestViewFocused([...parentNestViewPath, nest.name]),
34192
34378
  children: /* @__PURE__ */ jsx(View, {
34193
34379
  rootQuery,
34194
34380
  view: nest.view
@@ -34200,14 +34386,8 @@ function NestOperation({
34200
34386
  open: renameOpen,
34201
34387
  setOpen: setRenameOpen
34202
34388
  })]
34203
- })
34204
- }, nest.name);
34205
- }
34206
- function findParentNestQueryPanel(element2) {
34207
- if (!element2 || !element2.parentElement) return null;
34208
- const parentElement = element2.parentElement;
34209
- if (parentElement.dataset.nestPanel !== void 0) return parentElement;
34210
- return findParentNestQueryPanel(parentElement);
34389
+ }, nest.name)
34390
+ });
34211
34391
  }
34212
34392
  function Operations({
34213
34393
  rootQuery,
@@ -34237,7 +34417,8 @@ function Operations({
34237
34417
  orderBys.push(operation);
34238
34418
  } else if (operation instanceof ASTNestViewOperation) {
34239
34419
  nests.push(operation);
34240
- } else {
34420
+ } else if (operation instanceof ASTDrillViewOperation) ;
34421
+ else {
34241
34422
  limit = operation;
34242
34423
  }
34243
34424
  });
@@ -34376,19 +34557,13 @@ function Query({
34376
34557
  setQuery
34377
34558
  }) {
34378
34559
  const {
34379
- currentNestQueryPanel,
34380
- onCurrentNestQueryPanelChange,
34381
- onCurrentNestViewChange
34382
- } = React.useContext(QueryEditorContext);
34383
- const focusMainQueryPanel = () => {
34384
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
34385
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34386
- };
34387
- return /* @__PURE__ */ jsx("div", {
34388
- onPointerDownCapture: focusMainQueryPanel,
34560
+ focusMainView,
34561
+ isMainViewFocused
34562
+ } = useQueryFocus();
34563
+ return /* @__PURE__ */ jsx(FocusableView, {
34389
34564
  children: /* @__PURE__ */ jsxs(CollapsiblePanel, {
34390
34565
  title: "Main query",
34391
- isFocused: !currentNestQueryPanel,
34566
+ isFocused: isMainViewFocused,
34392
34567
  controls: /* @__PURE__ */ jsxs(Fragment, {
34393
34568
  children: [/* @__PURE__ */ jsx(DropdownMenu, {
34394
34569
  trigger: /* @__PURE__ */ jsx(Button, {
@@ -34402,7 +34577,7 @@ function Query({
34402
34577
  icon: "clear",
34403
34578
  label: "Clear query",
34404
34579
  onClick: () => {
34405
- focusMainQueryPanel();
34580
+ focusMainView();
34406
34581
  setQuery == null ? void 0 : setQuery(void 0);
34407
34582
  },
34408
34583
  disabled: rootQuery.isEmpty()
@@ -35896,62 +36071,83 @@ const FIELD_KIND_TO_TITLE = {
35896
36071
  dimension: "Dimensions"
35897
36072
  };
35898
36073
  function useOperations(view, field, path) {
35899
- const dimensionFields = useMemo(() => {
36074
+ const fullName = toFullName(path, field.name);
36075
+ const flattenedFields = useMemo(() => {
35900
36076
  const {
35901
36077
  fields
35902
36078
  } = getInputSchemaFromViewParent(view);
35903
- return new Set(flattenFieldsTree(fields).filter(({
35904
- field: field2
35905
- }) => field2.kind === "dimension").map(({
35906
- field: field2
35907
- }) => field2.name));
35908
- }, [view]);
35909
- const measureFields = useMemo(() => {
35910
- const {
35911
- fields
35912
- } = getInputSchemaFromViewParent(view);
35913
- return new Set(flattenFieldsTree(fields).filter(({
35914
- field: field2
35915
- }) => field2.kind === "measure").map(({
35916
- field: field2
35917
- }) => field2.name));
35918
- }, [view]);
35919
- const isGroupByAllowed = useMemo(() => {
35920
- if (!view) {
35921
- return false;
35922
- }
36079
+ const inputPath = path.join(".");
36080
+ return flattenFieldsTree(fields).filter((fieldItem) => {
36081
+ return fieldItem.path.join(".") === inputPath;
36082
+ });
36083
+ }, [path, view]);
36084
+ const matchingFieldItem = flattenedFields.find((fieldItem) => field.name === fieldItem.field.name);
36085
+ const groupByDisabledReason = useMemo(() => {
35923
36086
  const segment = getSegmentIfPresent(view);
35924
- return dimensionFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35925
- }, [view, field, path, dimensionFields]);
35926
- const isAggregateAllowed = useMemo(() => {
35927
- if (!view) {
35928
- return false;
36087
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "dimension") {
36088
+ return "Grouping is only available on a dimenion.";
36089
+ }
36090
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36091
+ return "Cannot group by a field already in the view.";
36092
+ }
36093
+ if (!isNotAnnotatedFilteredField(field)) {
36094
+ return "This field is annotated with #NO_UI.";
36095
+ }
36096
+ return "";
36097
+ }, [view, matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, field, path]);
36098
+ const aggregateDisabledReason = useMemo(() => {
36099
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "measure") {
36100
+ return "Aggregation only supports measure fields.";
35929
36101
  }
35930
36102
  const segment = getSegmentIfPresent(view);
35931
- return measureFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35932
- }, [view, field, path, measureFields]);
35933
- const isFilterAllowed = useMemo(() => {
35934
- if (!view) {
35935
- return false;
36103
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36104
+ return "This field is already used in the query.";
35936
36105
  }
35937
- const fieldName = field.name;
35938
- const inputSchemaFields = getInputSchemaFromViewParent(view).fields;
35939
- return inputSchemaFields.filter((field2) => field2.kind === "dimension" || field2.kind === "measure").filter((field2) => FILTERABLE_TYPES.includes(field2.type.kind)).some((field2) => field2.name === fieldName);
35940
- }, [view, field]);
35941
- const isOrderByAllowed = useMemo(() => {
35942
- if (!view) {
35943
- return false;
36106
+ if (!isNotAnnotatedFilteredField(field)) {
36107
+ return "This field is annotated with #NO_UI.";
36108
+ }
36109
+ return "";
36110
+ }, [matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, view, field, path]);
36111
+ const filterDisabledReason = useMemo(() => {
36112
+ if (!matchingFieldItem) {
36113
+ return `Unexpected Error: Could not find a field ${fullName}.`;
36114
+ }
36115
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36116
+ return `Filtering is only available for a dimension or measure.`;
36117
+ }
36118
+ if (!FILTERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36119
+ return "Filtering only supports string, boolean, number, date and time fields.";
36120
+ }
36121
+ return "";
36122
+ }, [fullName, matchingFieldItem]);
36123
+ const orderByDisabledReason = useMemo(() => {
36124
+ if (!matchingFieldItem) {
36125
+ return `Unexpected Error: Could not find a field ${fullName}.`;
35944
36126
  }
35945
- const fieldName = field.name;
35946
- const outputSchemaFields = view.getOutputSchema().fields;
35947
36127
  const segment = getSegmentIfPresent(view);
35948
- 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);
35949
- }, [view, field]);
36128
+ if (segment && segmentHasOrderBySourceField(segment, path, field.name)) {
36129
+ return "Query is already ordered by this field.";
36130
+ }
36131
+ if (!segment || !segmentHasFieldInOutputSpace(segment, path, field.name)) {
36132
+ return "Order by is only available for fields in the output.";
36133
+ }
36134
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36135
+ return "Order By is only available for dimension or measure fields.";
36136
+ }
36137
+ if (!ORDERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36138
+ return "Order By only supports string, boolean, number, date and time fields.";
36139
+ }
36140
+ return "";
36141
+ }, [matchingFieldItem, view, path, field.name, fullName]);
35950
36142
  return {
35951
- isGroupByAllowed,
35952
- isAggregateAllowed,
35953
- isFilterAllowed,
35954
- isOrderByAllowed
36143
+ isGroupByAllowed: !groupByDisabledReason,
36144
+ groupByDisabledReason,
36145
+ isAggregateAllowed: !aggregateDisabledReason,
36146
+ aggregateDisabledReason,
36147
+ isFilterAllowed: !filterDisabledReason,
36148
+ filterDisabledReason,
36149
+ isOrderByAllowed: !orderByDisabledReason,
36150
+ orderByDisabledReason
35955
36151
  };
35956
36152
  }
35957
36153
  const FILTERABLE_TYPES = ["string_type", "boolean_type", "number_type", "date_type", "timestamp_type"];
@@ -35963,27 +36159,29 @@ function FieldTokenWithActions({
35963
36159
  }) {
35964
36160
  const {
35965
36161
  rootQuery,
35966
- setQuery,
35967
- currentNestView
36162
+ setQuery
35968
36163
  } = React__default.useContext(QueryEditorContext);
35969
- const view = currentNestView ?? viewDef;
35970
36164
  const {
35971
- isGroupByAllowed,
35972
- isAggregateAllowed,
35973
- isFilterAllowed,
35974
- isOrderByAllowed
36165
+ focusedNestView
36166
+ } = useQueryFocus();
36167
+ const view = focusedNestView ?? viewDef;
36168
+ const {
36169
+ groupByDisabledReason,
36170
+ aggregateDisabledReason,
36171
+ filterDisabledReason,
36172
+ orderByDisabledReason
35975
36173
  } = useOperations(view, field, path);
35976
36174
  const [isFilterPopoverOpen, setIsFilterPopoverOpen] = useState();
35977
36175
  const [isTooltipOpen, setIsTooltipOpen] = useState(false);
35978
36176
  const handleAddOperationAction = (operation, filter) => {
35979
36177
  if (field.kind === "dimension" || field.kind === "measure") {
35980
- if (operation === "groupBy" && isGroupByAllowed) {
36178
+ if (operation === "groupBy" && !groupByDisabledReason) {
35981
36179
  addGroupBy(view, field, path);
35982
- } else if (operation === "aggregate" && isAggregateAllowed) {
36180
+ } else if (operation === "aggregate" && !aggregateDisabledReason) {
35983
36181
  addAggregate(view, field, path);
35984
- } else if (operation === "orderBy" && isOrderByAllowed) {
35985
- addOrderBy(view, field);
35986
- } else if (operation === "filter" && isFilterAllowed && filter) {
36182
+ } else if (operation === "orderBy" && !orderByDisabledReason) {
36183
+ addOrderByFromSource(view, path, field.name);
36184
+ } else if (operation === "filter" && !filterDisabledReason && filter) {
35987
36185
  addFilter(view, field, path, filter);
35988
36186
  }
35989
36187
  setQuery == null ? void 0 : setQuery(rootQuery == null ? void 0 : rootQuery.build());
@@ -36008,7 +36206,7 @@ function FieldTokenWithActions({
36008
36206
  icon: "insert",
36009
36207
  disabled: !(rootQuery == null ? void 0 : rootQuery.isEmpty()),
36010
36208
  onClick: handleSetView,
36011
- tooltip: "Add view",
36209
+ tooltip: !(rootQuery == null ? void 0 : rootQuery.isEmpty()) ? "Can only add a view to an empty query." : "Add view",
36012
36210
  onTooltipOpenChange: setIsTooltipOpen
36013
36211
  }), /* @__PURE__ */ jsx(ActionButton, {
36014
36212
  icon: "nest",
@@ -36019,8 +36217,8 @@ function FieldTokenWithActions({
36019
36217
  }) : field.kind === "measure" ? /* @__PURE__ */ jsxs(Fragment, {
36020
36218
  children: [/* @__PURE__ */ jsx(ActionButton, {
36021
36219
  icon: "aggregate",
36022
- tooltip: "Add as aggregate",
36023
- disabled: !isAggregateAllowed,
36220
+ tooltip: aggregateDisabledReason || "Add as aggregate",
36221
+ disabled: !!aggregateDisabledReason,
36024
36222
  onClick: () => handleAddOperationAction("aggregate"),
36025
36223
  onTooltipOpenChange: setIsTooltipOpen
36026
36224
  }), /* @__PURE__ */ jsx(FilterPopover, {
@@ -36029,23 +36227,23 @@ function FieldTokenWithActions({
36029
36227
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36030
36228
  trigger: /* @__PURE__ */ jsx(ActionButton, {
36031
36229
  icon: "filter",
36032
- tooltip: "Add as filter",
36033
- disabled: !isFilterAllowed,
36230
+ tooltip: filterDisabledReason || "Add as filter",
36231
+ disabled: !!filterDisabledReason,
36034
36232
  onTooltipOpenChange: setIsTooltipOpen
36035
36233
  }),
36036
36234
  onOpenChange: setIsFilterPopoverOpen
36037
36235
  }), /* @__PURE__ */ jsx(ActionButton, {
36038
36236
  icon: "orderBy",
36039
- tooltip: "Add as order by",
36040
- disabled: !isOrderByAllowed,
36237
+ tooltip: orderByDisabledReason || "Add as order by",
36238
+ disabled: !!orderByDisabledReason,
36041
36239
  onClick: () => handleAddOperationAction("orderBy"),
36042
36240
  onTooltipOpenChange: setIsTooltipOpen
36043
36241
  })]
36044
36242
  }) : field.kind === "dimension" ? /* @__PURE__ */ jsxs(Fragment, {
36045
36243
  children: [/* @__PURE__ */ jsx(ActionButton, {
36046
36244
  icon: "groupBy",
36047
- tooltip: "Add as group by",
36048
- disabled: !isGroupByAllowed,
36245
+ tooltip: groupByDisabledReason || "Add as group by",
36246
+ disabled: !!groupByDisabledReason,
36049
36247
  onClick: () => handleAddOperationAction("groupBy"),
36050
36248
  onTooltipOpenChange: setIsTooltipOpen
36051
36249
  }), /* @__PURE__ */ jsx(FilterPopover, {
@@ -36054,20 +36252,26 @@ function FieldTokenWithActions({
36054
36252
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36055
36253
  trigger: /* @__PURE__ */ jsx(ActionButton, {
36056
36254
  icon: "filter",
36057
- tooltip: "Add as filter",
36058
- disabled: !isFilterAllowed,
36255
+ tooltip: filterDisabledReason || "Add as filter",
36256
+ disabled: !!filterDisabledReason,
36059
36257
  onTooltipOpenChange: setIsTooltipOpen
36060
36258
  }),
36061
36259
  onOpenChange: setIsFilterPopoverOpen
36062
36260
  }), /* @__PURE__ */ jsx(ActionButton, {
36063
36261
  icon: "orderBy",
36064
- tooltip: "Add as order by",
36065
- disabled: !isOrderByAllowed,
36262
+ tooltip: orderByDisabledReason || "Add as order by",
36263
+ disabled: !!orderByDisabledReason,
36066
36264
  onClick: () => handleAddOperationAction("orderBy"),
36067
36265
  onTooltipOpenChange: setIsTooltipOpen
36068
36266
  })]
36069
36267
  }) : null,
36070
- onClick: field.kind === "dimension" && isGroupByAllowed ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && isAggregateAllowed ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => handleAddView() : void 0,
36268
+ onClick: field.kind === "dimension" && !groupByDisabledReason ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && !aggregateDisabledReason ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => {
36269
+ if (rootQuery == null ? void 0 : rootQuery.isEmpty()) {
36270
+ handleSetView();
36271
+ } else {
36272
+ handleAddView();
36273
+ }
36274
+ } : void 0,
36071
36275
  hoverActionsVisible: isFilterPopoverOpen || isTooltipOpen,
36072
36276
  tooltip: /* @__PURE__ */ jsx(FieldHoverCard, {
36073
36277
  field,