@malloydata/malloy-explorer 0.0.277-dev250515002611 → 0.0.278-dev250516210719

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.
@@ -19,6 +19,7 @@ const jsxRuntime = require("react/jsx-runtime");
19
19
  const React = require("react");
20
20
  const QB = require("@malloydata/malloy-query-builder");
21
21
  const malloyTag = require("@malloydata/malloy-tag");
22
+ const malloyFilter = require("@malloydata/malloy-filter");
22
23
  require("@malloydata/render/webcomponent");
23
24
  const ReactDOM = require("react-dom");
24
25
  function _interopNamespaceDefault(e) {
@@ -29949,6 +29950,11 @@ function getViewDefinition(parent) {
29949
29950
  return parent instanceof QB.ASTArrowQueryDefinition ? parent.view : parent.definition;
29950
29951
  }
29951
29952
  function getInputSchemaFromViewParent(parent) {
29953
+ if (!parent) {
29954
+ return {
29955
+ fields: []
29956
+ };
29957
+ }
29952
29958
  const definition = getViewDefinition(parent);
29953
29959
  return definition.getInputSchema();
29954
29960
  }
@@ -32399,12 +32405,49 @@ const styles$g = {
32399
32405
  $$css: true
32400
32406
  }
32401
32407
  };
32408
+ function toFullName(path, name) {
32409
+ return [...path || [], name].join(".");
32410
+ }
32402
32411
  function segmentHasLimit(segment) {
32403
32412
  return segment.operations.items.find((operation) => operation instanceof QB.ASTLimitViewOperation) !== void 0;
32404
32413
  }
32405
32414
  function segmentHasOrderBy(segment, name) {
32406
32415
  return segment.operations.items.find((operation) => operation instanceof QB.ASTOrderByViewOperation && operation.name === name) !== void 0;
32407
32416
  }
