@pythia-software/query-table-react 0.9.1 → 0.9.3
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/README.md +20 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +175 -148
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -120,3 +120,23 @@ and reject them through `validateQuery` so users can repair the selected tags.
|
|
|
120
120
|
`api.aggregations.preview(clauses, signal)` evaluates a draft without modifying the query. `replace(clauses, expected?)` commits the entire dashboard as one undoable change and can reject a stale editor baseline. Presentation-only changes reuse computation. Advanced remote execution uses `fetchMetrics` plus explicit v2 `metricCapabilities`; v1 transports continue to support representable simple metrics. All-matching scope requires the complete client dataset or a server metric service. Shown-row fallback uses committed inputs and reports missing dependencies.
|
|
121
121
|
|
|
122
122
|
See [the metrics integration guide](../../docs/metrics.md) for scopes, cancellation, worker-isolated regex, capability negotiation, computed revisions, and snapshot requirements.
|
|
123
|
+
|
|
124
|
+
For host-managed row windows, pass `metricTransport` to fetch metrics separately
|
|
125
|
+
while `clientRows` continues through local filtering, sorting, and paging.
|
|
126
|
+
`metricExecution` overrides the metric handshake independently of
|
|
127
|
+
`computedExecution`. Both default to the existing row transport/execution
|
|
128
|
+
selection. Computed grouping choices in `api.computed.catalogue` use the metric
|
|
129
|
+
transport and handshake, including definition revisions; row selection,
|
|
130
|
+
filtering, sorting, and value access keep their row capabilities.
|
|
131
|
+
Use `metricRowsReady(query)` to confirm that the external row window
|
|
132
|
+
matches the current query and origin before local shown-row evaluation or
|
|
133
|
+
previews. Remote `allMatching` metrics run while that gate is false.
|
|
134
|
+
|
|
135
|
+
A V2 transport must explicitly advertise `metricCapabilities.shownRows: true`
|
|
136
|
+
to execute supported shown-row clauses remotely. False or omitted uses the
|
|
137
|
+
committed page, provided its inputs are loaded. See the
|
|
138
|
+
[migration example](../../docs/metrics.md#independent-remote-metrics).
|
|
139
|
+
|
|
140
|
+
Standalone `useAggregations` calls with a complete client dataset derive the
|
|
141
|
+
shown page when readiness options are omitted. An explicit `rowsReady: false`
|
|
142
|
+
still blocks automatic shown-row evaluation and previews.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FieldSchema, WhereClause, QueryState, GroupedRow, GroupingModel, GroupingView, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, DistinctValuesResult, FieldStats, OrderByClause, Transport,
|
|
1
|
+
import { FieldSchema, WhereClause, QueryState, GroupedRow, GroupingModel, GroupingView, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedExecution, ComputedColumnStore, QueryLinkOptions, ObjectReference, ResolvedObjectReference } from '@pythia-software/query-table-core';
|
|
2
2
|
|
|
3
3
|
/** Filter controls for either the result query or grouping's source query. */
|
|
4
4
|
type QueryFilterApi<Row> = Pick<QueryTableApi<Row>, "query" | "setQuery" | "loading" | "addFilter" | "updateFilter" | "removeFilter" | "clearFilters" | "updatePredicate" | "removePredicate" | "negatePredicate" | "reorderFilters" | "mergeFilters" | "filterValues" | "fieldStats">;
|
|
@@ -266,8 +266,15 @@ interface UseQueryTableOptions<Row> {
|
|
|
266
266
|
/** Disable row-producing grouping for this instance. Enabled for complete client datasets by default. */
|
|
267
267
|
grouping?: boolean;
|
|
268
268
|
schema: FieldSchema<Row>;
|
|
269
|
-
/**
|
|
269
|
+
/** Row data access. Omit to filter, sort, and page `clientRows` locally. */
|
|
270
270
|
transport?: Transport<Row>;
|
|
271
|
+
/** Metric-only data access; defaults to transport and never selects server row mode. */
|
|
272
|
+
metricTransport?: Transport<Row>;
|
|
273
|
+
/** Authoritative metric handshake; defaults to the existing row execution selection. */
|
|
274
|
+
metricExecution?: ComputedExecution;
|
|
275
|
+
/** Additional host readiness gate for committed-page metrics and previews.
|
|
276
|
+
* Does not delay remote allMatching requests. Re-evaluated on each render. */
|
|
277
|
+
metricRowsReady?: (query: QueryState) => boolean;
|
|
271
278
|
/** Dataset already available in memory. Remote grouping/aggregation belongs
|
|
272
279
|
* on the server; do not download a population to supply this option. */
|
|
273
280
|
clientRows?: Row[];
|
package/dist/index.js
CHANGED
|
@@ -828,6 +828,8 @@ function evaluateFormulaRows(ast, inputs, signal, factory = createFormulaWorker)
|
|
|
828
828
|
// src/useComputedColumns.ts
|
|
829
829
|
function useComputedColumns(options) {
|
|
830
830
|
const { schema, query, rows, transport, clientRows, workerFactory } = options;
|
|
831
|
+
const metricTransport = options.metricTransport ?? transport;
|
|
832
|
+
const metricExecution = options.metricExecution ?? options.execution;
|
|
831
833
|
const store = useMemo3(
|
|
832
834
|
() => options.store ?? memoryComputedColumnStore(),
|
|
833
835
|
[options.store]
|
|
@@ -1042,7 +1044,7 @@ function useComputedColumns(options) {
|
|
|
1042
1044
|
},
|
|
1043
1045
|
aggregate: {
|
|
1044
1046
|
measure: !!entry?.plan,
|
|
1045
|
-
groupable: (!!
|
|
1047
|
+
groupable: (!!metricTransport?.fetchMetrics && metricTransport.metricCapabilities?.version === 2 || !!metricTransport?.fetchQueryV3) && !!metricExecution?.fields[computedFieldName(def.id)]?.group && executionRevision(metricExecution, def.id) === def.revision && !!entry?.plan && !hasRegex(entry.plan)
|
|
1046
1048
|
}
|
|
1047
1049
|
};
|
|
1048
1050
|
});
|
|
@@ -1076,6 +1078,8 @@ function useComputedColumns(options) {
|
|
|
1076
1078
|
loading,
|
|
1077
1079
|
transport,
|
|
1078
1080
|
options.execution,
|
|
1081
|
+
metricTransport,
|
|
1082
|
+
metricExecution,
|
|
1079
1083
|
options.serverComputed,
|
|
1080
1084
|
options.serverExecution
|
|
1081
1085
|
]);
|
|
@@ -1189,7 +1193,7 @@ import {
|
|
|
1189
1193
|
toServerQuery as toServerQuery2,
|
|
1190
1194
|
queriesEqual as queriesEqual2,
|
|
1191
1195
|
isOrGroup as isOrGroup2,
|
|
1192
|
-
metricPlans as
|
|
1196
|
+
metricPlans as metricPlans2,
|
|
1193
1197
|
validateMetric as validateMetric2,
|
|
1194
1198
|
metricDependencies,
|
|
1195
1199
|
isComputedField as isComputedField4,
|
|
@@ -1261,7 +1265,7 @@ function observeTransport(transport, publish, nextId) {
|
|
|
1261
1265
|
}
|
|
1262
1266
|
});
|
|
1263
1267
|
}
|
|
1264
|
-
function useRequestActivity(source) {
|
|
1268
|
+
function useRequestActivity(source, metricSource) {
|
|
1265
1269
|
const [entries, setEntries] = useState5([]);
|
|
1266
1270
|
const sequence = useRef5(0), mounted = useRef5(true);
|
|
1267
1271
|
useEffect5(() => {
|
|
@@ -1284,19 +1288,21 @@ function useRequestActivity(source) {
|
|
|
1284
1288
|
() => setEntries((previous) => previous.filter((e) => e.status === "pending")),
|
|
1285
1289
|
[]
|
|
1286
1290
|
);
|
|
1291
|
+
const metricTransport = useMemo4(
|
|
1292
|
+
() => !metricSource || metricSource === source ? transport : observeTransport(metricSource, publish, () => ++sequence.current),
|
|
1293
|
+
[metricSource, source, transport, publish]
|
|
1294
|
+
);
|
|
1287
1295
|
const activity = useMemo4(
|
|
1288
1296
|
() => ({ entries, clear }),
|
|
1289
1297
|
[entries, clear]
|
|
1290
1298
|
);
|
|
1291
|
-
return { transport, activity };
|
|
1299
|
+
return { transport, metricTransport, activity };
|
|
1292
1300
|
}
|
|
1293
1301
|
|
|
1294
1302
|
// src/useAggregations.ts
|
|
1295
1303
|
import { useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef6, useState as useState6 } from "react";
|
|
1296
1304
|
import {
|
|
1297
|
-
metricComputationKey
|
|
1298
|
-
metricPlans as metricPlans2,
|
|
1299
|
-
hasRegex as hasRegex3
|
|
1305
|
+
metricComputationKey
|
|
1300
1306
|
} from "@pythia-software/query-table-core";
|
|
1301
1307
|
|
|
1302
1308
|
// src/metricExecution.ts
|
|
@@ -1319,6 +1325,9 @@ import {
|
|
|
1319
1325
|
var abort = (signal) => {
|
|
1320
1326
|
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
1321
1327
|
};
|
|
1328
|
+
function usesLocalMetricPage(clause, context) {
|
|
1329
|
+
return !context.executeV3 && clause.scope === "shownRows" && (!context.transport?.fetchMetrics || context.transport.metricCapabilities?.version !== 2 || context.transport.metricCapabilities.shownRows !== true || !!clause.dateGrouping || metricPlans(clause, context.schema, context.resolveComputed).some(hasRegex2));
|
|
1330
|
+
}
|
|
1322
1331
|
async function localMetrics(rows, query, context, signal) {
|
|
1323
1332
|
const originalClauses = query.aggregations ?? [];
|
|
1324
1333
|
const advanced = (query.aggregations ?? []).filter(
|
|
@@ -1433,16 +1442,13 @@ async function executeMetrics(query, context, signal) {
|
|
|
1433
1442
|
const clauses = query.aggregations ?? [];
|
|
1434
1443
|
if (!clauses.length) return { metrics: [] };
|
|
1435
1444
|
if (context.executeV3) return context.executeV3(query, signal);
|
|
1445
|
+
if (context.rowsReady === false && clauses.some((c) => usesLocalMetricPage(c, context)))
|
|
1446
|
+
throw Error("Wait for the current table page before previewing shown-row metrics.");
|
|
1436
1447
|
const supportsV2 = !!context.transport?.fetchMetrics && context.transport.metricCapabilities?.version === 2;
|
|
1437
1448
|
if (context.clientRows && !supportsV2 && !context.transport?.fetchAggregations)
|
|
1438
1449
|
return localMetrics(context.clientRows, query, context, signal);
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
(c) => c.scope === "shownRows" && (!supportsV2 || needsWorker(c) || !!c.dateGrouping)
|
|
1442
|
-
);
|
|
1443
|
-
let matching = clauses.filter(
|
|
1444
|
-
(c) => c.scope !== "shownRows" || supportsV2 && !needsWorker(c) && !c.dateGrouping
|
|
1445
|
-
);
|
|
1450
|
+
const shown = clauses.filter((c) => usesLocalMetricPage(c, context));
|
|
1451
|
+
let matching = clauses.filter((c) => !shown.includes(c));
|
|
1446
1452
|
const results = [];
|
|
1447
1453
|
if (shown.length) {
|
|
1448
1454
|
if (!context.rowsReady || !context.shownRows)
|
|
@@ -1608,140 +1614,153 @@ function loadedPath(row, path) {
|
|
|
1608
1614
|
}
|
|
1609
1615
|
|
|
1610
1616
|
// src/useAggregations.ts
|
|
1611
|
-
function
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1617
|
+
function localPage(clause, context) {
|
|
1618
|
+
try {
|
|
1619
|
+
return usesLocalMetricPage(clause, context);
|
|
1620
|
+
} catch {
|
|
1621
|
+
return !context.executeV3 && clause.scope === "shownRows";
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function useMetricBatch(query, clauses, context, debounceMs, nonce, validateQuery, enabled, page) {
|
|
1625
|
+
const shown = clauses.some((c) => c.scope === "shownRows");
|
|
1626
|
+
const key = JSON.stringify([
|
|
1627
|
+
query.where,
|
|
1628
|
+
metricComputationKey(clauses),
|
|
1629
|
+
shown ? [query.orderBy, query.limit, query.offset] : null,
|
|
1630
|
+
context.revisions ?? {},
|
|
1631
|
+
context.execution ?? null,
|
|
1632
|
+
context.now,
|
|
1633
|
+
context.strictLocalMetrics,
|
|
1634
|
+
context.queryContextKey,
|
|
1635
|
+
page ? context.shownFields : null
|
|
1636
|
+
]);
|
|
1637
|
+
const localDataset = !context.executeV3 && !(context.transport?.fetchMetrics && context.transport.metricCapabilities?.version === 2) && !context.transport?.fetchAggregations;
|
|
1638
|
+
const pageReady = context.rowsReady ?? (localDataset && context.clientRows !== void 0);
|
|
1639
|
+
const validationToken = useMemo5(() => ({}), [
|
|
1640
|
+
key,
|
|
1641
|
+
context.schema,
|
|
1642
|
+
context.transport,
|
|
1643
|
+
nonce,
|
|
1621
1644
|
validateQuery,
|
|
1622
1645
|
enabled
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
schema,
|
|
1627
|
-
transport,
|
|
1628
|
-
clientRows,
|
|
1629
|
-
|
|
1646
|
+
]);
|
|
1647
|
+
const token = useMemo5(() => ({}), [
|
|
1648
|
+
key,
|
|
1649
|
+
context.schema,
|
|
1650
|
+
context.transport,
|
|
1651
|
+
localDataset ? context.clientRows : void 0,
|
|
1652
|
+
debounceMs,
|
|
1653
|
+
nonce,
|
|
1630
1654
|
validateQuery,
|
|
1631
|
-
enabled
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
const draft = { ...current.query, aggregations: clauses };
|
|
1638
|
-
current.validateQuery?.(draft);
|
|
1639
|
-
return executeMetrics(
|
|
1640
|
-
draft,
|
|
1641
|
-
{
|
|
1642
|
-
schema: current.schema,
|
|
1643
|
-
transport: current.transport,
|
|
1644
|
-
clientRows: current.clientRows,
|
|
1645
|
-
...current.options
|
|
1646
|
-
},
|
|
1647
|
-
signal
|
|
1648
|
-
);
|
|
1649
|
-
},
|
|
1650
|
-
[]
|
|
1651
|
-
);
|
|
1652
|
-
const shown = (query.aggregations ?? []).some((c) => c.scope === "shownRows");
|
|
1653
|
-
const localPage = !options.executeV3 && shown && (!transport?.fetchMetrics || transport.metricCapabilities?.version !== 2 || (query.aggregations ?? []).some((clause) => {
|
|
1654
|
-
if (clause.scope !== "shownRows") return false;
|
|
1655
|
-
try {
|
|
1656
|
-
return metricPlans2(clause, schema, options.resolveComputed).some(
|
|
1657
|
-
hasRegex3
|
|
1658
|
-
);
|
|
1659
|
-
} catch {
|
|
1660
|
-
return true;
|
|
1661
|
-
}
|
|
1662
|
-
}));
|
|
1663
|
-
const key = useMemo5(
|
|
1664
|
-
() => JSON.stringify([
|
|
1665
|
-
query.where,
|
|
1666
|
-
metricComputationKey(query.aggregations ?? []),
|
|
1667
|
-
shown ? [query.orderBy, query.limit, query.offset] : null
|
|
1668
|
-
]),
|
|
1669
|
-
[
|
|
1670
|
-
query.where,
|
|
1671
|
-
query.aggregations,
|
|
1672
|
-
query.orderBy,
|
|
1673
|
-
query.limit,
|
|
1674
|
-
query.offset,
|
|
1675
|
-
shown
|
|
1676
|
-
]
|
|
1677
|
-
);
|
|
1678
|
-
const dependencyKey = JSON.stringify([
|
|
1679
|
-
options.revisions ?? {},
|
|
1680
|
-
options.execution ?? null,
|
|
1681
|
-
options.now,
|
|
1682
|
-
options.strictLocalMetrics,
|
|
1683
|
-
options.queryContextKey
|
|
1655
|
+
enabled,
|
|
1656
|
+
page,
|
|
1657
|
+
context.workerFactory,
|
|
1658
|
+
page ? context.shownRows : void 0,
|
|
1659
|
+
page ? context.rowsReady : void 0,
|
|
1660
|
+
page ? context.rowsError : void 0
|
|
1684
1661
|
]);
|
|
1662
|
+
const latest = useRef6({ query, clauses, context, validateQuery });
|
|
1663
|
+
latest.current = { query, clauses, context, validateQuery };
|
|
1664
|
+
const [state, setState] = useState6(
|
|
1665
|
+
{ token, results: null, loading: enabled && clauses.length > 0, error: null }
|
|
1666
|
+
);
|
|
1685
1667
|
useEffect6(() => {
|
|
1686
|
-
if (!enabled || !latest.current.
|
|
1687
|
-
setResults(null);
|
|
1688
|
-
setLoading(false);
|
|
1689
|
-
setError(null);
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1668
|
+
if (!enabled || !latest.current.clauses.length) return;
|
|
1692
1669
|
const ac = new AbortController();
|
|
1693
|
-
setLoading(true);
|
|
1694
|
-
setResults(null);
|
|
1695
|
-
if (localPage && transport && !options.rowsReady) {
|
|
1696
|
-
try {
|
|
1697
|
-
latest.current.validateQuery?.(latest.current.query);
|
|
1698
|
-
setError(options.rowsError ?? null);
|
|
1699
|
-
if (options.rowsError) setLoading(false);
|
|
1700
|
-
} catch (e) {
|
|
1701
|
-
setError(e instanceof Error ? e : Error(String(e)));
|
|
1702
|
-
setLoading(false);
|
|
1703
|
-
}
|
|
1704
|
-
return;
|
|
1705
|
-
}
|
|
1706
1670
|
const timer = setTimeout(() => {
|
|
1707
|
-
|
|
1671
|
+
const current = latest.current;
|
|
1708
1672
|
try {
|
|
1709
|
-
|
|
1673
|
+
current.validateQuery?.(current.query);
|
|
1710
1674
|
} catch (e) {
|
|
1711
|
-
|
|
1712
|
-
setLoading(false);
|
|
1675
|
+
setState({ token, validationToken, results: null, loading: false, error: e instanceof Error ? e : Error(String(e)) });
|
|
1713
1676
|
return;
|
|
1714
1677
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1678
|
+
if (page && !pageReady) return;
|
|
1679
|
+
void executeMetrics({ ...current.query, aggregations: current.clauses }, current.context, ac.signal).then((results) => {
|
|
1680
|
+
if (!ac.signal.aborted) setState({ token, results, loading: false, error: null });
|
|
1717
1681
|
}).catch((e) => {
|
|
1718
|
-
if (!ac.signal.aborted)
|
|
1719
|
-
setError(e instanceof Error ? e : Error(String(e)));
|
|
1720
|
-
}).finally(() => {
|
|
1721
|
-
if (!ac.signal.aborted) setLoading(false);
|
|
1682
|
+
if (!ac.signal.aborted) setState({ token, results: null, loading: false, error: e instanceof Error ? e : Error(String(e)) });
|
|
1722
1683
|
});
|
|
1723
1684
|
}, debounceMs);
|
|
1724
1685
|
return () => {
|
|
1725
1686
|
ac.abort();
|
|
1726
1687
|
clearTimeout(timer);
|
|
1727
1688
|
};
|
|
1728
|
-
}, [
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1689
|
+
}, [token]);
|
|
1690
|
+
if (!enabled || !clauses.length) return { results: null, loading: false, error: null };
|
|
1691
|
+
if (state.validationToken === validationToken && state.error) return state;
|
|
1692
|
+
if (page && !pageReady && context.rowsError)
|
|
1693
|
+
return { results: null, loading: false, error: context.rowsError };
|
|
1694
|
+
if (state.token !== token) return { results: null, loading: true, error: null };
|
|
1695
|
+
return state;
|
|
1696
|
+
}
|
|
1697
|
+
function useAggregations(query, schema, transport, clientRows, debounceMs, nonce, validateQuery, options = {}, enabled = true) {
|
|
1698
|
+
const context = { schema, transport, clientRows, ...options };
|
|
1699
|
+
const clauses = query.aggregations ?? [];
|
|
1700
|
+
const pageClauses = clauses.filter((c) => localPage(c, context));
|
|
1701
|
+
const otherClauses = clauses.filter((c) => !pageClauses.includes(c));
|
|
1702
|
+
const other = useMetricBatch(query, otherClauses, context, debounceMs, nonce, validateQuery, enabled, false);
|
|
1703
|
+
const page = useMetricBatch(query, pageClauses, context, debounceMs, nonce, validateQuery, enabled, true);
|
|
1704
|
+
const previewKey = JSON.stringify([
|
|
1705
|
+
query.where,
|
|
1706
|
+
query.orderBy,
|
|
1707
|
+
query.limit,
|
|
1708
|
+
query.offset,
|
|
1709
|
+
metricComputationKey(clauses),
|
|
1710
|
+
options.revisions,
|
|
1711
|
+
options.execution,
|
|
1712
|
+
options.now,
|
|
1713
|
+
options.strictLocalMetrics,
|
|
1714
|
+
options.queryContextKey
|
|
1743
1715
|
]);
|
|
1744
|
-
|
|
1716
|
+
const previewToken = useMemo5(() => ({}), [previewKey, schema, transport, nonce, validateQuery, enabled, options.workerFactory]);
|
|
1717
|
+
const pageKey = JSON.stringify([query.orderBy, query.limit, query.offset, options.shownFields]);
|
|
1718
|
+
const pageToken = useMemo5(() => ({}), [previewToken, pageKey, clientRows, options.shownRows, options.rowsReady, options.rowsError]);
|
|
1719
|
+
const latest = useRef6({ query, context, validateQuery, enabled, previewToken, pageToken });
|
|
1720
|
+
latest.current = { query, context, validateQuery, enabled, previewToken, pageToken };
|
|
1721
|
+
const previews = useRef6(/* @__PURE__ */ new Set());
|
|
1722
|
+
useEffect6(() => () => {
|
|
1723
|
+
for (const pending of previews.current)
|
|
1724
|
+
if (pending.previewToken === previewToken) pending.controller.abort();
|
|
1725
|
+
}, [previewToken]);
|
|
1726
|
+
useEffect6(() => () => {
|
|
1727
|
+
for (const pending of previews.current)
|
|
1728
|
+
if (pending.page && pending.pageToken === pageToken) pending.controller.abort();
|
|
1729
|
+
}, [pageToken]);
|
|
1730
|
+
const preview = useCallback6(async (draftClauses, signal) => {
|
|
1731
|
+
const current = latest.current;
|
|
1732
|
+
if (!current.enabled) throw Error("Resolve the query link before previewing metrics.");
|
|
1733
|
+
const draft = { ...current.query, aggregations: draftClauses };
|
|
1734
|
+
current.validateQuery?.(draft);
|
|
1735
|
+
const pending = {
|
|
1736
|
+
controller: new AbortController(),
|
|
1737
|
+
page: draftClauses.some((c) => localPage(c, current.context)),
|
|
1738
|
+
previewToken: current.previewToken,
|
|
1739
|
+
pageToken: current.pageToken
|
|
1740
|
+
};
|
|
1741
|
+
const cancel = () => pending.controller.abort();
|
|
1742
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1743
|
+
if (signal?.aborted) cancel();
|
|
1744
|
+
previews.current.add(pending);
|
|
1745
|
+
try {
|
|
1746
|
+
const result = await executeMetrics(draft, current.context, pending.controller.signal);
|
|
1747
|
+
if (pending.controller.signal.aborted || current.previewToken !== latest.current.previewToken || pending.page && current.pageToken !== latest.current.pageToken)
|
|
1748
|
+
throw new DOMException("Aborted", "AbortError");
|
|
1749
|
+
return result;
|
|
1750
|
+
} finally {
|
|
1751
|
+
previews.current.delete(pending);
|
|
1752
|
+
signal?.removeEventListener("abort", cancel);
|
|
1753
|
+
}
|
|
1754
|
+
}, []);
|
|
1755
|
+
const orderKey = JSON.stringify(clauses.map((c) => c.id));
|
|
1756
|
+
const results = useMemo5(() => {
|
|
1757
|
+
if (!pageClauses.length) return other.results;
|
|
1758
|
+
if (!otherClauses.length) return page.results;
|
|
1759
|
+
if (!page.results || !other.results) return null;
|
|
1760
|
+
const byId = new Map([...page.results.metrics, ...other.results.metrics].map((m) => [m.id, m]));
|
|
1761
|
+
return { metrics: clauses.flatMap((c) => byId.get(c.id) ?? []) };
|
|
1762
|
+
}, [orderKey, pageClauses.length, otherClauses.length, page.results, other.results]);
|
|
1763
|
+
return { results, loading: page.loading || other.loading, error: page.error ?? other.error, preview };
|
|
1745
1764
|
}
|
|
1746
1765
|
|
|
1747
1766
|
// src/useSelection.ts
|
|
@@ -2248,7 +2267,7 @@ function useQueryTable(opts) {
|
|
|
2248
2267
|
const active = opts.active !== false;
|
|
2249
2268
|
const urlSyncEnabled = opts.syncUrl === true;
|
|
2250
2269
|
const syncUrl = urlSyncEnabled && active;
|
|
2251
|
-
const { transport: metadataTransport, activity: requestActivity } = useRequestActivity(sourceTransport);
|
|
2270
|
+
const { transport: metadataTransport, metricTransport, activity: requestActivity } = useRequestActivity(sourceTransport, opts.metricTransport);
|
|
2252
2271
|
const transport = metadataTransport;
|
|
2253
2272
|
const storage = useMemo9(
|
|
2254
2273
|
() => opts.storage ?? memoryStorageAdapter(),
|
|
@@ -2478,6 +2497,8 @@ function useQueryTable(opts) {
|
|
|
2478
2497
|
serverComputed,
|
|
2479
2498
|
...serverExecution ? { serverExecution } : {},
|
|
2480
2499
|
...opts.computedExecution ? { execution: opts.computedExecution } : {},
|
|
2500
|
+
...metricTransport ? { metricTransport } : {},
|
|
2501
|
+
...opts.metricExecution ? { metricExecution: opts.metricExecution } : {},
|
|
2481
2502
|
...opts.computedColumnStore ? { store: opts.computedColumnStore } : {},
|
|
2482
2503
|
...opts.formulaWorkerFactory ? { workerFactory: opts.formulaWorkerFactory } : {},
|
|
2483
2504
|
...transport ? { transport } : {},
|
|
@@ -2487,18 +2508,19 @@ function useQueryTable(opts) {
|
|
|
2487
2508
|
(name) => isComputedField4(name) ? computed.compile(`[${name.replace(/]/g, "]] ".trim())}]`) : void 0,
|
|
2488
2509
|
[computed.compile]
|
|
2489
2510
|
);
|
|
2511
|
+
const metricCompileSchema = useMemo9(() => opts.metricExecution ? sourceExecutionSchema(displaySchema, opts.metricExecution, computed.definitions) : displaySchema, [displaySchema, opts.metricExecution, computed.definitions]);
|
|
2490
2512
|
const metricCompile = useCallback12(
|
|
2491
2513
|
(clause) => {
|
|
2492
2514
|
const diagnostics = validateMetric2(
|
|
2493
2515
|
clause,
|
|
2494
|
-
|
|
2516
|
+
metricCompileSchema,
|
|
2495
2517
|
resolveComputed
|
|
2496
2518
|
);
|
|
2497
2519
|
if (diagnostics.length)
|
|
2498
2520
|
throw new Error(diagnostics.map((d) => d.message).join(" "));
|
|
2499
|
-
return
|
|
2521
|
+
return metricPlans2(clause, metricCompileSchema, resolveComputed);
|
|
2500
2522
|
},
|
|
2501
|
-
[
|
|
2523
|
+
[metricCompileSchema, resolveComputed]
|
|
2502
2524
|
);
|
|
2503
2525
|
const computedRevisions = useMemo9(
|
|
2504
2526
|
() => Object.fromEntries(computed.definitions.map((d) => [d.id, d.revision])),
|
|
@@ -2759,11 +2781,18 @@ function useQueryTable(opts) {
|
|
|
2759
2781
|
const dataToken = useRef9(0);
|
|
2760
2782
|
const dataVersion = useMemo9(
|
|
2761
2783
|
() => ++dataToken.current,
|
|
2762
|
-
[schema, transport, clientRows, nonce, definitionKey, executionKey]
|
|
2784
|
+
[schema, transport, clientRows, rows, nonce, definitionKey, executionKey, metricTransport, opts.metricExecution]
|
|
2785
|
+
);
|
|
2786
|
+
const metricRowsReady = pageReady && (opts.metricRowsReady?.(query) ?? true);
|
|
2787
|
+
const metricExecution = opts.metricExecution ?? (opts.computedExecution ? pageReady && serverExecution ? serverExecution : opts.computedExecution : void 0);
|
|
2788
|
+
const v3MetricExecution = opts.metricExecution ?? opts.computedExecution;
|
|
2789
|
+
const metricSourceCatalogue = useMemo9(
|
|
2790
|
+
() => sourceExecutionSchema(sourceSchema, v3MetricExecution),
|
|
2791
|
+
[sourceSchema, v3MetricExecution]
|
|
2763
2792
|
);
|
|
2764
2793
|
const executeV3Metrics = useCallback12(async (metricQuery, signal) => {
|
|
2765
|
-
if (!
|
|
2766
|
-
const capabilities =
|
|
2794
|
+
if (!metricTransport?.fetchQueryV3 || metricTransport.queryCapabilities?.version !== 3) throw Error("The v3 query executor is unavailable.");
|
|
2795
|
+
const capabilities = metricTransport.queryCapabilities;
|
|
2767
2796
|
const refused = /* @__PURE__ */ new Map();
|
|
2768
2797
|
for (const clause of metricQuery.aggregations ?? []) {
|
|
2769
2798
|
const issue = metricPopulationError2(clause, { filters: capabilities.metricFilters, minCount: capabilities.metricMinCount });
|
|
@@ -2777,12 +2806,12 @@ function useQueryTable(opts) {
|
|
|
2777
2806
|
query.grouping ? { ...scoped, grouping: query.grouping } : scoped,
|
|
2778
2807
|
sourceSchema,
|
|
2779
2808
|
capabilities,
|
|
2780
|
-
|
|
2809
|
+
v3MetricExecution,
|
|
2781
2810
|
{ now: groupingNow ?? (opts.evaluationNow ?? Date.now)(), rows: false }
|
|
2782
2811
|
);
|
|
2783
2812
|
if (request.diagnostics.length) throw Error(request.diagnostics.join(" "));
|
|
2784
|
-
const result = await
|
|
2785
|
-
validateQueryResultV3(result, request, query.grouping ? schema :
|
|
2813
|
+
const result = await metricTransport.fetchQueryV3(request, signal);
|
|
2814
|
+
validateQueryResultV3(result, request, query.grouping ? schema : metricSourceCatalogue);
|
|
2786
2815
|
metrics = result.metrics;
|
|
2787
2816
|
}
|
|
2788
2817
|
if (!refused.size) return { metrics };
|
|
@@ -2793,7 +2822,7 @@ function useQueryTable(opts) {
|
|
|
2793
2822
|
const metric = byId.get(clause.id);
|
|
2794
2823
|
return metric ? [metric] : [];
|
|
2795
2824
|
}) };
|
|
2796
|
-
}, [
|
|
2825
|
+
}, [metricTransport, sourceSchema, metricSourceCatalogue, schema, v3MetricExecution, groupKey, groupingNow, opts.evaluationNow]);
|
|
2797
2826
|
const previewGrouping = useCallback12(async (draft, signal) => {
|
|
2798
2827
|
if (!active || !storageReady || restoration.loading || restoration.error) throw Error("Wait for query restoration before previewing groups.");
|
|
2799
2828
|
if (!transport?.fetchQueryV3 || !transport.queryCapabilities?.groupedRows || !draft.grouping) throw Error("The server group preview is unavailable.");
|
|
@@ -2812,23 +2841,21 @@ function useQueryTable(opts) {
|
|
|
2812
2841
|
const aggState = useAggregations(
|
|
2813
2842
|
executionQuery,
|
|
2814
2843
|
schema,
|
|
2815
|
-
|
|
2844
|
+
metricTransport,
|
|
2816
2845
|
clientRows,
|
|
2817
2846
|
debounceMs,
|
|
2818
2847
|
nonce,
|
|
2819
2848
|
validateExecution,
|
|
2820
2849
|
{
|
|
2821
|
-
...
|
|
2850
|
+
...metricTransport?.fetchQueryV3 && metricTransport.queryCapabilities?.version === 3 && (query.grouping || query.aggregations?.some((metric) => metric.dateGrouping) || query.where.some((term) => ("any" in term ? term.any : [term]).some((predicate) => isComputedField4(predicate.field)))) ? { executeV3: executeV3Metrics, queryContextKey: groupKey } : {},
|
|
2822
2851
|
...groupingNow !== void 0 ? { now: groupingNow, strictLocalMetrics: true } : {},
|
|
2823
2852
|
shownRows: rows,
|
|
2824
|
-
rowsReady:
|
|
2853
|
+
rowsReady: metricRowsReady,
|
|
2825
2854
|
rowsError: error,
|
|
2826
2855
|
...transport ? { shownFields: committedFields } : {},
|
|
2827
2856
|
resolveComputed,
|
|
2828
2857
|
revisions: computedRevisions,
|
|
2829
|
-
...
|
|
2830
|
-
execution: pageReady && serverExecution ? serverExecution : opts.computedExecution
|
|
2831
|
-
} : {},
|
|
2858
|
+
...metricExecution ? { execution: metricExecution } : {},
|
|
2832
2859
|
...opts.formulaWorkerFactory ? { workerFactory: opts.formulaWorkerFactory } : {}
|
|
2833
2860
|
},
|
|
2834
2861
|
active && storageReady && !restoration.loading && !restoration.error
|
|
@@ -3100,7 +3127,7 @@ function useQueryTable(opts) {
|
|
|
3100
3127
|
preview: aggState.preview,
|
|
3101
3128
|
replace: replaceAggregations,
|
|
3102
3129
|
compile: metricCompile,
|
|
3103
|
-
contextKey: `${dataVersion}:${pageContext}:${
|
|
3130
|
+
contextKey: `${dataVersion}:${pageContext}:${metricRowsReady}:${JSON.stringify(query.where)}`
|
|
3104
3131
|
}),
|
|
3105
3132
|
[
|
|
3106
3133
|
query.aggregations,
|
|
@@ -3116,7 +3143,7 @@ function useQueryTable(opts) {
|
|
|
3116
3143
|
replaceAggregations,
|
|
3117
3144
|
metricCompile,
|
|
3118
3145
|
pageContext,
|
|
3119
|
-
|
|
3146
|
+
metricRowsReady,
|
|
3120
3147
|
dataVersion,
|
|
3121
3148
|
query.where
|
|
3122
3149
|
]
|