1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-18 03:13:47 +00:00

Resolve "Import JSON file when creating a table"

This commit is contained in:
Bram Wiepjes 2021-05-18 11:12:07 +00:00
parent fbf8cf4e88
commit 58dbde167f
7 changed files with 349 additions and 157 deletions

View file

@ -4,6 +4,7 @@
* Fixed bug where the grid view would fail hard if a cell is selected and the component
is destroyed.
* Made it possible to import a JSON file when creating a table.
* Made it possible to order the views by drag and drop.
* Made it possible to order the groups by drag and drop.

View file

@ -0,0 +1,99 @@
<template>
<Dropdown :value="value" @input="$emit('input', $event)">
<DropdownItem name="Unicode (UTF-8)" value="utf-8"></DropdownItem>
<DropdownItem name="Arabic (ISO-8859-6)" value="iso-8859-6"></DropdownItem>
<DropdownItem
name="Arabic (Windows-1256)"
value="windows-1256"
></DropdownItem>
<DropdownItem name="Baltic (ISO-8859-4)" value="iso-8859-4"></DropdownItem>
<DropdownItem
name="Baltic (windows-1257)"
value="windows-1257"
></DropdownItem>
<DropdownItem
name="Celtic (ISO-8859-14)"
value="iso-8859-14"
></DropdownItem>
<DropdownItem
name="Central European (ISO-8859-2)"
value="iso-8859-2"
></DropdownItem>
<DropdownItem
name="Central European (Windows-1250)"
value="windows-1250"
></DropdownItem>
<DropdownItem name="Chinese, Simplified (GBK)" value="gbk"></DropdownItem>
<DropdownItem name="Chinese (GB18030)" value="gb18030"></DropdownItem>
<DropdownItem name="Chinese Traditional (Big5)" value="big5"></DropdownItem>
<DropdownItem name="Cyrillic (KOI8-R)" value="koi8-r"></DropdownItem>
<DropdownItem name="Cyrillic (KOI8-U)" value="koi8-u"></DropdownItem>
<DropdownItem
name="Cyrillic (ISO-8859-5)"
value="iso-8859-5"
></DropdownItem>
<DropdownItem
name="Cyrillic (Windows-1251)"
value="windows-1251"
></DropdownItem>
<DropdownItem
name="Cyrillic Mac OS (x-mac-cyrillic)"
value="x-mac-cyrillic"
></DropdownItem>
<DropdownItem name="Greek (ISO-8859-7)" value="iso-8859-7"></DropdownItem>
<DropdownItem
name="Greek (Windows-1253)"
value="windows-1253"
></DropdownItem>
<DropdownItem name="Hebrew (ISO-8859-8)" value="iso-8859-8"></DropdownItem>
<DropdownItem
name="Hebrew (Windows-1255)"
value="windows-1255"
></DropdownItem>
<DropdownItem name="Japanese (EUC-JP)" value="euc-jp"></DropdownItem>
<DropdownItem
name="Japanese (ISO-2022-JP)"
value="iso-2022-jp"
></DropdownItem>
<DropdownItem name="Japanese (Shift-JIS)" value="shift-jis"></DropdownItem>
<DropdownItem name="Korean (EUC-KR)" value="euc-kr"></DropdownItem>
<DropdownItem name="Macintosh" value="macintosh"></DropdownItem>
<DropdownItem
name="Nordic (ISO-8859-10)"
value="iso-8859-10"
></DropdownItem>
<DropdownItem
name="South-Eastern European (ISO-8859-16)"
value="iso-8859-16"
></DropdownItem>
<DropdownItem name="Thai (Windows-874)" value="windows-874"></DropdownItem>
<DropdownItem
name="Turkish (Windows-1254)"
value="windows-1254"
></DropdownItem>
<DropdownItem
name="Vietnamese (Windows-1258)"
value="windows-1258"
></DropdownItem>
<DropdownItem
name="Western European (ISO-8859-1)"
value="iso-8859-1"
></DropdownItem>
<DropdownItem
name="Western European (Windows-1252)"
value="windows-1252"
></DropdownItem>
<DropdownItem name="Latin 3 (ISO-8859-3)" value="iso-8859-3"></DropdownItem>
</Dropdown>
</template>
<script>
export default {
name: 'CharsetDropdown',
props: {
value: {
type: String,
},
},
}
</script>

