@pythia-software/query-table-react 0.4.2 → 0.6.0
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 +17 -1
- package/dist/index.d.ts +57 -12
- package/dist/index.js +1065 -225
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
// src/computedExecution.ts
|
|
2
|
+
import {
|
|
3
|
+
isComputedField,
|
|
4
|
+
readFieldValue
|
|
5
|
+
} from "@pythia-software/query-table-core";
|
|
6
|
+
function executionRevision(execution, id) {
|
|
7
|
+
return execution?.resolvedRevisions[id] ?? execution?.resolvedRevisions[`@computed/${id}`];
|
|
8
|
+
}
|
|
9
|
+
function validateComputedResponse(request, result, schema) {
|
|
10
|
+
const execution = result.execution;
|
|
11
|
+
if (result.version !== 2 || !execution || execution.profile !== request.profile || request.planToken && execution.planToken !== request.planToken || request.snapshot !== void 0 && execution.snapshot !== request.snapshot)
|
|
12
|
+
throw Error(
|
|
13
|
+
"Server computed execution identity changed. Refresh the handshake."
|
|
14
|
+
);
|
|
15
|
+
for (const [name, revision] of Object.entries(request.expectedRevisions)) {
|
|
16
|
+
const id = name.replace(/^@computed\//, "");
|
|
17
|
+
if (executionRevision(execution, id) !== revision)
|
|
18
|
+
throw Error(
|
|
19
|
+
"Server computed definition revision changed. Refresh the handshake."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const idField = schema.fields.find((f) => f.name === schema.idField);
|
|
23
|
+
if (!idField)
|
|
24
|
+
throw Error("Stable row identity is required for computed values.");
|
|
25
|
+
const ids = new Set(result.rows.map((row) => readFieldValue(idField, row)));
|
|
26
|
+
if (ids.has(null) || ids.has(void 0) || ids.size !== result.rows.length)
|
|
27
|
+
throw Error(
|
|
28
|
+
"Computed response contains missing or duplicate row identities."
|
|
29
|
+
);
|
|
30
|
+
const sidecars = new Map(
|
|
31
|
+
result.computed.map((item) => [item.id, item.values])
|
|
32
|
+
);
|
|
33
|
+
if (sidecars.size !== result.computed.length || [...sidecars.keys()].some((id) => !ids.has(id)))
|
|
34
|
+
throw Error("Computed sidecars do not match returned row identities.");
|
|
35
|
+
for (const field of request.select.filter(isComputedField)) {
|
|
36
|
+
const id = field.slice("@computed/".length);
|
|
37
|
+
for (const rowId of ids) {
|
|
38
|
+
const value = sidecars.get(rowId)?.[id];
|
|
39
|
+
if (!value || value.value !== null && !["string", "number", "boolean"].includes(typeof value.value) || typeof value.value === "number" && !Number.isFinite(value.value))
|
|
40
|
+
throw Error(`Missing or invalid server computed result: ${field}.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
1
45
|
// src/useComputedColumns.ts
|
|
2
46
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
47
|
import {
|
|
@@ -5,12 +49,13 @@ import {
|
|
|
5
49
|
compileFormula,
|
|
6
50
|
computedFieldName,
|
|
7
51
|
groupPreview,
|
|
8
|
-
isComputedField,
|
|
52
|
+
isComputedField as isComputedField2,
|
|
9
53
|
memoryComputedColumnStore,
|
|
10
|
-
readFieldValue,
|
|
54
|
+
readFieldValue as readFieldValue2,
|
|
11
55
|
toServerQuery,
|
|
12
56
|
validateComputedColumn,
|
|
13
|
-
isSelectable
|
|
57
|
+
isSelectable,
|
|
58
|
+
hasRegex
|
|
14
59
|
} from "@pythia-software/query-table-core";
|
|
15
60
|
|
|
16
61
|
// src/formulaWorker.ts
|
|
@@ -51,9 +96,9 @@ function evaluateFormulaRows(ast, inputs, signal, factory = createFormulaWorker)
|
|
|
51
96
|
const cleanup = () => {
|
|
52
97
|
clearTimeout(timer);
|
|
53
98
|
worker.terminate();
|
|
54
|
-
signal?.removeEventListener("abort",
|
|
99
|
+
signal?.removeEventListener("abort", abort2);
|
|
55
100
|
};
|
|
56
|
-
const
|
|
101
|
+
const abort2 = () => {
|
|
57
102
|
cleanup();
|
|
58
103
|
reject(new DOMException("Aborted", "AbortError"));
|
|
59
104
|
};
|
|
@@ -82,7 +127,7 @@ function evaluateFormulaRows(ast, inputs, signal, factory = createFormulaWorker)
|
|
|
82
127
|
reject(e);
|
|
83
128
|
}
|
|
84
129
|
};
|
|
85
|
-
signal?.addEventListener("abort",
|
|
130
|
+
signal?.addEventListener("abort", abort2, { once: true });
|
|
86
131
|
worker.onerror = () => {
|
|
87
132
|
cleanup();
|
|
88
133
|
reject(new Error("Formula worker failed."));
|
|
@@ -164,7 +209,7 @@ function useComputedColumns(options) {
|
|
|
164
209
|
const stack = new Set(editingId ? [editingId] : []);
|
|
165
210
|
let expanded = 0;
|
|
166
211
|
const resolve = (name) => {
|
|
167
|
-
if (!
|
|
212
|
+
if (!isComputedField2(name)) return void 0;
|
|
168
213
|
const id = name.slice("@computed/".length), def = definitions.find((d) => d.id === id);
|
|
169
214
|
if (!def) throw new Error(`Computed column unavailable: ${id}`);
|
|
170
215
|
if (stack.has(id))
|
|
@@ -222,7 +267,7 @@ function useComputedColumns(options) {
|
|
|
222
267
|
const values = /* @__PURE__ */ Object.create(null);
|
|
223
268
|
for (const name of plan.dependencies) {
|
|
224
269
|
const field = inputFields.find((f) => f.name === name);
|
|
225
|
-
const raw =
|
|
270
|
+
const raw = readFieldValue2(field, row);
|
|
226
271
|
values[name] = raw instanceof Date ? raw.toISOString() : raw;
|
|
227
272
|
}
|
|
228
273
|
size += JSON.stringify(values).length;
|
|
@@ -236,12 +281,13 @@ function useComputedColumns(options) {
|
|
|
236
281
|
[inputFields]
|
|
237
282
|
);
|
|
238
283
|
const activeSelect = query.select.length ? query.select : schema.defaultSelect ?? [];
|
|
239
|
-
const activeIds = activeSelect.filter((c) =>
|
|
284
|
+
const activeIds = activeSelect.filter((c) => isComputedField2(c.field)).map((c) => c.field.slice("@computed/".length)).sort().join(",");
|
|
240
285
|
const [evaluated, setEvaluated] = useState(null);
|
|
241
286
|
useEffect(() => {
|
|
242
287
|
const ac = new AbortController();
|
|
243
288
|
const values = /* @__PURE__ */ new Map();
|
|
244
289
|
void (async () => {
|
|
290
|
+
if (options.execution && transport?.fetchRowsV2) return;
|
|
245
291
|
for (const id of activeIds.split(",").filter(Boolean)) {
|
|
246
292
|
const entry = plans.get(id);
|
|
247
293
|
if (!entry?.plan) continue;
|
|
@@ -269,7 +315,15 @@ function useComputedColumns(options) {
|
|
|
269
315
|
if (!ac.signal.aborted) setEvaluated({ rows, plans, values });
|
|
270
316
|
})();
|
|
271
317
|
return () => ac.abort();
|
|
272
|
-
}, [
|
|
318
|
+
}, [
|
|
319
|
+
activeIds,
|
|
320
|
+
rows,
|
|
321
|
+
plans,
|
|
322
|
+
inputsFor,
|
|
323
|
+
workerFactory,
|
|
324
|
+
options.execution,
|
|
325
|
+
transport
|
|
326
|
+
]);
|
|
273
327
|
const displaySchema = useMemo(() => {
|
|
274
328
|
const indices = new Map(rows.map((row, i) => [row, i]));
|
|
275
329
|
const fields = definitions.map((def) => {
|
|
@@ -285,17 +339,34 @@ function useComputedColumns(options) {
|
|
|
285
339
|
dependencies: entry?.plan ? backendDependencies(entry.plan) : [],
|
|
286
340
|
accessor: (row) => {
|
|
287
341
|
if (entry?.error) return { computedError: entry.error };
|
|
342
|
+
if (options.execution && transport?.fetchRowsV2) {
|
|
343
|
+
if (options.serverExecution?.profile !== options.execution.profile || options.serverExecution?.planToken !== options.execution.planToken || options.serverExecution?.snapshot !== options.execution.snapshot || executionRevision(options.execution, def.id) !== def.revision || executionRevision(options.serverExecution, def.id) !== def.revision)
|
|
344
|
+
return {
|
|
345
|
+
computedError: "Definition changed or values are pending. Refresh the server handshake."
|
|
346
|
+
};
|
|
347
|
+
const idField = schema.fields.find(
|
|
348
|
+
(f) => f.name === schema.idField
|
|
349
|
+
);
|
|
350
|
+
const id = idField ? readFieldValue2(idField, row) : null;
|
|
351
|
+
const server = id === null ? void 0 : options.serverComputed?.get(id)?.[def.id];
|
|
352
|
+
return server ? server.error ? { computedError: server.error.code } : server.value : { computedError: "Server computed result unavailable." };
|
|
353
|
+
}
|
|
288
354
|
const result = evaluated?.rows === rows && evaluated.plans === plans ? evaluated.values.get(def.id)?.[indices.get(row) ?? -1] : void 0;
|
|
289
355
|
return result?.error ? { computedError: result.error } : result ? result.value : { computedError: "Calculating\u2026" };
|
|
290
356
|
}
|
|
291
357
|
},
|
|
292
358
|
filter: { enabled: false },
|
|
293
|
-
sort: {
|
|
294
|
-
|
|
359
|
+
sort: {
|
|
360
|
+
enabled: !!transport?.fetchRowsV2 && !!options.execution?.fields[computedFieldName(def.id)]?.sort && executionRevision(options.execution, def.id) === def.revision && !!entry?.plan && !hasRegex(entry.plan)
|
|
361
|
+
},
|
|
362
|
+
aggregate: {
|
|
363
|
+
measure: !!entry?.plan,
|
|
364
|
+
groupable: !!transport?.fetchMetrics && transport.metricCapabilities?.version === 2 && !!options.execution?.fields[computedFieldName(def.id)]?.group && executionRevision(options.execution, def.id) === def.revision && !!entry?.plan && !hasRegex(entry.plan)
|
|
365
|
+
}
|
|
295
366
|
};
|
|
296
367
|
});
|
|
297
368
|
for (const col of activeSelect)
|
|
298
|
-
if (
|
|
369
|
+
if (isComputedField2(col.field) && !fields.some((f) => f.name === col.field))
|
|
299
370
|
fields.push({
|
|
300
371
|
name: col.field,
|
|
301
372
|
label: col.field.slice(10),
|
|
@@ -321,7 +392,11 @@ function useComputedColumns(options) {
|
|
|
321
392
|
rows,
|
|
322
393
|
query.select,
|
|
323
394
|
schema.defaultSelect,
|
|
324
|
-
loading
|
|
395
|
+
loading,
|
|
396
|
+
transport,
|
|
397
|
+
options.execution,
|
|
398
|
+
options.serverComputed,
|
|
399
|
+
options.serverExecution
|
|
325
400
|
]);
|
|
326
401
|
const save = useCallback(
|
|
327
402
|
async (column, expectedRevision) => {
|
|
@@ -422,85 +497,531 @@ function useComputedColumns(options) {
|
|
|
422
497
|
}
|
|
423
498
|
|
|
424
499
|
// src/useQueryTable.ts
|
|
425
|
-
import { useCallback as
|
|
500
|
+
import { useCallback as useCallback8, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef5, useState as useState7 } from "react";
|
|
426
501
|
import {
|
|
427
502
|
EMPTY_QUERY,
|
|
428
503
|
applyQuery as applyQuery2,
|
|
429
504
|
decodeQuery,
|
|
430
505
|
encodeQuery,
|
|
431
506
|
memoryStorageAdapter,
|
|
432
|
-
normalizeQueryState,
|
|
433
|
-
readFieldValue as
|
|
507
|
+
normalizeQueryState as normalizeQueryState2,
|
|
508
|
+
readFieldValue as readFieldValue4,
|
|
434
509
|
selectedFields as selectedFields2,
|
|
435
510
|
toServerQuery as toServerQuery2,
|
|
436
511
|
queriesEqual,
|
|
437
512
|
isOrGroup,
|
|
438
513
|
predicatesOf,
|
|
439
514
|
negateClause,
|
|
440
|
-
opsForField
|
|
515
|
+
opsForField,
|
|
516
|
+
metricPlans as metricPlans3,
|
|
517
|
+
validateMetric as validateMetric2,
|
|
518
|
+
metricDependencies,
|
|
519
|
+
isComputedField as isComputedField4,
|
|
520
|
+
toServerQueryV2
|
|
441
521
|
} from "@pythia-software/query-table-core";
|
|
442
522
|
|
|
443
|
-
// src/
|
|
444
|
-
import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
523
|
+
// src/useRequestActivity.ts
|
|
524
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
525
|
+
var methods = /* @__PURE__ */ new Set([
|
|
526
|
+
"fetchRows",
|
|
527
|
+
"fetchRowsV2",
|
|
528
|
+
"fetchMetrics",
|
|
529
|
+
"fetchAggregations",
|
|
530
|
+
"fetchRow",
|
|
531
|
+
"fetchDistinctValues",
|
|
532
|
+
"fetchFieldStats"
|
|
533
|
+
]);
|
|
534
|
+
function observeTransport(transport, publish, nextId) {
|
|
535
|
+
const wrappers = /* @__PURE__ */ new Map();
|
|
536
|
+
return new Proxy(Object.create(transport), {
|
|
537
|
+
get(_target, key) {
|
|
538
|
+
const method = Reflect.get(transport, key, transport);
|
|
539
|
+
if (!methods.has(String(key)) || typeof method !== "function")
|
|
540
|
+
return method;
|
|
541
|
+
if (!wrappers.has(key))
|
|
542
|
+
wrappers.set(key, async (...args) => {
|
|
543
|
+
const signal = args[1];
|
|
544
|
+
const start = performance.now();
|
|
545
|
+
const entry = {
|
|
546
|
+
id: nextId(),
|
|
547
|
+
kind: String(key).replace(/^fetch/, ""),
|
|
548
|
+
startedAt: Date.now(),
|
|
549
|
+
status: "pending"
|
|
550
|
+
};
|
|
551
|
+
publish(entry);
|
|
552
|
+
let done = false;
|
|
553
|
+
const finish = (status, error) => {
|
|
554
|
+
if (done) return;
|
|
555
|
+
done = true;
|
|
556
|
+
publish({
|
|
557
|
+
...entry,
|
|
558
|
+
status,
|
|
559
|
+
durationMs: Math.max(0, performance.now() - start),
|
|
560
|
+
...error !== void 0 ? {
|
|
561
|
+
error: error instanceof Error ? error.message : String(error)
|
|
562
|
+
} : {}
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
const abort2 = () => finish("aborted");
|
|
566
|
+
signal?.addEventListener("abort", abort2, { once: true });
|
|
567
|
+
if (signal?.aborted) abort2();
|
|
568
|
+
try {
|
|
569
|
+
const result = await method.apply(transport, args);
|
|
570
|
+
finish(signal?.aborted ? "aborted" : "success");
|
|
571
|
+
return result;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
finish(
|
|
574
|
+
signal?.aborted || error instanceof Error && error.name === "AbortError" ? "aborted" : "error",
|
|
575
|
+
error
|
|
576
|
+
);
|
|
577
|
+
throw error;
|
|
578
|
+
} finally {
|
|
579
|
+
signal?.removeEventListener("abort", abort2);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
return wrappers.get(key);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function useRequestActivity(source) {
|
|
587
|
+
const [entries, setEntries] = useState2([]);
|
|
588
|
+
const sequence = useRef2(0), mounted = useRef2(true);
|
|
456
589
|
useEffect2(() => {
|
|
457
|
-
|
|
590
|
+
mounted.current = true;
|
|
591
|
+
return () => {
|
|
592
|
+
mounted.current = false;
|
|
593
|
+
};
|
|
594
|
+
}, []);
|
|
595
|
+
const publish = useCallback2((entry) => {
|
|
596
|
+
if (mounted.current)
|
|
597
|
+
setEntries(
|
|
598
|
+
(previous) => [entry, ...previous.filter((e) => e.id !== entry.id)].sort((a, b) => b.id - a.id).slice(0, 100)
|
|
599
|
+
);
|
|
600
|
+
}, []);
|
|
601
|
+
const transport = useMemo2(
|
|
602
|
+
() => source ? observeTransport(source, publish, () => ++sequence.current) : void 0,
|
|
603
|
+
[source, publish]
|
|
604
|
+
);
|
|
605
|
+
const clear = useCallback2(
|
|
606
|
+
() => setEntries((previous) => previous.filter((e) => e.status === "pending")),
|
|
607
|
+
[]
|
|
608
|
+
);
|
|
609
|
+
const activity = useMemo2(
|
|
610
|
+
() => ({ entries, clear }),
|
|
611
|
+
[entries, clear]
|
|
612
|
+
);
|
|
613
|
+
return { transport, activity };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/useAggregations.ts
|
|
617
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
618
|
+
import {
|
|
619
|
+
metricComputationKey,
|
|
620
|
+
metricPlans as metricPlans2,
|
|
621
|
+
hasRegex as hasRegex3
|
|
622
|
+
} from "@pythia-software/query-table-core";
|
|
623
|
+
|
|
624
|
+
// src/metricExecution.ts
|
|
625
|
+
import {
|
|
626
|
+
applyAggregations,
|
|
627
|
+
normalizeQueryState,
|
|
628
|
+
evaluateMetrics,
|
|
629
|
+
metricPlans,
|
|
630
|
+
hasRegex as hasRegex2,
|
|
631
|
+
toAggregationQuery,
|
|
632
|
+
toMetricQuery,
|
|
633
|
+
validateMetric,
|
|
634
|
+
selectMetricShownRows,
|
|
635
|
+
readFieldValue as readFieldValue3,
|
|
636
|
+
resolveFieldName,
|
|
637
|
+
isComputedField as isComputedField3
|
|
638
|
+
} from "@pythia-software/query-table-core";
|
|
639
|
+
var abort = (signal) => {
|
|
640
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
641
|
+
};
|
|
642
|
+
async function localMetrics(rows, query, context, signal) {
|
|
643
|
+
const originalClauses = query.aggregations ?? [];
|
|
644
|
+
const advanced = (query.aggregations ?? []).filter(
|
|
645
|
+
(c) => isComputedField3(c.field ?? "") || c.groupBy.some(isComputedField3) || c.diagnostics?.length || c.expression !== void 0 || c.expressionY !== void 0 || c.scope !== void 0 || c.distribution || c.sort || c.groupLimit !== void 0
|
|
646
|
+
);
|
|
647
|
+
if (advanced.length && (originalClauses.length > 20 || normalizeQueryState(query).aggregations?.length !== originalClauses.length))
|
|
648
|
+
return evaluateMetrics(rows, query, context.schema, {
|
|
649
|
+
...signal ? { signal } : {},
|
|
650
|
+
...context.resolveComputed ? { resolveComputed: context.resolveComputed } : {}
|
|
651
|
+
});
|
|
652
|
+
const legacy = (query.aggregations ?? []).filter(
|
|
653
|
+
(c) => !advanced.includes(c)
|
|
654
|
+
);
|
|
655
|
+
const compatible = applyAggregations(
|
|
656
|
+
rows,
|
|
657
|
+
{ ...query, aggregations: legacy },
|
|
658
|
+
context.schema,
|
|
659
|
+
{
|
|
660
|
+
...signal ? { signal } : {}
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
if (!advanced.length) return compatible;
|
|
664
|
+
query = { ...query, aggregations: advanced };
|
|
665
|
+
const options = {
|
|
666
|
+
...context.resolveComputed ? { resolveComputed: context.resolveComputed } : {},
|
|
667
|
+
...signal ? { signal } : {}
|
|
668
|
+
};
|
|
669
|
+
for (const clause of query.aggregations ?? []) {
|
|
670
|
+
const diagnostics = validateMetric(
|
|
671
|
+
clause,
|
|
672
|
+
context.schema,
|
|
673
|
+
context.resolveComputed
|
|
674
|
+
);
|
|
675
|
+
if (diagnostics.length)
|
|
676
|
+
throw Error(diagnostics.map((d) => d.message).join(" "));
|
|
677
|
+
}
|
|
678
|
+
const requiresPage = (query.aggregations ?? []).some(
|
|
679
|
+
(clause) => clause.scope === "shownRows"
|
|
680
|
+
);
|
|
681
|
+
const page = requiresPage ? selectMetricShownRows(rows, query, context.schema, {
|
|
682
|
+
...options,
|
|
683
|
+
...context.rowsReady && context.shownRows ? { shownRows: context.shownRows } : {}
|
|
684
|
+
}) : [];
|
|
685
|
+
const caches = /* @__PURE__ */ new Map();
|
|
686
|
+
for (const clause of query.aggregations ?? []) {
|
|
687
|
+
for (const plan of metricPlans(
|
|
688
|
+
clause,
|
|
689
|
+
context.schema,
|
|
690
|
+
context.resolveComputed
|
|
691
|
+
)) {
|
|
692
|
+
if (!hasRegex2(plan)) continue;
|
|
693
|
+
const nodes = "aggregates" in plan ? plan.aggregates.flatMap((a) => a.args) : [plan.ast];
|
|
694
|
+
for (const node of nodes) {
|
|
695
|
+
if (!hasRegex2({ ...plan, ast: node })) continue;
|
|
696
|
+
const key = JSON.stringify(node);
|
|
697
|
+
if (caches.has(key)) continue;
|
|
698
|
+
let bytes = 0;
|
|
699
|
+
const inputs = page.map((row) => {
|
|
700
|
+
const values2 = /* @__PURE__ */ Object.create(null);
|
|
701
|
+
for (const name of plan.dependencies) {
|
|
702
|
+
const field = context.schema.fields.find((f) => f.name === name);
|
|
703
|
+
if (!field) throw Error(`Unavailable metric input: ${name}`);
|
|
704
|
+
const raw = readFieldValue3(field, row);
|
|
705
|
+
values2[name] = raw instanceof Date ? raw.toISOString() : raw ?? null;
|
|
706
|
+
}
|
|
707
|
+
bytes += JSON.stringify(values2).length;
|
|
708
|
+
if (bytes > 16e6)
|
|
709
|
+
throw Error(
|
|
710
|
+
"Metric worker input exceeds 16 MB. Reduce the shown rows."
|
|
711
|
+
);
|
|
712
|
+
return values2;
|
|
713
|
+
});
|
|
714
|
+
const values = await evaluateFormulaRows(
|
|
715
|
+
node,
|
|
716
|
+
inputs,
|
|
717
|
+
signal,
|
|
718
|
+
context.workerFactory
|
|
719
|
+
);
|
|
720
|
+
caches.set(
|
|
721
|
+
key,
|
|
722
|
+
new Map(page.map((row, i) => [row, values[i]]))
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
abort(signal);
|
|
728
|
+
const typed = evaluateMetrics(rows, query, context.schema, {
|
|
729
|
+
...options,
|
|
730
|
+
shownRows: page,
|
|
731
|
+
...caches.size ? {
|
|
732
|
+
evaluateInput: (node, row, plan) => {
|
|
733
|
+
const cached = caches.get(JSON.stringify(node));
|
|
734
|
+
if (cached)
|
|
735
|
+
return cached.get(row) ?? {
|
|
736
|
+
value: null,
|
|
737
|
+
error: "Metric worker result missing."
|
|
738
|
+
};
|
|
739
|
+
throw Error("Metric regex input was not evaluated in a worker.");
|
|
740
|
+
}
|
|
741
|
+
} : {}
|
|
742
|
+
});
|
|
743
|
+
const byId = new Map(
|
|
744
|
+
[...compatible.metrics, ...typed.metrics].map((m) => [m.id, m])
|
|
745
|
+
);
|
|
746
|
+
return { metrics: originalClauses.flatMap((c) => byId.get(c.id) ?? []) };
|
|
747
|
+
}
|
|
748
|
+
async function executeMetrics(query, context, signal) {
|
|
749
|
+
abort(signal);
|
|
750
|
+
const clauses = query.aggregations ?? [];
|
|
751
|
+
if (!clauses.length) return { metrics: [] };
|
|
752
|
+
const supportsV2 = !!context.transport?.fetchMetrics && context.transport.metricCapabilities?.version === 2;
|
|
753
|
+
if (context.clientRows && !supportsV2 && !context.transport?.fetchAggregations)
|
|
754
|
+
return localMetrics(context.clientRows, query, context, signal);
|
|
755
|
+
const needsWorker = (c) => metricPlans(c, context.schema, context.resolveComputed).some(hasRegex2);
|
|
756
|
+
const shown = clauses.filter(
|
|
757
|
+
(c) => c.scope === "shownRows" && (!supportsV2 || needsWorker(c))
|
|
758
|
+
);
|
|
759
|
+
const matching = clauses.filter(
|
|
760
|
+
(c) => c.scope !== "shownRows" || supportsV2 && !needsWorker(c)
|
|
761
|
+
);
|
|
762
|
+
const results = [];
|
|
763
|
+
if (shown.length) {
|
|
764
|
+
if (!context.rowsReady || !context.shownRows)
|
|
765
|
+
throw Error(
|
|
766
|
+
"Wait for the current table page before previewing shown-row metrics."
|
|
767
|
+
);
|
|
768
|
+
const names = new Set(
|
|
769
|
+
shown.flatMap((clause) => [
|
|
770
|
+
...clause.groupBy,
|
|
771
|
+
...metricPlans(clause, context.schema, context.resolveComputed).flatMap(
|
|
772
|
+
(p) => p.dependencies
|
|
773
|
+
)
|
|
774
|
+
])
|
|
775
|
+
);
|
|
776
|
+
for (const name of names) {
|
|
777
|
+
const canonical = resolveFieldName(context.schema, name) ?? name;
|
|
778
|
+
const field = context.schema.fields.find((f) => f.name === canonical);
|
|
779
|
+
const dependencies = field?.source.kind === "derived" ? field.source.dependencies ?? [] : [canonical];
|
|
780
|
+
const loaded = context.shownFields ? dependencies.every((dep) => context.shownFields.includes(dep)) : field && context.shownRows.every(
|
|
781
|
+
(row) => field.source.kind === "derived" || loadedPath(
|
|
782
|
+
row,
|
|
783
|
+
field.source.kind === "backend" ? field.source.path ?? field.name : field.name
|
|
784
|
+
)
|
|
785
|
+
);
|
|
786
|
+
if (!field || !loaded)
|
|
787
|
+
throw Error(
|
|
788
|
+
`Shown-page input ${name} is not loaded. Add it to the selected columns and refresh, or use a snapshot-capable metric transport.`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
const pageQuery = {
|
|
792
|
+
...query,
|
|
793
|
+
where: [],
|
|
794
|
+
orderBy: [],
|
|
795
|
+
limit: Math.max(1, context.shownRows.length),
|
|
796
|
+
offset: 0,
|
|
797
|
+
aggregations: shown
|
|
798
|
+
};
|
|
799
|
+
results.push(
|
|
800
|
+
...(await localMetrics(context.shownRows, pageQuery, context, signal)).metrics
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (matching.length) {
|
|
804
|
+
const transport = context.transport;
|
|
805
|
+
const scoped = { ...query, aggregations: matching };
|
|
806
|
+
if (transport?.fetchMetrics && transport.metricCapabilities?.version === 2) {
|
|
807
|
+
const request = toMetricQuery(scoped, context.schema, context.execution);
|
|
808
|
+
if (request.diagnostics.length)
|
|
809
|
+
throw Error(request.diagnostics.map((d) => d.message).join(" "));
|
|
810
|
+
if (matching.some((c) => c.scope === "shownRows") && !transport.metricCapabilities.shownRows)
|
|
811
|
+
throw Error("This server does not support shown-row metrics.");
|
|
812
|
+
if (request.profile && transport.metricCapabilities.profile && request.profile !== transport.metricCapabilities.profile)
|
|
813
|
+
throw Error(
|
|
814
|
+
"Metric transport and computed execution profiles do not match. Refresh the handshake."
|
|
815
|
+
);
|
|
816
|
+
const remoteFields = new Map(
|
|
817
|
+
context.schema.fields.map((field) => [field.name, field])
|
|
818
|
+
);
|
|
819
|
+
for (const [name, capability] of Object.entries(
|
|
820
|
+
context.execution?.fields ?? {}
|
|
821
|
+
))
|
|
822
|
+
if (isComputedField3(name))
|
|
823
|
+
remoteFields.set(name, {
|
|
824
|
+
name,
|
|
825
|
+
label: name,
|
|
826
|
+
type: capability.type,
|
|
827
|
+
source: { kind: "backend" }
|
|
828
|
+
});
|
|
829
|
+
const remoteSchema = {
|
|
830
|
+
...context.schema,
|
|
831
|
+
fields: [...remoteFields.values()]
|
|
832
|
+
};
|
|
833
|
+
if (matching.some(
|
|
834
|
+
(clause) => [
|
|
835
|
+
...clause.groupBy,
|
|
836
|
+
...metricPlans(clause, remoteSchema).flatMap(
|
|
837
|
+
(plan) => plan.dependencies
|
|
838
|
+
)
|
|
839
|
+
].some(isComputedField3)
|
|
840
|
+
) && !transport.metricCapabilities.computedFields)
|
|
841
|
+
throw Error("This metric server does not support computed fields.");
|
|
842
|
+
if (request.metrics.some(
|
|
843
|
+
(c) => !c.distribution && (c.expression !== void 0 || c.expressionY !== void 0)
|
|
844
|
+
) && !transport.metricCapabilities.expressions)
|
|
845
|
+
throw Error(
|
|
846
|
+
"This server does not support composed metric expressions."
|
|
847
|
+
);
|
|
848
|
+
if (matching.some(
|
|
849
|
+
(c) => c.distribution && (!c.display || ["box", "histogram"].includes(c.display.kind))
|
|
850
|
+
) && !transport.metricCapabilities.distributions)
|
|
851
|
+
throw Error("This server does not support distributions.");
|
|
852
|
+
results.push(...(await transport.fetchMetrics(request, signal)).metrics);
|
|
853
|
+
} else if (transport?.fetchAggregations) {
|
|
854
|
+
const request = toAggregationQuery(scoped, context.schema);
|
|
855
|
+
if (request.diagnostics?.length)
|
|
856
|
+
throw Error(request.diagnostics.map((d) => d.message).join(" "));
|
|
857
|
+
results.push(
|
|
858
|
+
...(await transport.fetchAggregations(request, signal)).metrics
|
|
859
|
+
);
|
|
860
|
+
} else
|
|
861
|
+
throw Error(
|
|
862
|
+
"All-matching metrics require a complete client dataset or a metric-capable server transport."
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
abort(signal);
|
|
866
|
+
const byId = new Map(results.map((r) => [r.id, r]));
|
|
867
|
+
return {
|
|
868
|
+
metrics: clauses.map(
|
|
869
|
+
(c) => byId.get(c.id) ?? {
|
|
870
|
+
id: c.id,
|
|
871
|
+
buckets: [],
|
|
872
|
+
error: "Server omitted this metric."
|
|
873
|
+
}
|
|
874
|
+
)
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function loadedPath(row, path) {
|
|
878
|
+
if (!row || typeof row !== "object") return false;
|
|
879
|
+
if (path in row) return true;
|
|
880
|
+
let value = row;
|
|
881
|
+
for (const part of path.split(".")) {
|
|
882
|
+
if (value === null) return true;
|
|
883
|
+
if (!value || typeof value !== "object" || !(part in value)) return false;
|
|
884
|
+
value = value[part];
|
|
885
|
+
}
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/useAggregations.ts
|
|
890
|
+
function useAggregations(query, schema, transport, clientRows, debounceMs, nonce, validateQuery, options = {}) {
|
|
891
|
+
const [results, setResults] = useState3(null);
|
|
892
|
+
const [loading, setLoading] = useState3(false);
|
|
893
|
+
const [error, setError] = useState3(null);
|
|
894
|
+
const latest = useRef3({
|
|
895
|
+
query,
|
|
896
|
+
schema,
|
|
897
|
+
transport,
|
|
898
|
+
clientRows,
|
|
899
|
+
options,
|
|
900
|
+
validateQuery
|
|
901
|
+
});
|
|
902
|
+
latest.current = {
|
|
903
|
+
query,
|
|
904
|
+
schema,
|
|
905
|
+
transport,
|
|
906
|
+
clientRows,
|
|
907
|
+
options,
|
|
908
|
+
validateQuery
|
|
909
|
+
};
|
|
910
|
+
const preview = useCallback3(
|
|
911
|
+
async (clauses, signal) => {
|
|
912
|
+
const current = latest.current;
|
|
913
|
+
const draft = { ...current.query, aggregations: clauses };
|
|
914
|
+
current.validateQuery?.(draft);
|
|
915
|
+
return executeMetrics(
|
|
916
|
+
draft,
|
|
917
|
+
{
|
|
918
|
+
schema: current.schema,
|
|
919
|
+
transport: current.transport,
|
|
920
|
+
clientRows: current.clientRows,
|
|
921
|
+
...current.options
|
|
922
|
+
},
|
|
923
|
+
signal
|
|
924
|
+
);
|
|
925
|
+
},
|
|
926
|
+
[]
|
|
927
|
+
);
|
|
928
|
+
const shown = (query.aggregations ?? []).some((c) => c.scope === "shownRows");
|
|
929
|
+
const localPage = shown && (!transport?.fetchMetrics || transport.metricCapabilities?.version !== 2 || (query.aggregations ?? []).some((clause) => {
|
|
930
|
+
if (clause.scope !== "shownRows") return false;
|
|
931
|
+
try {
|
|
932
|
+
return metricPlans2(clause, schema, options.resolveComputed).some(
|
|
933
|
+
hasRegex3
|
|
934
|
+
);
|
|
935
|
+
} catch {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
}));
|
|
939
|
+
const key = useMemo3(
|
|
940
|
+
() => JSON.stringify([
|
|
941
|
+
query.where,
|
|
942
|
+
metricComputationKey(query.aggregations ?? []),
|
|
943
|
+
shown ? [query.orderBy, query.limit, query.offset] : null
|
|
944
|
+
]),
|
|
945
|
+
[
|
|
946
|
+
query.where,
|
|
947
|
+
query.aggregations,
|
|
948
|
+
query.orderBy,
|
|
949
|
+
query.limit,
|
|
950
|
+
query.offset,
|
|
951
|
+
shown
|
|
952
|
+
]
|
|
953
|
+
);
|
|
954
|
+
const dependencyKey = JSON.stringify([
|
|
955
|
+
options.revisions ?? {},
|
|
956
|
+
options.execution ?? null
|
|
957
|
+
]);
|
|
958
|
+
useEffect3(() => {
|
|
959
|
+
if (!latest.current.query.aggregations?.length) {
|
|
458
960
|
setResults(null);
|
|
459
|
-
setError(null);
|
|
460
961
|
setLoading(false);
|
|
962
|
+
setError(null);
|
|
461
963
|
return;
|
|
462
964
|
}
|
|
463
965
|
const ac = new AbortController();
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
966
|
+
setLoading(true);
|
|
967
|
+
setResults(null);
|
|
968
|
+
if (localPage && transport && !options.rowsReady) {
|
|
969
|
+
try {
|
|
970
|
+
latest.current.validateQuery?.(latest.current.query);
|
|
971
|
+
setError(options.rowsError ?? null);
|
|
972
|
+
if (options.rowsError) setLoading(false);
|
|
973
|
+
} catch (e) {
|
|
974
|
+
setError(e instanceof Error ? e : Error(String(e)));
|
|
975
|
+
setLoading(false);
|
|
976
|
+
}
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const timer = setTimeout(() => {
|
|
467
980
|
setError(null);
|
|
468
981
|
try {
|
|
469
|
-
validateQuery?.(query);
|
|
470
|
-
let res;
|
|
471
|
-
if (transport?.fetchAggregations) {
|
|
472
|
-
res = await transport.fetchAggregations(toAggregationQuery(query, schema), ac.signal);
|
|
473
|
-
} else if (clientRows) {
|
|
474
|
-
res = applyAggregations(clientRows, query, schema);
|
|
475
|
-
} else {
|
|
476
|
-
res = EMPTY_RESULT;
|
|
477
|
-
}
|
|
478
|
-
if (!cancelled) setResults(res);
|
|
982
|
+
latest.current.validateQuery?.(latest.current.query);
|
|
479
983
|
} catch (e) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
} finally {
|
|
485
|
-
if (!cancelled) setLoading(false);
|
|
984
|
+
setError(e instanceof Error ? e : Error(String(e)));
|
|
985
|
+
setLoading(false);
|
|
986
|
+
return;
|
|
486
987
|
}
|
|
487
|
-
|
|
488
|
-
|
|
988
|
+
void preview(latest.current.query.aggregations ?? [], ac.signal).then((result) => {
|
|
989
|
+
if (!ac.signal.aborted) setResults(result);
|
|
990
|
+
}).catch((e) => {
|
|
991
|
+
if (!ac.signal.aborted)
|
|
992
|
+
setError(e instanceof Error ? e : Error(String(e)));
|
|
993
|
+
}).finally(() => {
|
|
994
|
+
if (!ac.signal.aborted) setLoading(false);
|
|
995
|
+
});
|
|
996
|
+
}, debounceMs);
|
|
489
997
|
return () => {
|
|
490
|
-
cancelled = true;
|
|
491
|
-
clearTimeout(t);
|
|
492
998
|
ac.abort();
|
|
999
|
+
clearTimeout(timer);
|
|
493
1000
|
};
|
|
494
|
-
}, [
|
|
495
|
-
|
|
1001
|
+
}, [
|
|
1002
|
+
key,
|
|
1003
|
+
schema,
|
|
1004
|
+
transport,
|
|
1005
|
+
clientRows,
|
|
1006
|
+
debounceMs,
|
|
1007
|
+
nonce,
|
|
1008
|
+
validateQuery,
|
|
1009
|
+
dependencyKey,
|
|
1010
|
+
localPage,
|
|
1011
|
+
localPage ? options.shownRows : void 0,
|
|
1012
|
+
localPage ? options.rowsReady : void 0,
|
|
1013
|
+
localPage ? options.rowsError : void 0,
|
|
1014
|
+
preview
|
|
1015
|
+
]);
|
|
1016
|
+
return { results, loading, error, preview };
|
|
496
1017
|
}
|
|
497
1018
|
|
|
498
1019
|
// src/useSelection.ts
|
|
499
|
-
import { useCallback as
|
|
1020
|
+
import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
|
|
500
1021
|
function useSelection(displayedIds) {
|
|
501
|
-
const [selected, setSelected] =
|
|
502
|
-
const anchor =
|
|
503
|
-
const toggle =
|
|
1022
|
+
const [selected, setSelected] = useState4(() => /* @__PURE__ */ new Set());
|
|
1023
|
+
const anchor = useRef4(null);
|
|
1024
|
+
const toggle = useCallback4(
|
|
504
1025
|
(id, shiftKey = false) => {
|
|
505
1026
|
setSelected((prev) => {
|
|
506
1027
|
const next = new Set(prev);
|
|
@@ -528,7 +1049,7 @@ function useSelection(displayedIds) {
|
|
|
528
1049
|
},
|
|
529
1050
|
[displayedIds]
|
|
530
1051
|
);
|
|
531
|
-
const setPage =
|
|
1052
|
+
const setPage = useCallback4((ids, select) => {
|
|
532
1053
|
setSelected((prev) => {
|
|
533
1054
|
const next = new Set(prev);
|
|
534
1055
|
for (const id of ids) {
|
|
@@ -538,12 +1059,12 @@ function useSelection(displayedIds) {
|
|
|
538
1059
|
return next;
|
|
539
1060
|
});
|
|
540
1061
|
}, []);
|
|
541
|
-
const replace =
|
|
1062
|
+
const replace = useCallback4((ids) => {
|
|
542
1063
|
const next = new Set(ids);
|
|
543
1064
|
anchor.current = null;
|
|
544
1065
|
setSelected(next);
|
|
545
1066
|
}, []);
|
|
546
|
-
const retain =
|
|
1067
|
+
const retain = useCallback4((ids) => {
|
|
547
1068
|
const allowed = new Set(ids);
|
|
548
1069
|
anchor.current = null;
|
|
549
1070
|
setSelected((prev) => {
|
|
@@ -552,7 +1073,7 @@ function useSelection(displayedIds) {
|
|
|
552
1073
|
return next;
|
|
553
1074
|
});
|
|
554
1075
|
}, []);
|
|
555
|
-
const pageState =
|
|
1076
|
+
const pageState = useCallback4(
|
|
556
1077
|
(pageIds) => {
|
|
557
1078
|
if (pageIds.length === 0) return "none";
|
|
558
1079
|
let n = 0;
|
|
@@ -561,11 +1082,11 @@ function useSelection(displayedIds) {
|
|
|
561
1082
|
},
|
|
562
1083
|
[selected]
|
|
563
1084
|
);
|
|
564
|
-
const clear =
|
|
1085
|
+
const clear = useCallback4(() => {
|
|
565
1086
|
anchor.current = null;
|
|
566
1087
|
setSelected(/* @__PURE__ */ new Set());
|
|
567
1088
|
}, []);
|
|
568
|
-
return
|
|
1089
|
+
return useMemo4(
|
|
569
1090
|
() => ({
|
|
570
1091
|
selected,
|
|
571
1092
|
isSelected: (id) => selected.has(id),
|
|
@@ -582,16 +1103,16 @@ function useSelection(displayedIds) {
|
|
|
582
1103
|
}
|
|
583
1104
|
|
|
584
1105
|
// src/useSelect.ts
|
|
585
|
-
import { useCallback as
|
|
1106
|
+
import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
|
|
586
1107
|
import { selectedFields } from "@pythia-software/query-table-core";
|
|
587
1108
|
function useSelect(query, setQuery, schema) {
|
|
588
|
-
const fields =
|
|
589
|
-
const visible =
|
|
590
|
-
const hidden =
|
|
1109
|
+
const fields = useMemo5(() => selectedFields(schema, query), [schema, query]);
|
|
1110
|
+
const visible = useMemo5(() => fields.map((f) => columnFor(query.select, f.name)), [fields, query.select]);
|
|
1111
|
+
const hidden = useMemo5(() => {
|
|
591
1112
|
const shown = new Set(fields.map((f) => f.name));
|
|
592
1113
|
return schema.fields.filter((f) => !shown.has(f.name) && (f.select?.enabled ?? true));
|
|
593
1114
|
}, [schema, fields]);
|
|
594
|
-
const writeSelect =
|
|
1115
|
+
const writeSelect = useCallback5(
|
|
595
1116
|
(mutate) => {
|
|
596
1117
|
setQuery((prev) => {
|
|
597
1118
|
const current = prev.select.length ? prev.select : visible;
|
|
@@ -600,9 +1121,9 @@ function useSelect(query, setQuery, schema) {
|
|
|
600
1121
|
},
|
|
601
1122
|
[setQuery, visible]
|
|
602
1123
|
);
|
|
603
|
-
const show =
|
|
604
|
-
const hide =
|
|
605
|
-
const move =
|
|
1124
|
+
const show = useCallback5((field) => writeSelect((c) => c.some((x) => x.field === field) ? c : [...c, { field }]), [writeSelect]);
|
|
1125
|
+
const hide = useCallback5((field) => writeSelect((c) => c.filter((x) => x.field !== field)), [writeSelect]);
|
|
1126
|
+
const move = useCallback5(
|
|
606
1127
|
(field, toIndex) => writeSelect((c) => {
|
|
607
1128
|
const from = c.findIndex((x) => x.field === field);
|
|
608
1129
|
if (from === -1) return c;
|
|
@@ -612,11 +1133,11 @@ function useSelect(query, setQuery, schema) {
|
|
|
612
1133
|
}),
|
|
613
1134
|
[writeSelect]
|
|
614
1135
|
);
|
|
615
|
-
const setWidth =
|
|
1136
|
+
const setWidth = useCallback5(
|
|
616
1137
|
(field, width) => writeSelect((c) => c.map((x) => x.field === field ? { ...x, width: Math.round(width) } : x)),
|
|
617
1138
|
[writeSelect]
|
|
618
1139
|
);
|
|
619
|
-
const reset =
|
|
1140
|
+
const reset = useCallback5(() => setQuery((prev) => ({ ...prev, select: [] })), [setQuery]);
|
|
620
1141
|
return { visible, fields, hidden, show, hide, move, setWidth, reset };
|
|
621
1142
|
}
|
|
622
1143
|
function columnFor(select, name) {
|
|
@@ -625,50 +1146,81 @@ function columnFor(select, name) {
|
|
|
625
1146
|
}
|
|
626
1147
|
|
|
627
1148
|
// src/useColumnDrag.ts
|
|
628
|
-
import { useCallback as
|
|
1149
|
+
import { useCallback as useCallback6, useMemo as useMemo6, useState as useState5 } from "react";
|
|
629
1150
|
function useColumnDrag() {
|
|
630
|
-
const [state, setState] =
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
(index) => (
|
|
1151
|
+
const [state, setState] = useState5(null);
|
|
1152
|
+
const [dropRevision, setDropRevision] = useState5(0);
|
|
1153
|
+
const start = useCallback6(
|
|
1154
|
+
(field, index, order) => setState({
|
|
1155
|
+
source: field,
|
|
1156
|
+
overIndex: index,
|
|
1157
|
+
order: order?.slice(),
|
|
1158
|
+
moved: false
|
|
1159
|
+
}),
|
|
1160
|
+
[]
|
|
1161
|
+
);
|
|
1162
|
+
const over = useCallback6(
|
|
1163
|
+
(index, order) => (
|
|
634
1164
|
// Keep the SAME object reference when unchanged so React can bail out of the
|
|
635
1165
|
// re-render — onDragOver fires continuously while the cursor moves.
|
|
636
|
-
setState((s) =>
|
|
1166
|
+
setState((s) => {
|
|
1167
|
+
if (!s) return s;
|
|
1168
|
+
if (s.moved && s.overIndex === index && (!order && !s.order || order && s.order && order.length === s.order.length && order.every((name, i) => name === s.order[i])))
|
|
1169
|
+
return s;
|
|
1170
|
+
return {
|
|
1171
|
+
source: s.source,
|
|
1172
|
+
overIndex: index,
|
|
1173
|
+
order: order?.slice(),
|
|
1174
|
+
moved: true
|
|
1175
|
+
};
|
|
1176
|
+
})
|
|
637
1177
|
),
|
|
638
1178
|
[]
|
|
639
1179
|
);
|
|
640
|
-
const end =
|
|
641
|
-
|
|
1180
|
+
const end = useCallback6((committed = false) => {
|
|
1181
|
+
if (committed) setDropRevision((revision) => revision + 1);
|
|
1182
|
+
setState(null);
|
|
1183
|
+
}, []);
|
|
1184
|
+
const preview = useCallback6(
|
|
642
1185
|
(order) => {
|
|
643
|
-
if (!state) return order;
|
|
1186
|
+
if (!state || state.order && !state.moved) return order;
|
|
644
1187
|
const without = order.filter((n) => n !== state.source);
|
|
645
1188
|
if (without.length === order.length) return order;
|
|
646
|
-
|
|
1189
|
+
let at = Math.max(0, Math.min(state.overIndex, without.length));
|
|
1190
|
+
if (state.order) {
|
|
1191
|
+
const reference = state.order.filter((name) => name !== state.source);
|
|
1192
|
+
const slot = Math.max(0, Math.min(state.overIndex, reference.length));
|
|
1193
|
+
const following = reference.slice(slot).find((name) => without.includes(name));
|
|
1194
|
+
const preceding = reference.slice(0, slot).reverse().find((name) => without.includes(name));
|
|
1195
|
+
if (following != null) at = without.indexOf(following);
|
|
1196
|
+
else if (preceding != null) at = without.indexOf(preceding) + 1;
|
|
1197
|
+
}
|
|
647
1198
|
return [...without.slice(0, at), state.source, ...without.slice(at)];
|
|
648
1199
|
},
|
|
649
1200
|
[state]
|
|
650
1201
|
);
|
|
651
|
-
return
|
|
1202
|
+
return useMemo6(
|
|
652
1203
|
() => ({
|
|
653
1204
|
source: state?.source ?? null,
|
|
654
1205
|
overIndex: state?.overIndex ?? null,
|
|
655
1206
|
active: state != null,
|
|
1207
|
+
dropRevision,
|
|
656
1208
|
start,
|
|
657
1209
|
over,
|
|
658
1210
|
end,
|
|
659
1211
|
preview
|
|
660
1212
|
}),
|
|
661
|
-
[state, start, over, end, preview]
|
|
1213
|
+
[state, dropRevision, start, over, end, preview]
|
|
662
1214
|
);
|
|
663
1215
|
}
|
|
664
1216
|
|
|
665
1217
|
// src/useSavedQueries.ts
|
|
666
|
-
import { useCallback as
|
|
1218
|
+
import { useCallback as useCallback7, useEffect as useEffect4, useState as useState6 } from "react";
|
|
667
1219
|
function useSavedQueries(key, currentQuery, applyQueryState, storage, now) {
|
|
668
|
-
const [items, setItems] =
|
|
669
|
-
const [defaultId, setDefaultId] =
|
|
670
|
-
const [loading, setLoading] =
|
|
671
|
-
const refresh =
|
|
1220
|
+
const [items, setItems] = useState6([]);
|
|
1221
|
+
const [defaultId, setDefaultId] = useState6(null);
|
|
1222
|
+
const [loading, setLoading] = useState6(true);
|
|
1223
|
+
const refresh = useCallback7(async () => {
|
|
672
1224
|
setLoading(true);
|
|
673
1225
|
try {
|
|
674
1226
|
const [savedItems, defaultSaved] = await Promise.all([
|
|
@@ -681,10 +1233,10 @@ function useSavedQueries(key, currentQuery, applyQueryState, storage, now) {
|
|
|
681
1233
|
setLoading(false);
|
|
682
1234
|
}
|
|
683
1235
|
}, [storage, key]);
|
|
684
|
-
|
|
1236
|
+
useEffect4(() => {
|
|
685
1237
|
void refresh();
|
|
686
1238
|
}, [refresh]);
|
|
687
|
-
const save =
|
|
1239
|
+
const save = useCallback7(
|
|
688
1240
|
async (name) => {
|
|
689
1241
|
const saved = await storage.saveNamed(key, name, currentQuery, now());
|
|
690
1242
|
await refresh();
|
|
@@ -692,25 +1244,25 @@ function useSavedQueries(key, currentQuery, applyQueryState, storage, now) {
|
|
|
692
1244
|
},
|
|
693
1245
|
[storage, key, currentQuery, now, refresh]
|
|
694
1246
|
);
|
|
695
|
-
const load =
|
|
1247
|
+
const load = useCallback7(
|
|
696
1248
|
(id) => {
|
|
697
1249
|
const found = items.find((q) => q.id === id);
|
|
698
1250
|
if (found) applyQueryState(found.query);
|
|
699
1251
|
},
|
|
700
1252
|
[items, applyQueryState]
|
|
701
1253
|
);
|
|
702
|
-
const setDefault =
|
|
1254
|
+
const setDefault = useCallback7(
|
|
703
1255
|
async (id) => {
|
|
704
1256
|
await storage.setDefaultSaved?.(key, id);
|
|
705
1257
|
await refresh();
|
|
706
1258
|
},
|
|
707
1259
|
[storage, key, refresh]
|
|
708
1260
|
);
|
|
709
|
-
const clearDefault =
|
|
1261
|
+
const clearDefault = useCallback7(async () => {
|
|
710
1262
|
await storage.setDefaultSaved?.(key, null);
|
|
711
1263
|
await refresh();
|
|
712
1264
|
}, [storage, key, refresh]);
|
|
713
|
-
const remove =
|
|
1265
|
+
const remove = useCallback7(
|
|
714
1266
|
async (id) => {
|
|
715
1267
|
if (id === defaultId) await storage.setDefaultSaved?.(key, null);
|
|
716
1268
|
await storage.deleteSaved(key, id);
|
|
@@ -725,7 +1277,7 @@ function useSavedQueries(key, currentQuery, applyQueryState, storage, now) {
|
|
|
725
1277
|
var AUTOCOMPLETE_LIMIT = 50;
|
|
726
1278
|
var MAX_AUTO_REFRESH_POLLS = 1e3;
|
|
727
1279
|
function defaultsFor(schema) {
|
|
728
|
-
return
|
|
1280
|
+
return normalizeQueryState2({
|
|
729
1281
|
select: schema.defaultSelect ?? [],
|
|
730
1282
|
where: [],
|
|
731
1283
|
orderBy: schema.defaultSort ?? [],
|
|
@@ -734,29 +1286,32 @@ function defaultsFor(schema) {
|
|
|
734
1286
|
});
|
|
735
1287
|
}
|
|
736
1288
|
function cloneWhereTerm(term) {
|
|
737
|
-
|
|
1289
|
+
const copy = isOrGroup(term) ? { ...term, any: term.any.map((c) => ({ ...c })) } : { ...term };
|
|
1290
|
+
if (term.setFilter)
|
|
1291
|
+
copy.setFilter = { ...term.setFilter, values: [...term.setFilter.values] };
|
|
1292
|
+
return copy;
|
|
738
1293
|
}
|
|
739
1294
|
function cloneQueryState(q) {
|
|
740
1295
|
const clone = {
|
|
741
1296
|
select: q.select.map((column) => ({ ...column })),
|
|
742
1297
|
where: q.where.map(cloneWhereTerm),
|
|
743
|
-
orderBy: q.orderBy.map(
|
|
1298
|
+
orderBy: q.orderBy.map(
|
|
1299
|
+
(term) => term.extract ? { ...term, extract: { ...term.extract } } : { ...term }
|
|
1300
|
+
),
|
|
744
1301
|
limit: q.limit,
|
|
745
1302
|
offset: q.offset
|
|
746
1303
|
};
|
|
747
|
-
if (q.aggregations)
|
|
1304
|
+
if (q.aggregations)
|
|
1305
|
+
clone.aggregations = JSON.parse(
|
|
1306
|
+
JSON.stringify(q.aggregations)
|
|
1307
|
+
);
|
|
748
1308
|
return clone;
|
|
749
1309
|
}
|
|
750
1310
|
function mergeAggregation(a, patch) {
|
|
751
|
-
const next = {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
};
|
|
756
|
-
const field = "field" in patch ? patch.field : a.field;
|
|
757
|
-
if (field) next.field = field;
|
|
758
|
-
const label = "label" in patch ? patch.label : a.label;
|
|
759
|
-
if (label) next.label = label;
|
|
1311
|
+
const next = { ...a, ...patch };
|
|
1312
|
+
for (const key of Object.keys(next)) {
|
|
1313
|
+
if (next[key] === void 0) delete next[key];
|
|
1314
|
+
}
|
|
760
1315
|
return next;
|
|
761
1316
|
}
|
|
762
1317
|
function isNullLike(value) {
|
|
@@ -771,7 +1326,7 @@ function asDistinctValue(value) {
|
|
|
771
1326
|
}
|
|
772
1327
|
function fieldHasNullInRows(field, rows) {
|
|
773
1328
|
for (const row of rows) {
|
|
774
|
-
if (isNullLike(
|
|
1329
|
+
if (isNullLike(readFieldValue4(field, row))) return true;
|
|
775
1330
|
}
|
|
776
1331
|
return false;
|
|
777
1332
|
}
|
|
@@ -780,73 +1335,110 @@ function distinctValuesFromRows(field, rows, search) {
|
|
|
780
1335
|
const values = [];
|
|
781
1336
|
const seen = /* @__PURE__ */ new Set();
|
|
782
1337
|
let hasMore = false;
|
|
783
|
-
for (const row of rows) {
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
seen.
|
|
789
|
-
|
|
790
|
-
|
|
1338
|
+
rowsLoop: for (const row of rows) {
|
|
1339
|
+
const value = readFieldValue4(field, row);
|
|
1340
|
+
const candidates = field.type === "textarray" && Array.isArray(value) ? value : [value];
|
|
1341
|
+
for (const candidate of candidates) {
|
|
1342
|
+
const raw = asDistinctValue(candidate);
|
|
1343
|
+
if (raw == null || seen.has(raw)) continue;
|
|
1344
|
+
if (target && !raw.toLowerCase().includes(target)) continue;
|
|
1345
|
+
if (values.length < AUTOCOMPLETE_LIMIT) {
|
|
1346
|
+
seen.add(raw);
|
|
1347
|
+
values.push(raw);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
hasMore = true;
|
|
1351
|
+
break rowsLoop;
|
|
791
1352
|
}
|
|
792
|
-
hasMore = true;
|
|
793
|
-
break;
|
|
794
1353
|
}
|
|
795
1354
|
return { values: values.sort((a, b) => a.localeCompare(b)), hasMore };
|
|
796
1355
|
}
|
|
797
1356
|
function resolveInitial(opts) {
|
|
798
1357
|
const defaults = defaultsFor(opts.schema);
|
|
799
|
-
if (opts.initialQuery)
|
|
1358
|
+
if (opts.initialQuery)
|
|
1359
|
+
return {
|
|
1360
|
+
q: normalizeQueryState2(opts.initialQuery, defaults),
|
|
1361
|
+
fromUrl: false
|
|
1362
|
+
};
|
|
800
1363
|
if (opts.syncUrl === true && typeof window !== "undefined") {
|
|
801
1364
|
const token = new URLSearchParams(window.location.search).get("q");
|
|
802
|
-
if (token)
|
|
1365
|
+
if (token)
|
|
1366
|
+
return {
|
|
1367
|
+
q: normalizeQueryState2(decodeQuery(token), defaults),
|
|
1368
|
+
fromUrl: true
|
|
1369
|
+
};
|
|
803
1370
|
}
|
|
804
1371
|
return { q: defaults, fromUrl: false };
|
|
805
1372
|
}
|
|
806
1373
|
function useQueryTable(opts) {
|
|
807
|
-
const {
|
|
808
|
-
|
|
1374
|
+
const {
|
|
1375
|
+
schema,
|
|
1376
|
+
transport: sourceTransport,
|
|
1377
|
+
clientRows,
|
|
1378
|
+
initialQuery,
|
|
1379
|
+
syncUrl = false,
|
|
1380
|
+
debounceMs = 200
|
|
1381
|
+
} = opts;
|
|
1382
|
+
const { transport, activity: requestActivity } = useRequestActivity(sourceTransport);
|
|
1383
|
+
const storage = useMemo7(
|
|
1384
|
+
() => opts.storage ?? memoryStorageAdapter(),
|
|
1385
|
+
[opts.storage]
|
|
1386
|
+
);
|
|
809
1387
|
const now = opts.now ?? Date.now;
|
|
810
|
-
const nowRef =
|
|
811
|
-
const onRefreshRef =
|
|
812
|
-
const defaults =
|
|
813
|
-
const byName =
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
const
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
|
|
1388
|
+
const nowRef = useRef5(now);
|
|
1389
|
+
const onRefreshRef = useRef5(opts.onRefresh);
|
|
1390
|
+
const defaults = useMemo7(() => defaultsFor(schema), [schema]);
|
|
1391
|
+
const byName = useMemo7(
|
|
1392
|
+
() => new Map(schema.fields.map((f) => [f.name, f])),
|
|
1393
|
+
[schema]
|
|
1394
|
+
);
|
|
1395
|
+
const initRef = useRef5(resolveInitial(opts));
|
|
1396
|
+
const aggIdRef = useRef5(0);
|
|
1397
|
+
const [storedQuery, setQueryState] = useState7(initRef.current.q);
|
|
1398
|
+
const query = useMemo7(
|
|
1399
|
+
() => opts.canonicalizeQuery?.(storedQuery) ?? storedQuery,
|
|
1400
|
+
[storedQuery, opts.canonicalizeQuery]
|
|
1401
|
+
);
|
|
1402
|
+
const undoStack = useRef5([cloneQueryState(initRef.current.q)]);
|
|
1403
|
+
const redoStack = useRef5([]);
|
|
1404
|
+
const [rows, setRows] = useState7([]);
|
|
1405
|
+
const [serverComputed, setServerComputed] = useState7(/* @__PURE__ */ new Map());
|
|
1406
|
+
const [committedFields, setCommittedFields] = useState7([]);
|
|
1407
|
+
const [serverExecution, setServerExecution] = useState7();
|
|
1408
|
+
const [total, setTotal] = useState7(null);
|
|
1409
|
+
const [loading, setLoading] = useState7(false);
|
|
1410
|
+
const [error, setError] = useState7(null);
|
|
1411
|
+
const [nonce, setNonce] = useState7(0);
|
|
1412
|
+
const [committedPage, setCommittedPage] = useState7(null);
|
|
1413
|
+
const [committedSource, setCommittedSource] = useState7(null);
|
|
1414
|
+
const [autoRefreshStatus, setAutoRefreshStatus] = useState7(null);
|
|
1415
|
+
useEffect5(() => {
|
|
827
1416
|
nowRef.current = now;
|
|
828
1417
|
}, [now]);
|
|
829
|
-
|
|
1418
|
+
useEffect5(() => {
|
|
830
1419
|
onRefreshRef.current = opts.onRefresh;
|
|
831
1420
|
}, [opts.onRefresh]);
|
|
832
|
-
const setQuery =
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1421
|
+
const setQuery = useCallback8(
|
|
1422
|
+
(next) => {
|
|
1423
|
+
setQueryState((prev) => {
|
|
1424
|
+
const candidate = typeof next === "function" ? next(prev) : next;
|
|
1425
|
+
const nextQuery = normalizeQueryState2(candidate, defaults);
|
|
1426
|
+
if (queriesEqual(prev, nextQuery)) return prev;
|
|
1427
|
+
undoStack.current.push(cloneQueryState(prev));
|
|
1428
|
+
redoStack.current = [];
|
|
1429
|
+
return cloneQueryState(nextQuery);
|
|
1430
|
+
});
|
|
1431
|
+
},
|
|
1432
|
+
[defaults]
|
|
1433
|
+
);
|
|
1434
|
+
useEffect5(() => {
|
|
843
1435
|
if (initRef.current.fromUrl || initialQuery) return;
|
|
844
1436
|
let cancelled = false;
|
|
845
1437
|
void (async () => {
|
|
846
1438
|
const defaultSaved = await storage.loadDefaultSaved?.(schema.name);
|
|
847
1439
|
const restored = defaultSaved?.query ?? await storage.loadLast(schema.name);
|
|
848
1440
|
if (!cancelled && restored) {
|
|
849
|
-
const normalized =
|
|
1441
|
+
const normalized = normalizeQueryState2(restored, defaults);
|
|
850
1442
|
undoStack.current = [cloneQueryState(normalized)];
|
|
851
1443
|
redoStack.current = [];
|
|
852
1444
|
setQueryState(normalized);
|
|
@@ -860,18 +1452,93 @@ function useQueryTable(opts) {
|
|
|
860
1452
|
schema,
|
|
861
1453
|
query,
|
|
862
1454
|
rows,
|
|
1455
|
+
serverComputed,
|
|
1456
|
+
...serverExecution ? { serverExecution } : {},
|
|
1457
|
+
...opts.computedExecution ? { execution: opts.computedExecution } : {},
|
|
863
1458
|
...opts.computedColumnStore ? { store: opts.computedColumnStore } : {},
|
|
864
1459
|
...opts.formulaWorkerFactory ? { workerFactory: opts.formulaWorkerFactory } : {},
|
|
865
1460
|
...transport ? { transport } : {},
|
|
866
1461
|
...clientRows ? { clientRows } : {}
|
|
867
1462
|
});
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
1463
|
+
const resolveComputed = useCallback8(
|
|
1464
|
+
(name) => isComputedField4(name) ? computed.compile(`[${name.replace(/]/g, "]] ".trim())}]`) : void 0,
|
|
1465
|
+
[computed.compile]
|
|
1466
|
+
);
|
|
1467
|
+
const metricCompile = useCallback8(
|
|
1468
|
+
(clause) => {
|
|
1469
|
+
const diagnostics = validateMetric2(
|
|
1470
|
+
clause,
|
|
1471
|
+
displaySchema,
|
|
1472
|
+
resolveComputed
|
|
1473
|
+
);
|
|
1474
|
+
if (diagnostics.length)
|
|
1475
|
+
throw new Error(diagnostics.map((d) => d.message).join(" "));
|
|
1476
|
+
return metricPlans3(clause, displaySchema, resolveComputed);
|
|
1477
|
+
},
|
|
1478
|
+
[displaySchema, resolveComputed]
|
|
1479
|
+
);
|
|
1480
|
+
const computedRevisions = useMemo7(
|
|
1481
|
+
() => Object.fromEntries(computed.definitions.map((d) => [d.id, d.revision])),
|
|
1482
|
+
[computed.definitions]
|
|
1483
|
+
);
|
|
1484
|
+
const definitionKey = JSON.stringify(computedRevisions);
|
|
1485
|
+
const queryKey = useMemo7(() => encodeQuery(query), [query]);
|
|
1486
|
+
const serverRowQuery = useMemo7(() => {
|
|
1487
|
+
const execution = opts.computedExecution;
|
|
1488
|
+
const v2Request = execution && transport?.fetchRowsV2 ? toServerQueryV2(query, displaySchema, execution) : void 0;
|
|
1489
|
+
const request = v2Request ?? toServerQuery2(query, displaySchema);
|
|
1490
|
+
if (v2Request)
|
|
1491
|
+
for (const definition of computed.definitions) {
|
|
1492
|
+
if (executionRevision(execution, definition.id) !== void 0 && executionRevision(execution, definition.id) !== definition.revision)
|
|
1493
|
+
v2Request.diagnostics.push({
|
|
1494
|
+
from: 0,
|
|
1495
|
+
to: 1,
|
|
1496
|
+
code: "definition_changed",
|
|
1497
|
+
message: "Computed definitions changed. Refresh the server execution handshake."
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
for (const clause of query.aggregations ?? [])
|
|
1501
|
+
if (clause.scope === "shownRows") {
|
|
1502
|
+
try {
|
|
1503
|
+
for (const name of metricDependencies(
|
|
1504
|
+
clause,
|
|
1505
|
+
schema,
|
|
1506
|
+
resolveComputed
|
|
1507
|
+
)) {
|
|
1508
|
+
const field = schema.fields.find((f) => f.name === name);
|
|
1509
|
+
if (field?.source.kind === "backend") request.select.push(name);
|
|
1510
|
+
else
|
|
1511
|
+
for (const dep of field?.source.kind === "derived" ? field.source.dependencies ?? [] : [])
|
|
1512
|
+
request.select.push(dep);
|
|
1513
|
+
}
|
|
1514
|
+
} catch {
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
request.select = [...new Set(request.select)].sort();
|
|
1518
|
+
return request;
|
|
1519
|
+
}, [
|
|
1520
|
+
queryKey,
|
|
1521
|
+
displaySchema,
|
|
1522
|
+
transport,
|
|
1523
|
+
computedRevisions,
|
|
1524
|
+
schema,
|
|
1525
|
+
resolveComputed,
|
|
1526
|
+
opts.computedExecution
|
|
1527
|
+
]);
|
|
1528
|
+
const rowQueryKey = useMemo7(
|
|
871
1529
|
() => transport ? `server:${JSON.stringify(serverRowQuery)}` : `client:${JSON.stringify([query.where, query.orderBy, query.limit, query.offset])}`,
|
|
872
|
-
[
|
|
1530
|
+
[
|
|
1531
|
+
transport,
|
|
1532
|
+
serverRowQuery,
|
|
1533
|
+
query.where,
|
|
1534
|
+
query.orderBy,
|
|
1535
|
+
query.limit,
|
|
1536
|
+
query.offset
|
|
1537
|
+
]
|
|
873
1538
|
);
|
|
874
|
-
|
|
1539
|
+
const executionKey = JSON.stringify(opts.computedExecution ?? null);
|
|
1540
|
+
const pageContext = `${schema.name}:${rowQueryKey}:${nonce}:${executionKey}`;
|
|
1541
|
+
useEffect5(() => {
|
|
875
1542
|
const ac = new AbortController();
|
|
876
1543
|
let cancelled = false;
|
|
877
1544
|
const run = async () => {
|
|
@@ -880,16 +1547,47 @@ function useQueryTable(opts) {
|
|
|
880
1547
|
try {
|
|
881
1548
|
opts.validateQuery?.(query);
|
|
882
1549
|
if (transport) {
|
|
883
|
-
const
|
|
1550
|
+
const useV2 = "version" in serverRowQuery && transport.fetchRowsV2;
|
|
1551
|
+
if ("diagnostics" in serverRowQuery && Array.isArray(serverRowQuery.diagnostics) && serverRowQuery.diagnostics.length)
|
|
1552
|
+
throw Error(
|
|
1553
|
+
serverRowQuery.diagnostics.map((d) => d.message).join(" ")
|
|
1554
|
+
);
|
|
1555
|
+
if (!useV2 && query.orderBy.some((s) => isComputedField4(s.field)))
|
|
1556
|
+
throw Error(
|
|
1557
|
+
"Global computed sorting requires a server execution handshake and fetchRowsV2."
|
|
1558
|
+
);
|
|
1559
|
+
const res = useV2 ? await transport.fetchRowsV2(
|
|
1560
|
+
serverRowQuery,
|
|
1561
|
+
ac.signal
|
|
1562
|
+
) : await transport.fetchRows(serverRowQuery, ac.signal);
|
|
1563
|
+
if (useV2)
|
|
1564
|
+
validateComputedResponse(
|
|
1565
|
+
serverRowQuery,
|
|
1566
|
+
res,
|
|
1567
|
+
schema
|
|
1568
|
+
);
|
|
884
1569
|
if (!cancelled) {
|
|
1570
|
+
setCommittedFields(serverRowQuery.select);
|
|
1571
|
+
setServerExecution(
|
|
1572
|
+
useV2 ? res.execution : void 0
|
|
1573
|
+
);
|
|
1574
|
+
if ("computed" in res && Array.isArray(res.computed))
|
|
1575
|
+
setServerComputed(
|
|
1576
|
+
new Map(res.computed.map((c) => [c.id, c.values]))
|
|
1577
|
+
);
|
|
1578
|
+
else setServerComputed(/* @__PURE__ */ new Map());
|
|
885
1579
|
setRows(res.rows);
|
|
886
1580
|
setTotal(res.total);
|
|
1581
|
+
setCommittedPage(pageContext);
|
|
1582
|
+
setCommittedSource({ schema, transport, clientRows });
|
|
887
1583
|
}
|
|
888
1584
|
} else {
|
|
889
1585
|
const res = applyQuery2(clientRows ?? [], query, schema);
|
|
890
1586
|
if (!cancelled) {
|
|
891
1587
|
setRows(res.rows);
|
|
892
1588
|
setTotal(res.total);
|
|
1589
|
+
setCommittedPage(pageContext);
|
|
1590
|
+
setCommittedSource({ schema, transport, clientRows });
|
|
893
1591
|
}
|
|
894
1592
|
}
|
|
895
1593
|
} catch (e) {
|
|
@@ -897,6 +1595,7 @@ function useQueryTable(opts) {
|
|
|
897
1595
|
setError(e);
|
|
898
1596
|
setRows([]);
|
|
899
1597
|
setTotal(null);
|
|
1598
|
+
setCommittedPage(null);
|
|
900
1599
|
}
|
|
901
1600
|
} finally {
|
|
902
1601
|
if (!cancelled) setLoading(false);
|
|
@@ -908,8 +1607,17 @@ function useQueryTable(opts) {
|
|
|
908
1607
|
clearTimeout(t);
|
|
909
1608
|
ac.abort();
|
|
910
1609
|
};
|
|
911
|
-
}, [
|
|
912
|
-
|
|
1610
|
+
}, [
|
|
1611
|
+
rowQueryKey,
|
|
1612
|
+
transport,
|
|
1613
|
+
clientRows,
|
|
1614
|
+
schema,
|
|
1615
|
+
debounceMs,
|
|
1616
|
+
nonce,
|
|
1617
|
+
opts.validateQuery,
|
|
1618
|
+
executionKey
|
|
1619
|
+
]);
|
|
1620
|
+
useEffect5(() => {
|
|
913
1621
|
if (!syncUrl || typeof window === "undefined") return;
|
|
914
1622
|
const url = new URL(window.location.href);
|
|
915
1623
|
const token = encodeQuery(query);
|
|
@@ -917,16 +1625,22 @@ function useQueryTable(opts) {
|
|
|
917
1625
|
else url.searchParams.delete("q");
|
|
918
1626
|
window.history.replaceState(null, "", url.toString());
|
|
919
1627
|
}, [queryKey, syncUrl, query]);
|
|
920
|
-
|
|
1628
|
+
useEffect5(() => {
|
|
921
1629
|
const t = setTimeout(() => void storage.saveLast(schema.name, query), 400);
|
|
922
1630
|
return () => clearTimeout(t);
|
|
923
1631
|
}, [queryKey, storage, schema.name, query]);
|
|
924
|
-
const idField =
|
|
925
|
-
|
|
926
|
-
|
|
1632
|
+
const idField = useMemo7(
|
|
1633
|
+
() => schema.fields.find((f) => f.name === schema.idField),
|
|
1634
|
+
[schema]
|
|
1635
|
+
);
|
|
1636
|
+
const rowId = useCallback8(
|
|
1637
|
+
(row) => idField ? readFieldValue4(idField, row) : null,
|
|
927
1638
|
[idField]
|
|
928
1639
|
);
|
|
929
|
-
const displayedIds =
|
|
1640
|
+
const displayedIds = useMemo7(
|
|
1641
|
+
() => rows.map(rowId).filter((id) => id != null),
|
|
1642
|
+
[rows, rowId]
|
|
1643
|
+
);
|
|
930
1644
|
const selection = useSelection(displayedIds);
|
|
931
1645
|
const select = useSelect(query, setQuery, displaySchema);
|
|
932
1646
|
const columnDrag = useColumnDrag();
|
|
@@ -937,10 +1651,54 @@ function useQueryTable(opts) {
|
|
|
937
1651
|
storage,
|
|
938
1652
|
now
|
|
939
1653
|
);
|
|
940
|
-
const
|
|
1654
|
+
const pageReady = committedPage === pageContext && committedSource?.schema === schema && committedSource?.transport === transport && committedSource?.clientRows === clientRows && !error;
|
|
1655
|
+
const dataToken = useRef5(0);
|
|
1656
|
+
const dataVersion = useMemo7(
|
|
1657
|
+
() => ++dataToken.current,
|
|
1658
|
+
[schema, transport, clientRows, nonce, definitionKey, executionKey]
|
|
1659
|
+
);
|
|
1660
|
+
const aggState = useAggregations(
|
|
1661
|
+
query,
|
|
1662
|
+
schema,
|
|
1663
|
+
transport,
|
|
1664
|
+
clientRows,
|
|
1665
|
+
debounceMs,
|
|
1666
|
+
nonce,
|
|
1667
|
+
opts.validateQuery,
|
|
1668
|
+
{
|
|
1669
|
+
shownRows: rows,
|
|
1670
|
+
rowsReady: pageReady,
|
|
1671
|
+
rowsError: error,
|
|
1672
|
+
...transport ? { shownFields: committedFields } : {},
|
|
1673
|
+
resolveComputed,
|
|
1674
|
+
revisions: computedRevisions,
|
|
1675
|
+
...opts.computedExecution ? {
|
|
1676
|
+
execution: pageReady && serverExecution ? serverExecution : opts.computedExecution
|
|
1677
|
+
} : {},
|
|
1678
|
+
...opts.formulaWorkerFactory ? { workerFactory: opts.formulaWorkerFactory } : {}
|
|
1679
|
+
}
|
|
1680
|
+
);
|
|
1681
|
+
const queryRef = useRef5(query);
|
|
1682
|
+
queryRef.current = query;
|
|
1683
|
+
const replaceAggregations = useCallback8(
|
|
1684
|
+
(clauses, expected) => {
|
|
1685
|
+
if (expected && JSON.stringify(queryRef.current.aggregations ?? []) !== JSON.stringify(expected))
|
|
1686
|
+
throw new Error(
|
|
1687
|
+
"Metrics changed while editing. Reopen the editor to use the latest configuration."
|
|
1688
|
+
);
|
|
1689
|
+
for (const clause of clauses) metricCompile(clause);
|
|
1690
|
+
setQuery((q) => ({
|
|
1691
|
+
...q,
|
|
1692
|
+
aggregations: JSON.parse(
|
|
1693
|
+
JSON.stringify(clauses)
|
|
1694
|
+
)
|
|
1695
|
+
}));
|
|
1696
|
+
},
|
|
1697
|
+
[setQuery, metricCompile]
|
|
1698
|
+
);
|
|
941
1699
|
const canUndo = undoStack.current.length > 0;
|
|
942
1700
|
const canRedo = redoStack.current.length > 0;
|
|
943
|
-
const undo =
|
|
1701
|
+
const undo = useCallback8(() => {
|
|
944
1702
|
setQueryState((prev) => {
|
|
945
1703
|
const previous = undoStack.current.pop();
|
|
946
1704
|
if (!previous) return prev;
|
|
@@ -948,7 +1706,7 @@ function useQueryTable(opts) {
|
|
|
948
1706
|
return cloneQueryState(previous);
|
|
949
1707
|
});
|
|
950
1708
|
}, []);
|
|
951
|
-
const redo =
|
|
1709
|
+
const redo = useCallback8(() => {
|
|
952
1710
|
setQueryState((prev) => {
|
|
953
1711
|
const next = redoStack.current.pop();
|
|
954
1712
|
if (!next) return prev;
|
|
@@ -956,30 +1714,50 @@ function useQueryTable(opts) {
|
|
|
956
1714
|
return cloneQueryState(next);
|
|
957
1715
|
});
|
|
958
1716
|
}, []);
|
|
959
|
-
const patch =
|
|
960
|
-
|
|
961
|
-
const updateFilter = useCallback6(
|
|
962
|
-
(index, clause) => setQuery((q) => ({ ...q, offset: 0, where: q.where.map((t, i) => i === index ? clause : t) })),
|
|
1717
|
+
const patch = useCallback8(
|
|
1718
|
+
(p) => setQuery((prev) => ({ ...prev, ...p })),
|
|
963
1719
|
[setQuery]
|
|
964
1720
|
);
|
|
965
|
-
const
|
|
966
|
-
(
|
|
1721
|
+
const addFilter = useCallback8(
|
|
1722
|
+
(clause) => setQuery((q) => ({ ...q, offset: 0, where: [...q.where, clause] })),
|
|
967
1723
|
[setQuery]
|
|
968
1724
|
);
|
|
969
|
-
const
|
|
970
|
-
|
|
1725
|
+
const updateFilter = useCallback8(
|
|
1726
|
+
(index, clause) => setQuery((q) => ({
|
|
1727
|
+
...q,
|
|
1728
|
+
offset: 0,
|
|
1729
|
+
where: q.where.map((t, i) => i === index ? clause : t)
|
|
1730
|
+
})),
|
|
1731
|
+
[setQuery]
|
|
1732
|
+
);
|
|
1733
|
+
const removeFilter = useCallback8(
|
|
1734
|
+
(index) => setQuery((q) => ({
|
|
1735
|
+
...q,
|
|
1736
|
+
offset: 0,
|
|
1737
|
+
where: q.where.filter((_, i) => i !== index)
|
|
1738
|
+
})),
|
|
1739
|
+
[setQuery]
|
|
1740
|
+
);
|
|
1741
|
+
const clearFilters = useCallback8(
|
|
1742
|
+
() => setQuery((q) => ({ ...q, offset: 0, where: [] })),
|
|
1743
|
+
[setQuery]
|
|
1744
|
+
);
|
|
1745
|
+
const updatePredicate = useCallback8(
|
|
971
1746
|
(termIndex, predIndex, clause) => setQuery((q) => ({
|
|
972
1747
|
...q,
|
|
973
1748
|
offset: 0,
|
|
974
1749
|
where: q.where.map((term, i) => {
|
|
975
1750
|
if (i !== termIndex) return term;
|
|
976
|
-
if (isOrGroup(term))
|
|
1751
|
+
if (isOrGroup(term))
|
|
1752
|
+
return {
|
|
1753
|
+
any: term.any.map((c, j) => j === predIndex ? clause : c)
|
|
1754
|
+
};
|
|
977
1755
|
return clause;
|
|
978
1756
|
})
|
|
979
1757
|
})),
|
|
980
1758
|
[setQuery]
|
|
981
1759
|
);
|
|
982
|
-
const removePredicate =
|
|
1760
|
+
const removePredicate = useCallback8(
|
|
983
1761
|
(termIndex, predIndex) => setQuery((q) => {
|
|
984
1762
|
const where = [];
|
|
985
1763
|
q.where.forEach((term, i) => {
|
|
@@ -998,26 +1776,29 @@ function useQueryTable(opts) {
|
|
|
998
1776
|
}),
|
|
999
1777
|
[setQuery]
|
|
1000
1778
|
);
|
|
1001
|
-
const negateOne =
|
|
1779
|
+
const negateOne = useCallback8(
|
|
1002
1780
|
(clause) => {
|
|
1003
1781
|
const field = byName.get(clause.field);
|
|
1004
1782
|
return negateClause(clause, field ? opsForField(field) : void 0);
|
|
1005
1783
|
},
|
|
1006
1784
|
[byName]
|
|
1007
1785
|
);
|
|
1008
|
-
const negatePredicate =
|
|
1786
|
+
const negatePredicate = useCallback8(
|
|
1009
1787
|
(termIndex, predIndex) => setQuery((q) => ({
|
|
1010
1788
|
...q,
|
|
1011
1789
|
offset: 0,
|
|
1012
1790
|
where: q.where.map((term, i) => {
|
|
1013
1791
|
if (i !== termIndex) return term;
|
|
1014
|
-
if (isOrGroup(term))
|
|
1792
|
+
if (isOrGroup(term))
|
|
1793
|
+
return {
|
|
1794
|
+
any: term.any.map((c, j) => j === predIndex ? negateOne(c) : c)
|
|
1795
|
+
};
|
|
1015
1796
|
return negateOne(term);
|
|
1016
1797
|
})
|
|
1017
1798
|
})),
|
|
1018
1799
|
[setQuery, negateOne]
|
|
1019
1800
|
);
|
|
1020
|
-
const reorderFilters =
|
|
1801
|
+
const reorderFilters = useCallback8(
|
|
1021
1802
|
(fromIndex, toIndex) => setQuery((q) => {
|
|
1022
1803
|
if (fromIndex < 0 || fromIndex >= q.where.length) return q;
|
|
1023
1804
|
const where = [...q.where];
|
|
@@ -1027,13 +1808,15 @@ function useQueryTable(opts) {
|
|
|
1027
1808
|
}),
|
|
1028
1809
|
[setQuery]
|
|
1029
1810
|
);
|
|
1030
|
-
const mergeFilters =
|
|
1811
|
+
const mergeFilters = useCallback8(
|
|
1031
1812
|
(sourceIndex, targetIndex) => setQuery((q) => {
|
|
1032
1813
|
if (sourceIndex === targetIndex) return q;
|
|
1033
1814
|
const source = q.where[sourceIndex];
|
|
1034
1815
|
const target = q.where[targetIndex];
|
|
1035
1816
|
if (!source || !target) return q;
|
|
1036
|
-
const merged = {
|
|
1817
|
+
const merged = {
|
|
1818
|
+
any: [...predicatesOf(target), ...predicatesOf(source)]
|
|
1819
|
+
};
|
|
1037
1820
|
const where = [];
|
|
1038
1821
|
q.where.forEach((term, i) => {
|
|
1039
1822
|
if (i === sourceIndex) return;
|
|
@@ -1043,24 +1826,37 @@ function useQueryTable(opts) {
|
|
|
1043
1826
|
}),
|
|
1044
1827
|
[setQuery]
|
|
1045
1828
|
);
|
|
1046
|
-
const resetAll =
|
|
1047
|
-
|
|
1829
|
+
const resetAll = useCallback8(
|
|
1830
|
+
() => setQuery(() => cloneQueryState(defaults)),
|
|
1831
|
+
[setQuery, defaults]
|
|
1832
|
+
);
|
|
1833
|
+
const addAggregation = useCallback8(
|
|
1048
1834
|
(partial) => {
|
|
1049
1835
|
const id = `a${nowRef.current()}-${aggIdRef.current++}`;
|
|
1050
|
-
const clause = {
|
|
1051
|
-
|
|
1836
|
+
const clause = {
|
|
1837
|
+
id,
|
|
1838
|
+
op: "count",
|
|
1839
|
+
groupBy: [],
|
|
1840
|
+
...partial
|
|
1841
|
+
};
|
|
1842
|
+
setQuery((q) => ({
|
|
1843
|
+
...q,
|
|
1844
|
+
aggregations: [...q.aggregations ?? [], clause]
|
|
1845
|
+
}));
|
|
1052
1846
|
return id;
|
|
1053
1847
|
},
|
|
1054
1848
|
[setQuery]
|
|
1055
1849
|
);
|
|
1056
|
-
const updateAggregation =
|
|
1850
|
+
const updateAggregation = useCallback8(
|
|
1057
1851
|
(id, patch2) => setQuery((q) => ({
|
|
1058
1852
|
...q,
|
|
1059
|
-
aggregations: (q.aggregations ?? []).map(
|
|
1853
|
+
aggregations: (q.aggregations ?? []).map(
|
|
1854
|
+
(a) => a.id === id ? mergeAggregation(a, patch2) : a
|
|
1855
|
+
)
|
|
1060
1856
|
})),
|
|
1061
1857
|
[setQuery]
|
|
1062
1858
|
);
|
|
1063
|
-
const moveAggregation =
|
|
1859
|
+
const moveAggregation = useCallback8(
|
|
1064
1860
|
(id, toIndex) => setQuery((q) => {
|
|
1065
1861
|
const current = q.aggregations ?? [];
|
|
1066
1862
|
const from = current.findIndex((a) => a.id === id);
|
|
@@ -1072,24 +1868,43 @@ function useQueryTable(opts) {
|
|
|
1072
1868
|
}),
|
|
1073
1869
|
[setQuery]
|
|
1074
1870
|
);
|
|
1075
|
-
const removeAggregation =
|
|
1076
|
-
(id) => setQuery((q) => ({
|
|
1871
|
+
const removeAggregation = useCallback8(
|
|
1872
|
+
(id) => setQuery((q) => ({
|
|
1873
|
+
...q,
|
|
1874
|
+
aggregations: (q.aggregations ?? []).filter((a) => a.id !== id)
|
|
1875
|
+
})),
|
|
1077
1876
|
[setQuery]
|
|
1078
1877
|
);
|
|
1079
|
-
const clearAggregations =
|
|
1878
|
+
const clearAggregations = useCallback8(
|
|
1080
1879
|
() => setQuery((q) => ({ ...q, aggregations: [] })),
|
|
1081
1880
|
[setQuery]
|
|
1082
1881
|
);
|
|
1083
|
-
const setSort =
|
|
1084
|
-
|
|
1085
|
-
(field, additive = false) => setQuery((q) => ({ ...q, offset: 0, orderBy: nextOrderBy(q.orderBy, field, additive) })),
|
|
1882
|
+
const setSort = useCallback8(
|
|
1883
|
+
(orderBy) => setQuery((q) => ({ ...q, offset: 0, orderBy })),
|
|
1086
1884
|
[setQuery]
|
|
1087
1885
|
);
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1886
|
+
const toggleSort = useCallback8(
|
|
1887
|
+
(field, additive = false) => setQuery((q) => ({
|
|
1888
|
+
...q,
|
|
1889
|
+
offset: 0,
|
|
1890
|
+
orderBy: nextOrderBy(q.orderBy, field, additive)
|
|
1891
|
+
})),
|
|
1892
|
+
[setQuery]
|
|
1893
|
+
);
|
|
1894
|
+
const setLimit = useCallback8(
|
|
1895
|
+
(limit) => patch({ limit, offset: 0 }),
|
|
1896
|
+
[patch]
|
|
1897
|
+
);
|
|
1898
|
+
const setOffset = useCallback8((offset) => patch({ offset }), [patch]);
|
|
1899
|
+
const nextPage = useCallback8(
|
|
1900
|
+
() => setQuery((q) => ({ ...q, offset: q.offset + q.limit })),
|
|
1901
|
+
[setQuery]
|
|
1902
|
+
);
|
|
1903
|
+
const prevPage = useCallback8(
|
|
1904
|
+
() => setQuery((q) => ({ ...q, offset: Math.max(0, q.offset - q.limit) })),
|
|
1905
|
+
[setQuery]
|
|
1906
|
+
);
|
|
1907
|
+
const filterValues = useCallback8(
|
|
1093
1908
|
async (field, search) => {
|
|
1094
1909
|
if (!transport?.fetchDistinctValues) {
|
|
1095
1910
|
const f = byName.get(field);
|
|
@@ -1106,7 +1921,7 @@ function useQueryTable(opts) {
|
|
|
1106
1921
|
},
|
|
1107
1922
|
[transport, byName, clientRows]
|
|
1108
1923
|
);
|
|
1109
|
-
const fieldStats =
|
|
1924
|
+
const fieldStats = useCallback8(
|
|
1110
1925
|
async (fields, signal) => {
|
|
1111
1926
|
const requested = [...new Set(fields)];
|
|
1112
1927
|
if (transport?.fetchFieldStats) {
|
|
@@ -1123,7 +1938,7 @@ function useQueryTable(opts) {
|
|
|
1123
1938
|
if (!field) continue;
|
|
1124
1939
|
const distinct = /* @__PURE__ */ new Set();
|
|
1125
1940
|
for (const row of clientRows) {
|
|
1126
|
-
const value =
|
|
1941
|
+
const value = readFieldValue4(field, row);
|
|
1127
1942
|
if (value !== null) distinct.add(JSON.stringify(value));
|
|
1128
1943
|
}
|
|
1129
1944
|
result[name] = { distinct: distinct.size };
|
|
@@ -1132,12 +1947,12 @@ function useQueryTable(opts) {
|
|
|
1132
1947
|
},
|
|
1133
1948
|
[transport, byName, clientRows]
|
|
1134
1949
|
);
|
|
1135
|
-
const refresh =
|
|
1950
|
+
const refresh = useCallback8(() => {
|
|
1136
1951
|
setNonce((n) => n + 1);
|
|
1137
1952
|
onRefreshRef.current?.();
|
|
1138
1953
|
}, []);
|
|
1139
|
-
const stopAutoRefresh =
|
|
1140
|
-
const startAutoRefresh =
|
|
1954
|
+
const stopAutoRefresh = useCallback8(() => setAutoRefreshStatus(null), []);
|
|
1955
|
+
const startAutoRefresh = useCallback8(
|
|
1141
1956
|
({ frequencyMs, turnOffAfterMs }) => {
|
|
1142
1957
|
const pollCount = frequencyMs > 0 && turnOffAfterMs > 0 ? Math.floor(turnOffAfterMs / frequencyMs) : 0;
|
|
1143
1958
|
if (pollCount < 1 || pollCount > MAX_AUTO_REFRESH_POLLS) {
|
|
@@ -1157,11 +1972,13 @@ function useQueryTable(opts) {
|
|
|
1157
1972
|
);
|
|
1158
1973
|
const autoRefreshFrequencyMs = autoRefreshStatus?.frequencyMs ?? null;
|
|
1159
1974
|
const autoRefreshStopsAt = autoRefreshStatus?.stopsAt ?? null;
|
|
1160
|
-
|
|
1975
|
+
useEffect5(() => {
|
|
1161
1976
|
if (autoRefreshFrequencyMs == null || autoRefreshStopsAt == null) return;
|
|
1162
1977
|
const tick = () => {
|
|
1163
1978
|
if (nowRef.current() >= autoRefreshStopsAt) {
|
|
1164
|
-
setAutoRefreshStatus(
|
|
1979
|
+
setAutoRefreshStatus(
|
|
1980
|
+
(current) => current?.stopsAt === autoRefreshStopsAt ? null : current
|
|
1981
|
+
);
|
|
1165
1982
|
return;
|
|
1166
1983
|
}
|
|
1167
1984
|
refresh();
|
|
@@ -1173,7 +1990,9 @@ function useQueryTable(opts) {
|
|
|
1173
1990
|
};
|
|
1174
1991
|
const interval = setInterval(tick, autoRefreshFrequencyMs);
|
|
1175
1992
|
const timeout = setTimeout(
|
|
1176
|
-
() => setAutoRefreshStatus(
|
|
1993
|
+
() => setAutoRefreshStatus(
|
|
1994
|
+
(current) => current?.stopsAt === autoRefreshStopsAt ? null : current
|
|
1995
|
+
),
|
|
1177
1996
|
Math.max(0, autoRefreshStopsAt - nowRef.current())
|
|
1178
1997
|
);
|
|
1179
1998
|
return () => {
|
|
@@ -1181,7 +2000,7 @@ function useQueryTable(opts) {
|
|
|
1181
2000
|
clearTimeout(timeout);
|
|
1182
2001
|
};
|
|
1183
2002
|
}, [autoRefreshFrequencyMs, autoRefreshStopsAt, refresh]);
|
|
1184
|
-
const autoRefresh =
|
|
2003
|
+
const autoRefresh = useMemo7(
|
|
1185
2004
|
() => ({
|
|
1186
2005
|
status: autoRefreshStatus,
|
|
1187
2006
|
start: startAutoRefresh,
|
|
@@ -1189,16 +2008,25 @@ function useQueryTable(opts) {
|
|
|
1189
2008
|
}),
|
|
1190
2009
|
[autoRefreshStatus, startAutoRefresh, stopAutoRefresh]
|
|
1191
2010
|
);
|
|
1192
|
-
const refreshRow =
|
|
2011
|
+
const refreshRow = useCallback8(
|
|
1193
2012
|
async (id) => {
|
|
2013
|
+
if (opts.computedExecution && transport?.fetchRowsV2) {
|
|
2014
|
+
refresh();
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1194
2017
|
if (!transport?.fetchRow) return;
|
|
1195
2018
|
const updated = await transport.fetchRow(id);
|
|
1196
|
-
setRows(
|
|
2019
|
+
setRows(
|
|
2020
|
+
(prev) => prev.map((r) => rowId(r) === id ? updated ?? r : r)
|
|
2021
|
+
);
|
|
1197
2022
|
},
|
|
1198
|
-
[transport, rowId]
|
|
2023
|
+
[transport, rowId, opts.computedExecution, refresh]
|
|
2024
|
+
);
|
|
2025
|
+
const visibleFields = useMemo7(
|
|
2026
|
+
() => selectedFields2(displaySchema, query),
|
|
2027
|
+
[displaySchema, query]
|
|
1199
2028
|
);
|
|
1200
|
-
const
|
|
1201
|
-
const aggregations = useMemo6(
|
|
2029
|
+
const aggregations = useMemo7(
|
|
1202
2030
|
() => ({
|
|
1203
2031
|
clauses: query.aggregations ?? [],
|
|
1204
2032
|
results: aggState.results,
|
|
@@ -1208,7 +2036,11 @@ function useQueryTable(opts) {
|
|
|
1208
2036
|
update: updateAggregation,
|
|
1209
2037
|
move: moveAggregation,
|
|
1210
2038
|
remove: removeAggregation,
|
|
1211
|
-
clear: clearAggregations
|
|
2039
|
+
clear: clearAggregations,
|
|
2040
|
+
preview: aggState.preview,
|
|
2041
|
+
replace: replaceAggregations,
|
|
2042
|
+
compile: metricCompile,
|
|
2043
|
+
contextKey: `${dataVersion}:${pageContext}:${pageReady}:${JSON.stringify(query.where)}`
|
|
1212
2044
|
}),
|
|
1213
2045
|
[
|
|
1214
2046
|
query.aggregations,
|
|
@@ -1219,7 +2051,14 @@ function useQueryTable(opts) {
|
|
|
1219
2051
|
updateAggregation,
|
|
1220
2052
|
moveAggregation,
|
|
1221
2053
|
removeAggregation,
|
|
1222
|
-
clearAggregations
|
|
2054
|
+
clearAggregations,
|
|
2055
|
+
aggState.preview,
|
|
2056
|
+
replaceAggregations,
|
|
2057
|
+
metricCompile,
|
|
2058
|
+
pageContext,
|
|
2059
|
+
pageReady,
|
|
2060
|
+
dataVersion,
|
|
2061
|
+
query.where
|
|
1223
2062
|
]
|
|
1224
2063
|
);
|
|
1225
2064
|
return {
|
|
@@ -1230,6 +2069,7 @@ function useQueryTable(opts) {
|
|
|
1230
2069
|
total,
|
|
1231
2070
|
loading,
|
|
1232
2071
|
error,
|
|
2072
|
+
requestActivity,
|
|
1233
2073
|
canUndo,
|
|
1234
2074
|
canRedo,
|
|
1235
2075
|
undo,
|