@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 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.200",
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.81",
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",
@@ -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 (objectIDs.length > 0) {
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 = typeof updater === "function" ? updater({ pageIndex, pageSize }) : updater
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
- {pagesLoaded && (
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
- !isPreloadCacheEnabled &&
2064
- !isServerReadOnly &&
2065
- typeof sortingField?.sorting === "object" &&
2066
- sortingField.sorting.direction
2067
- ) && (
2068
- <Button type="button" variant="outline" size="sm" onClick={prevPage} disabled={!canGetPrevPage()}>
2069
- Previous
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 const getFilterDisjunctions = (collection: CollectionSchema) => {
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
  }