mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
9.3 KiB
301 lines
9.3 KiB
<template> |
|
<div> |
|
<v-menu |
|
open-on-hover |
|
bottom |
|
offset-y |
|
> |
|
<template #activator="{on}"> |
|
<v-btn |
|
outlined |
|
class="nc-actions-menu-btn caption px-2 nc-remove-border font-weight-medium" |
|
small |
|
text |
|
v-on="on" |
|
> |
|
<v-icon small color="#777"> |
|
mdi-flash-outline |
|
</v-icon> |
|
More |
|
|
|
<v-icon small color="#777"> |
|
mdi-menu-down |
|
</v-icon> |
|
</v-btn> |
|
</template> |
|
|
|
<v-list dense> |
|
<v-list-item |
|
dense |
|
@click="exportCsv" |
|
> |
|
<v-list-item-title> |
|
<v-icon small class="mr-1"> |
|
mdi-download-outline |
|
</v-icon> |
|
<span class="caption"> |
|
Download as CSV |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-list-item |
|
v-if="_isUIAllowed('csvImport') && !isView" |
|
dense |
|
@click="importModal = true" |
|
> |
|
<v-list-item-title> |
|
<v-icon small class="mr-1" color=""> |
|
mdi-upload-outline |
|
</v-icon> |
|
<span class="caption "> |
|
Upload CSV |
|
</span> |
|
|
|
<span class="caption grey--text">(<x-icon small color="grey lighten-2"> |
|
mdi-alpha |
|
</x-icon> version)</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-list-item |
|
v-if="_isUIAllowed('csvImport') && !isView" |
|
dense |
|
@click="$emit('showAdditionalFeatOverlay', 'shared-views')" |
|
> |
|
<v-list-item-title> |
|
<v-icon small class="mr-1" color=""> |
|
mdi-view-list-outline |
|
</v-icon> |
|
<span class="caption "> |
|
Shared View List |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> <v-list-item |
|
v-if="_isUIAllowed('csvImport') && !isView" |
|
dense |
|
@click="$emit('webhook')" |
|
> |
|
<v-list-item-title> |
|
<v-icon small class="mr-1" color=""> |
|
mdi-hook |
|
</v-icon> |
|
<span class="caption "> |
|
Webhooks |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
</v-list> |
|
</v-menu> |
|
<drop-or-select-file-modal v-model="importModal" accept=".csv" text="CSV" @file="onCsvFileSelection" /> |
|
<column-mapping-modal |
|
v-if="columnMappingModal && meta" |
|
v-model="columnMappingModal" |
|
:meta="meta" |
|
:import-data-columns="parsedCsv.columns" |
|
:parsed-csv="parsedCsv" |
|
@import="importData" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<script> |
|
|
|
import FileSaver from 'file-saver' |
|
import DropOrSelectFileModal from '~/components/import/dropOrSelectFileModal' |
|
import ColumnMappingModal from '~/components/project/spreadsheet/components/importExport/columnMappingModal' |
|
import CSVTemplateAdapter from '~/components/import/templateParsers/CSVTemplateAdapter' |
|
|
|
export default { |
|
name: 'ExportImport', |
|
components: { ColumnMappingModal, DropOrSelectFileModal }, |
|
props: { |
|
meta: Object, |
|
nodes: Object, |
|
selectedView: Object, |
|
publicViewId: String, |
|
queryParams: Object, |
|
isView: Boolean |
|
}, |
|
data() { |
|
return { |
|
importModal: false, |
|
columnMappingModal: false, |
|
parsedCsv: {} |
|
} |
|
}, |
|
|
|
methods: { |
|
async onCsvFileSelection(file) { |
|
const reader = new FileReader() |
|
reader.onload = async(e) => { |
|
const templateGenerator = new CSVTemplateAdapter(file.name, e.target.result) |
|
await templateGenerator.init() |
|
templateGenerator.parseData() |
|
this.parsedCsv.columns = templateGenerator.getColumns() |
|
this.parsedCsv.data = templateGenerator.getData() |
|
this.columnMappingModal = true |
|
this.importModal = false |
|
} |
|
|
|
reader.readAsText(file) |
|
}, |
|
|
|
async extractCsvData() { |
|
return Promise.all(this.data.map(async(r) => { |
|
const row = {} |
|
for (const col of this.availableColumns) { |
|
if (col.virtual) { |
|
let prop, cn |
|
if (col.mm || (col.lk && col.lk.type === 'mm')) { |
|
const tn = col.mm ? col.mm.rtn : col.lk.ltn |
|
const _tn = col.mm ? col.mm._rtn : col.lk._ltn |
|
await this.$store.dispatch('meta/ActLoadMeta', { |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias, |
|
tn |
|
}) |
|
|
|
prop = `${_tn}MMList` |
|
cn = col.lk |
|
? col.lk._lcn |
|
: (this.$store.state.meta.metas[tn].columns.find(c => c.pv) || this.$store.state.meta.metas[tn].columns.find(c => c.pk) || {})._cn |
|
|
|
row[col._cn] = r.row[prop] && r.row[prop].map(r => cn && r[cn]) |
|
} else if (col.hm || (col.lk && col.lk.type === 'hm')) { |
|
const tn = col.hm ? col.hm.tn : col.lk.ltn |
|
const _tn = col.hm ? col.hm._tn : col.lk._ltn |
|
|
|
await this.$store.dispatch('meta/ActLoadMeta', { |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias, |
|
tn |
|
}) |
|
|
|
prop = `${_tn}List` |
|
cn = col.lk |
|
? col.lk._lcn |
|
: (this.$store.state.meta.metas[tn].columns.find(c => c.pv) || |
|
this.$store.state.meta.metas[tn].columns.find(c => c.pk))._cn |
|
row[col._cn] = r.row[prop] && r.row[prop].map(r => cn && r[cn]) |
|
} else if (col.bt || (col.lk && col.lk.type === 'bt')) { |
|
const tn = col.bt ? col.bt.rtn : col.lk.ltn |
|
const _tn = col.bt ? col.bt._rtn : col.lk._ltn |
|
await this.$store.dispatch('meta/ActLoadMeta', { |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias, |
|
tn |
|
}) |
|
|
|
prop = `${_tn}Read` |
|
cn = col.lk |
|
? col.lk._lcn |
|
: (this.$store.state.meta.metas[tn].columns.find(c => c.pv) || |
|
this.$store.state.meta.metas[tn].columns.find(c => c.pk) || {})._cn |
|
row[col._cn] = r.row[prop] && |
|
r.row[prop] && cn && r.row[prop][cn] |
|
} else { |
|
row[col._cn] = r.row[col._cn] |
|
} |
|
} else if (col.uidt === 'Attachment') { |
|
let data = [] |
|
try { |
|
if (typeof r.row[col._cn] === 'string') { |
|
data = JSON.parse(r.row[col._cn]) |
|
} else if (r.row[col._cn]) { |
|
data = r.row[col._cn] |
|
} |
|
} catch { |
|
} |
|
row[col._cn] = (data || []).map(a => `${a.title}(${a.url})`) |
|
} else { |
|
row[col._cn] = r.row[col._cn] |
|
} |
|
} |
|
return row |
|
})) |
|
}, |
|
async exportCsv() { |
|
// const fields = this.availableColumns.map(c => c._cn) |
|
// const blob = new Blob([Papaparse.unparse(await this.extractCsvData())], { type: 'text/plain;charset=utf-8' }) |
|
|
|
let offset = 0 |
|
let c = 1 |
|
|
|
try { |
|
while (!isNaN(offset) && offset > -1) { |
|
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [ |
|
this.publicViewId |
|
? null |
|
: { |
|
dbAlias: this.nodes.dbAlias, |
|
env: '_noco' |
|
}, |
|
this.publicViewId ? 'sharedViewExportAsCsv' : 'xcExportAsCsv', |
|
{ |
|
query: { offset }, |
|
localQuery: this.queryParams || {}, |
|
...(this.publicViewId |
|
? { |
|
view_id: this.publicViewId |
|
} |
|
: { |
|
view_name: this.selectedView.title, |
|
model_name: this.meta.tn |
|
}) |
|
}, |
|
null, |
|
{ |
|
responseType: 'blob' |
|
}, |
|
null, |
|
true |
|
]) |
|
const data = res.data |
|
offset = +res.headers['nc-export-offset'] |
|
const blob = new Blob([data], { type: 'text/plain;charset=utf-8' }) |
|
FileSaver.saveAs(blob, `${this.meta._tn}_exported_${c++}.csv`) |
|
if (offset > -1) { |
|
this.$toast.info('Downloading more files').goAway(3000) |
|
} else { |
|
this.$toast.success('Successfully exported all table data').goAway(3000) |
|
} |
|
} |
|
} catch (e) { |
|
this.$toast.error(e.message).goAway(3000) |
|
} |
|
}, |
|
async importData(columnMappings) { |
|
try { |
|
const api = this.$ncApis.get({ |
|
table: this.meta.tn |
|
}) |
|
|
|
const data = this.parsedCsv.data |
|
for (let i = 0, progress = 0; i < data.length; i += 500) { |
|
const batchData = data.slice(i, i + 500).map(row => columnMappings.reduce((res, col) => { |
|
// todo: parse data |
|
|
|
if (col.enabled && col.destCn) { |
|
res[col.destCn] = row[col.sourceCn] |
|
} |
|
return res |
|
}, {})) |
|
await api.insertBulk(batchData) |
|
progress += batchData.length |
|
this.$store.commit('loader/MutMessage', `Importing data : ${progress}/${data.length}`) |
|
this.$store.commit('loader/MutProgress', Math.round((100 * progress / data.length))) |
|
} |
|
this.columnMappingModal = false |
|
this.$store.commit('loader/MutClear') |
|
this.$toast.success('Successfully imported table data').goAway(3000) |
|
} catch (e) { |
|
this.$toast.error(e.message).goAway(3000) |
|
} |
|
} |
|
} |
|
|
|
} |
|
</script> |
|
|
|
<style scoped> |
|
|
|
</style>
|
|
|