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:
parent
fbf8cf4e88
commit
58dbde167f
7 changed files with 349 additions and 157 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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>
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue