Allow CSV upload in browser and add display column option

This commit is contained in:
Andrew Kingston 2020-10-19 19:24:05 +01:00
parent 4f43bc21d9
commit c0e0b48a80
4 changed files with 62 additions and 34 deletions

View File

@ -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>

View File

@ -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 })

View File

@ -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,
}
} }

View File

@ -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)