@ai-matrx/records-ui 0.93.72 → 0.93.73

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.93.73
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/records-ui/v0.93.72..npm/records-ui/v0.93.73 -- apps/shared/records-ui`).
7
+ No source changes intended and no consumer action required.
8
+
3
9
  ## 0.93.72
4
10
  ### Unreleased
5
11
 
package/dist/index.cjs CHANGED
@@ -9596,55 +9596,206 @@ var import_react51 = require("react");
9596
9596
  var import_react52 = require("@ai-matrx/records/react");
9597
9597
  var import_records17 = require("@ai-matrx/records");
9598
9598
  var import_data_table8 = require("@ai-matrx/design-system/data-table");
9599
- var MEASURE_OPS = [
9600
- { op: "sum", word: "Sum of", additive: true },
9601
- { op: "avg", word: "Average", additive: false },
9602
- { op: "min", word: "Lowest", additive: false },
9603
- { op: "max", word: "Highest", additive: false },
9604
- { op: "median", word: "Median", additive: false }
9605
- ];
9606
- var DIMENSION_KINDS = {
9607
- select: "choice",
9608
- checkbox: "boolean",
9609
- text: "text",
9610
- datetime: "time"
9611
- };
9612
- function drillDimensionsFor(fields, options) {
9613
- const out = [];
9614
- for (const field2 of fields) {
9615
- const kind = DIMENSION_KINDS[String(editorKindFor(field2))];
9616
- if (!kind) continue;
9617
- if (field2.multi) continue;
9618
- const dim = { key: field2.key, label: fieldName(field2), kind };
9619
- if (kind === "text") dim.cardinality = "high";
9620
- if (kind === "choice" && isChoiceField(field2)) {
9599
+ var ADDITIVE_OPS = /* @__PURE__ */ new Set(["count", "sum", "filled", "empty"]);
9600
+ function drillDimensionsOf(def, labels) {
9601
+ if (!def) return [];
9602
+ return def.dimensions.map((d) => {
9603
+ const dim = { key: d.key, label: d.label, kind: d.kind };
9604
+ if (d.cardinality) dim.cardinality = d.cardinality;
9605
+ if (d.grains) dim.grains = d.grains.filter((g) => g !== "hour");
9606
+ const choices = new Map((d.choices ?? []).map((c) => [c.value, c.label]));
9607
+ if (d.kind === "choice" || d.kind === "relation") {
9621
9608
  dim.labelFor = (value) => {
9622
9609
  if (value === null || value === "") return "No value";
9623
- return optionFor(options.get(field2.key), value)?.label ?? value;
9610
+ return choices.get(value) ?? labels.get(d.key)?.get(value) ?? value;
9624
9611
  };
9625
9612
  }
9626
- out.push(dim);
9627
- }
9628
- return out;
9613
+ return dim;
9614
+ });
9629
9615
  }
9630
- function drillMeasuresFor(fields) {
9631
- const out = [{ key: "count", label: "Count", additive: true }];
9632
- for (const field2 of fields) {
9633
- const kind = String(editorKindFor(field2));
9634
- if (kind !== "number" && kind !== "currency" && kind !== "percent") continue;
9635
- for (const m of MEASURE_OPS) {
9636
- out.push({ key: `${m.op}_${field2.key}`, label: `${m.word} ${fieldName(field2)}`, additive: m.additive });
9637
- }
9616
+ function drillMeasuresOf(def) {
9617
+ if (!def) return [];
9618
+ return def.measures.map((m) => ({ key: m.key, label: m.label, additive: m.additive ?? ADDITIVE_OPS.has(m.op) }));
9619
+ }
9620
+ function drillPathsOf(def) {
9621
+ return (def?.paths ?? []).map((p) => p.levels);
9622
+ }
9623
+ function useDrillDefinition(source) {
9624
+ const client = (0, import_react52.useRecordsClient)();
9625
+ const key = source ? JSON.stringify(source) : null;
9626
+ const [state, setState] = (0, import_react51.useState)({ key: null, def: null, error: null });
9627
+ (0, import_react51.useEffect)(() => {
9628
+ if (!key) return;
9629
+ let cancelled = false;
9630
+ void client.drillDescribe({ source: JSON.parse(key) }).then((got) => {
9631
+ if (cancelled) return;
9632
+ setState(got.ok ? { key, def: got.data ?? null, error: null } : { key, def: null, error: got.error.message || "This table's groupings could not be read." });
9633
+ });
9634
+ return () => {
9635
+ cancelled = true;
9636
+ };
9637
+ }, [client, key]);
9638
+ return state.key === key ? { def: state.def, error: state.error } : { def: null, error: null };
9639
+ }
9640
+ function drillWhere(base, trail, booleans) {
9641
+ const where = { ...base ?? {} };
9642
+ for (const w of trail) {
9643
+ const { key, grain } = (0, import_data_table8.parseDimensionRef)(w.dim);
9644
+ if (grain) where[w.dim] = w.value;
9645
+ else if (booleans.has(key) && (w.value === "true" || w.value === "false")) where[key] = w.value === "true";
9646
+ else where[key] = w.value;
9638
9647
  }
9639
- return out;
9648
+ return where;
9640
9649
  }
9641
- function measureFor(key) {
9650
+ function rowOf(row) {
9651
+ const groups = {};
9652
+ for (const [k, v] of Object.entries(row.groups ?? {})) groups[k] = v === null || v === void 0 ? null : String(v);
9653
+ const measures = {};
9654
+ for (const [k, v] of Object.entries(row.measures ?? {})) measures[k] = v === null || v === void 0 ? null : Number(v);
9655
+ return { groups, measures, row_count: Number(row.row_count ?? 0) };
9656
+ }
9657
+ function storeMeasure(key) {
9642
9658
  if (key === "count") return { op: "count" };
9643
9659
  const at = key.indexOf("_");
9644
9660
  if (at <= 0) return null;
9645
- const op2 = key.slice(0, at);
9646
- if (!MEASURE_OPS.some((m) => m.op === op2)) return null;
9647
- return { op: op2, key: key.slice(at + 1) };
9661
+ return { op: key.slice(0, at), key: key.slice(at + 1) };
9662
+ }
9663
+ function useDrillAnswers(args) {
9664
+ const client = (0, import_react52.useRecordsClient)();
9665
+ const { source, question, enabled, version } = args;
9666
+ const [answers, setAnswers] = (0, import_react51.useState)({});
9667
+ const [labels, setLabels] = (0, import_react51.useState)(() => /* @__PURE__ */ new Map());
9668
+ const [error, setError] = (0, import_react51.useState)(null);
9669
+ const [says, setSays] = (0, import_react51.useState)([]);
9670
+ const askKey2 = JSON.stringify({
9671
+ source,
9672
+ by: question.by,
9673
+ across: question.across ?? null,
9674
+ show: question.show,
9675
+ where: args.where,
9676
+ rule: args.rule && (0, import_records17.isRuleFilter)(args.rule) ? args.rule : null
9677
+ });
9678
+ (0, import_react51.useEffect)(() => {
9679
+ if (!enabled || !source || question.by.length === 0) return;
9680
+ const asked2 = JSON.parse(askKey2);
9681
+ let cancelled = false;
9682
+ setAnswers({});
9683
+ setError(null);
9684
+ const requests = (0, import_data_table8.drillRequests)({ by: asked2.by, across: asked2.across, show: asked2.show, where: [] });
9685
+ void Promise.all(
9686
+ requests.map(async (request) => {
9687
+ if (asked2.rule && asked2.source.kind === "table") {
9688
+ const time = request.by.find((ref) => (0, import_data_table8.parseDimensionRef)(ref).grain !== null) ?? null;
9689
+ const t = time ? (0, import_data_table8.parseDimensionRef)(time) : null;
9690
+ const measures = asked2.show.map(storeMeasure).filter((m) => m !== null);
9691
+ const got2 = await client.recordAggregate({
9692
+ table_id: asked2.source.id,
9693
+ groupBy: request.by.filter((ref) => ref !== time),
9694
+ measures,
9695
+ bucket: t && t.grain ? { key: t.key, by: t.grain } : null,
9696
+ filter: asked2.rule,
9697
+ limit: 200
9698
+ });
9699
+ if (!got2.ok) return { request, error: got2.error.message };
9700
+ return {
9701
+ request,
9702
+ rows: (got2.data ?? []).map((row) => {
9703
+ const groups = {};
9704
+ for (const ref of request.by) {
9705
+ const k = ref === time && t ? t.key : ref;
9706
+ groups[ref] = row.groups[k] ?? null;
9707
+ }
9708
+ return { groups, measures: row.measures, row_count: row.row_count };
9709
+ }),
9710
+ labels: {},
9711
+ says: []
9712
+ };
9713
+ }
9714
+ const got = await client.drillAsk({
9715
+ source: asked2.source,
9716
+ question: { by: request.by, show: asked2.show, where: asked2.where }
9717
+ });
9718
+ if (!got.ok) return { request, error: got.error.message || "The groups could not be read." };
9719
+ const answer = got.data;
9720
+ const kind = request.by.length === 0 ? "total" : "group";
9721
+ const rows = answer.rows.filter((r) => r.kind === kind && (kind === "group" || !r.groups || Object.keys(r.groups).length === 0)).map(rowOf);
9722
+ const found = {};
9723
+ for (const r of answer.rows) {
9724
+ for (const [dim, text] of Object.entries(r.labels ?? {})) {
9725
+ const value = r.groups?.[dim];
9726
+ if (value === null || value === void 0) continue;
9727
+ (found[dim] ??= {})[String(value)] = text;
9728
+ }
9729
+ }
9730
+ return { request, rows, labels: found, says: answer.says };
9731
+ })
9732
+ ).then((results) => {
9733
+ if (cancelled) return;
9734
+ const failed = results.find((r) => "error" in r);
9735
+ if (failed) {
9736
+ setError(failed.error);
9737
+ return;
9738
+ }
9739
+ const out = {};
9740
+ const nextLabels = /* @__PURE__ */ new Map();
9741
+ const sentences = [];
9742
+ for (const r of results) {
9743
+ if (!("rows" in r)) continue;
9744
+ out[r.request.key] = r.rows;
9745
+ for (const [dim, map] of Object.entries(r.labels ?? {})) {
9746
+ const held = nextLabels.get(dim) ?? /* @__PURE__ */ new Map();
9747
+ for (const [v, t] of Object.entries(map)) held.set(v, t);
9748
+ nextLabels.set(dim, held);
9749
+ }
9750
+ for (const s of r.says ?? []) if (!sentences.includes(s)) sentences.push(s);
9751
+ }
9752
+ setLabels(nextLabels);
9753
+ setSays(sentences);
9754
+ setAnswers(out);
9755
+ });
9756
+ return () => {
9757
+ cancelled = true;
9758
+ };
9759
+ }, [client, askKey2, enabled, version, question.by.length]);
9760
+ return { answers, labels, error, says };
9761
+ }
9762
+ function drillQuestionFor(question, def) {
9763
+ if (!def) return question;
9764
+ const known = (ref) => def.dimensions.some((d) => d.key === (0, import_data_table8.parseDimensionRef)(ref).key);
9765
+ const show = question.show.filter((key) => def.measures.some((m) => m.key === key));
9766
+ return {
9767
+ ...question,
9768
+ by: question.by.filter(known),
9769
+ across: question.across && known(question.across) ? question.across : null,
9770
+ show: show.length > 0 ? show : [def.measures[0]?.key ?? "count"],
9771
+ where: question.where.filter((w) => known(w.dim))
9772
+ };
9773
+ }
9774
+ function useDrill(args) {
9775
+ const enabled = args.enabled ?? true;
9776
+ const { def, error: describeError } = useDrillDefinition(enabled ? args.source : null);
9777
+ const question = (0, import_react51.useMemo)(() => drillQuestionFor(args.question, def), [args.question, def]);
9778
+ const booleans = (0, import_react51.useMemo)(() => new Set((def?.dimensions ?? []).filter((d) => d.kind === "boolean").map((d) => d.key)), [def]);
9779
+ const where = (0, import_react51.useMemo)(() => drillWhere(args.filter, question.where, booleans), [args.filter, question.where, booleans]);
9780
+ const { answers, labels, error } = useDrillAnswers({
9781
+ source: args.source,
9782
+ question,
9783
+ where,
9784
+ rule: args.rule,
9785
+ version: args.version,
9786
+ enabled: enabled && def !== null
9787
+ });
9788
+ const dimensions = (0, import_react51.useMemo)(() => drillDimensionsOf(def, labels), [def, labels]);
9789
+ const measures = (0, import_react51.useMemo)(() => drillMeasuresOf(def), [def]);
9790
+ const paths = (0, import_react51.useMemo)(() => drillPathsOf(def), [def]);
9791
+ const { onQuestionChange } = args;
9792
+ const rowNoun = args.rowNoun ?? "record";
9793
+ const rootLabel = args.rootLabel ?? "All records";
9794
+ const config = (0, import_react51.useMemo)(() => {
9795
+ if (!enabled || !def || dimensions.length === 0) return null;
9796
+ return { dimensions, measures, paths, question, answers, error: describeError ?? error, rowNoun, rootLabel, onQuestionChange };
9797
+ }, [enabled, def, dimensions, measures, paths, question, answers, describeError, error, rowNoun, rootLabel, onQuestionChange]);
9798
+ return { config, def };
9648
9799
  }
9649
9800
  function questionFromGrouping(grouping) {
9650
9801
  if (!grouping) return { by: [], across: null, show: ["count"], where: [] };
@@ -9705,110 +9856,49 @@ function filterWithTrail(fields, base, trail) {
9705
9856
  }
9706
9857
  return { op: "and", args };
9707
9858
  }
9708
- function useDrillAnswers(args) {
9709
- const client = (0, import_react52.useRecordsClient)();
9710
- const { tableId, question, enabled, version } = args;
9711
- const [answers, setAnswers] = (0, import_react51.useState)({});
9712
- const [error, setError] = (0, import_react51.useState)(null);
9713
- const askKey2 = JSON.stringify({ by: question.by, across: question.across ?? null, show: question.show, filter: args.filter ?? null });
9714
- (0, import_react51.useEffect)(() => {
9715
- if (!enabled || question.by.length === 0) return;
9716
- const asked2 = JSON.parse(askKey2);
9717
- const measures = asked2.show.map(measureFor).filter((m) => m !== null);
9718
- if (!measures.some((m) => m.op === "count")) measures.unshift({ op: "count" });
9719
- let cancelled = false;
9720
- setAnswers({});
9721
- setError(null);
9722
- const requests = (0, import_data_table8.drillRequests)({ by: asked2.by, across: asked2.across, show: asked2.show, where: [] });
9723
- void Promise.all(
9724
- requests.map(async (request) => {
9725
- const time = request.by.find((ref) => (0, import_data_table8.parseDimensionRef)(ref).grain !== null) ?? null;
9726
- const plain = request.by.filter((ref) => ref !== time);
9727
- const grain = time ? (0, import_data_table8.parseDimensionRef)(time) : null;
9728
- const got = await client.recordAggregate({
9729
- table_id: tableId,
9730
- groupBy: plain,
9731
- measures,
9732
- bucket: grain && grain.grain ? { key: grain.key, by: grain.grain } : null,
9733
- ...asked2.filter ? { filter: asked2.filter } : {},
9734
- limit: 200
9735
- });
9736
- if (!got.ok) return { request, error: got.error.message || "The groups could not be read." };
9737
- const rows = (got.data ?? []).map((row) => {
9738
- const groups = {};
9739
- for (const ref of plain) groups[ref] = row.groups[ref] ?? null;
9740
- if (time && grain) groups[time] = row.groups[grain.key] ?? null;
9741
- return { groups, measures: row.measures, row_count: row.row_count };
9742
- });
9743
- return { request, rows };
9744
- })
9745
- ).then((results) => {
9746
- if (cancelled) return;
9747
- const failed = results.find((r) => "error" in r);
9748
- if (failed) {
9749
- setError(failed.error);
9750
- return;
9751
- }
9752
- const out = {};
9753
- for (const r of results) if ("rows" in r) out[r.request.key] = r.rows;
9754
- setAnswers(out);
9755
- });
9756
- return () => {
9757
- cancelled = true;
9758
- };
9759
- }, [client, tableId, askKey2, enabled, version, question.by.length]);
9760
- return { answers, error };
9761
- }
9762
9859
  function useGridDrillQuestion(args) {
9763
- const { fields, presentation, filter } = args;
9764
- const choiceKeys = (0, import_react51.useMemo)(() => fields.filter((f) => isChoiceField(f) && !f.multi).map((f) => f.key), [fields]);
9765
- const options = useChoiceOptions(fields, args.enabled ? choiceKeys : []);
9766
- const dimensions = (0, import_react51.useMemo)(() => drillDimensionsFor(fields, options), [fields, options]);
9767
- const measures = (0, import_react51.useMemo)(() => drillMeasuresFor(fields), [fields]);
9860
+ const { tableId, fields, presentation, filter } = args;
9861
+ const source = (0, import_react51.useMemo)(() => ({ kind: "table", id: tableId }), [tableId]);
9862
+ const { def } = useDrillDefinition(args.enabled ? source : null);
9768
9863
  const saved = (0, import_react51.useMemo)(() => questionFromGrouping(presentation.grouping), [presentation.grouping]);
9769
9864
  const url = (0, import_data_table8.useDrillUrlState)({ fallback: saved });
9770
- const question = (0, import_react51.useMemo)(() => {
9771
- const known = (ref) => dimensions.some((d) => d.key === (0, import_data_table8.parseDimensionRef)(ref).key);
9772
- const show = url.question.show.filter((key) => measures.some((m) => m.key === key));
9773
- return {
9774
- ...url.question,
9775
- by: url.question.by.filter(known),
9776
- across: url.question.across && known(url.question.across) ? url.question.across : null,
9777
- show: show.length > 0 ? show : ["count"],
9778
- where: url.question.where.filter((w) => known(w.dim))
9779
- };
9780
- }, [url.question, dimensions, measures]);
9781
- const enabled = args.enabled && dimensions.length > 0;
9865
+ const question = (0, import_react51.useMemo)(() => drillQuestionFor(url.question, def), [url.question, def]);
9866
+ const enabled = args.enabled && def !== null && def.dimensions.length > 0;
9782
9867
  const trailFilter = (0, import_react51.useMemo)(
9783
9868
  () => enabled ? filterWithTrail(fields, filter, question.where) : filter,
9784
9869
  [enabled, fields, filter, question.where]
9785
9870
  );
9786
- return { enabled, dimensions, measures, question, setQuestion: url.setQuestion, trailFilter };
9871
+ return { enabled, source, def, question, setQuestion: url.setQuestion, filter, trailFilter };
9787
9872
  }
9788
9873
  function useGridDrillConfig(args) {
9789
- const { tableId, state, presentation, onPresentationChange, version } = args;
9790
- const { enabled, dimensions, measures, question, setQuestion, trailFilter } = state;
9791
- const { answers, error } = useDrillAnswers({ tableId, question, filter: trailFilter, version, enabled });
9792
- return (0, import_react51.useMemo)(() => {
9793
- if (!enabled) return null;
9794
- return {
9795
- dimensions,
9796
- measures,
9797
- question,
9798
- answers,
9799
- error,
9800
- rootLabel: "All records",
9801
- rowNoun: "record",
9802
- onQuestionChange: (next) => {
9803
- setQuestion(next);
9804
- if (onPresentationChange) {
9805
- const grouping = groupingFromQuestion(next, presentation.grouping);
9806
- const { grouping: _dropped, ...rest } = presentation;
9807
- onPresentationChange(grouping ? { ...rest, grouping } : rest);
9808
- }
9874
+ const { state, presentation, onPresentationChange, version } = args;
9875
+ const { setQuestion } = state;
9876
+ const onQuestionChange = (0, import_react51.useMemo)(
9877
+ () => (next) => {
9878
+ setQuestion(next);
9879
+ if (onPresentationChange) {
9880
+ const grouping = groupingFromQuestion(next, presentation.grouping);
9881
+ const { grouping: _dropped, ...rest } = presentation;
9882
+ onPresentationChange(grouping ? { ...rest, grouping } : rest);
9809
9883
  }
9810
- };
9811
- }, [enabled, dimensions, measures, question, answers, error, setQuestion, onPresentationChange, presentation]);
9884
+ },
9885
+ [setQuestion, onPresentationChange, presentation]
9886
+ );
9887
+ const flat = state.filter && !(0, import_records17.isRuleFilter)(state.filter) ? state.filter : void 0;
9888
+ const rule = state.filter && (0, import_records17.isRuleFilter)(state.filter) ? filterWithTrailRuleOnly(state) : void 0;
9889
+ const { config } = useDrill({
9890
+ source: state.source,
9891
+ question: state.question,
9892
+ onQuestionChange,
9893
+ filter: flat,
9894
+ rule,
9895
+ version,
9896
+ enabled: state.enabled
9897
+ });
9898
+ return config;
9899
+ }
9900
+ function filterWithTrailRuleOnly(state) {
9901
+ return state.trailFilter;
9812
9902
  }
9813
9903
 
9814
9904
  // src/Enrich.tsx
@@ -13554,7 +13644,7 @@ function useGridAssist(args) {
13554
13644
  (ok, message) => ok ? host.notify?.success(message) : host.notify?.error(message),
13555
13645
  [host.notify]
13556
13646
  );
13557
- const rowOf = (0, import_react71.useCallback)((rowId) => rows.find((r) => r.id === rowId), [rows]);
13647
+ const rowOf2 = (0, import_react71.useCallback)((rowId) => rows.find((r) => r.id === rowId), [rows]);
13558
13648
  const titleOf = (0, import_react71.useCallback)(
13559
13649
  (row) => row ? recordNameIn(table, row.document ?? {}) : "",
13560
13650
  [table]
@@ -13644,18 +13734,18 @@ function useGridAssist(args) {
13644
13734
  );
13645
13735
  const openWhole = (0, import_react71.useCallback)(
13646
13736
  (rowId, key) => {
13647
- const row = rowOf(rowId);
13737
+ const row = rowOf2(rowId);
13648
13738
  if (!row) return;
13649
13739
  const document2 = row.document ?? {};
13650
13740
  const value = editing.valueFor(row, fieldOf2(key));
13651
13741
  const pointer = typeof document2[key] === "string" ? (0, import_core9.wholeValueOf)(document2, key) : null;
13652
13742
  setWhole({ rowId, key, rowTitle: titleOf(row), value, pointer });
13653
13743
  },
13654
- [rowOf, fieldOf2, titleOf, editing]
13744
+ [rowOf2, fieldOf2, titleOf, editing]
13655
13745
  );
13656
13746
  const cellItems = (0, import_react71.useCallback)(
13657
13747
  (rowId, columnId) => {
13658
- const row = rowOf(rowId);
13748
+ const row = rowOf2(rowId);
13659
13749
  const field2 = fieldOf2(columnId);
13660
13750
  if (!row || !field2) return [];
13661
13751
  const items = [];
@@ -13680,11 +13770,11 @@ function useGridAssist(args) {
13680
13770
  items.push(...refItem("cell-reference", "Copy cell reference", refFor.cell(row, field2), "The cell"));
13681
13771
  return items;
13682
13772
  },
13683
- [rowOf, fieldOf2, openWhole, host.cleanText, editing, args, refItem, refFor]
13773
+ [rowOf2, fieldOf2, openWhole, host.cleanText, editing, args, refItem, refFor]
13684
13774
  );
13685
13775
  const rowItems = (0, import_react71.useCallback)(
13686
13776
  (rowId) => {
13687
- const row = rowOf(rowId);
13777
+ const row = rowOf2(rowId);
13688
13778
  const askAgent = args.askAgent;
13689
13779
  return [
13690
13780
  ...rowActionMenuItems({
@@ -13699,7 +13789,7 @@ function useGridAssist(args) {
13699
13789
  ...row ? refItem("row-reference", "Copy row reference", refFor.row(row), "The row") : []
13700
13790
  ];
13701
13791
  },
13702
- [rowOf, args, fields, refItem, refFor]
13792
+ [rowOf2, args, fields, refItem, refFor]
13703
13793
  );
13704
13794
  const tableItems = (0, import_react71.useCallback)(
13705
13795
  () => refItem("table-reference", "Copy table reference", refFor.table(), "The table"),
@@ -14630,6 +14720,7 @@ function Grid({
14630
14720
  [merged, fields.data, filter, query.columnFilters]
14631
14721
  );
14632
14722
  const drillState = useGridDrillQuestion({
14723
+ tableId,
14633
14724
  fields: fields.data ?? NO_DRILL_FIELDS,
14634
14725
  presentation,
14635
14726
  filter: question.filter,