View file

@ -57,134 +57,10 @@
<div class="control">
<label class="control__label">Encoding</label>
<div class="control__elements">
<Dropdown v-model="encoding" @input="reload()">
<DropdownItem name="Unicode (UTF-8)" value="utf-8"></DropdownItem>
<DropdownItem
name="Arabic (ISO-8859-6)"
value="iso-8859-6"
></DropdownItem>
<DropdownItem
name="Arabic (Windows-1256)"
value="windows-1256"
></DropdownItem>
<DropdownItem
name="Baltic (ISO-8859-4)"
value="iso-8859-4"
></DropdownItem>
<DropdownItem
name="Baltic (windows-1257)"
value="windows-1257"
></DropdownItem>
<DropdownItem
name="Celtic (ISO-8859-14)"
value="iso-8859-14"
></DropdownItem>
<DropdownItem
name="Central European (ISO-8859-2)"
value="iso-8859-2"
></DropdownItem>
<DropdownItem
name="Central European (Windows-1250)"
value="windows-1250"
></DropdownItem>
<DropdownItem
name="Chinese, Simplified (GBK)"
value="gbk"
></DropdownItem>
<DropdownItem
name="Chinese (GB18030)"
value="gb18030"
></DropdownItem>
<DropdownItem
name="Chinese Traditional (Big5)"
value="big5"
></DropdownItem>
<DropdownItem
name="Cyrillic (KOI8-R)"
value="koi8-r"
></DropdownItem>
<DropdownItem
name="Cyrillic (KOI8-U)"
value="koi8-u"
></DropdownItem>
<DropdownItem
name="Cyrillic (ISO-8859-5)"
value="iso-8859-5"
></DropdownItem>
<DropdownItem
name="Cyrillic (Windows-1251)"
value="windows-1251"
></DropdownItem>
<DropdownItem
name="Cyrillic Mac OS (x-mac-cyrillic)"
value="x-mac-cyrillic"
></DropdownItem>
<DropdownItem
name="Greek (ISO-8859-7)"
value="iso-8859-7"
></DropdownItem>
<DropdownItem
name="Greek (Windows-1253)"
value="windows-1253"
></DropdownItem>
<DropdownItem
name="Hebrew (ISO-8859-8)"
value="iso-8859-8"
></DropdownItem>
<DropdownItem
name="Hebrew (Windows-1255)"
value="windows-1255"
></DropdownItem>
<DropdownItem
name="Japanese (EUC-JP)"
value="euc-jp"
></DropdownItem>
<DropdownItem
name="Japanese (ISO-2022-JP)"
value="iso-2022-jp"
></DropdownItem>
<DropdownItem
name="Japanese (Shift-JIS)"
value="shift-jis"
></DropdownItem>
<DropdownItem
name="Korean (EUC-KR)"
value="euc-kr"
></DropdownItem>
<DropdownItem name="Macintosh" value="macintosh"></DropdownItem>
<DropdownItem
name="Nordic (ISO-8859-10)"
value="iso-8859-10"
></DropdownItem>
<DropdownItem
name="South-Eastern European (ISO-8859-16)"
value="iso-8859-16"
></DropdownItem>
<DropdownItem
name="Thai (Windows-874)"
value="windows-874"
></DropdownItem>
<DropdownItem
name="Turkish (Windows-1254)"
value="windows-1254"
></DropdownItem>
<DropdownItem
name="Vietnamese (Windows-1258)"
value="windows-1258"
></DropdownItem>
<DropdownItem
name="Western European (ISO-8859-1)"
value="iso-8859-1"
></DropdownItem>
<DropdownItem
name="Western European (Windows-1252)"
value="windows-1252"
></DropdownItem>
<DropdownItem
name="Latin 3 (ISO-8859-3)"
value="iso-8859-3"
></DropdownItem>
</Dropdown>
<CharsetDropdown
v-model="encoding"
@input="reload()"
></CharsetDropdown>
</div>
</div>
</div>
@ -225,12 +101,13 @@ import Papa from 'papaparse'
import { required } from 'vuelidate/lib/validators'
import form from '@baserow/modules/core/mixins/form'
import CharsetDropdown from '@baserow/modules/core/components/helpers/CharsetDropdown'
import importer from '@baserow/modules/database/mixins/importer'
import TableImporterPreview from '@baserow/modules/database/components/table/TableImporterPreview'
export default {
name: 'TableCSVImporter',
components: { TableImporterPreview },
components: { TableImporterPreview, CharsetDropdown },
mixins: [form, importer],
data() {
return {
@ -299,7 +176,6 @@ export default {
this.values.data = ''
this.error = `It is not possible to import more than ${limit} rows.`
this.preview = {}
this.$emit('input', this.value)
return
}
@ -323,8 +199,6 @@ export default {
this.values.firstRowHeader
)
}
this.$emit('input', this.value)
},
error(error) {
// Papa parse has resulted in an error which we need to display to the user.
@ -332,7 +206,6 @@ export default {
this.values.data = ''
this.error = error.errors[0].message
this.preview = {}
this.$emit('input', this.value)
},
})
},

