Add bulk row duplication to tables using throttled save row calls

This commit is contained in:
Andrew Kingston 2024-06-21 08:08:19 +01:00
parent f86c80af32
commit 64cc3efc2a
No known key found for this signature in database
6 changed files with 95 additions and 15 deletions

View File

@ -54,7 +54,10 @@
const newRowIndex = offset ? undefined : 0 const newRowIndex = offset ? undefined : 0
let rowToCreate = { ...newRow } let rowToCreate = { ...newRow }
delete rowToCreate._isNewRow delete rowToCreate._isNewRow
const savedRow = await rows.actions.addRow(rowToCreate, newRowIndex) const savedRow = await rows.actions.addRow({
row: rowToCreate,
idx: newRowIndex,
})
if (savedRow) { if (savedRow) {
// Reset state // Reset state
clear() clear()

View File

@ -21,6 +21,7 @@
notifications, notifications,
hasBudibaseIdentifiers, hasBudibaseIdentifiers,
selectedRowCount, selectedRowCount,
selectedRows,
} = getContext("grid") } = getContext("grid")
let anchor let anchor
@ -51,6 +52,18 @@
} }
} }
const bulkDuplicate = async () => {
menu.actions.close()
const rowsToDuplicate = Object.keys($selectedRows).map(id => {
return rows.actions.getRow(id)
})
const newRows = await rows.actions.bulkDuplicate(rowsToDuplicate)
if (newRows[0]) {
const column = $stickyColumn?.name || $columns[0].name
$focusedCellId = getCellID(newRows[0]._id, column)
}
}
const copyToClipboard = async value => { const copyToClipboard = async value => {
await Helpers.copyToClipboard(value) await Helpers.copyToClipboard(value)
$notifications.success("Copied to clipboard") $notifications.success("Copied to clipboard")
@ -66,8 +79,8 @@
{#if $menu.multiRowMode} {#if $menu.multiRowMode}
<MenuItem <MenuItem
icon="Duplicate" icon="Duplicate"
disabled={isNewRow || !$config.canAddRows} disabled={!$config.canAddRows}
on:click={duplicate} on:click={bulkDuplicate}
> >
Duplicate {$selectedRowCount} rows Duplicate {$selectedRowCount} rows
</MenuItem> </MenuItem>

View File

@ -10,7 +10,10 @@ export const createActions = context => {
} }
const saveRow = async row => { const saveRow = async row => {
row.tableId = get(datasource)?.tableId row = {
...row,
tableId: get(datasource)?.tableId,
}
return await API.saveRow(row, SuppressErrors) return await API.saveRow(row, SuppressErrors)
} }

View File

@ -11,8 +11,11 @@ export const createActions = context => {
const saveRow = async row => { const saveRow = async row => {
const $datasource = get(datasource) const $datasource = get(datasource)
row.tableId = $datasource?.tableId row = {
row._viewId = $datasource?.id ...row,
tableId: $datasource?.tableId,
_viewId: $datasource?.id,
}
return { return {
...(await API.saveRow(row, SuppressErrors)), ...(await API.saveRow(row, SuppressErrors)),
_viewId: row._viewId, _viewId: row._viewId,

View File

@ -4,6 +4,7 @@ import { NewRowID, RowPageSize } from "../lib/constants"
import { getCellID, parseCellID } from "../lib/utils" import { getCellID, parseCellID } from "../lib/utils"
import { tick } from "svelte" import { tick } from "svelte"
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
import { sleep } from "../../../utils/utils"
export const createStores = () => { export const createStores = () => {
const rows = writable([]) const rows = writable([])
@ -274,11 +275,9 @@ export const createActions = context => {
} }
// Adds a new row // Adds a new row
const addRow = async (row, idx, bubble = false) => { const addRow = async ({ row, idx, bubble = false, notify = true }) => {
try { try {
// Create row. Spread row so we can mutate and enrich safely. const newRow = await datasource.actions.addRow(row)
let newRow = { ...row }
newRow = await datasource.actions.addRow(newRow)
// Update state // Update state
if (idx != null) { if (idx != null) {
@ -291,8 +290,9 @@ export const createActions = context => {
handleNewRows([newRow]) handleNewRows([newRow])
} }
// Refresh row to ensure data is in the correct format if (notify) {
get(notifications).success("Row created successfully") get(notifications).success("Row created successfully")
}
return newRow return newRow
} catch (error) { } catch (error) {
if (bubble) { if (bubble) {
@ -305,17 +305,72 @@ 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 => {
let clone = { ...row } let clone = cleanRow(row)
delete clone._id delete clone._id
delete clone._rev delete clone._rev
delete clone.__idx
try { try {
return await addRow(clone, row.__idx + 1, true) const duped = await addRow({
row: clone,
idx: row.__idx + 1,
bubble: true,
notify: false,
})
get(notifications).success("Duplicated 1 row")
return duped
} catch (error) { } catch (error) {
handleValidationError(row._id, error) handleValidationError(row._id, error)
} }
} }
// Duplicates multiple rows, inserting them after the last source row
const bulkDuplicate = async rowsToDupe => {
// Find index of last row
const $rowLookupMap = get(rowLookupMap)
const index = Math.max(...rowsToDupe.map(row => $rowLookupMap[row._id]))
// Clone and clean rows
const clones = rowsToDupe.map(row => {
let clone = cleanRow(row)
delete clone._id
delete clone._rev
return clone
})
// Create rows
let saved = []
let failed = 0
for (let clone of clones) {
try {
saved.push(await datasource.actions.addRow(clone))
rowCacheMap[saved._id] = true
await sleep(50) // Small sleep to ensure we avoid rate limiting
} catch (error) {
failed++
console.error("Duplicating row failed", error)
}
}
// Add to state
if (saved.length) {
rows.update(state => {
return state.toSpliced(index + 1, 0, ...saved)
})
}
// Notify user
if (saved.length) {
get(notifications).success(
`Duplicated ${saved.length} row${saved.length === 1 ? "" : "s"}`
)
}
if (failed) {
get(notifications).error(
`Failed to duplicate ${failed} row${failed === 1 ? "" : "s"}`
)
}
return saved
}
// 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, row) => {
@ -541,6 +596,7 @@ export const createActions = context => {
actions: { actions: {
addRow, addRow,
duplicateRow, duplicateRow,
bulkDuplicate,
getRow, getRow,
updateValue, updateValue,
applyRowChanges, applyRowChanges,

View File

@ -1,6 +1,8 @@
import { makePropSafe as safe } from "@budibase/string-templates" import { makePropSafe as safe } from "@budibase/string-templates"
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
/** /**
* Utility to wrap an async function and ensure all invocations happen * Utility to wrap an async function and ensure all invocations happen
* sequentially. * sequentially.