@pythia-software/query-table-react 0.7.1 → 0.8.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/dist/index.d.ts +18 -2
- package/dist/index.js +109 -81
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { QueryState, GroupedRow, GroupingModel,
|
|
1
|
+
import { FieldSchema, WhereClause, QueryState, GroupedRow, GroupingModel, GroupingView, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedColumnStore, ComputedExecution, QueryLinkOptions, ObjectReference, ResolvedObjectReference } from '@pythia-software/query-table-core';
|
|
2
2
|
|
|
3
3
|
/** Filter controls for either the result query or grouping's source query. */
|
|
4
4
|
type QueryFilterApi<Row> = Pick<QueryTableApi<Row>, "query" | "setQuery" | "loading" | "addFilter" | "updateFilter" | "removeFilter" | "clearFilters" | "updatePredicate" | "removePredicate" | "negatePredicate" | "reorderFilters" | "mergeFilters" | "filterValues" | "fieldStats">;
|
|
5
|
+
/** Bind filter intents to a query stage, including the source WHERE of grouping. */
|
|
6
|
+
declare function useQueryFilters<Row>(schema: FieldSchema<Row>, setQuery: QueryTableApi<Row>["setQuery"]): {
|
|
7
|
+
addFilter: (clause: WhereClause) => void;
|
|
8
|
+
updateFilter: (index: number, clause: WhereClause) => void;
|
|
9
|
+
removeFilter: (index: number) => void;
|
|
10
|
+
clearFilters: () => void;
|
|
11
|
+
updatePredicate: (termIndex: number, predIndex: number, clause: WhereClause) => void;
|
|
12
|
+
removePredicate: (termIndex: number, predIndex: number) => void;
|
|
13
|
+
negatePredicate: (termIndex: number, predIndex: number) => void;
|
|
14
|
+
reorderFilters: (fromIndex: number, toIndex: number) => void;
|
|
15
|
+
mergeFilters: (sourceIndex: number, targetIndex: number) => void;
|
|
16
|
+
};
|
|
5
17
|
|
|
6
18
|
/** Source-stage controls. Rows/columns/metrics remain on the active result API. */
|
|
7
19
|
type GroupingSourceApi<Row> = QueryFilterApi<Row> & Pick<QueryTableApi<Row>, "itemDetails" | "rowId"> & {
|
|
@@ -21,6 +33,10 @@ interface GroupingApi<Row> {
|
|
|
21
33
|
grouped: QueryTableApi<Row>;
|
|
22
34
|
schema: FieldSchema<Row>;
|
|
23
35
|
view: GroupingView;
|
|
36
|
+
/** Problems with link columns. They never block the grouped table: an
|
|
37
|
+
* invalid link renders as plain text. Unregistered host targets are
|
|
38
|
+
* reported when rendering. */
|
|
39
|
+
diagnostics: string[];
|
|
24
40
|
inputs: Row[];
|
|
25
41
|
notice: string;
|
|
26
42
|
setNotice: (notice: string) => void;
|
|
@@ -463,4 +479,4 @@ declare function useObjectReferences(references: ObjectReference[], adapter?: Re
|
|
|
463
479
|
error: Error | null;
|
|
464
480
|
};
|
|
465
481
|
|
|
466
|
-
export { type AggregationPatch, type AggregationsApi, type AggregationsApiState, type AutoRefreshApi, type AutoRefreshConfig, type AutoRefreshStatus, type ColumnDragApi, type ComputedColumnsApi, type FormulaWorkerFactory, type GroupingApi, type GroupingSourceApi, type ItemDetailRecord, type ItemDetailsApi, type ItemDetailsOptions, type PropertyViewApi, type QueryFilterApi, type QueryLinksApi, type QueryTableApi, type RequestActivityApi, type RequestActivityEntry, type SavedQueriesApi, type SelectApi, type SelectionApi, type UsePropertyViewOptions, type UseQueryTableOptions, createFormulaWorker, evaluateFormulaRows, useAggregations, useColumnDrag, useObjectReferences, usePropertyView, useQueryTable, useSavedQueries, useSelect, useSelection };
|
|
482
|
+
export { type AggregationPatch, type AggregationsApi, type AggregationsApiState, type AutoRefreshApi, type AutoRefreshConfig, type AutoRefreshStatus, type ColumnDragApi, type ComputedColumnsApi, type FormulaWorkerFactory, type GroupingApi, type GroupingSourceApi, type ItemDetailRecord, type ItemDetailsApi, type ItemDetailsOptions, type PropertyViewApi, type QueryFilterApi, type QueryLinksApi, type QueryTableApi, type RequestActivityApi, type RequestActivityEntry, type SavedQueriesApi, type SelectApi, type SelectionApi, type UsePropertyViewOptions, type UseQueryTableOptions, createFormulaWorker, evaluateFormulaRows, useAggregations, useColumnDrag, useObjectReferences, usePropertyView, useQueryFilters, useQueryTable, useSavedQueries, useSelect, useSelection };
|
package/dist/index.js
CHANGED
|
@@ -138,6 +138,7 @@ import {
|
|
|
138
138
|
MAX_SUMMARY_COLUMNS,
|
|
139
139
|
normalizeQueryState,
|
|
140
140
|
predicatesOf as predicatesOf2,
|
|
141
|
+
withGroupingKeys,
|
|
141
142
|
withoutGrouping,
|
|
142
143
|
queriesEqual
|
|
143
144
|
} from "@pythia-software/query-table-core";
|
|
@@ -171,46 +172,12 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
171
172
|
if (!keys.length && stage)
|
|
172
173
|
previous.current = { query: api.query, revision: viewRevision };
|
|
173
174
|
api.setQuery((current) => {
|
|
174
|
-
|
|
175
|
-
if (!keys.length) return before ? before.source : current;
|
|
176
|
-
if (!before) {
|
|
177
|
-
if (cached?.grouping && canRestore(current))
|
|
178
|
-
return {
|
|
179
|
-
...cached,
|
|
180
|
-
grouping: { ...cached.grouping, source: current }
|
|
181
|
-
};
|
|
182
|
-
const grouping2 = {
|
|
183
|
-
keys,
|
|
184
|
-
summaries: [],
|
|
185
|
-
representative: null,
|
|
186
|
-
source: current
|
|
187
|
-
};
|
|
175
|
+
if (keys.length && !current.grouping && cached?.grouping && canRestore(current))
|
|
188
176
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
orderBy: [],
|
|
192
|
-
limit: current.limit,
|
|
193
|
-
offset: 0,
|
|
194
|
-
grouping: grouping2
|
|
177
|
+
...cached,
|
|
178
|
+
grouping: { ...cached.grouping, source: current }
|
|
195
179
|
};
|
|
196
|
-
|
|
197
|
-
const grouping = { ...before, keys };
|
|
198
|
-
const removed = new Set(before.keys.filter((key) => !keys.includes(key)));
|
|
199
|
-
const columns = current.select.length ? current.select : before.keys.map((field) => ({ field }));
|
|
200
|
-
return {
|
|
201
|
-
...current,
|
|
202
|
-
grouping,
|
|
203
|
-
offset: 0,
|
|
204
|
-
aggregations: [],
|
|
205
|
-
select: [
|
|
206
|
-
...keys.filter((key) => !before.keys.includes(key)).map((field) => ({ field })),
|
|
207
|
-
...columns.filter((c) => !removed.has(c.field))
|
|
208
|
-
],
|
|
209
|
-
where: current.where.filter(
|
|
210
|
-
(term) => !predicatesOf2(term).some((p) => removed.has(p.field))
|
|
211
|
-
),
|
|
212
|
-
orderBy: current.orderBy.filter((term) => !removed.has(term.field))
|
|
213
|
-
};
|
|
180
|
+
return withGroupingKeys(current, keys);
|
|
214
181
|
});
|
|
215
182
|
setNotice(
|
|
216
183
|
stage && api.query.aggregations?.length && keys.length ? "Grouping changed. Group metrics cleared; Undo restores them." : ""
|
|
@@ -274,9 +241,11 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
274
241
|
function changeSummary(id, op) {
|
|
275
242
|
if (!stage) return;
|
|
276
243
|
applyColumns(
|
|
277
|
-
stage.summaries.map(
|
|
278
|
-
(column
|
|
279
|
-
|
|
244
|
+
stage.summaries.map((column) => {
|
|
245
|
+
if (column.id !== id) return column;
|
|
246
|
+
const { display: _display, ...rest } = column;
|
|
247
|
+
return op === "row" ? { ...rest, op } : { ...column, op };
|
|
248
|
+
}),
|
|
280
249
|
api.select.visible
|
|
281
250
|
);
|
|
282
251
|
}
|
|
@@ -325,6 +294,7 @@ function useGroupingController(api, source, model, inputs, supported, viewRevisi
|
|
|
325
294
|
grouped: api,
|
|
326
295
|
schema: api.schema,
|
|
327
296
|
view,
|
|
297
|
+
diagnostics: stage ? model.linkDiagnostics(stage) : [],
|
|
328
298
|
inputs,
|
|
329
299
|
notice,
|
|
330
300
|
setNotice,
|
|
@@ -344,7 +314,8 @@ import {
|
|
|
344
314
|
withoutGrouping as withoutGrouping2,
|
|
345
315
|
sourceExecutionSchema,
|
|
346
316
|
toQueryRequestV3,
|
|
347
|
-
validateQueryResultV3
|
|
317
|
+
validateQueryResultV3,
|
|
318
|
+
metricPopulationError as metricPopulationError2
|
|
348
319
|
} from "@pythia-software/query-table-core";
|
|
349
320
|
import { ITEM_DETAILS_COLUMN, itemPageFields } from "@pythia-software/query-table-core";
|
|
350
321
|
|
|
@@ -753,6 +724,7 @@ function validateComputedResponse(request, result, schema) {
|
|
|
753
724
|
// src/useComputedColumns.ts
|
|
754
725
|
import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
|
|
755
726
|
import {
|
|
727
|
+
isGroupLinkField,
|
|
756
728
|
applyQuery,
|
|
757
729
|
compileFormula,
|
|
758
730
|
computedFieldName,
|
|
@@ -908,7 +880,7 @@ function useComputedColumns(options) {
|
|
|
908
880
|
}, [reload]);
|
|
909
881
|
const inputFields = useMemo3(
|
|
910
882
|
() => schema.fields.filter(isSelectable).filter(
|
|
911
|
-
(f) => f.source.kind === "backend" || f.source.accessor && f.source.dependencies
|
|
883
|
+
(f) => f.source.kind === "backend" || f.source.accessor && f.source.dependencies && !isGroupLinkField(f)
|
|
912
884
|
),
|
|
913
885
|
[schema]
|
|
914
886
|
);
|
|
@@ -1334,6 +1306,8 @@ import {
|
|
|
1334
1306
|
evaluateMetrics,
|
|
1335
1307
|
metricPlans,
|
|
1336
1308
|
hasRegex as hasRegex2,
|
|
1309
|
+
metricPopulationError,
|
|
1310
|
+
predicatesOf as predicatesOf3,
|
|
1337
1311
|
toAggregationQuery,
|
|
1338
1312
|
toMetricQuery,
|
|
1339
1313
|
validateMetric,
|
|
@@ -1348,7 +1322,7 @@ var abort = (signal) => {
|
|
|
1348
1322
|
async function localMetrics(rows, query, context, signal) {
|
|
1349
1323
|
const originalClauses = query.aggregations ?? [];
|
|
1350
1324
|
const advanced = (query.aggregations ?? []).filter(
|
|
1351
|
-
(c) => context.strictLocalMetrics || c.dateGrouping || 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
|
|
1325
|
+
(c) => context.strictLocalMetrics || c.dateGrouping || 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 || c.where !== void 0 || c.minCount !== void 0
|
|
1352
1326
|
);
|
|
1353
1327
|
if (advanced.length && (originalClauses.length > 20 || normalizeQueryState2(query).aggregations?.length !== originalClauses.length))
|
|
1354
1328
|
return evaluateMetrics(rows, query, context.schema, {
|
|
@@ -1466,7 +1440,7 @@ async function executeMetrics(query, context, signal) {
|
|
|
1466
1440
|
const shown = clauses.filter(
|
|
1467
1441
|
(c) => c.scope === "shownRows" && (!supportsV2 || needsWorker(c) || !!c.dateGrouping)
|
|
1468
1442
|
);
|
|
1469
|
-
|
|
1443
|
+
let matching = clauses.filter(
|
|
1470
1444
|
(c) => c.scope !== "shownRows" || supportsV2 && !needsWorker(c) && !c.dateGrouping
|
|
1471
1445
|
);
|
|
1472
1446
|
const results = [];
|
|
@@ -1478,6 +1452,9 @@ async function executeMetrics(query, context, signal) {
|
|
|
1478
1452
|
const names = new Set(
|
|
1479
1453
|
shown.flatMap((clause) => [
|
|
1480
1454
|
...clause.groupBy,
|
|
1455
|
+
...(clause.where ?? []).flatMap(
|
|
1456
|
+
(term) => predicatesOf3(term).map((predicate) => predicate.field)
|
|
1457
|
+
),
|
|
1481
1458
|
...metricPlans(clause, context.schema, context.resolveComputed).flatMap(
|
|
1482
1459
|
(p) => p.dependencies
|
|
1483
1460
|
)
|
|
@@ -1509,20 +1486,37 @@ async function executeMetrics(query, context, signal) {
|
|
|
1509
1486
|
...(await localMetrics(context.shownRows, pageQuery, context, signal)).metrics
|
|
1510
1487
|
);
|
|
1511
1488
|
}
|
|
1489
|
+
const unsupported = (clause, error) => results.push({ id: clause.id, buckets: [], error });
|
|
1490
|
+
const calendar = matching.filter((c) => c.dateGrouping);
|
|
1491
|
+
for (const clause of calendar)
|
|
1492
|
+
unsupported(
|
|
1493
|
+
clause,
|
|
1494
|
+
"This server does not support calendar grouping. Enable the v3 query executor."
|
|
1495
|
+
);
|
|
1496
|
+
matching = matching.filter((c) => !c.dateGrouping);
|
|
1497
|
+
{
|
|
1498
|
+
const transport = context.transport;
|
|
1499
|
+
if (transport?.fetchMetrics || transport?.fetchAggregations) {
|
|
1500
|
+
const support = transport.fetchMetrics && transport.metricCapabilities?.version === 2 ? transport.metricCapabilities : {};
|
|
1501
|
+
matching = matching.filter((c) => {
|
|
1502
|
+
const error = metricPopulationError(c, support);
|
|
1503
|
+
if (error) unsupported(c, error);
|
|
1504
|
+
return !error;
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1512
1508
|
if (matching.length) {
|
|
1513
|
-
if (matching.some((c) => c.dateGrouping))
|
|
1514
|
-
throw Error(
|
|
1515
|
-
"This server does not support calendar grouping. Enable the v3 query executor."
|
|
1516
|
-
);
|
|
1517
1509
|
const transport = context.transport;
|
|
1518
|
-
const scoped = { ...query, aggregations: matching };
|
|
1519
1510
|
if (transport?.fetchMetrics && transport.metricCapabilities?.version === 2) {
|
|
1520
|
-
const
|
|
1511
|
+
const capabilities = transport.metricCapabilities;
|
|
1512
|
+
const request = toMetricQuery(
|
|
1513
|
+
{ ...query, aggregations: matching },
|
|
1514
|
+
context.schema,
|
|
1515
|
+
context.execution
|
|
1516
|
+
);
|
|
1521
1517
|
if (request.diagnostics.length)
|
|
1522
1518
|
throw Error(request.diagnostics.map((d) => d.message).join(" "));
|
|
1523
|
-
if (
|
|
1524
|
-
throw Error("This server does not support shown-row metrics.");
|
|
1525
|
-
if (request.profile && transport.metricCapabilities.profile && request.profile !== transport.metricCapabilities.profile)
|
|
1519
|
+
if (request.profile && capabilities.profile && request.profile !== capabilities.profile)
|
|
1526
1520
|
throw Error(
|
|
1527
1521
|
"Metric transport and computed execution profiles do not match. Refresh the handshake."
|
|
1528
1522
|
);
|
|
@@ -1543,28 +1537,42 @@ async function executeMetrics(query, context, signal) {
|
|
|
1543
1537
|
...context.schema,
|
|
1544
1538
|
fields: [...remoteFields.values()]
|
|
1545
1539
|
};
|
|
1546
|
-
|
|
1547
|
-
|
|
1540
|
+
const wire = new Map(request.metrics.map((m) => [m.id, m]));
|
|
1541
|
+
const limitation = (clause) => {
|
|
1542
|
+
if (clause.scope === "shownRows" && !capabilities.shownRows)
|
|
1543
|
+
return "This server does not support shown-row metrics.";
|
|
1544
|
+
if (!capabilities.computedFields && [
|
|
1548
1545
|
...clause.groupBy,
|
|
1549
1546
|
...metricPlans(clause, remoteSchema).flatMap(
|
|
1550
1547
|
(plan) => plan.dependencies
|
|
1551
1548
|
)
|
|
1552
|
-
].some(isComputedField3)
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1549
|
+
].some(isComputedField3))
|
|
1550
|
+
return "This metric server does not support computed fields.";
|
|
1551
|
+
const sent = wire.get(clause.id);
|
|
1552
|
+
if (!capabilities.expressions && sent && !sent.distribution && (sent.expression !== void 0 || sent.expressionY !== void 0))
|
|
1553
|
+
return "This server does not support composed metric expressions.";
|
|
1554
|
+
if (!capabilities.distributions && clause.distribution && (!clause.display || ["box", "histogram"].includes(clause.display.kind)))
|
|
1555
|
+
return "This server does not support distributions.";
|
|
1556
|
+
return void 0;
|
|
1557
|
+
};
|
|
1558
|
+
const rejected = /* @__PURE__ */ new Set();
|
|
1559
|
+
for (const clause of matching) {
|
|
1560
|
+
const error = limitation(clause);
|
|
1561
|
+
if (error) {
|
|
1562
|
+
rejected.add(clause.id);
|
|
1563
|
+
unsupported(clause, error);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
const metrics = request.metrics.filter((m) => !rejected.has(m.id));
|
|
1567
|
+
if (metrics.length)
|
|
1568
|
+
results.push(
|
|
1569
|
+
...(await transport.fetchMetrics({ ...request, metrics }, signal)).metrics
|
|
1560
1570
|
);
|
|
1561
|
-
if (matching.some(
|
|
1562
|
-
(c) => c.distribution && (!c.display || ["box", "histogram"].includes(c.display.kind))
|
|
1563
|
-
) && !transport.metricCapabilities.distributions)
|
|
1564
|
-
throw Error("This server does not support distributions.");
|
|
1565
|
-
results.push(...(await transport.fetchMetrics(request, signal)).metrics);
|
|
1566
1571
|
} else if (transport?.fetchAggregations) {
|
|
1567
|
-
const request = toAggregationQuery(
|
|
1572
|
+
const request = toAggregationQuery(
|
|
1573
|
+
{ ...query, aggregations: matching },
|
|
1574
|
+
context.schema
|
|
1575
|
+
);
|
|
1568
1576
|
if (request.diagnostics?.length)
|
|
1569
1577
|
throw Error(request.diagnostics.map((d) => d.message).join(" "));
|
|
1570
1578
|
results.push(
|
|
@@ -2755,17 +2763,36 @@ function useQueryTable(opts) {
|
|
|
2755
2763
|
);
|
|
2756
2764
|
const executeV3Metrics = useCallback12(async (metricQuery, signal) => {
|
|
2757
2765
|
if (!transport?.fetchQueryV3 || transport.queryCapabilities?.version !== 3) throw Error("The v3 query executor is unavailable.");
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
);
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2766
|
+
const capabilities = transport.queryCapabilities;
|
|
2767
|
+
const refused = /* @__PURE__ */ new Map();
|
|
2768
|
+
for (const clause of metricQuery.aggregations ?? []) {
|
|
2769
|
+
const issue = metricPopulationError2(clause, { filters: capabilities.metricFilters, minCount: capabilities.metricMinCount });
|
|
2770
|
+
if (issue) refused.set(clause.id, issue);
|
|
2771
|
+
}
|
|
2772
|
+
const runnable = (metricQuery.aggregations ?? []).filter((clause) => !refused.has(clause.id));
|
|
2773
|
+
let metrics = [];
|
|
2774
|
+
if (runnable.length) {
|
|
2775
|
+
const scoped = { ...metricQuery, aggregations: runnable };
|
|
2776
|
+
const request = toQueryRequestV3(
|
|
2777
|
+
query.grouping ? { ...scoped, grouping: query.grouping } : scoped,
|
|
2778
|
+
sourceSchema,
|
|
2779
|
+
capabilities,
|
|
2780
|
+
opts.computedExecution,
|
|
2781
|
+
{ now: groupingNow ?? (opts.evaluationNow ?? Date.now)(), rows: false }
|
|
2782
|
+
);
|
|
2783
|
+
if (request.diagnostics.length) throw Error(request.diagnostics.join(" "));
|
|
2784
|
+
const result = await transport.fetchQueryV3(request, signal);
|
|
2785
|
+
validateQueryResultV3(result, request, query.grouping ? schema : sourceCatalogue);
|
|
2786
|
+
metrics = result.metrics;
|
|
2787
|
+
}
|
|
2788
|
+
if (!refused.size) return { metrics };
|
|
2789
|
+
const byId = new Map(metrics.map((metric) => [metric.id, metric]));
|
|
2790
|
+
return { metrics: (metricQuery.aggregations ?? []).flatMap((clause) => {
|
|
2791
|
+
const issue = refused.get(clause.id);
|
|
2792
|
+
if (issue) return [{ id: clause.id, buckets: [], error: issue }];
|
|
2793
|
+
const metric = byId.get(clause.id);
|
|
2794
|
+
return metric ? [metric] : [];
|
|
2795
|
+
}) };
|
|
2769
2796
|
}, [transport, sourceSchema, sourceCatalogue, schema, opts.computedExecution, groupKey, groupingNow, opts.evaluationNow]);
|
|
2770
2797
|
const previewGrouping = useCallback12(async (draft, signal) => {
|
|
2771
2798
|
if (!active || !storageReady || restoration.loading || restoration.error) throw Error("Wait for query restoration before previewing groups.");
|
|
@@ -3287,6 +3314,7 @@ export {
|
|
|
3287
3314
|
useColumnDrag,
|
|
3288
3315
|
useObjectReferences,
|
|
3289
3316
|
usePropertyView,
|
|
3317
|
+
useQueryFilters,
|
|
3290
3318
|
useQueryTable,
|
|
3291
3319
|
useSavedQueries,
|
|
3292
3320
|
useSelect,
|