@pythia-software/query-table-react 0.9.6 → 0.9.7
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 +66 -0
- package/dist/index.d.ts +69 -7
- package/dist/index.js +344 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
// src/useQueryFilters.ts
|
|
2
2
|
import { useCallback, useMemo } from "react";
|
|
3
3
|
import {
|
|
4
|
+
branchesOf,
|
|
4
5
|
isOrGroup,
|
|
5
|
-
|
|
6
|
+
isWhereConjunction,
|
|
6
7
|
negateClause,
|
|
7
8
|
opsForField
|
|
8
9
|
} from "@pythia-software/query-table-core";
|
|
10
|
+
function editBranchPredicate(term, predIndex, edit) {
|
|
11
|
+
let k = 0;
|
|
12
|
+
const any = [];
|
|
13
|
+
for (const member of term.any) {
|
|
14
|
+
const kept = [];
|
|
15
|
+
for (const clause of isWhereConjunction(member) ? member.all : [member]) {
|
|
16
|
+
const next = k++ === predIndex ? edit(clause) : clause;
|
|
17
|
+
if (next) kept.push(next);
|
|
18
|
+
}
|
|
19
|
+
if (kept.length === 1) any.push(kept[0]);
|
|
20
|
+
else if (kept.length > 1) any.push({ all: kept });
|
|
21
|
+
}
|
|
22
|
+
if (any.length > 1) return [{ any }];
|
|
23
|
+
const only = any[0];
|
|
24
|
+
return !only ? [] : isWhereConjunction(only) ? only.all : [only];
|
|
25
|
+
}
|
|
9
26
|
function useQueryFilters(schema, setQuery) {
|
|
10
27
|
const byName = useMemo(
|
|
11
28
|
() => new Map(schema.fields.map((field) => [field.name, field])),
|
|
@@ -45,8 +62,9 @@ function useQueryFilters(schema, setQuery) {
|
|
|
45
62
|
return {
|
|
46
63
|
any: term.any.map((c, j) => j === predIndex ? clause : c)
|
|
47
64
|
};
|
|
65
|
+
if ("any" in term) return editBranchPredicate(term, predIndex, () => clause);
|
|
48
66
|
return clause;
|
|
49
|
-
})
|
|
67
|
+
}).flat()
|
|
50
68
|
})),
|
|
51
69
|
[setQuery]
|
|
52
70
|
);
|
|
@@ -64,6 +82,10 @@ function useQueryFilters(schema, setQuery) {
|
|
|
64
82
|
else if (members.length > 1) where.push({ any: members });
|
|
65
83
|
return;
|
|
66
84
|
}
|
|
85
|
+
if ("any" in term) {
|
|
86
|
+
where.push(...editBranchPredicate(term, predIndex, () => null));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
67
89
|
});
|
|
68
90
|
return { ...q, offset: 0, where };
|
|
69
91
|
}),
|
|
@@ -86,8 +108,9 @@ function useQueryFilters(schema, setQuery) {
|
|
|
86
108
|
return {
|
|
87
109
|
any: term.any.map((c, j) => j === predIndex ? negateOne(c) : c)
|
|
88
110
|
};
|
|
111
|
+
if ("any" in term) return editBranchPredicate(term, predIndex, negateOne);
|
|
89
112
|
return negateOne(term);
|
|
90
|
-
})
|
|
113
|
+
}).flat()
|
|
91
114
|
})),
|
|
92
115
|
[setQuery, negateOne]
|
|
93
116
|
);
|
|
@@ -108,7 +131,9 @@ function useQueryFilters(schema, setQuery) {
|
|
|
108
131
|
const target = q.where[targetIndex];
|
|
109
132
|
if (!source || !target) return q;
|
|
110
133
|
const merged = {
|
|
111
|
-
any: [...
|
|
134
|
+
any: [...branchesOf(target), ...branchesOf(source)].map(
|
|
135
|
+
(branch) => branch.length === 1 ? branch[0] : { all: branch }
|
|
136
|
+
)
|
|
112
137
|
};
|
|
113
138
|
const where = [];
|
|
114
139
|
q.where.forEach((term, i) => {
|
|
@@ -135,9 +160,10 @@ function useQueryFilters(schema, setQuery) {
|
|
|
135
160
|
// src/useGrouping.ts
|
|
136
161
|
import { useEffect, useRef, useState } from "react";
|
|
137
162
|
import {
|
|
163
|
+
groupKeyFields,
|
|
138
164
|
MAX_SUMMARY_COLUMNS,
|
|
139
165
|
normalizeQueryState,
|
|
140
|
-
predicatesOf
|
|
166
|
+
predicatesOf,
|
|
141
167
|
withGroupingKeys,
|
|
142
168
|
withoutGrouping,
|
|
143
169
|
queriesEqual
|
|
@@ -150,7 +176,8 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
150
176
|
const view = {
|
|
151
177
|
version: 2,
|
|
152
178
|
mode: stage ? "groups" : "rows",
|
|
153
|
-
keys: stage?.keys ?? [],
|
|
179
|
+
keys: groupKeyFields(stage?.keys ?? []),
|
|
180
|
+
groupKeys: stage?.keys ?? [],
|
|
154
181
|
summaries: stage?.summaries ?? [],
|
|
155
182
|
representative: stage?.representative ?? null,
|
|
156
183
|
input: source.query,
|
|
@@ -205,7 +232,7 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
205
232
|
offset: 0,
|
|
206
233
|
aggregations: [],
|
|
207
234
|
where: q.where.filter(
|
|
208
|
-
(term) => !
|
|
235
|
+
(term) => !predicatesOf(term).some((p) => changed.has(p.field))
|
|
209
236
|
),
|
|
210
237
|
orderBy: q.orderBy.filter((term) => !changed.has(term.field))
|
|
211
238
|
} : {}
|
|
@@ -213,7 +240,7 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
213
240
|
);
|
|
214
241
|
setNotice(
|
|
215
242
|
changed.size && (api.query.where.some(
|
|
216
|
-
(term) =>
|
|
243
|
+
(term) => predicatesOf(term).some((p) => changed.has(p.field))
|
|
217
244
|
) || api.query.orderBy.some((term) => changed.has(term.field)) || api.query.aggregations?.length) ? "Columns updated. Affected filters, sorts, and metrics cleared; Undo restores them." : ""
|
|
218
245
|
);
|
|
219
246
|
}
|
|
@@ -509,7 +536,7 @@ function initialDetailModes(config) {
|
|
|
509
536
|
const available = ALL_MODES.filter((mode) => !config?.modes || config.modes.includes(mode));
|
|
510
537
|
return config?.defaultModes ? available.filter((mode) => config.defaultModes.includes(mode)) : available.includes("modal") ? ["modal"] : available.slice(0, 1);
|
|
511
538
|
}
|
|
512
|
-
function useDetailProperties(mode, schema, config, query, setQuery, enabled) {
|
|
539
|
+
function useDetailProperties(mode, schema, config, query, setQuery, enabled, readOnly = false) {
|
|
513
540
|
const snapshot = query.itemDetails?.views?.[mode] ?? query.itemDetails?.view;
|
|
514
541
|
const seed = useRef3(snapshot);
|
|
515
542
|
const properties = usePropertyView({
|
|
@@ -539,7 +566,7 @@ function useDetailProperties(mode, schema, config, query, setQuery, enabled) {
|
|
|
539
566
|
if (target !== localViewKey) properties.setView(next);
|
|
540
567
|
} else if (pendingView.current && pendingView.current.query === queryViewKey) {
|
|
541
568
|
if (pendingView.current.local === localViewKey) pendingView.current = null;
|
|
542
|
-
} else if (!properties.loading && localViewKey !== queryViewKey && (queryViewKey !== void 0 || localViewKey !== previous.local)) {
|
|
569
|
+
} else if (!readOnly && !properties.loading && localViewKey !== queryViewKey && (queryViewKey !== void 0 || localViewKey !== previous.local)) {
|
|
543
570
|
setQuery((current) => ({ ...current, itemDetails: {
|
|
544
571
|
modes: current.itemDetails?.modes ?? initialDetailModes(config),
|
|
545
572
|
...current.itemDetails,
|
|
@@ -561,7 +588,9 @@ function useItemDetails({
|
|
|
561
588
|
refreshKey,
|
|
562
589
|
links,
|
|
563
590
|
queryReady = true,
|
|
564
|
-
queryParameter: queryParameter2
|
|
591
|
+
queryParameter: queryParameter2,
|
|
592
|
+
lookupRow,
|
|
593
|
+
subject
|
|
565
594
|
}) {
|
|
566
595
|
const enabled = Boolean(options);
|
|
567
596
|
const config = typeof options === "object" ? options : void 0;
|
|
@@ -573,9 +602,10 @@ function useItemDetails({
|
|
|
573
602
|
const sharedModal = requested?.mode === "modal" && query.itemDetails?.modes.includes("modal") && (availableModes.includes("hover") || availableModes.includes("page") && !page.urlTemplate.trim());
|
|
574
603
|
const active = requested && (modes.includes(requested.mode) || sharedModal) ? requested : null;
|
|
575
604
|
const syncProperties = enabled && queryReady;
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
const
|
|
605
|
+
const readOnly = subject === "group";
|
|
606
|
+
const modalProperties = useDetailProperties("modal", detailSchema, config, query, setQuery, syncProperties, readOnly);
|
|
607
|
+
const hoverProperties = useDetailProperties("hover", detailSchema, config, query, setQuery, syncProperties, readOnly);
|
|
608
|
+
const expandProperties = useDetailProperties("expand", detailSchema, config, query, setQuery, syncProperties, readOnly);
|
|
579
609
|
const propertiesByMode = { modal: modalProperties, hover: hoverProperties, expand: expandProperties };
|
|
580
610
|
const views = {
|
|
581
611
|
modal: modalProperties.view,
|
|
@@ -585,10 +615,14 @@ function useItemDetails({
|
|
|
585
615
|
const customLoad = config?.loadRow;
|
|
586
616
|
const customLoadRef = useRef3(customLoad);
|
|
587
617
|
customLoadRef.current = customLoad;
|
|
618
|
+
const lookupRef = useRef3(lookupRow);
|
|
619
|
+
lookupRef.current = lookupRow;
|
|
620
|
+
const hasLookup = Boolean(lookupRow);
|
|
588
621
|
const hasCustomLoad = Boolean(customLoad);
|
|
589
622
|
const sourceType = config?.objectType ?? detailSchema.name;
|
|
590
623
|
const fallbackRows = !customLoad && !clientRows && !transport?.fetchRow ? rows : void 0;
|
|
591
624
|
const loadRow = useCallback3(async (id, signal) => {
|
|
625
|
+
if (lookupRef.current) return lookupRef.current(id) ?? null;
|
|
592
626
|
if (customLoadRef.current) return customLoadRef.current(id, signal);
|
|
593
627
|
if (clientRows) {
|
|
594
628
|
const row2 = clientRows.find((row3) => rowId(row3) === id);
|
|
@@ -600,10 +634,10 @@ function useItemDetails({
|
|
|
600
634
|
}
|
|
601
635
|
const row = rows.find((row2) => rowId(row2) === id);
|
|
602
636
|
return row ? { row, loadedFields: Object.keys(row) } : null;
|
|
603
|
-
}, [hasCustomLoad, sourceType, clientRows, transport?.fetchRow, fallbackRows, rowId]);
|
|
637
|
+
}, [hasLookup, hasCustomLoad, sourceType, clientRows, transport?.fetchRow, fallbackRows, rowId]);
|
|
604
638
|
const [retryKey, setRetryKey] = useState3(0);
|
|
605
639
|
const [result, setResult] = useState3({ source: void 0, epoch: "", id: void 0, record: null, loading: false, error: null });
|
|
606
|
-
const activeId = enabled ? active?.id : void 0;
|
|
640
|
+
const activeId = enabled && !lookupRow ? active?.id : void 0;
|
|
607
641
|
const epoch = `${refreshKey}:${retryKey}`;
|
|
608
642
|
const currentResult = result.id === activeId && result.source === loadRow && result.epoch === epoch;
|
|
609
643
|
useEffect3(() => {
|
|
@@ -628,7 +662,9 @@ function useItemDetails({
|
|
|
628
662
|
return () => controller.abort();
|
|
629
663
|
}, [activeId, loadRow, refreshKey, retryKey]);
|
|
630
664
|
if (!enabled) return;
|
|
665
|
+
const looked = lookupRow && active ? lookupRow(active.id) : null;
|
|
631
666
|
return {
|
|
667
|
+
...subject ? { subject } : {},
|
|
632
668
|
...links ? { links } : {},
|
|
633
669
|
schema: detailSchema,
|
|
634
670
|
queryParameter: queryParameter2 ?? config?.queryParameter ?? "q",
|
|
@@ -639,9 +675,9 @@ function useItemDetails({
|
|
|
639
675
|
active,
|
|
640
676
|
page,
|
|
641
677
|
pageSchema: schema,
|
|
642
|
-
record: currentResult ? result.record : null,
|
|
643
|
-
loading: activeId !== void 0 && (!currentResult || result.loading),
|
|
644
|
-
error: currentResult ? result.error : null,
|
|
678
|
+
record: lookupRow ? looked ?? null : currentResult ? result.record : null,
|
|
679
|
+
loading: lookupRow ? looked === void 0 : activeId !== void 0 && (!currentResult || result.loading),
|
|
680
|
+
error: lookupRow ? null : currentResult ? result.error : null,
|
|
645
681
|
...config?.relationships ? { relationships: config.relationships } : {},
|
|
646
682
|
loadRow,
|
|
647
683
|
retry: () => setRetryKey((value) => value + 1),
|
|
@@ -674,6 +710,7 @@ function useItemDetails({
|
|
|
674
710
|
|
|
675
711
|
// src/computedExecution.ts
|
|
676
712
|
import {
|
|
713
|
+
predicatesOf as predicatesOf2,
|
|
677
714
|
isComputedField,
|
|
678
715
|
readFieldValue
|
|
679
716
|
} from "@pythia-software/query-table-core";
|
|
@@ -689,7 +726,7 @@ function validateComputedResponse(request, result, schema) {
|
|
|
689
726
|
const executed = /* @__PURE__ */ new Set([
|
|
690
727
|
...request.select.filter(isComputedField),
|
|
691
728
|
...request.orderBy.map((term) => term.field).filter(isComputedField),
|
|
692
|
-
...request.where.flatMap((term) => (
|
|
729
|
+
...request.where.flatMap((term) => predicatesOf2(term).map((predicate) => predicate.field)).filter(isComputedField)
|
|
693
730
|
]);
|
|
694
731
|
for (const [name, revision] of Object.entries(request.expectedRevisions)) {
|
|
695
732
|
const id = name.replace(/^@computed\//, "");
|
|
@@ -724,6 +761,7 @@ function validateComputedResponse(request, result, schema) {
|
|
|
724
761
|
|
|
725
762
|
// src/previewRows.ts
|
|
726
763
|
import {
|
|
764
|
+
predicatesOf as predicatesOf3,
|
|
727
765
|
sourceExecutionSchema,
|
|
728
766
|
toQueryRequestV3,
|
|
729
767
|
toServerQuery,
|
|
@@ -734,13 +772,13 @@ async function fetchPreviewRows(transport, query, schema, execution, definitions
|
|
|
734
772
|
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
735
773
|
const sourceSchema = sourceExecutionSchema(baseSchema, execution, definitions);
|
|
736
774
|
let result;
|
|
737
|
-
const computedFilter = query.where.some((term) => (
|
|
775
|
+
const computedFilter = query.where.some((term) => predicatesOf3(term).some((predicate) => predicate.field.startsWith("@computed/")));
|
|
738
776
|
if (transport.fetchRowsV2 && execution && !query.grouping && !computedFilter) {
|
|
739
777
|
const request = toServerQueryV2(query, sourceSchema, execution);
|
|
740
778
|
if (request.diagnostics.length)
|
|
741
779
|
throw Error(request.diagnostics.map((issue) => issue.message).join(" "));
|
|
742
780
|
for (const definition of definitions) {
|
|
743
|
-
if (executionRevision(execution, definition.id) !== definition.revision && (request.select.includes(`@computed/${definition.id}`) || request.orderBy.some((term) => term.field === `@computed/${definition.id}`) || request.where.some((term) => (
|
|
781
|
+
if (executionRevision(execution, definition.id) !== definition.revision && (request.select.includes(`@computed/${definition.id}`) || request.orderBy.some((term) => term.field === `@computed/${definition.id}`) || request.where.some((term) => predicatesOf3(term).some((predicate) => predicate.field === `@computed/${definition.id}`))))
|
|
744
782
|
throw Error("Computed definitions changed. Refresh the server execution handshake.");
|
|
745
783
|
}
|
|
746
784
|
const response = await transport.fetchRowsV2(request, signal);
|
|
@@ -772,6 +810,7 @@ async function fetchPreviewRows(transport, query, schema, execution, definitions
|
|
|
772
810
|
// src/useComputedColumns.ts
|
|
773
811
|
import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
|
|
774
812
|
import {
|
|
813
|
+
predicatesOf as predicatesOf4,
|
|
775
814
|
isGroupLinkField,
|
|
776
815
|
applyQuery,
|
|
777
816
|
compileFormula,
|
|
@@ -1173,7 +1212,7 @@ function useComputedColumns(options) {
|
|
|
1173
1212
|
offset: 0
|
|
1174
1213
|
};
|
|
1175
1214
|
const now = options.now ?? Date.now();
|
|
1176
|
-
const computedFilter = request.where.some((term) => (
|
|
1215
|
+
const computedFilter = request.where.some((term) => predicatesOf4(term).some((predicate) => isComputedField2(predicate.field)));
|
|
1177
1216
|
const v3 = !(transport.fetchRowsV2 && options.execution && !request.grouping && !computedFilter) && transport.fetchQueryV3 && transport.queryCapabilities?.version === 3;
|
|
1178
1217
|
const pageSize = Math.min(500, v3 ? transport.queryCapabilities.maxRows : 500);
|
|
1179
1218
|
if (!Number.isInteger(pageSize) || pageSize < 1) throw Error("Invalid negotiated preview row limit.");
|
|
@@ -1262,17 +1301,22 @@ import {
|
|
|
1262
1301
|
applyQuery as applyQuery2,
|
|
1263
1302
|
memoryStorageAdapter,
|
|
1264
1303
|
normalizeQueryState as normalizeQueryState3,
|
|
1304
|
+
parseHostQueryState,
|
|
1265
1305
|
readFieldValue as readFieldValue4,
|
|
1266
1306
|
selectedFields as selectedFields2,
|
|
1267
1307
|
toServerQuery as toServerQuery2,
|
|
1268
1308
|
queriesEqual as queriesEqual2,
|
|
1269
|
-
|
|
1309
|
+
isWhereConjunction as isWhereConjunction2,
|
|
1310
|
+
isWhereGroup,
|
|
1311
|
+
predicatesOf as predicatesOf6,
|
|
1270
1312
|
metricPlans as metricPlans2,
|
|
1271
1313
|
validateMetric as validateMetric2,
|
|
1272
|
-
metricDependencies,
|
|
1314
|
+
metricDependencies as metricDependencies2,
|
|
1273
1315
|
isComputedField as isComputedField4,
|
|
1274
1316
|
computedFieldName as computedFieldName2,
|
|
1275
|
-
toServerQueryV2 as toServerQueryV22
|
|
1317
|
+
toServerQueryV2 as toServerQueryV22,
|
|
1318
|
+
groupKeyField,
|
|
1319
|
+
groupKeyFields as groupKeyFields2
|
|
1276
1320
|
} from "@pythia-software/query-table-core";
|
|
1277
1321
|
|
|
1278
1322
|
// src/useRequestActivity.ts
|
|
@@ -1385,11 +1429,12 @@ import {
|
|
|
1385
1429
|
normalizeQueryState as normalizeQueryState2,
|
|
1386
1430
|
evaluateMetrics,
|
|
1387
1431
|
metricPlans,
|
|
1432
|
+
metricDependencies,
|
|
1388
1433
|
hasRegex as hasRegex2,
|
|
1389
1434
|
metricPopulationError,
|
|
1390
1435
|
hasMetricSeries,
|
|
1391
1436
|
planMetricSeries,
|
|
1392
|
-
predicatesOf as
|
|
1437
|
+
predicatesOf as predicatesOf5,
|
|
1393
1438
|
toAggregationQuery,
|
|
1394
1439
|
toMetricQuery,
|
|
1395
1440
|
validateMetric,
|
|
@@ -1545,11 +1590,10 @@ async function executeMetrics(query, context, signal) {
|
|
|
1545
1590
|
shown.flatMap((clause) => [
|
|
1546
1591
|
...clause.groupBy,
|
|
1547
1592
|
...(clause.where ?? []).flatMap(
|
|
1548
|
-
(term) =>
|
|
1593
|
+
(term) => predicatesOf5(term).map((predicate) => predicate.field)
|
|
1549
1594
|
),
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
)
|
|
1595
|
+
// Includes a measured minimum's field, which the page must also hold.
|
|
1596
|
+
...metricDependencies(clause, context.schema, context.resolveComputed)
|
|
1553
1597
|
])
|
|
1554
1598
|
);
|
|
1555
1599
|
for (const name of names) {
|
|
@@ -2134,7 +2178,7 @@ function useSavedQueries(key, currentQuery, applyQueryState, storage, now, snaps
|
|
|
2134
2178
|
// src/useQueryLinks.ts
|
|
2135
2179
|
import { useCallback as useCallback11, useEffect as useEffect9, useRef as useRef8, useState as useState10 } from "react";
|
|
2136
2180
|
import { encodeQuery } from "@pythia-software/query-table-core";
|
|
2137
|
-
function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, onUrlChange) {
|
|
2181
|
+
function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, onUrlChange, decorate = (url) => url) {
|
|
2138
2182
|
const latest = useRef8(query);
|
|
2139
2183
|
latest.current = query;
|
|
2140
2184
|
const [kind, setKind] = useState10(null);
|
|
@@ -2153,14 +2197,15 @@ function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, o
|
|
|
2153
2197
|
if (!syncUrl || !ready || typeof window === "undefined") return;
|
|
2154
2198
|
try {
|
|
2155
2199
|
const result = service.local(query, window.location.href, parameter);
|
|
2156
|
-
|
|
2157
|
-
|
|
2200
|
+
const url = decorate(result.url, query);
|
|
2201
|
+
window.history.replaceState(window.history.state, "", url);
|
|
2202
|
+
onUrlChange(url);
|
|
2158
2203
|
setKind(result.kind);
|
|
2159
2204
|
setError(null);
|
|
2160
2205
|
} catch (e) {
|
|
2161
2206
|
setError(e instanceof Error ? e : Error(String(e)));
|
|
2162
2207
|
}
|
|
2163
|
-
}, [queryKey, syncUrl, ready, service, parameter, onUrlChange]);
|
|
2208
|
+
}, [queryKey, syncUrl, ready, service, parameter, onUrlChange, decorate]);
|
|
2164
2209
|
const snapshot = useCallback11(async (q) => {
|
|
2165
2210
|
const captured = encodeQuery(q);
|
|
2166
2211
|
const base = typeof window !== "undefined" ? window.location.href : void 0;
|
|
@@ -2174,8 +2219,9 @@ function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, o
|
|
|
2174
2219
|
const url = new URL(base);
|
|
2175
2220
|
url.searchParams.set(parameter, "@" + saved.code);
|
|
2176
2221
|
const result = service.local(q, url.toString(), parameter);
|
|
2177
|
-
|
|
2178
|
-
|
|
2222
|
+
const next = decorate(result.url, q);
|
|
2223
|
+
window.history.replaceState(window.history.state, "", next);
|
|
2224
|
+
onUrlChange(next);
|
|
2179
2225
|
setKind("durable");
|
|
2180
2226
|
}
|
|
2181
2227
|
return saved;
|
|
@@ -2186,7 +2232,7 @@ function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, o
|
|
|
2186
2232
|
operations.current--;
|
|
2187
2233
|
if (mounted.current) setBusy(operations.current > 0);
|
|
2188
2234
|
}
|
|
2189
|
-
}, [service, syncUrl, ready, parameter, onUrlChange]);
|
|
2235
|
+
}, [service, syncUrl, ready, parameter, onUrlChange, decorate]);
|
|
2190
2236
|
const create = useCallback11(async (q = latest.current, options = {}) => {
|
|
2191
2237
|
const baseUrl = options.baseUrl ?? window.location.href;
|
|
2192
2238
|
const param = options.parameter ?? parameter;
|
|
@@ -2196,7 +2242,8 @@ function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, o
|
|
|
2196
2242
|
setError(null);
|
|
2197
2243
|
try {
|
|
2198
2244
|
if (!ready) throw Error("Resolve or reset the query link before sharing it.");
|
|
2199
|
-
const
|
|
2245
|
+
const durable = await service.durable(q, baseUrl, param);
|
|
2246
|
+
const result = param === parameter ? { ...durable, url: decorate(durable.url, q) } : durable;
|
|
2200
2247
|
if (mounted.current && syncUrl && ready && param === parameter && window.location.href === baseUrl && encodeQuery(latest.current) === captured) {
|
|
2201
2248
|
window.history.replaceState(window.history.state, "", result.url);
|
|
2202
2249
|
onUrlChange(result.url);
|
|
@@ -2210,8 +2257,11 @@ function useQueryLinks(query, service, syncUrl, ready, parameter, maxUrlBytes, o
|
|
|
2210
2257
|
operations.current--;
|
|
2211
2258
|
if (mounted.current) setBusy(operations.current > 0);
|
|
2212
2259
|
}
|
|
2213
|
-
}, [service, syncUrl, ready, parameter, onUrlChange]);
|
|
2214
|
-
const local = useCallback11((q, base = window.location.href, param = parameter) =>
|
|
2260
|
+
}, [service, syncUrl, ready, parameter, onUrlChange, decorate]);
|
|
2261
|
+
const local = useCallback11((q, base = window.location.href, param = parameter) => {
|
|
2262
|
+
const result = service.local(q, base, param);
|
|
2263
|
+
return param === parameter ? { ...result, url: decorate(result.url, q) } : result;
|
|
2264
|
+
}, [service, parameter, decorate]);
|
|
2215
2265
|
return { maxUrlBytes, kind, busy, error, create, local, snapshot };
|
|
2216
2266
|
}
|
|
2217
2267
|
|
|
@@ -2248,7 +2298,7 @@ function defaultsFor(schema, details = false) {
|
|
|
2248
2298
|
});
|
|
2249
2299
|
}
|
|
2250
2300
|
function cloneWhereTerm(term) {
|
|
2251
|
-
const copy =
|
|
2301
|
+
const copy = isWhereGroup(term) ? { ...term, any: term.any.map((c) => isWhereConjunction2(c) ? { all: c.all.map((m) => ({ ...m })) } : { ...c }) } : { ...term };
|
|
2252
2302
|
if (term.setFilter)
|
|
2253
2303
|
copy.setFilter = { ...term.setFilter, values: [...term.setFilter.values] };
|
|
2254
2304
|
return copy;
|
|
@@ -2264,6 +2314,7 @@ function cloneQueryState(q) {
|
|
|
2264
2314
|
offset: q.offset
|
|
2265
2315
|
};
|
|
2266
2316
|
if (q.fitToScreen) clone.fitToScreen = true;
|
|
2317
|
+
if (q.hostState) clone.hostState = { version: q.hostState.version, params: Object.fromEntries(Object.entries(q.hostState.params).map(([name, values]) => [name, [...values]])) };
|
|
2267
2318
|
if (q.aggregations)
|
|
2268
2319
|
clone.aggregations = JSON.parse(
|
|
2269
2320
|
JSON.stringify(q.aggregations)
|
|
@@ -2325,16 +2376,61 @@ function hasQueryLink(url, parameter) {
|
|
|
2325
2376
|
function queryParameter(opts) {
|
|
2326
2377
|
return opts.queryParameter ?? (typeof opts.itemDetails === "object" ? opts.itemDetails.queryParameter : void 0) ?? "q";
|
|
2327
2378
|
}
|
|
2379
|
+
function withHostState(query, host) {
|
|
2380
|
+
if (!host) {
|
|
2381
|
+
if (query.hostState) throw Error("This query includes host state that this table does not support.");
|
|
2382
|
+
return query;
|
|
2383
|
+
}
|
|
2384
|
+
const raw = query.hostState ?? host.defaults;
|
|
2385
|
+
if (raw.version === 0) throw Error("This query's host state could not be read.");
|
|
2386
|
+
const state = parseHostQueryState(host.normalize ? host.normalize(parseHostQueryState(raw)) : raw);
|
|
2387
|
+
return { ...query, hostState: state };
|
|
2388
|
+
}
|
|
2389
|
+
function withoutHostState(query) {
|
|
2390
|
+
const { hostState: _hostState, ...rest } = query;
|
|
2391
|
+
return rest;
|
|
2392
|
+
}
|
|
2393
|
+
function hostStateFromUrl(location, host) {
|
|
2394
|
+
const url = new URL(location);
|
|
2395
|
+
const params = {};
|
|
2396
|
+
for (const name of host.ownedParameters) {
|
|
2397
|
+
const values = url.searchParams.getAll(name);
|
|
2398
|
+
if (values.length) params[name] = values;
|
|
2399
|
+
}
|
|
2400
|
+
return Object.keys(params).length ? { version: host.defaults.version, params } : void 0;
|
|
2401
|
+
}
|
|
2402
|
+
function writeHostParameters(url, host, state) {
|
|
2403
|
+
const next = new URL(url);
|
|
2404
|
+
for (const name of host.ownedParameters) next.searchParams.delete(name);
|
|
2405
|
+
for (const name of host.ownedParameters)
|
|
2406
|
+
for (const value of state?.params[name] ?? []) next.searchParams.append(name, value);
|
|
2407
|
+
return next.toString();
|
|
2408
|
+
}
|
|
2409
|
+
var HOST_REQUEST_METHODS = /* @__PURE__ */ new Set(["fetchRows", "fetchRowsV2", "fetchQueryV3", "fetchMetrics", "fetchAggregations", "fetchDistinctValues"]);
|
|
2410
|
+
function withHostTransport(transport, hostStateKey) {
|
|
2411
|
+
if (!transport || !hostStateKey) return transport;
|
|
2412
|
+
const hostState = JSON.parse(hostStateKey);
|
|
2413
|
+
const wrappers = /* @__PURE__ */ new Map();
|
|
2414
|
+
return new Proxy(Object.create(transport), {
|
|
2415
|
+
get(_target, key) {
|
|
2416
|
+
const method = Reflect.get(transport, key, transport);
|
|
2417
|
+
if (!HOST_REQUEST_METHODS.has(String(key)) || typeof method !== "function") return method;
|
|
2418
|
+
if (!wrappers.has(key))
|
|
2419
|
+
wrappers.set(key, (request, signal) => method.call(transport, { ...request, hostState }, signal));
|
|
2420
|
+
return wrappers.get(key);
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2328
2424
|
function resolveInitial(opts) {
|
|
2329
|
-
const defaults = defaultsFor(opts.schema, Boolean(opts.itemDetails));
|
|
2425
|
+
const defaults = withHostState(defaultsFor(opts.schema, Boolean(opts.itemDetails)), opts.hostQuery);
|
|
2330
2426
|
if (opts.initialQuery)
|
|
2331
2427
|
return {
|
|
2332
|
-
q: normalizeQueryState3(opts.initialQuery, defaults),
|
|
2428
|
+
q: withHostState(normalizeQueryState3(opts.initialQuery, defaults), opts.hostQuery),
|
|
2333
2429
|
fromUrl: false
|
|
2334
2430
|
};
|
|
2335
2431
|
if (opts.syncUrl === true && opts.active !== false && typeof window !== "undefined") {
|
|
2336
2432
|
const parameter = queryParameter(opts);
|
|
2337
|
-
if (hasQueryLink(window.location.href, parameter))
|
|
2433
|
+
if (hasQueryLink(window.location.href, parameter) || opts.hostQuery && hostStateFromUrl(window.location.href, opts.hostQuery))
|
|
2338
2434
|
return {
|
|
2339
2435
|
q: defaults,
|
|
2340
2436
|
fromUrl: true
|
|
@@ -2353,8 +2449,13 @@ function useQueryTable(opts) {
|
|
|
2353
2449
|
const active = opts.active !== false;
|
|
2354
2450
|
const urlSyncEnabled = opts.syncUrl === true;
|
|
2355
2451
|
const syncUrl = urlSyncEnabled && active;
|
|
2356
|
-
const { transport:
|
|
2357
|
-
const
|
|
2452
|
+
const { transport: activityTransport, metricTransport: activityMetricTransport, activity: requestActivity } = useRequestActivity(sourceTransport, opts.metricTransport);
|
|
2453
|
+
const host = opts.hostQuery;
|
|
2454
|
+
const hostRef = useRef9(host);
|
|
2455
|
+
hostRef.current = host;
|
|
2456
|
+
const hostDefaultsKey = host ? JSON.stringify(host.defaults) : "";
|
|
2457
|
+
if (host?.ownedParameters.includes(queryParameter(opts)))
|
|
2458
|
+
throw Error(`hostQuery.ownedParameters cannot include the table's own "${queryParameter(opts)}" parameter.`);
|
|
2358
2459
|
const storage = useMemo9(
|
|
2359
2460
|
() => opts.storage ?? memoryStorageAdapter(),
|
|
2360
2461
|
[opts.storage]
|
|
@@ -2367,7 +2468,10 @@ function useQueryTable(opts) {
|
|
|
2367
2468
|
const now = opts.now ?? Date.now;
|
|
2368
2469
|
const nowRef = useRef9(now);
|
|
2369
2470
|
const onRefreshRef = useRef9(opts.onRefresh);
|
|
2370
|
-
const defaults = useMemo9(
|
|
2471
|
+
const defaults = useMemo9(
|
|
2472
|
+
() => withHostState(defaultsFor(sourceSchema, Boolean(opts.itemDetails)), hostRef.current),
|
|
2473
|
+
[sourceSchema, Boolean(opts.itemDetails), hostDefaultsKey]
|
|
2474
|
+
);
|
|
2371
2475
|
const initRef = useRef9(resolveInitial(opts));
|
|
2372
2476
|
const [restoration, setRestoration] = useState11({ loading: initRef.current.fromUrl, error: null });
|
|
2373
2477
|
const [storageReady, setStorageReady] = useState11(Boolean(initialQuery));
|
|
@@ -2404,6 +2508,10 @@ function useQueryTable(opts) {
|
|
|
2404
2508
|
() => opts.canonicalizeQuery?.(storedQuery) ?? storedQuery,
|
|
2405
2509
|
[storedQuery, opts.canonicalizeQuery]
|
|
2406
2510
|
);
|
|
2511
|
+
const hostStateKey = host && query.hostState ? JSON.stringify(query.hostState) : "";
|
|
2512
|
+
const metadataTransport = useMemo9(() => withHostTransport(activityTransport, hostStateKey), [activityTransport, hostStateKey]);
|
|
2513
|
+
const metricTransport = useMemo9(() => withHostTransport(activityMetricTransport, hostStateKey), [activityMetricTransport, hostStateKey]);
|
|
2514
|
+
const transport = metadataTransport;
|
|
2407
2515
|
const sourceCatalogue = useMemo9(() => sourceExecutionSchema2(sourceSchema, opts.computedExecution), [sourceSchema, opts.computedExecution]);
|
|
2408
2516
|
const sourceRows = providedRows;
|
|
2409
2517
|
const groupingModel = useMemo9(() => createGroupingModel(sourceCatalogue), [sourceCatalogue]);
|
|
@@ -2433,6 +2541,7 @@ function useQueryTable(opts) {
|
|
|
2433
2541
|
const executionQuery = useMemo9(() => withoutGrouping2(query), [query]);
|
|
2434
2542
|
const byName = useMemo9(() => new Map(schema.fields.map((field) => [field.name, field])), [schema]);
|
|
2435
2543
|
const detailsEnabled = Boolean(opts.itemDetails) && !query.grouping;
|
|
2544
|
+
const groupDetailsEnabled = Boolean(opts.itemDetails) && Boolean(query.grouping);
|
|
2436
2545
|
const validateExecution = useCallback12((q) => {
|
|
2437
2546
|
if (prepared?.error) throw prepared.error;
|
|
2438
2547
|
opts.validateQuery?.(query.grouping ? { ...q, grouping: query.grouping } : q);
|
|
@@ -2445,6 +2554,7 @@ function useQueryTable(opts) {
|
|
|
2445
2554
|
const [loading, setLoading] = useState11(false);
|
|
2446
2555
|
const [error, setError] = useState11(null);
|
|
2447
2556
|
const [committedPage, setCommittedPage] = useState11(null);
|
|
2557
|
+
const [failedPage, setFailedPage] = useState11(null);
|
|
2448
2558
|
const [committedSource, setCommittedSource] = useState11(null);
|
|
2449
2559
|
const rows = useMemo9(() => committedSource?.schema === schema ? committedRows : [], [committedSource?.schema, schema, committedRows]);
|
|
2450
2560
|
const [autoRefreshStatus, setAutoRefreshStatus] = useState11(null);
|
|
@@ -2464,7 +2574,8 @@ function useQueryTable(opts) {
|
|
|
2464
2574
|
updateQueryHistory((history) => {
|
|
2465
2575
|
const prev = history.query;
|
|
2466
2576
|
const candidate = typeof next === "function" ? next(prev) : next;
|
|
2467
|
-
const
|
|
2577
|
+
const normalized = normalizeQueryState3(candidate, defaults);
|
|
2578
|
+
const nextQuery = withHostState(normalized.hostState || !prev.hostState ? normalized : { ...normalized, hostState: prev.hostState }, hostRef.current);
|
|
2468
2579
|
if (queriesEqual2(prev, nextQuery)) return history;
|
|
2469
2580
|
return { query: cloneQueryState(nextQuery), past: [...history.past, cloneQueryState(prev)], future: [] };
|
|
2470
2581
|
});
|
|
@@ -2481,9 +2592,15 @@ function useQueryTable(opts) {
|
|
|
2481
2592
|
}, [setQuery]);
|
|
2482
2593
|
const sourceFilters = useQueryFilters(sourceCatalogue, setSourceQuery);
|
|
2483
2594
|
const observedQueryUrl = useRef9(null);
|
|
2595
|
+
const tableUrlKey = (url) => {
|
|
2596
|
+
const params = new URL(url).searchParams;
|
|
2597
|
+
return JSON.stringify([params.getAll(parameter), ...(hostRef.current?.ownedParameters ?? []).map((name) => params.getAll(name))]);
|
|
2598
|
+
};
|
|
2484
2599
|
const recordQueryUrl = useCallback12((url) => {
|
|
2485
|
-
observedQueryUrl.current =
|
|
2600
|
+
observedQueryUrl.current = tableUrlKey(url);
|
|
2486
2601
|
}, [parameter]);
|
|
2602
|
+
const ownedParametersKey = JSON.stringify(host?.ownedParameters ?? null);
|
|
2603
|
+
const decorateLink = useCallback12((url, q) => hostRef.current ? writeHostParameters(url, hostRef.current, q.hostState) : url, [ownedParametersKey]);
|
|
2487
2604
|
const links = useQueryLinks(
|
|
2488
2605
|
query,
|
|
2489
2606
|
linkService,
|
|
@@ -2491,7 +2608,8 @@ function useQueryTable(opts) {
|
|
|
2491
2608
|
storageReady && !restoration.loading && !restoration.error,
|
|
2492
2609
|
parameter,
|
|
2493
2610
|
opts.links?.maxUrlBytes ?? 2e3,
|
|
2494
|
-
recordQueryUrl
|
|
2611
|
+
recordQueryUrl,
|
|
2612
|
+
decorateLink
|
|
2495
2613
|
);
|
|
2496
2614
|
const restoreSources = useRef9({ storage, linkService, defaults, initialQuery });
|
|
2497
2615
|
restoreSources.current = { storage, linkService, defaults, initialQuery };
|
|
@@ -2513,17 +2631,21 @@ function useQueryTable(opts) {
|
|
|
2513
2631
|
signal?.addEventListener("abort", abort2, { once: true });
|
|
2514
2632
|
if (signal?.aborted) currentController.abort();
|
|
2515
2633
|
setStorageReady(false);
|
|
2516
|
-
const
|
|
2634
|
+
const host2 = hostRef.current;
|
|
2635
|
+
const hostFromUrl = host2 && location && (navigation || urlSyncEnabled) ? hostStateFromUrl(location, host2) : void 0;
|
|
2636
|
+
const explicitLink = !!location && (navigation || urlSyncEnabled) && (hasQueryLink(location, parameter) || !!hostFromUrl);
|
|
2517
2637
|
setRestoration({ loading: true, error: null });
|
|
2518
2638
|
try {
|
|
2519
2639
|
if (currentController.signal.aborted) throw new DOMException("Query restoration cancelled.", "AbortError");
|
|
2520
2640
|
const fromUrl = location && (navigation || urlSyncEnabled) ? await linkService2.resolve(location, parameter, currentController.signal) : null;
|
|
2521
|
-
const
|
|
2522
|
-
const
|
|
2641
|
+
const linked = fromUrl ?? (hostFromUrl ? defaults2 : null);
|
|
2642
|
+
const defaultSaved = linked ? null : await storage2.loadDefaultSaved?.(sourceSchema.name);
|
|
2643
|
+
let restored = linked ?? defaultSaved?.query ?? (navigation ? defaults2 : await storage2.loadLast(sourceSchema.name));
|
|
2523
2644
|
if (currentController.signal.aborted) throw new DOMException("Query restoration cancelled.", "AbortError");
|
|
2645
|
+
if (linked && !fromUrl?.hostState && hostFromUrl) restored = { ...linked, hostState: hostFromUrl };
|
|
2524
2646
|
if (!cancelled && run === generation && epoch === manualQueryEpoch.current) {
|
|
2525
2647
|
if (restored) {
|
|
2526
|
-
const normalized = normalizeQueryState3(restored, defaults2);
|
|
2648
|
+
const normalized = withHostState(normalizeQueryState3(restored, defaults2), host2);
|
|
2527
2649
|
savedBaseline.current = defaultSaved || fromUrl?.grouping ? { source: sourceSchema.name, query: cloneQueryState(normalized) } : null;
|
|
2528
2650
|
viewRevision.current++;
|
|
2529
2651
|
updateQueryHistory(() => ({ query: normalized, past: [], future: [] }));
|
|
@@ -2548,7 +2670,7 @@ function useQueryTable(opts) {
|
|
|
2548
2670
|
}
|
|
2549
2671
|
restoreHandler.current = async (location, signal) => {
|
|
2550
2672
|
location = new URL(location, typeof window !== "undefined" ? window.location.href : void 0).href;
|
|
2551
|
-
const key =
|
|
2673
|
+
const key = tableUrlKey(location);
|
|
2552
2674
|
const current = restorationCurrent.current;
|
|
2553
2675
|
if (key === observedQueryUrl.current && current.storageReady && !current.restoration.loading && !current.restoration.error) return;
|
|
2554
2676
|
recordQueryUrl(location);
|
|
@@ -2558,7 +2680,7 @@ function useQueryTable(opts) {
|
|
|
2558
2680
|
if (typeof window !== "undefined") recordQueryUrl(window.location.href);
|
|
2559
2681
|
const navigate = () => {
|
|
2560
2682
|
if (!restorationCurrent.current.active) return;
|
|
2561
|
-
const key =
|
|
2683
|
+
const key = tableUrlKey(window.location.href);
|
|
2562
2684
|
if (key === observedQueryUrl.current) return;
|
|
2563
2685
|
observedQueryUrl.current = key;
|
|
2564
2686
|
void restore(true).catch(() => {
|
|
@@ -2618,7 +2740,7 @@ function useQueryTable(opts) {
|
|
|
2618
2740
|
const definitionKey = JSON.stringify(computedRevisions);
|
|
2619
2741
|
const queryKey = useMemo9(() => JSON.stringify(query), [query]);
|
|
2620
2742
|
const serverRowQuery = useMemo9(() => {
|
|
2621
|
-
const
|
|
2743
|
+
const execution2 = opts.computedExecution;
|
|
2622
2744
|
const detailConfig = typeof opts.itemDetails === "object" ? opts.itemDetails : void 0;
|
|
2623
2745
|
const page = query.itemDetails?.page ?? detailConfig?.page;
|
|
2624
2746
|
const pageEnabled = Boolean(opts.itemDetails) && (query.itemDetails?.modes ?? initialDetailModes(detailConfig)).includes("page") && (!detailConfig?.modes || detailConfig.modes.includes("page"));
|
|
@@ -2627,13 +2749,13 @@ function useQueryTable(opts) {
|
|
|
2627
2749
|
...query.select.length ? query.select : defaults.select,
|
|
2628
2750
|
...pageFields.filter((name) => displaySchema.fields.some((field) => field.name === name)).map((field) => ({ field }))
|
|
2629
2751
|
] } : executionQuery;
|
|
2630
|
-
const v2Request =
|
|
2752
|
+
const v2Request = execution2 && transport?.fetchRowsV2 ? toServerQueryV22(requestQuery, displaySchema, execution2) : void 0;
|
|
2631
2753
|
const request = v2Request ?? toServerQuery2(requestQuery, displaySchema);
|
|
2632
2754
|
if (v2Request)
|
|
2633
2755
|
for (const definition of computed.definitions) {
|
|
2634
|
-
if ((
|
|
2756
|
+
if ((execution2?.fields[computedFieldName2(definition.id)]?.select || v2Request.orderBy.some(
|
|
2635
2757
|
(term) => term.field === computedFieldName2(definition.id)
|
|
2636
|
-
)) && executionRevision(
|
|
2758
|
+
)) && executionRevision(execution2, definition.id) !== void 0 && executionRevision(execution2, definition.id) !== definition.revision)
|
|
2637
2759
|
v2Request.diagnostics.push({
|
|
2638
2760
|
from: 0,
|
|
2639
2761
|
to: 1,
|
|
@@ -2644,7 +2766,7 @@ function useQueryTable(opts) {
|
|
|
2644
2766
|
for (const clause of query.aggregations ?? [])
|
|
2645
2767
|
if (clause.scope === "shownRows") {
|
|
2646
2768
|
try {
|
|
2647
|
-
for (const name of
|
|
2769
|
+
for (const name of metricDependencies2(
|
|
2648
2770
|
clause,
|
|
2649
2771
|
schema,
|
|
2650
2772
|
resolveComputed
|
|
@@ -2694,7 +2816,7 @@ function useQueryTable(opts) {
|
|
|
2694
2816
|
]
|
|
2695
2817
|
);
|
|
2696
2818
|
const executionKey = JSON.stringify(opts.computedExecution ?? null);
|
|
2697
|
-
const pageContext = `${schema.name}:${rowQueryKey}:${nonce}:${executionKey}`;
|
|
2819
|
+
const pageContext = `${schema.name}:${rowQueryKey}:${nonce}:${executionKey}:${hostStateKey}`;
|
|
2698
2820
|
useEffect10(() => {
|
|
2699
2821
|
if (!active || !storageReady || restoration.loading || restoration.error) {
|
|
2700
2822
|
setRows((previous) => previous.length ? [] : previous);
|
|
@@ -2708,10 +2830,11 @@ function useQueryTable(opts) {
|
|
|
2708
2830
|
const run = async () => {
|
|
2709
2831
|
setLoading(true);
|
|
2710
2832
|
setError(null);
|
|
2833
|
+
setFailedPage(null);
|
|
2711
2834
|
try {
|
|
2712
2835
|
validateExecution(query);
|
|
2713
2836
|
if (transport) {
|
|
2714
|
-
if (transport.fetchQueryV3 && transport.queryCapabilities?.version === 3 && (query.grouping || query.select.some((column) => isComputedField4(column.field)) || query.orderBy.some((term) => isComputedField4(term.field)) || query.where.some((term) => (
|
|
2837
|
+
if (transport.fetchQueryV3 && transport.queryCapabilities?.version === 3 && (query.grouping || query.select.some((column) => isComputedField4(column.field)) || query.orderBy.some((term) => isComputedField4(term.field)) || query.where.some((term) => predicatesOf6(term).some((predicate) => isComputedField4(predicate.field))) || !transport.fetchRows && !(transport.fetchRowsV2 && opts.computedExecution))) {
|
|
2715
2838
|
const request = toQueryRequestV32(
|
|
2716
2839
|
{ ...query, aggregations: [], select: [
|
|
2717
2840
|
...query.select.filter((column) => isComputedField4(column.field)),
|
|
@@ -2792,6 +2915,7 @@ function useQueryTable(opts) {
|
|
|
2792
2915
|
} catch (e) {
|
|
2793
2916
|
if (!cancelled && !ac.signal.aborted) {
|
|
2794
2917
|
setError(e);
|
|
2918
|
+
setFailedPage(pageContext);
|
|
2795
2919
|
setRows([]);
|
|
2796
2920
|
setTotal(null);
|
|
2797
2921
|
setCommittedPage(null);
|
|
@@ -2820,11 +2944,13 @@ function useQueryTable(opts) {
|
|
|
2820
2944
|
active,
|
|
2821
2945
|
storageReady
|
|
2822
2946
|
]);
|
|
2947
|
+
const persistLast = opts.persistLast ?? "always";
|
|
2948
|
+
const hostStorageUnsupported = Boolean(host) && !storage.storesHostState;
|
|
2823
2949
|
useEffect10(() => {
|
|
2824
|
-
if (!storageReady || restoration.loading || restoration.error) return;
|
|
2950
|
+
if (persistLast !== "always" || !storageReady || restoration.loading || restoration.error || hostStorageUnsupported) return;
|
|
2825
2951
|
const t = setTimeout(() => void storage.saveLast(sourceSchema.name, query), 400);
|
|
2826
2952
|
return () => clearTimeout(t);
|
|
2827
|
-
}, [queryKey, storage, sourceSchema.name, query, restoration, storageReady]);
|
|
2953
|
+
}, [persistLast, queryKey, storage, sourceSchema.name, query, restoration, storageReady, hostStorageUnsupported]);
|
|
2828
2954
|
const idField = useMemo9(
|
|
2829
2955
|
() => schema.fields.find((f) => f.name === schema.idField),
|
|
2830
2956
|
[schema]
|
|
@@ -2845,7 +2971,7 @@ function useQueryTable(opts) {
|
|
|
2845
2971
|
useEffect10(() => {
|
|
2846
2972
|
selection.clear();
|
|
2847
2973
|
}, [identityScope, selection.clear]);
|
|
2848
|
-
const columnSchema = useMemo9(() => detailColumnSchema(displaySchema, detailsEnabled), [displaySchema, detailsEnabled]);
|
|
2974
|
+
const columnSchema = useMemo9(() => detailColumnSchema(displaySchema, detailsEnabled || groupDetailsEnabled), [displaySchema, detailsEnabled, groupDetailsEnabled]);
|
|
2849
2975
|
const resultDefaults = useMemo9(() => query.grouping ? defaultsFor(schema) : defaults, [Boolean(query.grouping), schema, defaults]);
|
|
2850
2976
|
const select = useSelect(query, setQuery, columnSchema);
|
|
2851
2977
|
const columnDrag = useColumnDrag();
|
|
@@ -2853,7 +2979,7 @@ function useQueryTable(opts) {
|
|
|
2853
2979
|
sourceSchema.name,
|
|
2854
2980
|
query,
|
|
2855
2981
|
(q) => {
|
|
2856
|
-
const normalized = normalizeQueryState3(q, defaults);
|
|
2982
|
+
const normalized = withHostState(normalizeQueryState3(q, defaults), hostRef.current);
|
|
2857
2983
|
savedBaseline.current = { source: sourceSchema.name, query: cloneQueryState(normalized) };
|
|
2858
2984
|
viewRevision.current++;
|
|
2859
2985
|
setQuery(normalized);
|
|
@@ -2862,11 +2988,23 @@ function useQueryTable(opts) {
|
|
|
2862
2988
|
now,
|
|
2863
2989
|
links.snapshot
|
|
2864
2990
|
);
|
|
2865
|
-
const
|
|
2991
|
+
const hostStorageError = host && !storage.storesHostState ? "This storage adapter cannot save host query state (set StorageAdapter.storesHostState once it keeps QueryState.hostState)." : null;
|
|
2992
|
+
const savedItems = useMemo9(() => !host ? savedQueries.items : savedQueries.items.map((item) => {
|
|
2993
|
+
try {
|
|
2994
|
+
return { ...item, query: withHostState(normalizeQueryState3(item.query, defaults), hostRef.current) };
|
|
2995
|
+
} catch {
|
|
2996
|
+
return item;
|
|
2997
|
+
}
|
|
2998
|
+
}), [savedQueries.items, Boolean(host), defaults]);
|
|
2999
|
+
const saved = { ...savedQueries, items: savedItems, save: async (name) => {
|
|
3000
|
+
if (hostStorageError) throw Error(hostStorageError);
|
|
2866
3001
|
const item = await savedQueries.save(name);
|
|
2867
|
-
savedBaseline.current = { source: sourceSchema.name, query: cloneQueryState(normalizeQueryState3(item.query, defaults)) };
|
|
3002
|
+
savedBaseline.current = { source: sourceSchema.name, query: cloneQueryState(withHostState(normalizeQueryState3(item.query, defaults), hostRef.current)) };
|
|
2868
3003
|
viewRevision.current++;
|
|
2869
3004
|
return item;
|
|
3005
|
+
}, update: async (id) => {
|
|
3006
|
+
if (hostStorageError) throw Error(hostStorageError);
|
|
3007
|
+
return savedQueries.update(id);
|
|
2870
3008
|
} };
|
|
2871
3009
|
const pageReady = committedPage === pageContext && committedSource?.schema === schema && committedSource?.transport === transport && committedSource?.clientRows === clientRows && !error;
|
|
2872
3010
|
const dataToken = useRef9(0);
|
|
@@ -2886,7 +3024,7 @@ function useQueryTable(opts) {
|
|
|
2886
3024
|
const capabilities = metricTransport.queryCapabilities;
|
|
2887
3025
|
const refused = /* @__PURE__ */ new Map();
|
|
2888
3026
|
for (const clause of metricQuery.aggregations ?? []) {
|
|
2889
|
-
const issue = metricPopulationError2(clause, { filters: capabilities.metricFilters, minCount: capabilities.metricMinCount });
|
|
3027
|
+
const issue = metricPopulationError2(clause, { filters: capabilities.metricFilters, minCount: capabilities.metricMinCount, minCountMeasures: capabilities.metricMinCountMeasures });
|
|
2890
3028
|
if (issue) refused.set(clause.id, issue);
|
|
2891
3029
|
}
|
|
2892
3030
|
const runnable = (metricQuery.aggregations ?? []).filter((clause) => !refused.has(clause.id));
|
|
@@ -2938,7 +3076,7 @@ function useQueryTable(opts) {
|
|
|
2938
3076
|
nonce,
|
|
2939
3077
|
validateExecution,
|
|
2940
3078
|
{
|
|
2941
|
-
...metricTransport?.fetchQueryV3 && metricTransport.queryCapabilities?.version === 3 && (query.grouping || query.aggregations?.some((metric) => metric.dateGrouping) || query.where.some((term) => (
|
|
3079
|
+
...metricTransport?.fetchQueryV3 && metricTransport.queryCapabilities?.version === 3 && (query.grouping || query.aggregations?.some((metric) => metric.dateGrouping) || query.where.some((term) => predicatesOf6(term).some((predicate) => isComputedField4(predicate.field)))) ? { executeV3: executeV3Metrics, queryContextKey: groupKey } : {},
|
|
2942
3080
|
...groupingNow !== void 0 ? { now: groupingNow, strictLocalMetrics: true } : {},
|
|
2943
3081
|
shownRows: rows,
|
|
2944
3082
|
rowsReady: metricRowsReady,
|
|
@@ -3239,6 +3377,62 @@ function useQueryTable(opts) {
|
|
|
3239
3377
|
query.where
|
|
3240
3378
|
]
|
|
3241
3379
|
);
|
|
3380
|
+
const executionIdentity = JSON.stringify([pageContext, query.aggregations ?? [], query.where, groupKey, nonce]);
|
|
3381
|
+
const executionGeneration = useRef9({ key: "", generation: 0 });
|
|
3382
|
+
if (executionGeneration.current.key !== executionIdentity)
|
|
3383
|
+
executionGeneration.current = { key: executionIdentity, generation: executionGeneration.current.generation + 1 };
|
|
3384
|
+
const execution = useMemo9(() => {
|
|
3385
|
+
if (!active || !storageReady || restoration.loading || restoration.error) return null;
|
|
3386
|
+
const rowsStatus = pageReady ? "succeeded" : error && failedPage === pageContext ? "failed" : "running";
|
|
3387
|
+
const metricError = aggState.error ?? aggState.results?.metrics.find((entry) => entry.error)?.error;
|
|
3388
|
+
const metricsStatus = !query.aggregations?.length ? "none" : aggState.loading || !aggState.results && !aggState.error ? "running" : metricError ? "failed" : "succeeded";
|
|
3389
|
+
const status = rowsStatus === "failed" || metricsStatus === "failed" ? "failed" : rowsStatus === "succeeded" && metricsStatus !== "running" ? "succeeded" : "running";
|
|
3390
|
+
return {
|
|
3391
|
+
query,
|
|
3392
|
+
key: executionIdentity,
|
|
3393
|
+
generation: executionGeneration.current.generation,
|
|
3394
|
+
status,
|
|
3395
|
+
rows: rowsStatus,
|
|
3396
|
+
metrics: metricsStatus,
|
|
3397
|
+
error: rowsStatus === "failed" ? error : metricError ? metricError instanceof Error ? metricError : Error(String(metricError)) : null
|
|
3398
|
+
};
|
|
3399
|
+
}, [
|
|
3400
|
+
active,
|
|
3401
|
+
storageReady,
|
|
3402
|
+
restoration.loading,
|
|
3403
|
+
restoration.error,
|
|
3404
|
+
pageReady,
|
|
3405
|
+
error,
|
|
3406
|
+
failedPage,
|
|
3407
|
+
pageContext,
|
|
3408
|
+
aggState.error,
|
|
3409
|
+
aggState.results,
|
|
3410
|
+
aggState.loading,
|
|
3411
|
+
query,
|
|
3412
|
+
executionIdentity
|
|
3413
|
+
]);
|
|
3414
|
+
const succeededQuery = execution?.status === "succeeded" ? execution.query : null;
|
|
3415
|
+
const pendingLast = useRef9(null);
|
|
3416
|
+
const savedLast = useRef9(null);
|
|
3417
|
+
useEffect10(() => {
|
|
3418
|
+
if (persistLast !== "succeeded" || !succeededQuery || hostStorageUnsupported) return;
|
|
3419
|
+
const key = JSON.stringify([sourceSchema.name, succeededQuery]);
|
|
3420
|
+
if (pendingLast.current?.key === key || !pendingLast.current && savedLast.current === key) return;
|
|
3421
|
+
if (pendingLast.current) clearTimeout(pendingLast.current.timer);
|
|
3422
|
+
const save = () => {
|
|
3423
|
+
pendingLast.current = null;
|
|
3424
|
+
savedLast.current = key;
|
|
3425
|
+
void storage.saveLast(sourceSchema.name, succeededQuery);
|
|
3426
|
+
};
|
|
3427
|
+
pendingLast.current = { key, save, timer: setTimeout(save, 400) };
|
|
3428
|
+
}, [persistLast, succeededQuery, storage, sourceSchema.name, hostStorageUnsupported]);
|
|
3429
|
+
useEffect10(() => () => {
|
|
3430
|
+
const pending = pendingLast.current;
|
|
3431
|
+
if (pending) {
|
|
3432
|
+
clearTimeout(pending.timer);
|
|
3433
|
+
pending.save();
|
|
3434
|
+
}
|
|
3435
|
+
}, []);
|
|
3242
3436
|
const sourceRowId = useCallback12((row) => {
|
|
3243
3437
|
const field = sourceSchema.fields.find((f) => f.name === sourceSchema.idField);
|
|
3244
3438
|
const value = field ? readFieldValue4(field, row) : row[sourceSchema.idField];
|
|
@@ -3258,16 +3452,71 @@ function useQueryTable(opts) {
|
|
|
3258
3452
|
queryParameter: parameter,
|
|
3259
3453
|
queryReady: storageReady && !restoration.loading && !restoration.error
|
|
3260
3454
|
});
|
|
3455
|
+
const groupPopulation = query.grouping ? JSON.stringify([query.grouping, query.where, nonce, hostStateKey]) : "";
|
|
3456
|
+
const groupRecords = useRef9({ population: "", source: [], rows: /* @__PURE__ */ new Map() });
|
|
3457
|
+
const groupSource = [clientRows, transport, schema];
|
|
3458
|
+
if (groupRecords.current.population !== groupPopulation || groupSource.some((part, i) => part !== groupRecords.current.source[i]))
|
|
3459
|
+
groupRecords.current = { population: groupPopulation, source: groupSource, rows: /* @__PURE__ */ new Map() };
|
|
3460
|
+
const groupsSettled = groupDetailsEnabled && pageReady && !restoration.loading;
|
|
3461
|
+
if (groupsSettled) for (const row of rows) {
|
|
3462
|
+
const id = rowId(row);
|
|
3463
|
+
if (id != null) groupRecords.current.rows.set(id, row);
|
|
3464
|
+
}
|
|
3465
|
+
const groupDetailOptions = useMemo9(() => {
|
|
3466
|
+
if (!groupDetailsEnabled) return void 0;
|
|
3467
|
+
const config = typeof opts.itemDetails === "object" ? opts.itemDetails : void 0;
|
|
3468
|
+
const modes = (config?.modes ?? ["hover", "modal", "expand"]).filter((mode) => mode !== "page");
|
|
3469
|
+
return {
|
|
3470
|
+
modes,
|
|
3471
|
+
defaultModes: initialDetailModes(config).filter((mode) => modes.includes(mode)),
|
|
3472
|
+
objectType: `${config?.objectType ?? sourceSchema.name}:groups`,
|
|
3473
|
+
...config?.storage ? { storage: config.storage } : {}
|
|
3474
|
+
};
|
|
3475
|
+
}, [groupDetailsEnabled, opts.itemDetails, sourceSchema.name]);
|
|
3476
|
+
const groupDetailSchema = useMemo9(() => ({
|
|
3477
|
+
...displaySchema,
|
|
3478
|
+
defaultSelect: displaySchema.fields.filter((field) => field.name !== displaySchema.idField).map((field) => ({ field: field.name })),
|
|
3479
|
+
fields: displaySchema.fields.map((field) => field.name === displaySchema.idField ? { ...field, property: { enabled: false } } : field)
|
|
3480
|
+
}), [displaySchema]);
|
|
3481
|
+
const setGroupQuery = useCallback12((next) => setQuery((current) => current.grouping ? typeof next === "function" ? next(current) : next : current), [setQuery]);
|
|
3482
|
+
const groupDetails = useItemDetails({
|
|
3483
|
+
schema: groupDetailSchema,
|
|
3484
|
+
query,
|
|
3485
|
+
setQuery: setGroupQuery,
|
|
3486
|
+
options: groupDetailOptions,
|
|
3487
|
+
rows,
|
|
3488
|
+
clientRows: void 0,
|
|
3489
|
+
rowId,
|
|
3490
|
+
transport: void 0,
|
|
3491
|
+
refreshKey: nonce,
|
|
3492
|
+
links,
|
|
3493
|
+
queryParameter: parameter,
|
|
3494
|
+
queryReady: storageReady && !restoration.loading && !restoration.error,
|
|
3495
|
+
lookupRow: (id) => {
|
|
3496
|
+
const row = groupRecords.current.rows.get(id);
|
|
3497
|
+
return row ? { row } : groupsSettled || error ? null : void 0;
|
|
3498
|
+
},
|
|
3499
|
+
subject: "group"
|
|
3500
|
+
});
|
|
3261
3501
|
const api = {
|
|
3262
3502
|
restoration: {
|
|
3263
3503
|
status: restoration.error ? "error" : !storageReady || restoration.loading ? "resolving" : "ready",
|
|
3264
3504
|
ready: active && storageReady && !restoration.loading && !restoration.error,
|
|
3265
3505
|
error: restoration.error
|
|
3266
3506
|
},
|
|
3507
|
+
execution,
|
|
3508
|
+
...host && query.hostState ? {
|
|
3509
|
+
definition: { query: withoutHostState(query), hostState: query.hostState },
|
|
3510
|
+
setDefinition: (next) => {
|
|
3511
|
+
const current = queryHistoryRef.current.query;
|
|
3512
|
+
const value = typeof next === "function" ? next({ query: withoutHostState(current), hostState: current.hostState }) : next;
|
|
3513
|
+
setQuery(withHostState({ ...normalizeQueryState3(value.query, defaults), hostState: value.hostState }, hostRef.current));
|
|
3514
|
+
}
|
|
3515
|
+
} : {},
|
|
3267
3516
|
restoreLocation,
|
|
3268
3517
|
links,
|
|
3269
3518
|
schema: columnSchema,
|
|
3270
|
-
...itemDetails && detailsEnabled ? { itemDetails } : {},
|
|
3519
|
+
...itemDetails && detailsEnabled ? { itemDetails } : groupDetails ? { itemDetails: groupDetails } : {},
|
|
3271
3520
|
query,
|
|
3272
3521
|
defaults: resultDefaults,
|
|
3273
3522
|
setQuery,
|
|
@@ -3310,6 +3559,25 @@ function useQueryTable(opts) {
|
|
|
3310
3559
|
visibleFields,
|
|
3311
3560
|
rowId
|
|
3312
3561
|
};
|
|
3562
|
+
const sourceFilterValues = useCallback12(async (name, search) => {
|
|
3563
|
+
if (metadataTransport?.fetchDistinctValues) return metadataTransport.fetchDistinctValues({ field: name, search });
|
|
3564
|
+
const field = groupingModel.sourceField(name);
|
|
3565
|
+
return field && sourceRows ? { ...distinctValuesFromRows(field, sourceRows, search), hasNull: fieldHasNullInRows(field, sourceRows) } : { values: [], hasMore: false };
|
|
3566
|
+
}, [metadataTransport, groupingModel, sourceRows]);
|
|
3567
|
+
const sourceFieldStats = useCallback12(async (names, signal) => {
|
|
3568
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
3569
|
+
const requested = [...new Set(names)];
|
|
3570
|
+
if (metadataTransport?.fetchFieldStats) {
|
|
3571
|
+
const backendFields = requested.filter(
|
|
3572
|
+
(name) => sourceCatalogue.fields.find((field) => field.name === name)?.source.kind === "backend"
|
|
3573
|
+
);
|
|
3574
|
+
return backendFields.length ? metadataTransport.fetchFieldStats(backendFields, signal) : {};
|
|
3575
|
+
}
|
|
3576
|
+
return Object.fromEntries(requested.flatMap((name) => {
|
|
3577
|
+
const field = groupingModel.sourceField(name);
|
|
3578
|
+
return field && sourceRows ? [[name, { distinct: new Set(sourceRows.map((row) => JSON.stringify(readFieldValue4(field, row)))).size }]] : [];
|
|
3579
|
+
}));
|
|
3580
|
+
}, [metadataTransport, groupingModel, sourceRows, sourceCatalogue]);
|
|
3313
3581
|
const sourceApi = {
|
|
3314
3582
|
...sourceFilters,
|
|
3315
3583
|
loading,
|
|
@@ -3318,25 +3586,8 @@ function useQueryTable(opts) {
|
|
|
3318
3586
|
setQuery: setSourceQuery,
|
|
3319
3587
|
...itemDetails ? { itemDetails } : {},
|
|
3320
3588
|
schema: sourceCatalogue,
|
|
3321
|
-
filterValues:
|
|
3322
|
-
|
|
3323
|
-
const field = groupingModel.sourceField(name);
|
|
3324
|
-
return field && sourceRows ? { ...distinctValuesFromRows(field, sourceRows, search), hasNull: fieldHasNullInRows(field, sourceRows) } : { values: [], hasMore: false };
|
|
3325
|
-
},
|
|
3326
|
-
fieldStats: async (names, signal) => {
|
|
3327
|
-
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
3328
|
-
const requested = [...new Set(names)];
|
|
3329
|
-
if (metadataTransport?.fetchFieldStats) {
|
|
3330
|
-
const backendFields = requested.filter(
|
|
3331
|
-
(name) => sourceCatalogue.fields.find((field) => field.name === name)?.source.kind === "backend"
|
|
3332
|
-
);
|
|
3333
|
-
return backendFields.length ? metadataTransport.fetchFieldStats(backendFields, signal) : {};
|
|
3334
|
-
}
|
|
3335
|
-
return Object.fromEntries(requested.flatMap((name) => {
|
|
3336
|
-
const field = groupingModel.sourceField(name);
|
|
3337
|
-
return field && sourceRows ? [[name, { distinct: new Set(sourceRows.map((row) => JSON.stringify(readFieldValue4(field, row)))).size }]] : [];
|
|
3338
|
-
}));
|
|
3339
|
-
}
|
|
3589
|
+
filterValues: sourceFilterValues,
|
|
3590
|
+
fieldStats: sourceFieldStats
|
|
3340
3591
|
};
|
|
3341
3592
|
const grouping = useGroupingController(
|
|
3342
3593
|
api,
|
|
@@ -3350,7 +3601,7 @@ function useQueryTable(opts) {
|
|
|
3350
3601
|
return {
|
|
3351
3602
|
...api,
|
|
3352
3603
|
...groupingSupported || query.grouping ? { grouping } : {},
|
|
3353
|
-
select: { ...select, hide: (field) => query.grouping
|
|
3604
|
+
select: { ...select, hide: (field) => query.grouping && groupKeyFields2(query.grouping.keys).includes(field) ? grouping.changeKeys(query.grouping.keys.filter((key) => groupKeyField(key) !== field)) : select.hide(field) }
|
|
3354
3605
|
};
|
|
3355
3606
|
}
|
|
3356
3607
|
function nextOrderBy(existing, field, additive) {
|