Initial row conversion

This commit is contained in:
Adria Navarro 2024-12-27 17:38:43 +01:00
parent f92eb73e11
commit 0c53610231
3 changed files with 113 additions and 44 deletions

View File

@ -10,8 +10,8 @@ interface DatasourceBaseActions<
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
> { > {
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void> saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
addRow: (row: SaveRowRequest) => Promise<UIRow | void> addRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
updateRow: (row: SaveRowRequest) => Promise<UIRow | void> updateRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
deleteRows: (rows: UIRow[]) => Promise<void> deleteRows: (rows: UIRow[]) => Promise<void>
getRow: (id: string) => Promise<UIRow | void> getRow: (id: string) => Promise<UIRow | void>
isDatasourceValid: (datasource: UIDatasource) => boolean | void isDatasourceValid: (datasource: UIDatasource) => boolean | void

View File

@ -69,12 +69,10 @@ export type Store = BaseStore &
Clipboard.Store & Clipboard.Store &
Scroll.Store & { Scroll.Store & {
// TODO while typing the rest of stores // TODO while typing the rest of stores
fetch: Writable<any>
sort: Writable<any> sort: Writable<any>
initialFilter: Writable<any> initialFilter: Writable<any>
initialSortColumn: Writable<any> initialSortColumn: Writable<any>
initialSortOrder: Writable<any> initialSortOrder: Writable<any>
rows: Writable<any> & { actions: any }
subscribe: any subscribe: any
config: Writable<any> config: Writable<any>
dispatch: (event: string, data: any) => any dispatch: (event: string, data: any) => any
@ -82,13 +80,11 @@ export type Store = BaseStore &
schemaOverrides: Writable<any> schemaOverrides: Writable<any>
gridID: string gridID: string
props: Writable<any> props: Writable<any>
rowLookupMap: Writable<any>
width: Writable<number> width: Writable<number>
fixedRowHeight: Writable<number> fixedRowHeight: Writable<number>
rowChangeCache: Readable<any>
bounds: Readable<any> bounds: Readable<any>
height: Readable<number> height: Readable<number>
} } & Rows.Store
export const attachStores = (context: Store): Store => { export const attachStores = (context: Store): Store => {
// Atomic store creation // Atomic store creation

View File

@ -1,4 +1,4 @@
import { writable, derived, get } from "svelte/store" import { writable, derived, get, Writable, Readable } from "svelte/store"
import { fetchData } from "../../../fetch" import { fetchData } from "../../../fetch"
import { NewRowID, RowPageSize } from "../lib/constants" import { NewRowID, RowPageSize } from "../lib/constants"
import { import {
@ -10,10 +10,49 @@ import {
import { tick } from "svelte" import { tick } from "svelte"
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
import { sleep } from "../../../utils/utils" import { sleep } from "../../../utils/utils"
import { FieldType } from "@budibase/types" import { FieldType, Row, UIRow } from "@budibase/types"
import { getRelatedTableValues } from "../../../utils" import { getRelatedTableValues } from "../../../utils"
import { Store as StoreContext } from "."
export const createStores = () => { interface RowStore {
rows: Writable<any[]>
fetch: Writable<any>
loaded: Writable<any>
refreshing: Writable<any>
loading: Writable<any>
rowChangeCache: Writable<any>
inProgressChanges: Writable<any>
hasNextPage: Writable<any>
error: Writable<any>
}
interface RowDerivedStore {
rows: RowStore["rows"]
rowLookupMap: Readable<any>
}
interface RowActionStore {
rows: RowStore["rows"] & {
actions: {
addRow: any
duplicateRow: any
bulkDuplicate: any
updateValue: any
applyRowChanges: any
deleteRows: any
loadNextPage: any
refreshRow: any
replaceRow: any
refreshData: any
cleanRow: any
bulkUpdate: any
}
}
}
export type Store = RowStore & RowDerivedStore & RowActionStore
export const createStores = (): RowStore => {
const rows = writable([]) const rows = writable([])
const loading = writable(false) const loading = writable(false)
const loaded = writable(false) const loaded = writable(false)
@ -47,7 +86,7 @@ export const createStores = () => {
} }
} }
export const deriveStores = context => { export const deriveStores = (context: StoreContext): RowDerivedStore => {
const { rows, enrichedSchema } = context const { rows, enrichedSchema } = context
// Enrich rows with an index property and any pending changes // Enrich rows with an index property and any pending changes
@ -60,8 +99,8 @@ export const deriveStores = context => {
return $rows.map((row, idx) => ({ return $rows.map((row, idx) => ({
...row, ...row,
__idx: idx, __idx: idx,
...customColumns.reduce((map, column) => { ...customColumns.reduce((map: any, column: any) => {
const fromField = $enrichedSchema[column.related.field] const fromField = $enrichedSchema![column.related!.field]
map[column.name] = getRelatedTableValues(row, column, fromField) map[column.name] = getRelatedTableValues(row, column, fromField)
return map return map
}, {}), }, {}),
@ -71,7 +110,7 @@ export const deriveStores = context => {
// Generate a lookup map to quick find a row by ID // Generate a lookup map to quick find a row by ID
const rowLookupMap = derived(enrichedRows, $enrichedRows => { const rowLookupMap = derived(enrichedRows, $enrichedRows => {
let map = {} let map: Record<string, UIRow> = {}
for (let i = 0; i < $enrichedRows.length; i++) { for (let i = 0; i < $enrichedRows.length; i++) {
map[$enrichedRows[i]._id] = $enrichedRows[i] map[$enrichedRows[i]._id] = $enrichedRows[i]
} }
@ -87,7 +126,7 @@ export const deriveStores = context => {
} }
} }
export const createActions = context => { export const createActions = (context: StoreContext): RowActionStore => {
const { const {
rows, rows,
rowLookupMap, rowLookupMap,
@ -114,11 +153,11 @@ export const createActions = context => {
const instanceLoaded = writable(false) const instanceLoaded = writable(false)
// Local cache of row IDs to speed up checking if a row exists // Local cache of row IDs to speed up checking if a row exists
let rowCacheMap = {} let rowCacheMap: Record<string, boolean> = {}
// Reset everything when datasource changes // Reset everything when datasource changes
let unsubscribe = null let unsubscribe: (() => void) | null = null
let lastResetKey = null let lastResetKey: string | null = null
datasource.subscribe(async $datasource => { datasource.subscribe(async $datasource => {
// Unsub from previous fetch if one exists // Unsub from previous fetch if one exists
unsubscribe?.() unsubscribe?.()
@ -157,7 +196,7 @@ export const createActions = context => {
}) })
// Subscribe to changes of this fetch model // Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe(async $fetch => { unsubscribe = newFetch.subscribe(async ($fetch: any) => {
if ($fetch.error) { if ($fetch.error) {
// Present a helpful error to the user // Present a helpful error to the user
let message = "An unknown error occurred" let message = "An unknown error occurred"
@ -214,7 +253,7 @@ export const createActions = context => {
// Handles validation errors from the rows API and updates local validation // Handles validation errors from the rows API and updates local validation
// state, storing error messages against relevant cells // state, storing error messages against relevant cells
const handleValidationError = (rowId, error) => { const handleValidationError = (rowId: string, error: any) => {
let errorString let errorString
if (typeof error === "string") { if (typeof error === "string") {
errorString = error errorString = error
@ -287,9 +326,19 @@ export const createActions = context => {
} }
// Adds a new row // Adds a new row
const addRow = async ({ row, idx, bubble = false, notify = true }) => { const addRow = async ({
row,
idx,
bubble = false,
notify = true,
}: {
row: Row
idx: number
bubble: boolean
notify: boolean
}) => {
try { try {
const newRow = await datasource.actions.addRow(row) const newRow = (await datasource.actions.addRow(row))!
// Update state // Update state
if (idx != null) { if (idx != null) {
@ -317,7 +366,7 @@ export const createActions = context => {
} }
// Duplicates a row, inserting the duplicate row after the existing one // Duplicates a row, inserting the duplicate row after the existing one
const duplicateRow = async row => { const duplicateRow = async (row: UIRow) => {
let clone = cleanRow(row) let clone = cleanRow(row)
delete clone._id delete clone._id
delete clone._rev delete clone._rev
@ -337,10 +386,13 @@ export const createActions = context => {
} }
// Duplicates multiple rows, inserting them after the last source row // Duplicates multiple rows, inserting them after the last source row
const bulkDuplicate = async (rowsToDupe, progressCallback) => { const bulkDuplicate = async (
rowsToDupe: UIRow[],
progressCallback: (any: number) => void
) => {
// Find index of last row // Find index of last row
const $rowLookupMap = get(rowLookupMap) const $rowLookupMap = get(rowLookupMap)
const indices = rowsToDupe.map(row => $rowLookupMap[row._id]?.__idx) const indices = rowsToDupe.map(row => $rowLookupMap[row._id!]?.__idx)
const index = Math.max(...indices) const index = Math.max(...indices)
const count = rowsToDupe.length const count = rowsToDupe.length
@ -357,8 +409,9 @@ export const createActions = context => {
let failed = 0 let failed = 0
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
try { try {
saved.push(await datasource.actions.addRow(clones[i])) const newRow = (await datasource.actions.addRow(clones[i]))!
rowCacheMap[saved._id] = true saved.push(newRow)
rowCacheMap[newRow._id] = true
await sleep(50) // Small sleep to ensure we avoid rate limiting await sleep(50) // Small sleep to ensure we avoid rate limiting
} catch (error) { } catch (error) {
failed++ failed++
@ -370,7 +423,7 @@ export const createActions = context => {
// Add to state // Add to state
if (saved.length) { if (saved.length) {
rows.update(state => { rows.update(state => {
return state.toSpliced(index + 1, 0, ...saved) return state.splice(index + 1, 0, ...saved)
}) })
} }
@ -385,7 +438,7 @@ export const createActions = context => {
// Replaces a row in state with the newly defined row, handling updates, // Replaces a row in state with the newly defined row, handling updates,
// addition and deletion // addition and deletion
const replaceRow = (id, row) => { const replaceRow = (id: string, row: UIRow | undefined) => {
// Get index of row to check if it exists // Get index of row to check if it exists
const $rows = get(rows) const $rows = get(rows)
const $rowLookupMap = get(rowLookupMap) const $rowLookupMap = get(rowLookupMap)
@ -410,10 +463,10 @@ export const createActions = context => {
} }
// Refreshes a specific row // Refreshes a specific row
const refreshRow = async id => { const refreshRow = async (id: string) => {
try { try {
const row = await datasource.actions.getRow(id) const row = await datasource.actions.getRow(id)
replaceRow(id, row) replaceRow(id, row!)
} catch { } catch {
// Do nothing - we probably just don't support refreshing individual rows // Do nothing - we probably just don't support refreshing individual rows
} }
@ -425,7 +478,7 @@ export const createActions = context => {
} }
// Checks if a changeset for a row actually mutates the row or not // Checks if a changeset for a row actually mutates the row or not
const changesAreValid = (row, changes) => { const changesAreValid = (row: UIRow, changes: Record<string, any>) => {
const columns = Object.keys(changes || {}) const columns = Object.keys(changes || {})
if (!row || !columns.length) { if (!row || !columns.length) {
return false return false
@ -437,7 +490,7 @@ export const createActions = context => {
// Patches a row with some changes in local state, and returns whether a // Patches a row with some changes in local state, and returns whether a
// valid pending change was made or not // valid pending change was made or not
const stashRowChanges = (rowId, changes) => { const stashRowChanges = (rowId: string, changes: Record<string, any>) => {
const $rowLookupMap = get(rowLookupMap) const $rowLookupMap = get(rowLookupMap)
const $columnLookupMap = get(columnLookupMap) const $columnLookupMap = get(columnLookupMap)
const row = $rowLookupMap[rowId] const row = $rowLookupMap[rowId]
@ -477,13 +530,18 @@ export const createActions = context => {
changes = null, changes = null,
updateState = true, updateState = true,
handleErrors = true, handleErrors = true,
}: {
rowId: string
changes?: any
updateState?: boolean
handleErrors?: boolean
}) => { }) => {
const $rowLookupMap = get(rowLookupMap) const $rowLookupMap = get(rowLookupMap)
const row = $rowLookupMap[rowId] const row = $rowLookupMap[rowId]
if (row == null) { if (row == null) {
return return
} }
let savedRow let savedRow: UIRow | undefined = undefined
// Save change // Save change
try { try {
@ -496,7 +554,7 @@ export const createActions = context => {
// Update row // Update row
const stashedChanges = get(rowChangeCache)[rowId] const stashedChanges = get(rowChangeCache)[rowId]
const newRow = { ...cleanRow(row), ...stashedChanges, ...changes } const newRow = { ...cleanRow(row), ...stashedChanges, ...changes }
savedRow = await datasource.actions.updateRow(newRow) savedRow = (await datasource.actions.updateRow(newRow))!
// Update row state after a successful change // Update row state after a successful change
if (savedRow?._id) { if (savedRow?._id) {
@ -537,14 +595,27 @@ export const createActions = context => {
} }
// Updates a value of a row // Updates a value of a row
const updateValue = async ({ rowId, column, value, apply = true }) => { const updateValue = async ({
rowId,
column,
value,
apply = true,
}: {
rowId: string
column: any
value: any
apply: boolean
}) => {
const success = stashRowChanges(rowId, { [column]: value }) const success = stashRowChanges(rowId, { [column]: value })
if (success && apply) { if (success && apply) {
await applyRowChanges({ rowId }) await applyRowChanges({ rowId })
} }
} }
const bulkUpdate = async (changeMap, progressCallback) => { const bulkUpdate = async (
changeMap: Record<string, any>,
progressCallback: (any: number) => void
) => {
const rowIds = Object.keys(changeMap || {}) const rowIds = Object.keys(changeMap || {})
const count = rowIds.length const count = rowIds.length
if (!count) { if (!count) {
@ -613,7 +684,7 @@ export const createActions = context => {
} }
// Deletes an array of rows // Deletes an array of rows
const deleteRows = async rowsToDelete => { const deleteRows = async (rowsToDelete: UIRow[]) => {
if (!rowsToDelete?.length) { if (!rowsToDelete?.length) {
return return
} }
@ -628,7 +699,7 @@ export const createActions = context => {
// Local handler to process new rows inside the fetch, and append any new // Local handler to process new rows inside the fetch, and append any new
// rows to state that we haven't encountered before // rows to state that we haven't encountered before
const handleNewRows = (newRows, resetRows) => { const handleNewRows = (newRows: UIRow[], resetRows?: boolean) => {
if (resetRows) { if (resetRows) {
rowCacheMap = {} rowCacheMap = {}
} }
@ -658,7 +729,7 @@ export const createActions = context => {
} }
// Local handler to remove rows from state // Local handler to remove rows from state
const handleRemoveRows = rowsToRemove => { const handleRemoveRows = (rowsToRemove: UIRow[]) => {
const deletedIds = rowsToRemove.map(row => row._id) const deletedIds = rowsToRemove.map(row => row._id)
// We deliberately do not remove IDs from the cache map as the data may // We deliberately do not remove IDs from the cache map as the data may
@ -675,11 +746,11 @@ export const createActions = context => {
// Cleans a row by removing any internal grid metadata from it. // Cleans a row by removing any internal grid metadata from it.
// Call this before passing a row to any sort of external flow. // Call this before passing a row to any sort of external flow.
const cleanRow = row => { const cleanRow = (row: Row) => {
let clone = { ...row } let clone = { ...row }
delete clone.__idx delete clone.__idx
delete clone.__metadata delete clone.__metadata
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id)) { if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) {
delete clone._id delete clone._id
} }
return clone return clone
@ -706,7 +777,7 @@ export const createActions = context => {
} }
} }
export const initialise = context => { export const initialise = (context: StoreContext) => {
const { const {
rowChangeCache, rowChangeCache,
inProgressChanges, inProgressChanges,
@ -733,7 +804,9 @@ export const initialise = context => {
if (!id) { if (!id) {
return return
} }
const { rowId, field } = parseCellID(id) let { rowId, field } = parseCellID(id)
rowId = rowId!
field = field!
const hasChanges = field in (get(rowChangeCache)[rowId] || {}) const hasChanges = field in (get(rowChangeCache)[rowId] || {})
const hasErrors = validation.actions.rowHasErrors(rowId) const hasErrors = validation.actions.rowHasErrors(rowId)
const isSavingChanges = get(inProgressChanges)[rowId] const isSavingChanges = get(inProgressChanges)[rowId]