Allow CSV upload in browser and add display column option
This commit is contained in:
parent
4f43bc21d9
commit
c0e0b48a80
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Heading, Body, Button, Select } from "@budibase/bbui"
|
import { Heading, Body, Button, Select, Label } from "@budibase/bbui"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
@ -14,15 +14,17 @@
|
||||||
schema: {},
|
schema: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
let parseResult
|
let csvString
|
||||||
|
let primaryDisplay
|
||||||
|
let schema = {}
|
||||||
|
let fields = []
|
||||||
|
|
||||||
$: schema = parseResult && parseResult.schema
|
$: valid = !schema || fields.every(column => schema[column].success)
|
||||||
$: valid =
|
|
||||||
!schema || Object.keys(schema).every(column => schema[column].success)
|
|
||||||
$: dataImport = {
|
$: dataImport = {
|
||||||
valid,
|
valid,
|
||||||
schema: buildTableSchema(schema),
|
schema: buildTableSchema(schema),
|
||||||
path: files[0] && files[0].path,
|
csvString,
|
||||||
|
primaryDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTableSchema(schema) {
|
function buildTableSchema(schema) {
|
||||||
|
@ -43,11 +45,20 @@
|
||||||
|
|
||||||
async function validateCSV() {
|
async function validateCSV() {
|
||||||
const response = await api.post("/api/tables/csv/validate", {
|
const response = await api.post("/api/tables/csv/validate", {
|
||||||
file: files[0],
|
csvString,
|
||||||
schema: schema || {},
|
schema: schema || {},
|
||||||
})
|
})
|
||||||
|
|
||||||
parseResult = await response.json()
|
const parseResult = await response.json()
|
||||||
|
schema = parseResult && parseResult.schema
|
||||||
|
fields = Object.keys(schema || {}).filter(
|
||||||
|
key => schema[key].type !== "omit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check primary display is valid
|
||||||
|
if (!primaryDisplay || fields.indexOf(primaryDisplay) === -1) {
|
||||||
|
primaryDisplay = fields[0]
|
||||||
|
}
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
notifier.danger("CSV Invalid, please try another CSV file")
|
notifier.danger("CSV Invalid, please try another CSV file")
|
||||||
|
@ -57,13 +68,7 @@
|
||||||
|
|
||||||
async function handleFile(evt) {
|
async function handleFile(evt) {
|
||||||
const fileArray = Array.from(evt.target.files)
|
const fileArray = Array.from(evt.target.files)
|
||||||
const filesToProcess = fileArray.map(({ name, path, size }) => ({
|
if (fileArray.some(file => file.size >= FILE_SIZE_LIMIT)) {
|
||||||
name,
|
|
||||||
path,
|
|
||||||
size,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (filesToProcess.some(file => file.size >= FILE_SIZE_LIMIT)) {
|
|
||||||
notifier.danger(
|
notifier.danger(
|
||||||
`Files cannot exceed ${FILE_SIZE_LIMIT /
|
`Files cannot exceed ${FILE_SIZE_LIMIT /
|
||||||
BYTES_IN_MB}MB. Please try again with smaller files.`
|
BYTES_IN_MB}MB. Please try again with smaller files.`
|
||||||
|
@ -71,9 +76,14 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
files = filesToProcess
|
// Read CSV as plain text to upload alongside schema
|
||||||
|
let reader = new FileReader()
|
||||||
await validateCSV()
|
reader.addEventListener("load", function(e) {
|
||||||
|
csvString = e.target.result
|
||||||
|
files = fileArray
|
||||||
|
validateCSV()
|
||||||
|
})
|
||||||
|
reader.readAsBinaryString(fileArray[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function omitColumn(columnName) {
|
async function omitColumn(columnName) {
|
||||||
|
@ -94,8 +104,8 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="schema-fields">
|
<div class="schema-fields">
|
||||||
{#if schema}
|
{#if fields.length}
|
||||||
{#each Object.keys(schema).filter(key => schema[key].type !== 'omit') as columnName}
|
{#each fields as columnName}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span>{columnName}</span>
|
<span>{columnName}</span>
|
||||||
<Select
|
<Select
|
||||||
|
@ -117,6 +127,16 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if fields.length}
|
||||||
|
<div class="display-column">
|
||||||
|
<Label extraSmall grey>Display Column</Label>
|
||||||
|
<Select thin secondary bind:value={primaryDisplay}>
|
||||||
|
{#each fields as field}
|
||||||
|
<option value={field}>{field}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dropzone {
|
.dropzone {
|
||||||
|
@ -188,4 +208,8 @@
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.display-column {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -38,12 +38,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTable() {
|
async function saveTable() {
|
||||||
// Create table
|
let newTable = {
|
||||||
const table = await backendUiStore.actions.tables.save({
|
|
||||||
name,
|
name,
|
||||||
schema: dataImport.schema || {},
|
schema: dataImport.schema || {},
|
||||||
dataImport,
|
dataImport,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Only set primary display if defined
|
||||||
|
if (dataImport.primaryDisplay && dataImport.primaryDisplay.length) {
|
||||||
|
newTable.primaryDisplay = dataImport.primaryDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create table
|
||||||
|
const table = await backendUiStore.actions.tables.save(newTable)
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
analytics.captureEvent("Table Created", { name })
|
analytics.captureEvent("Table Created", { name })
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ exports.save = async function(ctx) {
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave)
|
ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave)
|
||||||
|
|
||||||
if (dataImport && dataImport.path) {
|
if (dataImport && dataImport.csvString) {
|
||||||
// Populate the table with rows imported from CSV in a bulk update
|
// Populate the table with rows imported from CSV in a bulk update
|
||||||
const data = await csvParser.transform(dataImport)
|
const data = await csvParser.transform(dataImport)
|
||||||
|
|
||||||
|
@ -156,10 +156,7 @@ exports.destroy = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.validateCSVSchema = async function(ctx) {
|
exports.validateCSVSchema = async function(ctx) {
|
||||||
const { file, schema = {} } = ctx.request.body
|
const { csvString, schema = {} } = ctx.request.body
|
||||||
const result = await csvParser.parse(file.path, schema)
|
const result = await csvParser.parse(csvString, schema)
|
||||||
ctx.body = {
|
ctx.body = { schema: result }
|
||||||
schema: result,
|
|
||||||
path: file.path,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ const PARSERS = {
|
||||||
datetime: attribute => new Date(attribute).toISOString(),
|
datetime: attribute => new Date(attribute).toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(path, parsers) {
|
function parse(csvString, parsers) {
|
||||||
const result = csv().fromFile(path)
|
const result = csv().fromString(csvString)
|
||||||
|
|
||||||
const schema = {}
|
const schema = {}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ function parse(path, parsers) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function transform({ schema, path }) {
|
async function transform({ schema, csvString }) {
|
||||||
const colParser = {}
|
const colParser = {}
|
||||||
|
|
||||||
for (let key in schema) {
|
for (let key in schema) {
|
||||||
|
@ -60,7 +60,7 @@ async function transform({ schema, path }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = await csv({ colParser }).fromFile(path)
|
const json = await csv({ colParser }).fromString(csvString)
|
||||||
return json
|
return json
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error transforming CSV to JSON for data import`, err)
|
console.error(`Error transforming CSV to JSON for data import`, err)
|
||||||
|
|
Loading…
Reference in New Issue