View file

@ -0,0 +1,199 @@
<template>
<div>
<div class="control">
<label class="control__label">Choose JSON file</label>
<div class="control__description">
You can import an existing JSON file by uploading the .json file with
tabular data, i.e.:
<pre>
[
{
"to": "Tove",
"from": "Jani",
"heading": "Reminder",
"body": "Don't forget me this weekend!"
},
{
"to": "Bram",
"from": "Nigel",
"heading": "Reminder",
"body": "Don't forget about the export feature this week"
}
]
</pre>
</div>
<div class="control__elements">
<div class="file-upload">
<input
v-show="false"
ref="file"
type="file"
accept=".json"
@change="select($event)"
/>
<a
class="button button--large button--ghost file-upload__button"
@click.prevent="$refs.file.click($event)"
>
<i class="fas fa-cloud-upload-alt"></i>
Choose JSON file
</a>
<div class="file-upload__file">{{ filename }}</div>
</div>
<div v-if="$v.filename.$error" class="error">
This field is required.
</div>
</div>
</div>
<div v-if="filename !== ''" class="control">
<label class="control__label">Encoding</label>
<div class="control__elements">
<CharsetDropdown v-model="encoding" @input="reload()"></CharsetDropdown>
</div>
</div>
<div v-if="error !== ''" class="alert alert--error alert--has-icon">
<div class="alert__icon">
<i class="fas fa-exclamation"></i>
</div>
<div class="alert__title">Something went wrong</div>
<p class="alert__content">
{{ error }}
</p>
</div>
<TableImporterPreview
v-if="error === '' && Object.keys(preview).length !== 0"
:preview="preview"
></TableImporterPreview>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators'
import form from '@baserow/modules/core/mixins/form'
import CharsetDropdown from '@baserow/modules/core/components/helpers/CharsetDropdown'
import importer from '@baserow/modules/database/mixins/importer'
import TableImporterPreview from '@baserow/modules/database/components/table/TableImporterPreview'
export default {
name: 'TableCSVImporter',
components: { TableImporterPreview, CharsetDropdown },
mixins: [form, importer],
data() {
return {
values: {
data: '',
firstRowHeader: true,
},
encoding: 'utf-8',
filename: '',
error: '',
rawData: null,
preview: {},
}
},
validations: {
values: {
data: { required },
},
filename: { required },
},
methods: {
/**
* Method that is called when a file has been chosen. It will check if the file is
* not larger than 15MB. Otherwise it will take a long time and possibly a crash
* if so many entries have to be loaded into memory. If the file is valid, the
* contents will be loaded into memory and the reload method will be called which
* parses the content.
*/
select(event) {
if (event.target.files.length === 0) {
return
}
const file = event.target.files[0]
const maxSize = 1024 * 1024 * 15
if (file.size > maxSize) {
this.filename = ''
this.values.data = ''
this.error = 'The maximum file size is 15MB.'
this.preview = {}
this.$emit('input', this.value)
} else {
this.filename = file.name
const reader = new FileReader()
reader.addEventListener('load', (event) => {
this.rawData = event.target.result
this.reload()
})
reader.readAsArrayBuffer(event.target.files[0])
}
},
reload() {
let json
try {
const decoder = new TextDecoder(this.encoding)
const decoded = decoder.decode(this.rawData)
json = JSON.parse(decoded)
} catch (error) {
this.values.data = ''
this.error = `Error occured while parsing JSON: ${error.message}`
this.preview = {}
return
}
if (json.length === 0) {
this.values.data = ''
this.error = 'This JSON file is empty.'
this.preview = {}
return
}
if (!Array.isArray(json)) {
this.values.data = ''
this.error = `The JSON file is not an array.`
this.preview = {}
return
}
const limit = this.$env.INITIAL_TABLE_DATA_LIMIT
if (limit !== null && json.length > limit - 1) {
this.values.data = ''
this.error = `It is not possible to import more than ${limit} rows.`
this.preview = {}
return
}
const header = []
const data = []
json.forEach((entry) => {
const keys = Object.keys(entry)
const row = []
keys.forEach((key) => {
if (!header.includes(key)) {
header.push(key)
}
})
header.forEach((key) => {
const exists = Object.prototype.hasOwnProperty.call(entry, key)
const value = exists ? entry[key].toString() : ''
row.push(value)
})
data.push(row)
})
data.unshift(header)
this.values.data = JSON.stringify(data)
this.error = ''
this.preview = this.getPreview(data, true)
},
},
}
</script>

