@budibase/frontend-core 3.41.0 → 3.41.2
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/package.json +2 -2
- package/src/api/agents.ts +20 -5
- package/src/api/chatApps.ts +3 -3
- package/src/api/chatLinks.ts +8 -5
- package/src/api/index.ts +2 -2
- package/src/api/types.ts +1 -1
- package/src/api/{app.ts → workspace.ts} +80 -74
- package/src/components/Chatbox/index.svelte +12 -7
- package/src/components/grid/cells/HeaderCell.svelte +17 -14
- package/src/components/grid/stores/datasources/nonPlus.ts +16 -6
- package/src/components/grid/stores/datasources/table.ts +16 -6
- package/src/components/grid/stores/datasources/viewV2.ts +59 -44
- package/src/components/grid/stores/rows.ts +5 -2
- package/src/components/grid/stores/sort.ts +47 -25
- package/src/fetch/DataFetch.ts +44 -35
- package/src/fetch/TableFetch.ts +28 -6
- package/src/fetch/ViewV2Fetch.ts +38 -8
- package/src/fetch/sort.test.ts +73 -0
- package/src/fetch/sort.ts +70 -0
- package/src/components/grid/layout/RowCount.svelte +0 -9
- package/src/icons/BBAI.svelte +0 -96
- /package/src/utils/validation/yup/{app.ts → workspace.ts} +0 -0
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Row,
|
|
4
4
|
SaveRowRequest,
|
|
5
5
|
SortOrder,
|
|
6
|
+
SortField,
|
|
6
7
|
UIDatasource,
|
|
7
8
|
UpdateViewRequest,
|
|
8
9
|
} from "@budibase/types"
|
|
@@ -12,6 +13,44 @@ import ViewV2Fetch from "../../../../fetch/ViewV2Fetch"
|
|
|
12
13
|
|
|
13
14
|
const SuppressErrors = true
|
|
14
15
|
|
|
16
|
+
type GridSortEntry = {
|
|
17
|
+
column: string
|
|
18
|
+
order: SortOrder
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const normalizeViewSort = (sort?: SortField | SortField[]) => {
|
|
22
|
+
if (!sort) {
|
|
23
|
+
return []
|
|
24
|
+
}
|
|
25
|
+
const sortEntries = Array.isArray(sort) ? sort : [sort]
|
|
26
|
+
return sortEntries
|
|
27
|
+
.filter(sortEntry => sortEntry?.field)
|
|
28
|
+
.map(sortEntry => ({
|
|
29
|
+
column: sortEntry.field,
|
|
30
|
+
order: sortEntry.order || SortOrder.ASCENDING,
|
|
31
|
+
}))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const toViewSort = (sorts: GridSortEntry[]) => {
|
|
35
|
+
const entries = sorts
|
|
36
|
+
.filter(sortEntry => sortEntry.column)
|
|
37
|
+
.map(sortEntry => ({
|
|
38
|
+
field: sortEntry.column,
|
|
39
|
+
order: sortEntry.order || SortOrder.ASCENDING,
|
|
40
|
+
}))
|
|
41
|
+
return entries.length ? entries : undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sortArraysEqual = (a: GridSortEntry[], b: GridSortEntry[]) => {
|
|
45
|
+
if (a.length !== b.length) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
return a.every(
|
|
49
|
+
(entry, index) =>
|
|
50
|
+
entry.column === b[index]?.column && entry.order === b[index]?.order
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
15
54
|
interface ViewActions {
|
|
16
55
|
viewV2: {
|
|
17
56
|
actions: DatasourceViewActions
|
|
@@ -123,10 +162,17 @@ export const initialise = (context: StoreContext) => {
|
|
|
123
162
|
// Reset state for new view
|
|
124
163
|
filter.set(get(initialFilter) ?? undefined)
|
|
125
164
|
inlineFilters.set([])
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
const initialColumn = get(initialSortColumn)
|
|
166
|
+
sort.set(
|
|
167
|
+
initialColumn
|
|
168
|
+
? [
|
|
169
|
+
{
|
|
170
|
+
column: initialColumn,
|
|
171
|
+
order: get(initialSortOrder) || SortOrder.ASCENDING,
|
|
172
|
+
},
|
|
173
|
+
]
|
|
174
|
+
: []
|
|
175
|
+
)
|
|
130
176
|
|
|
131
177
|
// Keep sort and filter state in line with the view definition when in builder
|
|
132
178
|
unsubscribers.push(
|
|
@@ -142,10 +188,7 @@ export const initialise = (context: StoreContext) => {
|
|
|
142
188
|
}
|
|
143
189
|
// Only override sorting if we don't have an initial sort column
|
|
144
190
|
if (!get(initialSortColumn)) {
|
|
145
|
-
sort.set(
|
|
146
|
-
column: $definition.sort?.field,
|
|
147
|
-
order: $definition.sort?.order || SortOrder.ASCENDING,
|
|
148
|
-
})
|
|
191
|
+
sort.set(normalizeViewSort($definition.sort))
|
|
149
192
|
}
|
|
150
193
|
// Only override filter state if we don't have an initial filter
|
|
151
194
|
if (!get(initialFilter)) {
|
|
@@ -154,35 +197,6 @@ export const initialise = (context: StoreContext) => {
|
|
|
154
197
|
})
|
|
155
198
|
)
|
|
156
199
|
|
|
157
|
-
function sortHasChanged(
|
|
158
|
-
newSort: {
|
|
159
|
-
column: string | null | undefined
|
|
160
|
-
order: SortOrder
|
|
161
|
-
},
|
|
162
|
-
existingSort?: {
|
|
163
|
-
field: string
|
|
164
|
-
order?: SortOrder
|
|
165
|
-
}
|
|
166
|
-
) {
|
|
167
|
-
const newColumn = newSort.column ?? null
|
|
168
|
-
const existingColumn = existingSort?.field ?? null
|
|
169
|
-
if (newColumn !== existingColumn) {
|
|
170
|
-
return true
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!newColumn) {
|
|
174
|
-
return false
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const newOrder = newSort.order ?? null
|
|
178
|
-
const existingOrder = existingSort?.order ?? null
|
|
179
|
-
if (newOrder !== existingOrder) {
|
|
180
|
-
return true
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return false
|
|
184
|
-
}
|
|
185
|
-
|
|
186
200
|
// When sorting changes, ensure view definition is kept up to date
|
|
187
201
|
unsubscribers.push(
|
|
188
202
|
sort.subscribe(async $sort => {
|
|
@@ -196,7 +210,8 @@ export const initialise = (context: StoreContext) => {
|
|
|
196
210
|
}
|
|
197
211
|
|
|
198
212
|
// Skip if nothing actually changed
|
|
199
|
-
|
|
213
|
+
const existingSorts = normalizeViewSort($view.sort)
|
|
214
|
+
if (sortArraysEqual($sort, existingSorts)) {
|
|
200
215
|
return
|
|
201
216
|
}
|
|
202
217
|
|
|
@@ -204,10 +219,7 @@ export const initialise = (context: StoreContext) => {
|
|
|
204
219
|
if (get(config).canSaveSchema) {
|
|
205
220
|
await datasource.actions.saveDefinition({
|
|
206
221
|
...$view,
|
|
207
|
-
sort:
|
|
208
|
-
field: $sort.column!,
|
|
209
|
-
order: $sort.order || SortOrder.ASCENDING,
|
|
210
|
-
},
|
|
222
|
+
sort: toViewSort($sort),
|
|
211
223
|
})
|
|
212
224
|
}
|
|
213
225
|
|
|
@@ -217,9 +229,12 @@ export const initialise = (context: StoreContext) => {
|
|
|
217
229
|
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
|
218
230
|
return
|
|
219
231
|
}
|
|
232
|
+
const sorts = $sort.map(sortEntry => ({
|
|
233
|
+
field: sortEntry.column,
|
|
234
|
+
order: sortEntry.order,
|
|
235
|
+
}))
|
|
220
236
|
$fetch.update({
|
|
221
|
-
|
|
222
|
-
sortColumn: $sort.column ?? undefined,
|
|
237
|
+
sorts,
|
|
223
238
|
})
|
|
224
239
|
})
|
|
225
240
|
)
|
|
@@ -236,6 +236,10 @@ export const createActions = (context: StoreContext): RowActionStore => {
|
|
|
236
236
|
await tick()
|
|
237
237
|
const $allFilters = get(allFilters)
|
|
238
238
|
const $sort = get(sort)
|
|
239
|
+
const sorts = $sort.map(sortEntry => ({
|
|
240
|
+
field: sortEntry.column,
|
|
241
|
+
order: sortEntry.order,
|
|
242
|
+
}))
|
|
239
243
|
|
|
240
244
|
// Create new fetch model
|
|
241
245
|
const newFetch = fetchData({
|
|
@@ -243,8 +247,7 @@ export const createActions = (context: StoreContext): RowActionStore => {
|
|
|
243
247
|
datasource: $datasource,
|
|
244
248
|
options: {
|
|
245
249
|
filter: $allFilters,
|
|
246
|
-
|
|
247
|
-
sortOrder: $sort.order,
|
|
250
|
+
sorts,
|
|
248
251
|
limit: RowPageSize,
|
|
249
252
|
paginate: true,
|
|
250
253
|
|
|
@@ -3,11 +3,13 @@ import { memo } from "../../../utils"
|
|
|
3
3
|
import { SortOrder } from "@budibase/types"
|
|
4
4
|
import { Store as StoreContext } from "."
|
|
5
5
|
|
|
6
|
+
export interface SortEntry {
|
|
7
|
+
column: string
|
|
8
|
+
order: SortOrder
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
interface SortStore {
|
|
7
|
-
sort: Writable<
|
|
8
|
-
column: string | null | undefined
|
|
9
|
-
order: SortOrder
|
|
10
|
-
}>
|
|
12
|
+
sort: Writable<SortEntry[]>
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export type Store = SortStore
|
|
@@ -17,10 +19,16 @@ export const createStores = (context: StoreContext): SortStore => {
|
|
|
17
19
|
const $props = get(props)
|
|
18
20
|
|
|
19
21
|
// Initialise to default props
|
|
20
|
-
const sort = memo(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const sort = memo<SortEntry[]>(
|
|
23
|
+
$props.initialSortColumn
|
|
24
|
+
? [
|
|
25
|
+
{
|
|
26
|
+
column: $props.initialSortColumn,
|
|
27
|
+
order: $props.initialSortOrder || SortOrder.ASCENDING,
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
: []
|
|
31
|
+
)
|
|
24
32
|
|
|
25
33
|
return {
|
|
26
34
|
sort,
|
|
@@ -32,30 +40,44 @@ export const initialise = (context: StoreContext) => {
|
|
|
32
40
|
|
|
33
41
|
// Reset sort when initial sort props change
|
|
34
42
|
initialSortColumn.subscribe(newSortColumn => {
|
|
35
|
-
sort.
|
|
43
|
+
sort.set(
|
|
44
|
+
newSortColumn
|
|
45
|
+
? [
|
|
46
|
+
{
|
|
47
|
+
column: newSortColumn,
|
|
48
|
+
order: get(initialSortOrder) || SortOrder.ASCENDING,
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
: []
|
|
52
|
+
)
|
|
36
53
|
})
|
|
37
54
|
initialSortOrder.subscribe(newSortOrder => {
|
|
38
|
-
sort.update(state =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
sort.update(state => {
|
|
56
|
+
if (!state.length) {
|
|
57
|
+
return state
|
|
58
|
+
}
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
...state[0],
|
|
62
|
+
order: newSortOrder || SortOrder.ASCENDING,
|
|
63
|
+
},
|
|
64
|
+
...state.slice(1),
|
|
65
|
+
]
|
|
66
|
+
})
|
|
42
67
|
})
|
|
43
68
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
if (!$
|
|
47
|
-
return
|
|
69
|
+
// Remove any sort columns that don't exist in the schema
|
|
70
|
+
const validSorts = derived([sort, schema], ([$sort, $schema]) => {
|
|
71
|
+
if (!$schema) {
|
|
72
|
+
return $sort
|
|
48
73
|
}
|
|
49
|
-
return $schema[
|
|
74
|
+
return $sort.filter(sortEntry => $schema[sortEntry.column] != null)
|
|
50
75
|
})
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
sort.set(
|
|
56
|
-
column: null,
|
|
57
|
-
order: SortOrder.ASCENDING,
|
|
58
|
-
})
|
|
77
|
+
validSorts.subscribe($validSorts => {
|
|
78
|
+
const current = get(sort)
|
|
79
|
+
if (JSON.stringify($validSorts) !== JSON.stringify(current)) {
|
|
80
|
+
sort.set($validSorts)
|
|
59
81
|
}
|
|
60
82
|
})
|
|
61
83
|
}
|
package/src/fetch/DataFetch.ts
CHANGED
|
@@ -13,8 +13,9 @@ import { DataFetchType } from "."
|
|
|
13
13
|
import { APIClient } from "../api/types"
|
|
14
14
|
import { QueryUtils } from "../utils"
|
|
15
15
|
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
|
16
|
+
import { normalizeSorts } from "./sort"
|
|
16
17
|
|
|
17
|
-
const { buildQuery, limit: queryLimit, runQuery, sort } = QueryUtils
|
|
18
|
+
const { buildQuery, limit: queryLimit, multiSort, runQuery, sort } = QueryUtils
|
|
18
19
|
|
|
19
20
|
interface DataFetchStore<TDefinition, TQuery, TRow extends Row> {
|
|
20
21
|
rows: TRow[]
|
|
@@ -109,6 +110,7 @@ export default abstract class BaseDataFetch<
|
|
|
109
110
|
sortColumn: null,
|
|
110
111
|
sortOrder: SortOrder.ASCENDING,
|
|
111
112
|
sortType: null,
|
|
113
|
+
sorts: null,
|
|
112
114
|
|
|
113
115
|
// Pagination config
|
|
114
116
|
paginate: true,
|
|
@@ -219,42 +221,22 @@ export default abstract class BaseDataFetch<
|
|
|
219
221
|
}
|
|
220
222
|
schema = this.enrichSchema(schema)
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// If no sort column, get the default column for this datasource
|
|
228
|
-
if (!this.options.sortColumn) {
|
|
229
|
-
this.options.sortColumn = this.getDefaultSortColumn(definition, schema)
|
|
230
|
-
}
|
|
224
|
+
const normalizedSorts = normalizeSorts(
|
|
225
|
+
this.options,
|
|
226
|
+
schema,
|
|
227
|
+
this.getDefaultSortColumn(definition, schema)
|
|
228
|
+
)
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (!this.options.sortColumn) {
|
|
230
|
+
if (!normalizedSorts.length) {
|
|
231
|
+
this.options.sortColumn = null
|
|
235
232
|
this.options.sortOrder = SortOrder.ASCENDING
|
|
236
233
|
this.options.sortType = null
|
|
234
|
+
this.options.sorts = []
|
|
237
235
|
} else {
|
|
238
|
-
|
|
239
|
-
this.options.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
fieldSchema?.type === FieldType.NUMBER ||
|
|
243
|
-
fieldSchema?.type === FieldType.BIGINT ||
|
|
244
|
-
fieldSchema?.responseType === FieldType.NUMBER ||
|
|
245
|
-
("calculationType" in fieldSchema && fieldSchema?.calculationType)
|
|
246
|
-
) {
|
|
247
|
-
this.options.sortType = SortType.NUMBER
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// If no sort order, default to ascending
|
|
251
|
-
if (!this.options.sortOrder) {
|
|
252
|
-
this.options.sortOrder = SortOrder.ASCENDING
|
|
253
|
-
} else {
|
|
254
|
-
// Ensure sortOrder matches the enum
|
|
255
|
-
this.options.sortOrder =
|
|
256
|
-
this.options.sortOrder.toLowerCase() as SortOrder
|
|
257
|
-
}
|
|
236
|
+
this.options.sorts = normalizedSorts
|
|
237
|
+
this.options.sortColumn = normalizedSorts[0]?.field ?? null
|
|
238
|
+
this.options.sortOrder = normalizedSorts[0]?.order || SortOrder.ASCENDING
|
|
239
|
+
this.options.sortType = normalizedSorts[0]?.type || null
|
|
258
240
|
}
|
|
259
241
|
|
|
260
242
|
// Build the query
|
|
@@ -298,6 +280,7 @@ export default abstract class BaseDataFetch<
|
|
|
298
280
|
sortColumn,
|
|
299
281
|
sortOrder,
|
|
300
282
|
sortType,
|
|
283
|
+
sorts,
|
|
301
284
|
limit,
|
|
302
285
|
clientSideSearching,
|
|
303
286
|
clientSideSorting,
|
|
@@ -314,8 +297,34 @@ export default abstract class BaseDataFetch<
|
|
|
314
297
|
}
|
|
315
298
|
|
|
316
299
|
// If we don't support sorting, do a client-side sort
|
|
317
|
-
if (!this.features.supportsSort && clientSideSorting
|
|
318
|
-
|
|
300
|
+
if (!this.features.supportsSort && clientSideSorting) {
|
|
301
|
+
const activeSorts = (sorts || []).filter(
|
|
302
|
+
sortEntry => sortEntry?.field && sortEntry?.order
|
|
303
|
+
)
|
|
304
|
+
if (activeSorts.length > 1) {
|
|
305
|
+
rows = multiSort(
|
|
306
|
+
rows,
|
|
307
|
+
Object.fromEntries(
|
|
308
|
+
activeSorts.map(sortEntry => [
|
|
309
|
+
sortEntry.field,
|
|
310
|
+
{
|
|
311
|
+
direction: sortEntry.order || SortOrder.ASCENDING,
|
|
312
|
+
type: sortEntry.type,
|
|
313
|
+
},
|
|
314
|
+
])
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
} else if (activeSorts.length === 1) {
|
|
318
|
+
const [entry] = activeSorts
|
|
319
|
+
rows = sort(
|
|
320
|
+
rows,
|
|
321
|
+
entry.field as any,
|
|
322
|
+
entry.order || sortOrder,
|
|
323
|
+
entry.type || sortType || SortType.STRING
|
|
324
|
+
)
|
|
325
|
+
} else if (sortType && sortColumn) {
|
|
326
|
+
rows = sort(rows, sortColumn as any, sortOrder, sortType)
|
|
327
|
+
}
|
|
319
328
|
}
|
|
320
329
|
|
|
321
330
|
// If we don't support pagination, do a client-side limit
|
package/src/fetch/TableFetch.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { get } from "svelte/store"
|
|
2
2
|
import BaseDataFetch from "./DataFetch"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
SortField,
|
|
5
|
+
SortJson,
|
|
6
|
+
SortOrder,
|
|
7
|
+
SortType,
|
|
8
|
+
Table,
|
|
9
|
+
TableDatasource,
|
|
10
|
+
} from "@budibase/types"
|
|
11
|
+
|
|
12
|
+
const buildSortPayload = (sorts?: SortField[] | null) => {
|
|
13
|
+
if (!sorts?.length) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
const sort: SortJson = {}
|
|
17
|
+
sorts.forEach(sortEntry => {
|
|
18
|
+
if (!sortEntry?.field) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
sort[sortEntry.field] = {
|
|
22
|
+
direction: sortEntry.order || SortOrder.ASCENDING,
|
|
23
|
+
...(sortEntry.type ? { type: sortEntry.type as SortType } : {}),
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
return Object.keys(sort).length ? sort : undefined
|
|
27
|
+
}
|
|
4
28
|
|
|
5
29
|
export default class TableFetch extends BaseDataFetch<TableDatasource, Table> {
|
|
6
30
|
async determineFeatureFlags() {
|
|
@@ -29,19 +53,17 @@ export default class TableFetch extends BaseDataFetch<TableDatasource, Table> {
|
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
async getData() {
|
|
32
|
-
const { datasource, limit,
|
|
33
|
-
this.options
|
|
56
|
+
const { datasource, limit, sorts, paginate } = this.options
|
|
34
57
|
const { tableId } = datasource
|
|
35
58
|
const { cursor, query } = get(this.store)
|
|
59
|
+
const sort = buildSortPayload(sorts)
|
|
36
60
|
|
|
37
61
|
// Search table
|
|
38
62
|
try {
|
|
39
63
|
const res = await this.API.searchTable(tableId, {
|
|
40
64
|
query,
|
|
41
65
|
limit,
|
|
42
|
-
sort
|
|
43
|
-
sortOrder: sortOrder ?? SortOrder.ASCENDING,
|
|
44
|
-
sortType,
|
|
66
|
+
sort,
|
|
45
67
|
paginate,
|
|
46
68
|
bookmark: cursor,
|
|
47
69
|
})
|
package/src/fetch/ViewV2Fetch.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SortOrder,
|
|
3
|
+
SortField,
|
|
4
|
+
SortJson,
|
|
5
|
+
SortType,
|
|
3
6
|
ViewDatasource,
|
|
4
7
|
ViewV2Enriched,
|
|
5
8
|
ViewV2Type,
|
|
@@ -8,6 +11,23 @@ import BaseDataFetch from "./DataFetch"
|
|
|
8
11
|
import { get } from "svelte/store"
|
|
9
12
|
import { helpers } from "@budibase/shared-core"
|
|
10
13
|
|
|
14
|
+
const buildSortPayload = (sorts?: SortField[] | null) => {
|
|
15
|
+
if (!sorts?.length) {
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
const sort: SortJson = {}
|
|
19
|
+
sorts.forEach(sortEntry => {
|
|
20
|
+
if (!sortEntry?.field) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
sort[sortEntry.field] = {
|
|
24
|
+
direction: sortEntry.order || SortOrder.ASCENDING,
|
|
25
|
+
...(sortEntry.type ? { type: sortEntry.type as SortType } : {}),
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
return Object.keys(sort).length ? sort : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
export default class ViewV2Fetch extends BaseDataFetch<
|
|
12
32
|
ViewDatasource,
|
|
13
33
|
ViewV2Enriched
|
|
@@ -40,8 +60,15 @@ export default class ViewV2Fetch extends BaseDataFetch<
|
|
|
40
60
|
}
|
|
41
61
|
|
|
42
62
|
async getData() {
|
|
43
|
-
const {
|
|
44
|
-
|
|
63
|
+
const {
|
|
64
|
+
datasource,
|
|
65
|
+
limit,
|
|
66
|
+
sortColumn,
|
|
67
|
+
sortOrder,
|
|
68
|
+
sortType,
|
|
69
|
+
sorts,
|
|
70
|
+
paginate,
|
|
71
|
+
} = this.options
|
|
45
72
|
const { cursor, query, definition } = get(this.store)
|
|
46
73
|
|
|
47
74
|
// If this is a calculation view and we have no calculations, return nothing
|
|
@@ -62,20 +89,23 @@ export default class ViewV2Fetch extends BaseDataFetch<
|
|
|
62
89
|
// If sort/filter params are not defined, update options to store the
|
|
63
90
|
// params built in to this view. This ensures that we can accurately
|
|
64
91
|
// compare old and new params and skip a redundant API call.
|
|
65
|
-
if (!sortColumn && definition?.sort?.
|
|
66
|
-
this.options.
|
|
67
|
-
|
|
92
|
+
if (!sortColumn && definition?.sort && !sorts?.length) {
|
|
93
|
+
this.options.sorts = (
|
|
94
|
+
Array.isArray(definition.sort) ? definition.sort : [definition.sort]
|
|
95
|
+
).filter(sortEntry => sortEntry?.field)
|
|
68
96
|
}
|
|
69
97
|
|
|
98
|
+
const sortPayload = buildSortPayload(this.options.sorts)
|
|
99
|
+
|
|
70
100
|
try {
|
|
71
101
|
const request = {
|
|
72
102
|
query,
|
|
73
103
|
paginate,
|
|
74
104
|
limit,
|
|
75
105
|
bookmark: cursor,
|
|
76
|
-
sort: sortColumn,
|
|
77
|
-
sortOrder: sortOrder,
|
|
78
|
-
sortType,
|
|
106
|
+
sort: sortPayload || sortColumn,
|
|
107
|
+
sortOrder: sortPayload ? undefined : sortOrder,
|
|
108
|
+
sortType: sortPayload ? undefined : sortType,
|
|
79
109
|
}
|
|
80
110
|
if (paginate) {
|
|
81
111
|
const res = await this.API.viewV2.fetch(datasource.id, {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { FieldType, SortOrder, SortType } from "@budibase/types"
|
|
3
|
+
import type { TableSchema } from "@budibase/types"
|
|
4
|
+
import { normalizeSorts } from "./sort"
|
|
5
|
+
|
|
6
|
+
describe("sort normalization", () => {
|
|
7
|
+
it("does not restore the single-sort options after sorts are explicitly cleared", () => {
|
|
8
|
+
const schema: TableSchema = {
|
|
9
|
+
name: {
|
|
10
|
+
name: "name",
|
|
11
|
+
type: FieldType.STRING,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
expect(
|
|
16
|
+
normalizeSorts(
|
|
17
|
+
{
|
|
18
|
+
sorts: [],
|
|
19
|
+
sortColumn: "name",
|
|
20
|
+
sortOrder: SortOrder.DESCENDING,
|
|
21
|
+
sortType: null,
|
|
22
|
+
},
|
|
23
|
+
schema,
|
|
24
|
+
null
|
|
25
|
+
)
|
|
26
|
+
).toEqual([])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("normalizes every configured sort", () => {
|
|
30
|
+
const schema: TableSchema = {
|
|
31
|
+
name: {
|
|
32
|
+
name: "name",
|
|
33
|
+
type: FieldType.STRING,
|
|
34
|
+
},
|
|
35
|
+
count: {
|
|
36
|
+
name: "count",
|
|
37
|
+
type: FieldType.NUMBER,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(
|
|
42
|
+
normalizeSorts(
|
|
43
|
+
{
|
|
44
|
+
sorts: [
|
|
45
|
+
{
|
|
46
|
+
field: "name",
|
|
47
|
+
order: SortOrder.DESCENDING,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
field: "count",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
sortColumn: null,
|
|
54
|
+
sortOrder: SortOrder.ASCENDING,
|
|
55
|
+
sortType: null,
|
|
56
|
+
},
|
|
57
|
+
schema,
|
|
58
|
+
null
|
|
59
|
+
)
|
|
60
|
+
).toEqual([
|
|
61
|
+
{
|
|
62
|
+
field: "name",
|
|
63
|
+
order: SortOrder.DESCENDING,
|
|
64
|
+
type: SortType.STRING,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
field: "count",
|
|
68
|
+
order: SortOrder.ASCENDING,
|
|
69
|
+
type: SortType.NUMBER,
|
|
70
|
+
},
|
|
71
|
+
])
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FieldType, SortOrder, SortType } from "@budibase/types"
|
|
2
|
+
import type { SortField, TableSchema } from "@budibase/types"
|
|
3
|
+
|
|
4
|
+
interface SortNormalizationOptions {
|
|
5
|
+
sorts?: SortField[] | null
|
|
6
|
+
sortColumn: string | null
|
|
7
|
+
sortOrder: SortOrder
|
|
8
|
+
sortType: SortType | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getSortType = (schema: TableSchema, field: string) => {
|
|
12
|
+
const fieldSchema = schema[field]
|
|
13
|
+
if (
|
|
14
|
+
fieldSchema?.type === FieldType.NUMBER ||
|
|
15
|
+
fieldSchema?.type === FieldType.BIGINT ||
|
|
16
|
+
("responseType" in fieldSchema &&
|
|
17
|
+
fieldSchema.responseType === FieldType.NUMBER) ||
|
|
18
|
+
("calculationType" in fieldSchema && fieldSchema.calculationType)
|
|
19
|
+
) {
|
|
20
|
+
return SortType.NUMBER
|
|
21
|
+
}
|
|
22
|
+
return SortType.STRING
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalizeSortOrder = (order?: SortOrder) =>
|
|
26
|
+
order?.toLowerCase() === SortOrder.DESCENDING
|
|
27
|
+
? SortOrder.DESCENDING
|
|
28
|
+
: SortOrder.ASCENDING
|
|
29
|
+
|
|
30
|
+
export const normalizeSorts = (
|
|
31
|
+
options: SortNormalizationOptions,
|
|
32
|
+
schema: TableSchema,
|
|
33
|
+
defaultSortColumn: string | null
|
|
34
|
+
): SortField[] => {
|
|
35
|
+
let normalizedSorts: SortField[] =
|
|
36
|
+
options.sorts
|
|
37
|
+
?.filter(sortEntry => sortEntry?.field && schema[sortEntry.field])
|
|
38
|
+
.map(
|
|
39
|
+
sortEntry =>
|
|
40
|
+
({
|
|
41
|
+
...sortEntry,
|
|
42
|
+
order: normalizeSortOrder(sortEntry.order),
|
|
43
|
+
type: sortEntry.type || getSortType(schema, sortEntry.field),
|
|
44
|
+
}) satisfies SortField
|
|
45
|
+
) || []
|
|
46
|
+
|
|
47
|
+
if (!normalizedSorts.length && options.sorts == null && options.sortColumn) {
|
|
48
|
+
if (schema[options.sortColumn]) {
|
|
49
|
+
normalizedSorts = [
|
|
50
|
+
{
|
|
51
|
+
field: options.sortColumn,
|
|
52
|
+
order: normalizeSortOrder(options.sortOrder),
|
|
53
|
+
type: options.sortType || getSortType(schema, options.sortColumn),
|
|
54
|
+
} satisfies SortField,
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!normalizedSorts.length && defaultSortColumn) {
|
|
60
|
+
normalizedSorts = [
|
|
61
|
+
{
|
|
62
|
+
field: defaultSortColumn,
|
|
63
|
+
order: SortOrder.ASCENDING,
|
|
64
|
+
type: getSortType(schema, defaultSortColumn),
|
|
65
|
+
} satisfies SortField,
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return normalizedSorts
|
|
70
|
+
}
|