Update table data via CSV import (#10313)
* Add identifierFields select for import * Update rows on import (Internal DB) * Only allow internal DB to upsert import CSV * Clear identifierFields when turning off update * Passing table instead of tableId * Pass table * Pass tableType
This commit is contained in:
parent
82ac46e5eb
commit
b5c98871ad
|
@ -32,6 +32,7 @@
|
||||||
<Grid
|
<Grid
|
||||||
{API}
|
{API}
|
||||||
tableId={id}
|
tableId={id}
|
||||||
|
tableType={$tables.selected?.type}
|
||||||
allowAddRows={!isUsersTable}
|
allowAddRows={!isUsersTable}
|
||||||
allowDeleteRows={!isUsersTable}
|
allowDeleteRows={!isUsersTable}
|
||||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import ImportModal from "../modals/ImportModal.svelte"
|
import ImportModal from "../modals/ImportModal.svelte"
|
||||||
|
|
||||||
export let tableId
|
export let tableId
|
||||||
|
export let tableType
|
||||||
export let disabled
|
export let disabled
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
@ -12,5 +13,5 @@
|
||||||
Import
|
Import
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ImportModal {tableId} on:importrows />
|
<ImportModal {tableId} {tableType} on:importrows />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
|
||||||
const { rows, tableId } = getContext("grid")
|
const { rows, tableId, tableType } = getContext("grid")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ImportButton
|
<ImportButton
|
||||||
{disabled}
|
{disabled}
|
||||||
tableId={$tableId}
|
tableId={$tableId}
|
||||||
|
{tableType}
|
||||||
on:importrows={rows.actions.refreshData}
|
on:importrows={rows.actions.refreshData}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,15 +13,18 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let tableId
|
export let tableId
|
||||||
|
export let tableType
|
||||||
let rows = []
|
let rows = []
|
||||||
let allValid = false
|
let allValid = false
|
||||||
let displayColumn = null
|
let displayColumn = null
|
||||||
|
let identifierFields = []
|
||||||
|
|
||||||
async function importData() {
|
async function importData() {
|
||||||
try {
|
try {
|
||||||
await API.importTableData({
|
await API.importTableData({
|
||||||
tableId,
|
tableId,
|
||||||
rows,
|
rows,
|
||||||
|
identifierFields,
|
||||||
})
|
})
|
||||||
notifications.success("Rows successfully imported")
|
notifications.success("Rows successfully imported")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -45,6 +48,13 @@
|
||||||
</Body>
|
</Body>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Label grey extraSmall>CSV or JSON file to import</Label>
|
<Label grey extraSmall>CSV or JSON file to import</Label>
|
||||||
<TableDataImport {tableId} bind:rows bind:allValid bind:displayColumn />
|
<TableDataImport
|
||||||
|
{tableId}
|
||||||
|
{tableType}
|
||||||
|
bind:rows
|
||||||
|
bind:allValid
|
||||||
|
bind:displayColumn
|
||||||
|
bind:identifierFields
|
||||||
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select, Toggle, Multiselect } from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { parseFile } from "./utils"
|
import { parseFile } from "./utils"
|
||||||
|
@ -9,14 +9,17 @@
|
||||||
let fileType = null
|
let fileType = null
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
|
let updateExistingRows = false
|
||||||
let validation = {}
|
let validation = {}
|
||||||
let validateHash = ""
|
let validateHash = ""
|
||||||
let schema = null
|
let schema = null
|
||||||
let invalidColumns = []
|
let invalidColumns = []
|
||||||
|
|
||||||
export let tableId = null
|
export let tableId = null
|
||||||
|
export let tableType
|
||||||
export let rows = []
|
export let rows = []
|
||||||
export let allValid = false
|
export let allValid = false
|
||||||
|
export let identifierFields = []
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{
|
{
|
||||||
|
@ -159,6 +162,22 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{#if tableType === "internal"}
|
||||||
|
<br />
|
||||||
|
<Toggle
|
||||||
|
bind:value={updateExistingRows}
|
||||||
|
on:change={() => (identifierFields = [])}
|
||||||
|
thin
|
||||||
|
text="Update existing rows"
|
||||||
|
/>
|
||||||
|
{#if updateExistingRows}
|
||||||
|
<Multiselect
|
||||||
|
label="Identifier field(s)"
|
||||||
|
options={Object.keys(validation)}
|
||||||
|
bind:value={identifierFields}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
{#if invalidColumns.length > 0}
|
{#if invalidColumns.length > 0}
|
||||||
<p class="spectrum-FieldLabel spectrum-FieldLabel--sizeM">
|
<p class="spectrum-FieldLabel spectrum-FieldLabel--sizeM">
|
||||||
The following columns are present in the data you wish to import, but do
|
The following columns are present in the data you wish to import, but do
|
||||||
|
|
|
@ -62,13 +62,15 @@ export const buildTableEndpoints = API => ({
|
||||||
/**
|
/**
|
||||||
* Imports data into an existing table
|
* Imports data into an existing table
|
||||||
* @param tableId the table ID to import to
|
* @param tableId the table ID to import to
|
||||||
* @param data the data import object
|
* @param rows the data import object
|
||||||
|
* @param identifierFields column names to be used as keys for overwriting existing rows
|
||||||
*/
|
*/
|
||||||
importTableData: async ({ tableId, rows }) => {
|
importTableData: async ({ tableId, rows, identifierFields }) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: `/api/tables/${tableId}/import`,
|
url: `/api/tables/${tableId}/import`,
|
||||||
body: {
|
body: {
|
||||||
rows,
|
rows,
|
||||||
|
identifierFields,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
export let API = null
|
export let API = null
|
||||||
export let tableId = null
|
export let tableId = null
|
||||||
|
export let tableType = null
|
||||||
export let schemaOverrides = null
|
export let schemaOverrides = null
|
||||||
export let allowAddRows = true
|
export let allowAddRows = true
|
||||||
export let allowAddColumns = true
|
export let allowAddColumns = true
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
rand,
|
rand,
|
||||||
config,
|
config,
|
||||||
tableId: tableIdStore,
|
tableId: tableIdStore,
|
||||||
|
tableType,
|
||||||
schemaOverrides: schemaOverridesStore,
|
schemaOverrides: schemaOverridesStore,
|
||||||
}
|
}
|
||||||
context = { ...context, ...createEventManagers() }
|
context = { ...context, ...createEventManagers() }
|
||||||
|
|
|
@ -186,11 +186,7 @@ export async function destroy(ctx: any) {
|
||||||
export async function bulkImport(ctx: any) {
|
export async function bulkImport(ctx: any) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const table = await sdk.tables.getTable(ctx.params.tableId)
|
const table = await sdk.tables.getTable(ctx.params.tableId)
|
||||||
const { rows } = ctx.request.body
|
const { rows, identifierFields } = ctx.request.body
|
||||||
await handleDataImport(ctx.user, table, rows)
|
await handleDataImport(ctx.user, table, rows, identifierFields)
|
||||||
|
|
||||||
// Ensure auto id and other table updates are persisted
|
|
||||||
await db.put(table)
|
|
||||||
|
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,12 @@ export function importToRows(
|
||||||
return finalData
|
return finalData
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleDataImport(user: any, table: any, rows: any) {
|
export async function handleDataImport(
|
||||||
|
user: any,
|
||||||
|
table: any,
|
||||||
|
rows: any,
|
||||||
|
identifierFields: Array<string> = []
|
||||||
|
) {
|
||||||
const schema: unknown = table.schema
|
const schema: unknown = table.schema
|
||||||
|
|
||||||
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
||||||
|
@ -161,6 +166,32 @@ export async function handleDataImport(user: any, table: any, rows: any) {
|
||||||
|
|
||||||
let finalData: any = importToRows(data, table, user)
|
let finalData: any = importToRows(data, table, user)
|
||||||
|
|
||||||
|
//Set IDs of finalData to match existing row if an update is expected
|
||||||
|
if (identifierFields.length > 0) {
|
||||||
|
const allDocs = await db.allDocs(
|
||||||
|
getRowParams(table._id, null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
allDocs.rows
|
||||||
|
.map(existingRow => existingRow.doc)
|
||||||
|
.forEach((doc: any) => {
|
||||||
|
finalData.forEach((finalItem: any) => {
|
||||||
|
let match = true
|
||||||
|
for (const field of identifierFields) {
|
||||||
|
if (finalItem[field] !== doc[field]) {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
finalItem._id = doc._id
|
||||||
|
finalItem._rev = doc._rev
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData), {
|
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData), {
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue