Add bulk row duplication to tables using throttled save row calls
This commit is contained in:
parent
f86c80af32
commit
64cc3efc2a
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue