@stoker-platform/web-app 0.5.200 → 0.5.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +2 -2
- package/src/Collection.tsx +51 -24
- package/src/List.tsx +44 -19
- package/src/utils/getFilterDisjunctions.ts +28 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.202
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix: fix incorrect multipleQueries option
|
|
8
|
+
|
|
9
|
+
## 0.5.201
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- feat: improve server side full text search
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @stoker-platform/web-client@0.5.82
|
|
16
|
+
|
|
3
17
|
## 0.5.200
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoker-platform/web-app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.202",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"scripts": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@sentry/react": "^10.56.0",
|
|
54
54
|
"@stoker-platform/node-client": "0.5.78",
|
|
55
55
|
"@stoker-platform/utils": "0.5.69",
|
|
56
|
-
"@stoker-platform/web-client": "0.5.
|
|
56
|
+
"@stoker-platform/web-client": "0.5.82",
|
|
57
57
|
"@tanstack/react-table": "^8.21.3",
|
|
58
58
|
"@types/react": "18.3.13",
|
|
59
59
|
"@types/react-dom": "18.3.1",
|
package/src/Collection.tsx
CHANGED
|
@@ -492,6 +492,8 @@ function Collection({
|
|
|
492
492
|
setIsRouteLoading("+", location.pathname)
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
const multipleQueries: QueryConstraint[][] = []
|
|
496
|
+
|
|
495
497
|
if (
|
|
496
498
|
fullTextSearch &&
|
|
497
499
|
!isPreloadCacheEnabled &&
|
|
@@ -500,11 +502,6 @@ function Collection({
|
|
|
500
502
|
tab !== "map" &&
|
|
501
503
|
tab !== "calendar"
|
|
502
504
|
) {
|
|
503
|
-
const disjunctions = getFilterDisjunctions(collection)
|
|
504
|
-
const hitsPerPage =
|
|
505
|
-
disjunctions === 0
|
|
506
|
-
? Math.min(30, itemsPerPage || 10)
|
|
507
|
-
: Math.min(itemsPerPage || 10, Math.max(1, Math.floor(30 / disjunctions)))
|
|
508
505
|
let latestFilters = filters
|
|
509
506
|
if (tab === "cards" && prevTabRef.current !== "cards") {
|
|
510
507
|
latestFilters = [...filters]
|
|
@@ -517,10 +514,32 @@ function Collection({
|
|
|
517
514
|
}
|
|
518
515
|
}
|
|
519
516
|
}
|
|
517
|
+
const disjunctions = getFilterDisjunctions(collection, {
|
|
518
|
+
assignable,
|
|
519
|
+
isAssigning: queryIsAssigning,
|
|
520
|
+
filters: latestFilters,
|
|
521
|
+
relationList,
|
|
522
|
+
relationParent,
|
|
523
|
+
})
|
|
524
|
+
const searchOptions = customization.admin?.searchOptions
|
|
525
|
+
const exactPhrase = searchOptions?.fuzzy === false && searchOptions?.prefix === false
|
|
526
|
+
const batchSize =
|
|
527
|
+
disjunctions === 0
|
|
528
|
+
? Math.min(30, itemsPerPage || 10)
|
|
529
|
+
: Math.min(itemsPerPage || 10, Math.max(1, Math.floor(30 / disjunctions)))
|
|
530
|
+
let hitsPerPage = 0
|
|
531
|
+
if (exactPhrase && !(hasEntityRestrictions.length > 0 || hasEntityParentFilters.length > 0)) {
|
|
532
|
+
hitsPerPage = 1000
|
|
533
|
+
} else {
|
|
534
|
+
hitsPerPage = batchSize
|
|
535
|
+
}
|
|
520
536
|
const constraints = getFilterConstraints(latestFilters, false, true) as [string, "==" | "in", unknown][]
|
|
521
537
|
const objectIDs = await performFullTextSearch(collection, search, hitsPerPage, constraints)
|
|
522
538
|
searchResults.current = { ...searchResults.current, [key]: objectIDs }
|
|
523
|
-
if (
|
|
539
|
+
if (
|
|
540
|
+
objectIDs.length > 0 &&
|
|
541
|
+
(!exactPhrase || hasEntityRestrictions.length > 0 || hasEntityParentFilters.length > 0)
|
|
542
|
+
) {
|
|
524
543
|
if (isServerReadOnly) {
|
|
525
544
|
query.queries = query.queries.map((q) => ({
|
|
526
545
|
...q,
|
|
@@ -536,6 +555,10 @@ function Collection({
|
|
|
536
555
|
constraints: [...q.constraints, where("id", "in", objectIDs)] as QueryConstraint[],
|
|
537
556
|
}))
|
|
538
557
|
}
|
|
558
|
+
} else if (objectIDs.length > 0 && exactPhrase) {
|
|
559
|
+
for (let i = 0; i < objectIDs.length; i += batchSize) {
|
|
560
|
+
multipleQueries.push([where("id", "in", objectIDs.slice(i, i + batchSize))])
|
|
561
|
+
}
|
|
539
562
|
} else if (search) {
|
|
540
563
|
setServerList((prev) => ({ ...prev, [key]: [] }))
|
|
541
564
|
setOptimisticList([], key)
|
|
@@ -639,6 +662,27 @@ function Collection({
|
|
|
639
662
|
}
|
|
640
663
|
}
|
|
641
664
|
|
|
665
|
+
const subscribeOptions = {
|
|
666
|
+
...currentQuery.options,
|
|
667
|
+
constraints: combineQueryConstraints([
|
|
668
|
+
...(currentQuery.constraints as QueryConstraint[]),
|
|
669
|
+
...(additionalConstraintsRef.current?.map((constraint) =>
|
|
670
|
+
where(constraint[0], constraint[1] as WhereFilterOp, constraint[2]),
|
|
671
|
+
) || []),
|
|
672
|
+
]),
|
|
673
|
+
tempCache:
|
|
674
|
+
isPreloadCacheEnabled && relationList?.loadAll
|
|
675
|
+
? {
|
|
676
|
+
label: `${labels.collection}-${relationList?.field}`,
|
|
677
|
+
constraints: [[`${relationList?.field}_Single.id`, "==", relationParent?.id]],
|
|
678
|
+
}
|
|
679
|
+
: undefined,
|
|
680
|
+
multipleQueries: multipleQueries.length > 0 ? multipleQueries : undefined,
|
|
681
|
+
} as SubscribeManyOptions
|
|
682
|
+
if (multipleQueries.length > 0) {
|
|
683
|
+
delete subscribeOptions.pagination
|
|
684
|
+
}
|
|
685
|
+
|
|
642
686
|
// TODO: subcollection support
|
|
643
687
|
const result = await subscribeMany(
|
|
644
688
|
[labels.collection],
|
|
@@ -663,24 +707,7 @@ function Collection({
|
|
|
663
707
|
window.location.reload()
|
|
664
708
|
}
|
|
665
709
|
},
|
|
666
|
-
|
|
667
|
-
...currentQuery.options,
|
|
668
|
-
constraints: combineQueryConstraints([
|
|
669
|
-
...(currentQuery.constraints as QueryConstraint[]),
|
|
670
|
-
...(additionalConstraintsRef.current?.map((constraint) =>
|
|
671
|
-
where(constraint[0], constraint[1] as WhereFilterOp, constraint[2]),
|
|
672
|
-
) || []),
|
|
673
|
-
]),
|
|
674
|
-
tempCache:
|
|
675
|
-
isPreloadCacheEnabled && relationList?.loadAll
|
|
676
|
-
? {
|
|
677
|
-
label: `${labels.collection}-${relationList?.field}`,
|
|
678
|
-
constraints: [
|
|
679
|
-
[`${relationList?.field}_Single.id`, "==", relationParent?.id],
|
|
680
|
-
],
|
|
681
|
-
}
|
|
682
|
-
: undefined,
|
|
683
|
-
} as SubscribeManyOptions,
|
|
710
|
+
subscribeOptions,
|
|
684
711
|
)
|
|
685
712
|
const { unsubscribe: newUnsubscribe, count: newCount, pages: newPages } = result
|
|
686
713
|
promiseLoaded = true
|
package/src/List.tsx
CHANGED
|
@@ -677,6 +677,8 @@ export function List({
|
|
|
677
677
|
return allColumns
|
|
678
678
|
}, [fields, isPreloadCacheEnabled, isServerReadOnly, recordTitleField, connectionStatus])
|
|
679
679
|
|
|
680
|
+
const isServerFullTextSearch = !!(search && fullTextSearch && !isPreloadCacheEnabled && !isServerReadOnly)
|
|
681
|
+
|
|
680
682
|
const isSearchRelevanceOrder = !!(search && isPreloadCacheEnabled)
|
|
681
683
|
|
|
682
684
|
const searchList = useMemo(() => {
|
|
@@ -694,6 +696,8 @@ export function List({
|
|
|
694
696
|
return list || []
|
|
695
697
|
}, [isPreloadCacheEnabled, isServerReadOnly, list, search])
|
|
696
698
|
|
|
699
|
+
const tablePageSize = isServerFullTextSearch ? Math.max(searchList.length, 1) : pageSize
|
|
700
|
+
|
|
697
701
|
const selectedRecords = useMemo(() => {
|
|
698
702
|
const selectedIds = Object.keys(rowSelection)
|
|
699
703
|
if (selectedIds.length === 0) return []
|
|
@@ -761,12 +765,13 @@ export function List({
|
|
|
761
765
|
columnFilters,
|
|
762
766
|
rowSelection,
|
|
763
767
|
pagination: {
|
|
764
|
-
pageSize,
|
|
765
|
-
pageIndex,
|
|
768
|
+
pageSize: tablePageSize,
|
|
769
|
+
pageIndex: isServerFullTextSearch ? 0 : pageIndex,
|
|
766
770
|
},
|
|
767
771
|
},
|
|
768
772
|
onPaginationChange: (updater) => {
|
|
769
|
-
const newPagination =
|
|
773
|
+
const newPagination =
|
|
774
|
+
typeof updater === "function" ? updater({ pageIndex, pageSize: tablePageSize }) : updater
|
|
770
775
|
if (pageCount && newPagination.pageIndex < pageCount) {
|
|
771
776
|
setPageIndex(newPagination.pageIndex)
|
|
772
777
|
}
|
|
@@ -868,7 +873,7 @@ export function List({
|
|
|
868
873
|
}
|
|
869
874
|
}, 750)
|
|
870
875
|
|
|
871
|
-
if (isInitialized && (isPreloadCacheEnabled || isServerReadOnly)) {
|
|
876
|
+
if (isInitialized && (isPreloadCacheEnabled || isServerReadOnly || isServerFullTextSearch)) {
|
|
872
877
|
setPageIndex(0)
|
|
873
878
|
setState(`collection-page-number-${labels.collection.toLowerCase()}`, "page", 1)
|
|
874
879
|
}
|
|
@@ -1070,12 +1075,15 @@ export function List({
|
|
|
1070
1075
|
}, [table, list, pageSize, isLoading, cursor, pageNumber, pageCount, constraints, orderByField, orderByDirection])
|
|
1071
1076
|
|
|
1072
1077
|
const canGetNextPage = useCallback(() => {
|
|
1078
|
+
if (isServerFullTextSearch) {
|
|
1079
|
+
return false
|
|
1080
|
+
}
|
|
1073
1081
|
if (isPreloadCacheEnabled || isServerReadOnly) {
|
|
1074
1082
|
return table.getCanNextPage()
|
|
1075
1083
|
} else {
|
|
1076
1084
|
return !isLoadingDebounced && pageCount && pageNumber < pageCount && list?.length === pageSize
|
|
1077
1085
|
}
|
|
1078
|
-
}, [table, isLoadingDebounced, pageNumber, pageCount, list, pageSize])
|
|
1086
|
+
}, [table, isLoadingDebounced, pageNumber, pageCount, list, pageSize, isServerFullTextSearch])
|
|
1079
1087
|
|
|
1080
1088
|
const prevPage = useCallback(() => {
|
|
1081
1089
|
if (isLoading) return
|
|
@@ -1161,12 +1169,15 @@ export function List({
|
|
|
1161
1169
|
}, [table, list, pageSize, isLoading, cursor, prevCursor, pageNumber, constraints, orderByField, orderByDirection])
|
|
1162
1170
|
|
|
1163
1171
|
const canGetPrevPage = useCallback(() => {
|
|
1172
|
+
if (isServerFullTextSearch) {
|
|
1173
|
+
return false
|
|
1174
|
+
}
|
|
1164
1175
|
if (isPreloadCacheEnabled || isServerReadOnly) {
|
|
1165
1176
|
return table.getCanPreviousPage()
|
|
1166
1177
|
} else {
|
|
1167
1178
|
return !isLoadingDebounced && pageNumber > 1
|
|
1168
1179
|
}
|
|
1169
|
-
}, [table, isLoadingDebounced, pageNumber])
|
|
1180
|
+
}, [table, isLoadingDebounced, pageNumber, isServerFullTextSearch])
|
|
1170
1181
|
|
|
1171
1182
|
const onChangePageNumber = useCallback(
|
|
1172
1183
|
(event: React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -2019,7 +2030,12 @@ export function List({
|
|
|
2019
2030
|
</ScrollArea>
|
|
2020
2031
|
</Card>
|
|
2021
2032
|
<div className="flex items-center justify-end space-x-2 py-4 print:hidden">
|
|
2022
|
-
{
|
|
2033
|
+
{isServerFullTextSearch && searchList.length > 0 && (
|
|
2034
|
+
<Badge variant="secondary" className="hidden sm:block">
|
|
2035
|
+
{searchList.length} {searchList.length === 1 ? "result" : "results"}
|
|
2036
|
+
</Badge>
|
|
2037
|
+
)}
|
|
2038
|
+
{pagesLoaded && !isServerFullTextSearch && (
|
|
2023
2039
|
<Badge variant="secondary" className="hidden sm:block">
|
|
2024
2040
|
Page{" "}
|
|
2025
2041
|
{isPreloadCacheEnabled || isServerReadOnly
|
|
@@ -2046,7 +2062,7 @@ export function List({
|
|
|
2046
2062
|
/>
|
|
2047
2063
|
</div>
|
|
2048
2064
|
)}
|
|
2049
|
-
{!isPreloadCacheEnabled && !isServerReadOnly && (
|
|
2065
|
+
{!isServerFullTextSearch && !isPreloadCacheEnabled && !isServerReadOnly && (
|
|
2050
2066
|
<Button
|
|
2051
2067
|
type="button"
|
|
2052
2068
|
variant="outline"
|
|
@@ -2059,19 +2075,28 @@ export function List({
|
|
|
2059
2075
|
Back to start
|
|
2060
2076
|
</Button>
|
|
2061
2077
|
)}
|
|
2062
|
-
{!
|
|
2063
|
-
!
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2078
|
+
{!isServerFullTextSearch &&
|
|
2079
|
+
!(
|
|
2080
|
+
!isPreloadCacheEnabled &&
|
|
2081
|
+
!isServerReadOnly &&
|
|
2082
|
+
typeof sortingField?.sorting === "object" &&
|
|
2083
|
+
sortingField.sorting.direction
|
|
2084
|
+
) && (
|
|
2085
|
+
<Button
|
|
2086
|
+
type="button"
|
|
2087
|
+
variant="outline"
|
|
2088
|
+
size="sm"
|
|
2089
|
+
onClick={prevPage}
|
|
2090
|
+
disabled={!canGetPrevPage()}
|
|
2091
|
+
>
|
|
2092
|
+
Previous
|
|
2093
|
+
</Button>
|
|
2094
|
+
)}
|
|
2095
|
+
{!isServerFullTextSearch && (
|
|
2096
|
+
<Button type="button" variant="outline" size="sm" onClick={nextPage} disabled={!canGetNextPage()}>
|
|
2097
|
+
Next
|
|
2070
2098
|
</Button>
|
|
2071
2099
|
)}
|
|
2072
|
-
<Button type="button" variant="outline" size="sm" onClick={nextPage} disabled={!canGetNextPage()}>
|
|
2073
|
-
Next
|
|
2074
|
-
</Button>
|
|
2075
2100
|
</div>
|
|
2076
2101
|
</>
|
|
2077
2102
|
)
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { CollectionSchema } from "@stoker-platform/types"
|
|
1
|
+
import { Assignable, CollectionSchema, Filter, RelationList, StokerRecord } from "@stoker-platform/types"
|
|
2
2
|
import { getAttributeRestrictions } from "@stoker-platform/utils"
|
|
3
3
|
import { getCurrentUserPermissions } from "@stoker-platform/web-client"
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export interface FilterDisjunctionsOptions {
|
|
6
|
+
assignable?: Assignable
|
|
7
|
+
isAssigning?: boolean
|
|
8
|
+
filters?: Filter[]
|
|
9
|
+
relationList?: RelationList
|
|
10
|
+
relationParent?: StokerRecord
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const getFilterDisjunctions = (collection: CollectionSchema, options?: FilterDisjunctionsOptions) => {
|
|
6
14
|
const permissions = getCurrentUserPermissions()
|
|
7
15
|
if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
|
|
8
16
|
let disjunctions = 0
|
|
@@ -25,5 +33,23 @@ export const getFilterDisjunctions = (collection: CollectionSchema) => {
|
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
}
|
|
36
|
+
if (
|
|
37
|
+
options?.isAssigning &&
|
|
38
|
+
options.relationList &&
|
|
39
|
+
options.relationParent?.id &&
|
|
40
|
+
options.assignable?.includeAssignedInFilters?.length &&
|
|
41
|
+
options.filters
|
|
42
|
+
) {
|
|
43
|
+
const activeOrCount = options.filters.filter(
|
|
44
|
+
(filter) =>
|
|
45
|
+
filter.type === "select" &&
|
|
46
|
+
filter.value &&
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
48
|
+
options.assignable!.includeAssignedInFilters!.includes(filter.field),
|
|
49
|
+
).length
|
|
50
|
+
for (let i = 0; i < activeOrCount; i++) {
|
|
51
|
+
incrementDisjunctions(2)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
28
54
|
return disjunctions
|
|
29
55
|
}
|