View file

@ -113,7 +113,6 @@ export default {
this.values.data = ''
this.error = 'The maximum file size is 15MB.'
this.preview = {}
this.$emit('input', this.value)
} else {
this.filename = file.name
const reader = new FileReader()
@ -126,38 +125,38 @@ export default {
},
reload() {
const [header, xmlData, errors] = parseXML(this.rawData)
if (errors.length > 0) {
this.values.data = ''
this.error = `Error occured while processing XML: ${errors.join('\n')}`
this.preview = {}
} else if (xmlData.length > 0) {
let hasHeader = false
if (header.length > 0) {
xmlData.unshift(header)
hasHeader = true
}
return
}
const limit = this.$env.INITIAL_TABLE_DATA_LIMIT
if (limit !== null) {
const count = xmlData.length
if (count > limit) {
this.values.data = ''
this.error = `It is not possible to import more than ${limit} rows.`
this.preview = {}
this.$emit('input', this.value)
return
}
}
this.values.data = JSON.stringify(xmlData)
this.error = ''
this.preview = this.getPreview(xmlData, hasHeader)
} else {
if (xmlData.length === 0) {
this.values.data = ''
this.error = 'This XML file is empty.'
this.preview = {}
return
}
this.$emit('input', this.value)
let hasHeader = false
if (header.length > 0) {
xmlData.unshift(header)
hasHeader = true
}
const limit = this.$env.INITIAL_TABLE_DATA_LIMIT
if (limit !== null && xmlData.length > limit) {
this.values.data = ''
this.error = `It is not possible to import more than ${limit} rows.`
this.preview = {}
return
}
this.values.data = JSON.stringify(xmlData)
this.error = ''
this.preview = this.getPreview(xmlData, hasHeader)
},
},
}

View file

@ -3,6 +3,7 @@ import { Registerable } from '@baserow/modules/core/registry'
import TableCSVImporter from '@baserow/modules/database/components/table/TableCSVImporter'
import TablePasteImporter from '@baserow/modules/database/components/table/TablePasteImporter'
import TableXMLImporter from '@baserow/modules/database/components/table/TableXMLImporter'
import TableJSONImporter from '@baserow/modules/database/components/table/TableJSONImporter'
export class ImporterType extends Registerable {
/**
@ -103,3 +104,21 @@ export class XMLImporterType extends ImporterType {
return TableXMLImporter
}
}
export class JSONImporterType extends ImporterType {
getType() {
return 'json'
}
getIconClass() {
return 'file-code'
}
getName() {
return 'Import a JSON file'
}
getFormComponent() {
return TableJSONImporter
}
}

View file

@ -33,6 +33,7 @@ import {
CSVImporterType,
PasteImporterType,
XMLImporterType,
JSONImporterType,
} from '@baserow/modules/database/importerTypes'
import { APITokenSettingsType } from '@baserow/modules/database/settingsTypes'
@ -80,6 +81,7 @@ export default ({ store, app }) => {
app.$registry.register('importer', new CSVImporterType())
app.$registry.register('importer', new PasteImporterType())
app.$registry.register('importer', new XMLImporterType())
app.$registry.register('importer', new JSONImporterType())
app.$registry.register('settings', new APITokenSettingsType())
registerRealtimeEvents(app.$realtime)