32417
+ function getOutputNameToInputNameMap(segment) {
32418
+ const nameMap = /* @__PURE__ */ new Map();
32419
+ for (const operation of segment.operations.items) {
32420
+ if (operation instanceof QB.ASTGroupByViewOperation || operation instanceof QB.ASTAggregateViewOperation) {
32421
+ const reference = operation.field.getReference();
32422
+ nameMap.set(operation.name, toFullName(reference.path, reference.name));
32423
+ }
32424
+ }
32425
+ return nameMap;
32426
+ }
32427
+ function segmentHasOrderBySourceField(segment, path, name) {
32428
+ const nameMap = getOutputNameToInputNameMap(segment);
32429
+ const fullInputName = toFullName(path, name);
32430
+ return !!segment.operations.items.find((operation) => {
32431
+ if (operation instanceof QB.ASTOrderByViewOperation && nameMap.has(operation.name)) {
32432
+ return fullInputName === nameMap.get(operation.name);
32433
+ }
32434
+ return false;
32435
+ });
32436
+ }
32437
+ function areReferencesEqual(path1, name1, path2, name2) {
32438
+ return name1 === name2 && (path1 || []).join(".") === (path2 || []).join(".");
32439
+ }
32440
+ function segmentHasFieldInOutputSpace(segment, path, name) {
32441
+ const match = segment.operations.items.find((operation) => {
32442
+ if (operation instanceof QB.ASTGroupByViewOperation || operation instanceof QB.ASTAggregateViewOperation) {
32443
+ const reference = operation.field.getReference();
32444
+ const isEqual = areReferencesEqual(path, name, reference.path, reference.name);
32445
+ return isEqual;
32446
+ }
32447
+ return false;
32448
+ });
32449
+ return !!match;
32450
+ }
32408
32451
  function segmentNestNo(segment, name) {
32409
32452
  return segment.operations.items.reduce((acc, operation) => {
32410
32453
  if (operation instanceof QB.ASTNestViewOperation) {
@@ -32459,6 +32502,19 @@ function addNest(view, field) {
32459
32502
  }
32460
32503
  segment.addNest(field.name, rename);
32461
32504
  }
32505
+ function addOrderByFromSource(view, path, name, direction = "desc") {
32506
+ const fullInputName = toFullName(path, name);
32507
+ let orderByName = name;
32508
+ const segment = view.getOrAddDefaultSegment();
32509
+ const nameMap = getOutputNameToInputNameMap(segment);
32510
+ for (const entry of nameMap.entries()) {
32511
+ if (entry[1] === fullInputName) {
32512
+ orderByName = entry[0];
32513
+ break;
32514
+ }
32515
+ }
32516
+ segment.addOrderBy(orderByName, direction);
32517
+ }
32462
32518
  function addOrderBy(view, field, direction = "desc") {
32463
32519
  const segment = view.getOrAddDefaultSegment();
32464
32520
  segment.addOrderBy(field.name, direction);
@@ -32472,6 +32528,9 @@ function addFilter(view, field, path, filter) {
32472
32528
  }
32473
32529
  }
32474
32530
  function getSegmentIfPresent(parent) {
32531
+ if (!parent) {
32532
+ return void 0;
32533
+ }
32475
32534
  const definition = getViewDefinition(parent);
32476
32535
  if (definition instanceof QB.ASTSegmentViewDefinition) {
32477
32536
  return definition;
@@ -33272,94 +33331,6 @@ class ErrorElement extends React__namespace.Component {
33272
33331
  return this.props.children;
33273
33332
  }
33274
33333
  }
33275
- function FilterOperations({
33276
- rootQuery,
33277
- filters
33278
- }) {
33279
- const {
33280
- setQuery
33281
- } = React.useContext(QueryEditorContext);
33282
- if (filters.length === 0) {
33283
- return null;
33284
- }
33285
- return /* @__PURE__ */ jsxRuntime.jsxs("div", {
33286
- children: [/* @__PURE__ */ jsxRuntime.jsx("div", {
33287
- ..._stylex.props(styles$s.title),
33288
- children: "filter by"
33289
- }), /* @__PURE__ */ jsxRuntime.jsx("div", {
33290
- ..._stylex.props(styles$s.tokenContainer),
33291
- children: filters.map((filterOperation, key2) => {
33292
- return /* @__PURE__ */ jsxRuntime.jsx(ErrorElement, {
33293
- fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", {
33294
- children: ["Invalid filter", /* @__PURE__ */ jsxRuntime.jsx(ClearButton, {
33295
- onClick: () => {
33296
- filterOperation.delete();
33297
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33298
- }
33299
- })]
33300
- }),
33301
- children: /* @__PURE__ */ jsxRuntime.jsx(SingleFilterOperation, {
33302
- filterOperation,
33303
- rootQuery
33304
- })
33305
- }, key2);
33306
- })
33307
- })]
33308
- });
33309
- }
33310
- function SingleFilterOperation({
33311
- rootQuery,
33312
- filterOperation
33313
- }) {
33314
- const {
33315
- fieldReference,
33316
- filterString
33317
- } = filterOperation.filter;
33318
- const filter = filterOperation.filter.getFilter();
33319
- const fieldInfo = fieldReference.getFieldInfo();
33320
- const {
33321
- setQuery
33322
- } = React.useContext(QueryEditorContext);
33323
- if (fieldInfo.kind !== "dimension" && fieldInfo.kind !== "measure") {
33324
- throw new Error(`Invalid filter field kind: ${fieldInfo.kind}`);
33325
- }
33326
- const setFilter = React.useCallback((filter2) => {
33327
- filterOperation.filter.setFilter(filter2);
33328
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33329
- }, [filterOperation.filter, rootQuery, setQuery]);
33330
- const {
33331
- op,
33332
- value
33333
- } = parsedToLabels(filter, filterString);
33334
- const label = `${fieldInfo.name} ${op} ${value}`;
33335
- return /* @__PURE__ */ jsxRuntime.jsxs("div", {
33336
- ..._stylex.props(hoverStyles.main),
33337
- children: [/* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
33338
- fieldInfo,
33339
- path: fieldReference.path ?? [],
33340
- filter,
33341
- setFilter,
33342
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Token, {
33343
- icon: "filter",
33344
- color: "cyan",
33345
- label
33346
- }),
33347
- layoutProps: {
33348
- align: "start",
33349
- side: "bottom",
33350
- sideOffset: 1
33351
- }
33352
- }), /* @__PURE__ */ jsxRuntime.jsx("div", {
33353
- ..._stylex.props(hoverStyles.hoverActions),
33354
- children: /* @__PURE__ */ jsxRuntime.jsx(ClearButton, {
33355
- onClick: () => {
33356
- filterOperation.delete();
33357
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33358
- }
33359
- })
33360
- })]
33361
- });
33362
- }
33363
33334
  const parsedToLabels = (parsed, filterString) => {
33364
33335
  if (parsed.parsed === null) {
33365
33336
  return {
@@ -33539,6 +33510,94 @@ function displayTimeFromMoment(momentObj) {
33539
33510
  }
33540
33511
  return momentObj.moment;
33541
33512
  }
33513
+ function FilterOperations({
33514
+ rootQuery,
33515
+ filters
33516
+ }) {
33517
+ const {
33518
+ setQuery
33519
+ } = React.useContext(QueryEditorContext);
33520
+ if (filters.length === 0) {
33521
+ return null;
33522
+ }
33523
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", {
33524
+ children: [/* @__PURE__ */ jsxRuntime.jsx("div", {
33525
+ ..._stylex.props(styles$s.title),
33526
+ children: "filter by"
33527
+ }), /* @__PURE__ */ jsxRuntime.jsx("div", {
33528
+ ..._stylex.props(styles$s.tokenContainer),
33529
+ children: filters.map((filterOperation, key2) => {
33530
+ return /* @__PURE__ */ jsxRuntime.jsx(ErrorElement, {
33531
+ fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", {
33532
+ children: ["Invalid filter", /* @__PURE__ */ jsxRuntime.jsx(ClearButton, {
33533
+ onClick: () => {
33534
+ filterOperation.delete();
33535
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33536
+ }
33537
+ })]
33538
+ }),
33539
+ children: /* @__PURE__ */ jsxRuntime.jsx(SingleFilterOperation, {
33540
+ filterOperation,
33541
+ rootQuery
33542
+ })
33543
+ }, key2);
33544
+ })
33545
+ })]
33546
+ });
33547
+ }
33548
+ function SingleFilterOperation({
33549
+ rootQuery,
33550
+ filterOperation
33551
+ }) {
33552
+ const {
33553
+ fieldReference,
33554
+ filterString
33555
+ } = filterOperation.filter;
33556
+ const filter = filterOperation.filter.getFilter();
33557
+ const fieldInfo = fieldReference.getFieldInfo();
33558
+ const {
33559
+ setQuery
33560
+ } = React.useContext(QueryEditorContext);
33561
+ if (fieldInfo.kind !== "dimension" && fieldInfo.kind !== "measure") {
33562
+ throw new Error(`Invalid filter field kind: ${fieldInfo.kind}`);
33563
+ }
33564
+ const setFilter = React.useCallback((filter2) => {
33565
+ filterOperation.filter.setFilter(filter2);
33566
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33567
+ }, [filterOperation.filter, rootQuery, setQuery]);
33568
+ const {
33569
+ op,
33570
+ value
33571
+ } = parsedToLabels(filter, filterString);
33572
+ const label = `${fieldInfo.name} ${op} ${value}`;
33573
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", {
33574
+ ..._stylex.props(hoverStyles.main),
33575
+ children: [/* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
33576
+ fieldInfo,
33577
+ path: fieldReference.path ?? [],
33578
+ filter,
33579
+ setFilter,
33580
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Token, {
33581
+ icon: "filter",
33582
+ color: "cyan",
33583
+ label
33584
+ }),
33585
+ layoutProps: {
33586
+ align: "start",
33587
+ side: "bottom",
33588
+ sideOffset: 1
33589
+ }
33590
+ }), /* @__PURE__ */ jsxRuntime.jsx("div", {
33591
+ ..._stylex.props(hoverStyles.hoverActions),
33592
+ children: /* @__PURE__ */ jsxRuntime.jsx(ClearButton, {
33593
+ onClick: () => {
33594
+ filterOperation.delete();
33595
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33596
+ }
33597
+ })
33598
+ })]
33599
+ });
33600
+ }
33542
33601
  function LimitOperation({
33543
33602
  rootQuery,
33544
33603
  limit
@@ -34644,8 +34703,128 @@ const styles$a = {
34644
34703
  $$css: true
34645
34704
  }
34646
34705
  };
34706
+ function FilterLiteralEditor({
34707
+ filterType,
34708
+ value,
34709
+ setValue
34710
+ }) {
34711
+ let filter = null;
34712
+ switch (filterType) {
34713
+ case "string_type":
34714
+ {
34715
+ const {
34716
+ parsed
34717
+ } = malloyFilter.StringFilterExpression.parse(value.filter_expression_value);
34718
+ filter = {
34719
+ kind: "string",
34720
+ parsed
34721
+ };
34722
+ }
34723
+ break;
34724
+ case "number_type":
34725
+ {
34726
+ const {
34727
+ parsed
34728
+ } = malloyFilter.NumberFilterExpression.parse(value.filter_expression_value);
34729
+ filter = {
34730
+ kind: "number",
34731
+ parsed
34732
+ };
34733
+ }
34734
+ break;
34735
+ case "boolean_type":
34736
+ {
34737
+ const {
34738
+ parsed
34739
+ } = malloyFilter.BooleanFilterExpression.parse(value.filter_expression_value);
34740
+ filter = {
34741
+ kind: "boolean",
34742
+ parsed
34743
+ };
34744
+ }
34745
+ break;
34746
+ case "date_type":
34747
+ {
34748
+ const {
34749
+ parsed
34750
+ } = malloyFilter.TemporalFilterExpression.parse(value.filter_expression_value);
34751
+ filter = {
34752
+ kind: "date",
34753
+ parsed
34754
+ };
34755
+ }
34756
+ break;
34757
+ case "timestamp_type":
34758
+ {
34759
+ const {
34760
+ parsed
34761
+ } = malloyFilter.TemporalFilterExpression.parse(value.filter_expression_value);
34762
+ filter = {
34763
+ kind: "timestamp",
34764
+ parsed
34765
+ };
34766
+ }
34767
+ break;
34768
+ }
34769
+ const {
34770
+ op,
34771
+ value: filterValue
34772
+ } = parsedToLabels(filter, value.filter_expression_value);
34773
+ const label = `${op} ${filterValue}`;
34774
+ const fieldInfo = {
34775
+ kind: "dimension",
34776
+ name: "parameter",
34777
+ type: {
34778
+ kind: filterType
34779
+ }
34780
+ };
34781
+ const setFilter = (parsed) => {
34782
+ switch (parsed.kind) {
34783
+ case "string":
34784
+ setValue({
34785
+ kind: "filter_expression_literal",
34786
+ filter_expression_value: malloyFilter.StringFilterExpression.unparse(parsed.parsed)
34787
+ });
34788
+ break;
34789
+ case "boolean":
34790
+ setValue({
34791
+ kind: "filter_expression_literal",
34792
+ filter_expression_value: malloyFilter.BooleanFilterExpression.unparse(parsed.parsed)
34793
+ });
34794
+ break;
34795
+ case "number":
34796
+ setValue({
34797
+ kind: "filter_expression_literal",
34798
+ filter_expression_value: malloyFilter.NumberFilterExpression.unparse(parsed.parsed)
34799
+ });
34800
+ break;
34801
+ case "date":
34802
+ case "timestamp":
34803
+ setValue({
34804
+ kind: "filter_expression_literal",
34805
+ filter_expression_value: malloyFilter.TemporalFilterExpression.unparse(parsed.parsed)
34806
+ });
34807
+ break;
34808
+ }
34809
+ };
34810
+ return /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
34811
+ fieldInfo,
34812
+ path: [],
34813
+ filter,
34814
+ setFilter,
34815
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Token, {
34816
+ label
34817
+ }),
34818
+ layoutProps: {
34819
+ align: "start",
34820
+ side: "bottom",
34821
+ sideOffset: 1
34822
+ }
34823
+ });
34824
+ }
34647
34825
  function LiteralValueEditor({
34648
34826
  value,
34827
+ filterType,
34649
34828
  setValue,
34650
34829
  customStyle
34651
34830
  }) {
@@ -34700,12 +34879,10 @@ function LiteralValueEditor({
34700
34879
  customStyle
34701
34880
  });
34702
34881
  case "filter_expression_literal":
34703
- return /* @__PURE__ */ jsxRuntime.jsx(EditableToken, {
34704
- value: value.filter_expression_value,
34705
- onChange: (value2) => setValue({
34706
- kind: "filter_expression_literal",
34707
- filter_expression_value: value2
34708
- }),
34882
+ return /* @__PURE__ */ jsxRuntime.jsx(FilterLiteralEditor, {
34883
+ value,
34884
+ filterType,
34885
+ setValue,
34709
34886
  customStyle
34710
34887
  });
34711
34888
  }
@@ -34736,6 +34913,7 @@ function Parameters({
34736
34913
  icon: atomicTypeToIcon(parameter.type.kind),
34737
34914
  label: parameter.name
34738
34915
  }), /* @__PURE__ */ jsxRuntime.jsx(LiteralValueEditor, {
34916
+ filterType: parameter.type.kind === "filter_expression_type" ? parameter.type.filter_type.kind : "string_type",
34739
34917
  value: ((_a2 = source.tryGetParameter(parameter.name)) == null ? void 0 : _a2.parameter.value) ?? parameter.default_value,
34740
34918
  setValue: (value) => {
34741
34919
  source.setParameter(parameter.name, value);
@@ -35794,62 +35972,83 @@ const FIELD_KIND_TO_TITLE = {
35794
35972
  dimension: "Dimensions"
35795
35973
  };
35796
35974
  function useOperations(view, field, path) {
35797
- const dimensionFields = React.useMemo(() => {
35798
- const {
35799
- fields
35800
- } = getInputSchemaFromViewParent(view);
35801
- return new Set(flattenFieldsTree(fields).filter(({
35802
- field: field2
35803
- }) => field2.kind === "dimension").map(({
35804
- field: field2
35805
- }) => field2.name));
35806
- }, [view]);
35807
- const measureFields = React.useMemo(() => {
35975
+ const fullName = toFullName(path, field.name);
35976
+ const flattenedFields = React.useMemo(() => {
35808
35977
  const {
35809
35978
  fields
35810
35979
  } = getInputSchemaFromViewParent(view);
35811
- return new Set(flattenFieldsTree(fields).filter(({
35812
- field: field2
35813
- }) => field2.kind === "measure").map(({
35814
- field: field2
35815
- }) => field2.name));
35816
- }, [view]);
35817
- const isGroupByAllowed = React.useMemo(() => {
35818
- if (!view) {
35819
- return false;
35820
- }
35980
+ const inputPath = path.join(".");
35981
+ return flattenFieldsTree(fields).filter((fieldItem) => {
35982
+ return fieldItem.path.join(".") === inputPath;
35983
+ });
35984
+ }, [path, view]);
35985
+ const matchingFieldItem = flattenedFields.find((fieldItem) => field.name === fieldItem.field.name);
35986
+ const groupByDisabledReason = React.useMemo(() => {
35821
35987
  const segment = getSegmentIfPresent(view);
35822
- return dimensionFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35823
- }, [view, field, path, dimensionFields]);
35824
- const isAggregateAllowed = React.useMemo(() => {
35825
- if (!view) {
35826
- return false;
35988
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "dimension") {
35989
+ return "Grouping is only available on a dimenion.";
35990
+ }
35991
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
35992
+ return "Cannot group by a field already in the view.";
35993
+ }
35994
+ if (!isNotAnnotatedFilteredField(field)) {
35995
+ return "This field is annotated with #NO_UI.";
35996
+ }
35997
+ return "";
35998
+ }, [view, matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, field, path]);
35999
+ const aggregateDisabledReason = React.useMemo(() => {
36000
+ if ((matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind) !== "measure") {
36001
+ return "Aggregation only supports measure fields.";
35827
36002
  }
35828
36003
  const segment = getSegmentIfPresent(view);
35829
- return measureFields.has(field.name) && !(segment == null ? void 0 : segment.hasField(field.name, path)) && isNotAnnotatedFilteredField(field);
35830
- }, [view, field, path, measureFields]);
35831
- const isFilterAllowed = React.useMemo(() => {
35832
- if (!view) {
35833
- return false;
36004
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36005
+ return "This field is already used in the query.";
35834
36006
  }
35835
- const fieldName = field.name;
35836
- const inputSchemaFields = getInputSchemaFromViewParent(view).fields;
35837
- return inputSchemaFields.filter((field2) => field2.kind === "dimension" || field2.kind === "measure").filter((field2) => FILTERABLE_TYPES.includes(field2.type.kind)).some((field2) => field2.name === fieldName);
35838
- }, [view, field]);
35839
- const isOrderByAllowed = React.useMemo(() => {
35840
- if (!view) {
35841
- return false;
36007
+ if (!isNotAnnotatedFilteredField(field)) {
36008
+ return "This field is annotated with #NO_UI.";
36009
+ }
36010
+ return "";
36011
+ }, [matchingFieldItem == null ? void 0 : matchingFieldItem.field.kind, view, field, path]);
36012
+ const filterDisabledReason = React.useMemo(() => {
36013
+ if (!matchingFieldItem) {
36014
+ return `Unexpected Error: Could not find a field ${fullName}.`;
36015
+ }
36016
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36017
+ return `Filtering is only available for a dimension or measure.`;
36018
+ }
36019
+ if (!FILTERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36020
+ return "Filtering only supports string, boolean, number, date and time fields.";
36021
+ }
36022
+ return "";
36023
+ }, [fullName, matchingFieldItem]);
36024
+ const orderByDisabledReason = React.useMemo(() => {
36025
+ if (!matchingFieldItem) {
36026
+ return `Unexpected Error: Could not find a field ${fullName}.`;
35842
36027
  }
35843
- const fieldName = field.name;
35844
- const outputSchemaFields = view.getOutputSchema().fields;
35845
36028
  const segment = getSegmentIfPresent(view);
35846
- 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);
35847
- }, [view, field]);
36029
+ if (segment && segmentHasOrderBySourceField(segment, path, field.name)) {
36030
+ return "Query is already ordered by this field.";
36031
+ }
36032
+ if (!segment || !segmentHasFieldInOutputSpace(segment, path, field.name)) {
36033
+ return "Order by is only available for fields in the output.";
36034
+ }
36035
+ if (!["dimension", "measure"].includes(matchingFieldItem.field.kind)) {
36036
+ return "Order By is only available for dimension or measure fields.";
36037
+ }
36038
+ if (!ORDERABLE_TYPES.includes(matchingFieldItem.field.type.kind)) {
36039
+ return "Order By only supports string, boolean, number, date and time fields.";
36040
+ }
36041
+ return "";
36042
+ }, [matchingFieldItem, view, path, field.name, fullName]);
35848
36043
  return {
35849
- isGroupByAllowed,
35850
- isAggregateAllowed,
35851
- isFilterAllowed,
35852
- isOrderByAllowed
36044
+ isGroupByAllowed: !groupByDisabledReason,
36045
+ groupByDisabledReason,
36046
+ isAggregateAllowed: !aggregateDisabledReason,
36047
+ aggregateDisabledReason,
36048
+ isFilterAllowed: !filterDisabledReason,
36049
+ filterDisabledReason,
36050
+ isOrderByAllowed: !orderByDisabledReason,
36051
+ orderByDisabledReason
35853
36052
  };
35854
36053
  }
35855
36054
  const FILTERABLE_TYPES = ["string_type", "boolean_type", "number_type", "date_type", "timestamp_type"];
@@ -35866,22 +36065,22 @@ function FieldTokenWithActions({
35866
36065
  } = React.useContext(QueryEditorContext);
35867
36066
  const view = currentNestView ?? viewDef;
35868
36067
  const {
35869
- isGroupByAllowed,
35870
- isAggregateAllowed,
35871
- isFilterAllowed,
35872
- isOrderByAllowed
36068
+ groupByDisabledReason,
36069
+ aggregateDisabledReason,
36070
+ filterDisabledReason,
36071
+ orderByDisabledReason
35873
36072
  } = useOperations(view, field, path);
35874
36073
  const [isFilterPopoverOpen, setIsFilterPopoverOpen] = React.useState();
35875
36074
  const [isTooltipOpen, setIsTooltipOpen] = React.useState(false);
35876
36075
  const handleAddOperationAction = (operation, filter) => {
35877
36076
  if (field.kind === "dimension" || field.kind === "measure") {
35878
- if (operation === "groupBy" && isGroupByAllowed) {
36077
+ if (operation === "groupBy" && !groupByDisabledReason) {
35879
36078
  addGroupBy(view, field, path);
35880
- } else if (operation === "aggregate" && isAggregateAllowed) {
36079
+ } else if (operation === "aggregate" && !aggregateDisabledReason) {
35881
36080
  addAggregate(view, field, path);
35882
- } else if (operation === "orderBy" && isOrderByAllowed) {
35883
- addOrderBy(view, field);
35884
- } else if (operation === "filter" && isFilterAllowed && filter) {
36081
+ } else if (operation === "orderBy" && !orderByDisabledReason) {
36082
+ addOrderByFromSource(view, path, field.name);
36083
+ } else if (operation === "filter" && !filterDisabledReason && filter) {
35885
36084
  addFilter(view, field, path, filter);
35886
36085
  }
35887
36086
  setQuery == null ? void 0 : setQuery(rootQuery == null ? void 0 : rootQuery.build());
@@ -35906,7 +36105,7 @@ function FieldTokenWithActions({
35906
36105
  icon: "insert",
35907
36106
  disabled: !(rootQuery == null ? void 0 : rootQuery.isEmpty()),
35908
36107
  onClick: handleSetView,
35909
- tooltip: "Add view",
36108
+ tooltip: !(rootQuery == null ? void 0 : rootQuery.isEmpty()) ? "Can only add a view to an empty query." : "Add view",
35910
36109
  onTooltipOpenChange: setIsTooltipOpen
35911
36110
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35912
36111
  icon: "nest",
@@ -35917,8 +36116,8 @@ function FieldTokenWithActions({
35917
36116
  }) : field.kind === "measure" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
35918
36117
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35919
36118
  icon: "aggregate",
35920
- tooltip: "Add as aggregate",
35921
- disabled: !isAggregateAllowed,
36119
+ tooltip: aggregateDisabledReason || "Add as aggregate",
36120
+ disabled: !!aggregateDisabledReason,
35922
36121
  onClick: () => handleAddOperationAction("aggregate"),
35923
36122
  onTooltipOpenChange: setIsTooltipOpen
35924
36123
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -35927,23 +36126,23 @@ function FieldTokenWithActions({
35927
36126
  setFilter: (filter) => handleAddOperationAction("filter", filter),
35928
36127
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35929
36128
  icon: "filter",
35930
- tooltip: "Add as filter",
35931
- disabled: !isFilterAllowed,
36129
+ tooltip: filterDisabledReason || "Add as filter",
36130
+ disabled: !!filterDisabledReason,
35932
36131
  onTooltipOpenChange: setIsTooltipOpen
35933
36132
  }),
35934
36133
  onOpenChange: setIsFilterPopoverOpen
35935
36134
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35936
36135
  icon: "orderBy",
35937
- tooltip: "Add as order by",
35938
- disabled: !isOrderByAllowed,
36136
+ tooltip: orderByDisabledReason || "Add as order by",
36137
+ disabled: !!orderByDisabledReason,
35939
36138
  onClick: () => handleAddOperationAction("orderBy"),
35940
36139
  onTooltipOpenChange: setIsTooltipOpen
35941
36140
  })]
35942
36141
  }) : field.kind === "dimension" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
35943
36142
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35944
36143
  icon: "groupBy",
35945
- tooltip: "Add as group by",
35946
- disabled: !isGroupByAllowed,
36144
+ tooltip: groupByDisabledReason || "Add as group by",
36145
+ disabled: !!groupByDisabledReason,
35947
36146
  onClick: () => handleAddOperationAction("groupBy"),
35948
36147
  onTooltipOpenChange: setIsTooltipOpen
35949
36148
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -35952,20 +36151,20 @@ function FieldTokenWithActions({
35952
36151
  setFilter: (filter) => handleAddOperationAction("filter", filter),
35953
36152
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35954
36153
  icon: "filter",
35955
- tooltip: "Add as filter",
35956
- disabled: !isFilterAllowed,
36154
+ tooltip: filterDisabledReason || "Add as filter",
36155
+ disabled: !!filterDisabledReason,
35957
36156
  onTooltipOpenChange: setIsTooltipOpen
35958
36157
  }),
35959
36158
  onOpenChange: setIsFilterPopoverOpen
35960
36159
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
35961
36160
  icon: "orderBy",
35962
- tooltip: "Add as order by",
35963
- disabled: !isOrderByAllowed,
36161
+ tooltip: orderByDisabledReason || "Add as order by",
36162
+ disabled: !!orderByDisabledReason,
35964
36163
  onClick: () => handleAddOperationAction("orderBy"),
35965
36164
  onTooltipOpenChange: setIsTooltipOpen
35966
36165
  })]
35967
36166
  }) : null,
35968
- onClick: field.kind === "dimension" && isGroupByAllowed ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && isAggregateAllowed ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => handleAddView() : void 0,
36167
+ onClick: field.kind === "dimension" && !groupByDisabledReason ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && !aggregateDisabledReason ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => handleAddView() : void 0,
35969
36168
  hoverActionsVisible: isFilterPopoverOpen || isTooltipOpen,
35970
36169
  tooltip: /* @__PURE__ */ jsxRuntime.jsx(FieldHoverCard, {
35971
36170
  field,