@malloydata/malloy-explorer 0.0.278-dev250515234639 → 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.
@@ -29950,6 +29950,11 @@ function getViewDefinition(parent) {
29950
29950
  return parent instanceof QB.ASTArrowQueryDefinition ? parent.view : parent.definition;
29951
29951
  }
29952
29952
  function getInputSchemaFromViewParent(parent) {
29953
+ if (!parent) {
29954
+ return {
29955
+ fields: []
29956
+ };
29957
+ }
29953
29958
  const definition = getViewDefinition(parent);
29954
29959
  return definition.getInputSchema();
29955
29960
  }
@@ -32400,12 +32405,49 @@ const styles$g = {
32400
32405
  $$css: true
32401
32406
  }
32402
32407
  };
32408
+ function toFullName(path, name) {
32409
+ return [...path || [], name].join(".");
32410
+ }
32403
32411
  function segmentHasLimit(segment) {
32404
32412
  return segment.operations.items.find((operation) => operation instanceof QB.ASTLimitViewOperation) !== void 0;
32405
32413
  }
32406
32414
  function segmentHasOrderBy(segment, name) {
32407
32415
  return segment.operations.items.find((operation) => operation instanceof QB.ASTOrderByViewOperation && operation.name === name) !== void 0;
32408
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
+ }
32409
32451
  function segmentNestNo(segment, name) {
32410
32452
  return segment.operations.items.reduce((acc, operation) => {
32411
32453
  if (operation instanceof QB.ASTNestViewOperation) {
@@ -32460,6 +32502,19 @@ function addNest(view, field) {
32460
32502
  }
32461
32503
  segment.addNest(field.name, rename);
32462
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
+ }
32463
32518
  function addOrderBy(view, field, direction = "desc") {
32464
32519
  const segment = view.getOrAddDefaultSegment();
32465
32520
  segment.addOrderBy(field.name, direction);
@@ -32473,6 +32528,9 @@ function addFilter(view, field, path, filter) {
32473
32528
  }
32474
32529
  }
32475
32530
  function getSegmentIfPresent(parent) {
32531
+ if (!parent) {
32532
+ return void 0;
32533
+ }
32476
32534
  const definition = getViewDefinition(parent);
32477
32535
  if (definition instanceof QB.ASTSegmentViewDefinition) {
32478
32536
  return definition;
@@ -35914,62 +35972,83 @@ const FIELD_KIND_TO_TITLE = {
35914
35972
  dimension: "Dimensions"
35915
35973
  };
35916
35974
  function useOperations(view, field, path) {
35917
- const dimensionFields = React.useMemo(() => {
35975
+ const fullName = toFullName(path, field.name);
35976
+ const flattenedFields = React.useMemo(() => {
35918
35977
  const {
35919
35978
  fields
35920
35979
  } = 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
- }
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(() => {
35941
35987
  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;
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.";
35947
36002
  }
35948
36003
  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;
36004
+ if (segment == null ? void 0 : segment.hasField(field.name, path)) {
36005
+ return "This field is already used in the query.";
35954
36006
  }
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;
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}.`;
35962
36027
  }
35963
- const fieldName = field.name;
35964
- const outputSchemaFields = view.getOutputSchema().fields;
35965
36028
  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]);
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]);
35968
36043
  return {
35969
- isGroupByAllowed,
35970
- isAggregateAllowed,
35971
- isFilterAllowed,
35972
- isOrderByAllowed
36044
+ isGroupByAllowed: !groupByDisabledReason,
36045
+ groupByDisabledReason,
36046
+ isAggregateAllowed: !aggregateDisabledReason,
36047
+ aggregateDisabledReason,
36048
+ isFilterAllowed: !filterDisabledReason,
36049
+ filterDisabledReason,
36050
+ isOrderByAllowed: !orderByDisabledReason,
36051
+ orderByDisabledReason
35973
36052
  };
35974
36053
  }
35975
36054
  const FILTERABLE_TYPES = ["string_type", "boolean_type", "number_type", "date_type", "timestamp_type"];
@@ -35986,22 +36065,22 @@ function FieldTokenWithActions({
35986
36065
  } = React.useContext(QueryEditorContext);
35987
36066
  const view = currentNestView ?? viewDef;
35988
36067
  const {
35989
- isGroupByAllowed,
35990
- isAggregateAllowed,
35991
- isFilterAllowed,
35992
- isOrderByAllowed
36068
+ groupByDisabledReason,
36069
+ aggregateDisabledReason,
36070
+ filterDisabledReason,
36071
+ orderByDisabledReason
35993
36072
  } = useOperations(view, field, path);
35994
36073
  const [isFilterPopoverOpen, setIsFilterPopoverOpen] = React.useState();
35995
36074
  const [isTooltipOpen, setIsTooltipOpen] = React.useState(false);
35996
36075
  const handleAddOperationAction = (operation, filter) => {
35997
36076
  if (field.kind === "dimension" || field.kind === "measure") {
35998
- if (operation === "groupBy" && isGroupByAllowed) {
36077
+ if (operation === "groupBy" && !groupByDisabledReason) {
35999
36078
  addGroupBy(view, field, path);
36000
- } else if (operation === "aggregate" && isAggregateAllowed) {
36079
+ } else if (operation === "aggregate" && !aggregateDisabledReason) {
36001
36080
  addAggregate(view, field, path);
36002
- } else if (operation === "orderBy" && isOrderByAllowed) {
36003
- addOrderBy(view, field);
36004
- } 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) {
36005
36084
  addFilter(view, field, path, filter);
36006
36085
  }
36007
36086
  setQuery == null ? void 0 : setQuery(rootQuery == null ? void 0 : rootQuery.build());
@@ -36026,7 +36105,7 @@ function FieldTokenWithActions({
36026
36105
  icon: "insert",
36027
36106
  disabled: !(rootQuery == null ? void 0 : rootQuery.isEmpty()),
36028
36107
  onClick: handleSetView,
36029
- tooltip: "Add view",
36108
+ tooltip: !(rootQuery == null ? void 0 : rootQuery.isEmpty()) ? "Can only add a view to an empty query." : "Add view",
36030
36109
  onTooltipOpenChange: setIsTooltipOpen
36031
36110
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36032
36111
  icon: "nest",
@@ -36037,8 +36116,8 @@ function FieldTokenWithActions({
36037
36116
  }) : field.kind === "measure" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
36038
36117
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36039
36118
  icon: "aggregate",
36040
- tooltip: "Add as aggregate",
36041
- disabled: !isAggregateAllowed,
36119
+ tooltip: aggregateDisabledReason || "Add as aggregate",
36120
+ disabled: !!aggregateDisabledReason,
36042
36121
  onClick: () => handleAddOperationAction("aggregate"),
36043
36122
  onTooltipOpenChange: setIsTooltipOpen
36044
36123
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -36047,23 +36126,23 @@ function FieldTokenWithActions({
36047
36126
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36048
36127
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36049
36128
  icon: "filter",
36050
- tooltip: "Add as filter",
36051
- disabled: !isFilterAllowed,
36129
+ tooltip: filterDisabledReason || "Add as filter",
36130
+ disabled: !!filterDisabledReason,
36052
36131
  onTooltipOpenChange: setIsTooltipOpen
36053
36132
  }),
36054
36133
  onOpenChange: setIsFilterPopoverOpen
36055
36134
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36056
36135
  icon: "orderBy",
36057
- tooltip: "Add as order by",
36058
- disabled: !isOrderByAllowed,
36136
+ tooltip: orderByDisabledReason || "Add as order by",
36137
+ disabled: !!orderByDisabledReason,
36059
36138
  onClick: () => handleAddOperationAction("orderBy"),
36060
36139
  onTooltipOpenChange: setIsTooltipOpen
36061
36140
  })]
36062
36141
  }) : field.kind === "dimension" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
36063
36142
  children: [/* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36064
36143
  icon: "groupBy",
36065
- tooltip: "Add as group by",
36066
- disabled: !isGroupByAllowed,
36144
+ tooltip: groupByDisabledReason || "Add as group by",
36145
+ disabled: !!groupByDisabledReason,
36067
36146
  onClick: () => handleAddOperationAction("groupBy"),
36068
36147
  onTooltipOpenChange: setIsTooltipOpen
36069
36148
  }), /* @__PURE__ */ jsxRuntime.jsx(FilterPopover, {
@@ -36072,20 +36151,20 @@ function FieldTokenWithActions({
36072
36151
  setFilter: (filter) => handleAddOperationAction("filter", filter),
36073
36152
  trigger: /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36074
36153
  icon: "filter",
36075
- tooltip: "Add as filter",
36076
- disabled: !isFilterAllowed,
36154
+ tooltip: filterDisabledReason || "Add as filter",
36155
+ disabled: !!filterDisabledReason,
36077
36156
  onTooltipOpenChange: setIsTooltipOpen
36078
36157
  }),
36079
36158
  onOpenChange: setIsFilterPopoverOpen
36080
36159
  }), /* @__PURE__ */ jsxRuntime.jsx(ActionButton, {
36081
36160
  icon: "orderBy",
36082
- tooltip: "Add as order by",
36083
- disabled: !isOrderByAllowed,
36161
+ tooltip: orderByDisabledReason || "Add as order by",
36162
+ disabled: !!orderByDisabledReason,
36084
36163
  onClick: () => handleAddOperationAction("orderBy"),
36085
36164
  onTooltipOpenChange: setIsTooltipOpen
36086
36165
  })]
36087
36166
  }) : null,
36088
- 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,
36089
36168
  hoverActionsVisible: isFilterPopoverOpen || isTooltipOpen,
36090
36169
  tooltip: /* @__PURE__ */ jsxRuntime.jsx(FieldHoverCard, {
36091
36170
  field,