mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
1 changed files with 79 additions and 312 deletions
@ -1,324 +1,91 @@ |
|||||||
<script> |
<script setup lang="ts"> |
||||||
import FileSaver from 'file-saver' |
import MdiFlashOutlineIcon from '~icons/mdi/flash-outline' |
||||||
import { ExportTypes } from 'nocodb-sdk' |
import MdiDownloadOutlineIcon from '~icons/mdi/download-outline' |
||||||
import { NOCO } from '~/lib/constants' |
import MdiUploadOutlineIcon from '~icons/mdi/upload-outline' |
||||||
import DropOrSelectFileModal from '~/components/import/DropOrSelectFileModal' |
import MdiViewListOutlineIcon from '~icons/mdi/view-list-outline' |
||||||
import ColumnMappingModal from '~/components/project/spreadsheet/components/importExport/ColumnMappingModal' |
import MdiHookIcon from '~icons/mdi/hook' |
||||||
import CSVTemplateAdapter from '~/components/import/templateParsers/CSVTemplateAdapter' |
|
||||||
import { UITypes } from '~/components/project/spreadsheet/helpers/uiTypes' |
const { isUIAllowed } = useUIPermission() |
||||||
import WebhookModal from '~/components/project/tableTabs/webhook/WebhookModal' |
// TODO:: identify based on meta |
||||||
import WebhookSlider from '~/components/project/tableTabs/webhook/WebhookSlider' |
const isView = ref(false) |
||||||
|
|
||||||
export default { |
function exportCsv() { |
||||||
name: 'ExportImport', |
// TODO |
||||||
components: { |
} |
||||||
WebhookSlider, |
|
||||||
ColumnMappingModal, |
|
||||||
DropOrSelectFileModal, |
|
||||||
}, |
|
||||||
props: { |
|
||||||
meta: Object, |
|
||||||
nodes: Object, |
|
||||||
selectedView: Object, |
|
||||||
publicViewId: String, |
|
||||||
queryParams: Object, |
|
||||||
isView: Boolean, |
|
||||||
reqPayload: Object, |
|
||||||
}, |
|
||||||
emits: ['reload', 'showAdditionalFeatOverlay'], |
|
||||||
data() { |
|
||||||
return { |
|
||||||
importModal: false, |
|
||||||
columnMappingModal: false, |
|
||||||
parsedCsv: {}, |
|
||||||
webhookModal: false, |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
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 title = col.mm ? col.mm._rtn : col.lk._ltn |
|
||||||
await this.$store.dispatch('meta/ActLoadMeta', { |
|
||||||
env: this.nodes.env, |
|
||||||
dbAlias: this.nodes.dbAlias, |
|
||||||
tn, |
|
||||||
}) |
|
||||||
|
|
||||||
prop = `${title}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) || |
|
||||||
{} |
|
||||||
).title |
|
||||||
|
|
||||||
row[col.title] = 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.table_name : col.lk.ltn |
|
||||||
const title = col.hm ? col.hm.title : col.lk._ltn |
|
||||||
|
|
||||||
await this.$store.dispatch('meta/ActLoadMeta', { |
|
||||||
env: this.nodes.env, |
|
||||||
dbAlias: this.nodes.dbAlias, |
|
||||||
tn, |
|
||||||
}) |
|
||||||
|
|
||||||
prop = `${title}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) |
|
||||||
).title |
|
||||||
row[col.title] = 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 title = col.bt ? col.bt._rtn : col.lk._ltn |
|
||||||
await this.$store.dispatch('meta/ActLoadMeta', { |
|
||||||
env: this.nodes.env, |
|
||||||
dbAlias: this.nodes.dbAlias, |
|
||||||
tn, |
|
||||||
}) |
|
||||||
|
|
||||||
prop = `${title}Read` |
function importCsv() { |
||||||
cn = col.lk |
// TODO |
||||||
? 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) || |
|
||||||
{} |
|
||||||
).title |
|
||||||
row[col.title] = r.row[prop] && r.row[prop] && cn && r.row[prop][cn] |
|
||||||
} else { |
|
||||||
row[col.title] = r.row[col.title] |
|
||||||
} |
|
||||||
} else if (col.uidt === 'Attachment') { |
|
||||||
let data = [] |
|
||||||
try { |
|
||||||
if (typeof r.row[col.title] === 'string') { |
|
||||||
data = JSON.parse(r.row[col.title]) |
|
||||||
} else if (r.row[col.title]) { |
|
||||||
data = r.row[col.title] |
|
||||||
} |
|
||||||
} catch {} |
|
||||||
row[col.title] = (data || []).map((a) => `${a.title}(${a.url})`) |
|
||||||
} else { |
|
||||||
row[col.title] = r.row[col.title] |
|
||||||
} |
|
||||||
} |
|
||||||
return row |
|
||||||
}), |
|
||||||
) |
|
||||||
}, |
|
||||||
async exportCsv() { |
|
||||||
let offset = 0 |
|
||||||
let c = 1 |
|
||||||
|
|
||||||
try { |
function openSharedViewModal() { |
||||||
while (!isNaN(offset) && offset > -1) { |
// TODO: |
||||||
let res |
} |
||||||
if (this.publicViewId) { |
|
||||||
res = await this.$api.public.csvExport(this.publicViewId, ExportTypes.CSV, { |
|
||||||
responseType: 'blob', |
|
||||||
query: { |
|
||||||
fields: |
|
||||||
this.queryParams && |
|
||||||
this.queryParams.fieldsOrder && |
|
||||||
this.queryParams.fieldsOrder.filter((c) => this.queryParams.showFields[c]), |
|
||||||
offset, |
|
||||||
sortArrJson: JSON.stringify( |
|
||||||
this.reqPayload && |
|
||||||
this.reqPayload.sorts && |
|
||||||
this.reqPayload.sorts.map(({ fk_column_id, direction }) => ({ |
|
||||||
direction, |
|
||||||
fk_column_id, |
|
||||||
})), |
|
||||||
), |
|
||||||
filterArrJson: JSON.stringify(this.reqPayload && this.reqPayload.filters), |
|
||||||
}, |
|
||||||
headers: { |
|
||||||
'xc-password': this.reqPayload && this.reqPayload.password, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} else { |
|
||||||
res = await this.$api.dbViewRow.export( |
|
||||||
NOCO, |
|
||||||
this.projectName, |
|
||||||
this.meta.title, |
|
||||||
this.selectedView.title, |
|
||||||
ExportTypes.CSV, |
|
||||||
{ |
|
||||||
responseType: 'blob', |
|
||||||
query: { |
|
||||||
offset, |
|
||||||
}, |
|
||||||
}, |
|
||||||
) |
|
||||||
} |
|
||||||
const { data } = res |
|
||||||
|
|
||||||
offset = +res.headers['nc-export-offset'] |
function openWebhookModal() { |
||||||
const blob = new Blob([data], { type: 'text/plain;charset=utf-8' }) |
// TODO: |
||||||
FileSaver.saveAs(blob, `${this.meta.title}_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) { |
|
||||||
console.log(e) |
|
||||||
this.$toast.error(e.message).goAway(3000) |
|
||||||
} |
|
||||||
}, |
|
||||||
async importData(columnMappings) { |
|
||||||
try { |
|
||||||
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) { |
|
||||||
const v = this.meta && this.meta.columns.find((c) => c.title === col.destCn) |
|
||||||
let input = row[col.sourceCn] |
|
||||||
// parse potential boolean values |
|
||||||
if (v.uidt === UITypes.Checkbox) { |
|
||||||
input = input.replace(/["']/g, '').toLowerCase().trim() |
|
||||||
if (input === 'false' || input === 'no' || input === 'n') { |
|
||||||
input = '0' |
|
||||||
} else if (input === 'true' || input === 'yes' || input === 'y') { |
|
||||||
input = '1' |
|
||||||
} |
|
||||||
} else if (v.uidt === UITypes.Number) { |
|
||||||
if (input === '') { |
|
||||||
input = null |
|
||||||
} |
|
||||||
} else if (v.uidt === UITypes.SingleSelect || v.uidt === UITypes.MultiSelect) { |
|
||||||
if (input === '') { |
|
||||||
input = null |
|
||||||
} |
|
||||||
} |
|
||||||
res[col.destCn] = input |
|
||||||
} |
|
||||||
return res |
|
||||||
}, {}), |
|
||||||
) |
|
||||||
await this.$api.dbTableRow.bulkCreate(NOCO, this.projectName, this.meta.title, 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.$emit('reload') |
|
||||||
this.$toast.success('Successfully imported table data').goAway(3000) |
|
||||||
} catch (e) { |
|
||||||
this.$toast.error(e.message).goAway(3000) |
|
||||||
} |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
} |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<div> |
<a-menu offset-y transition="slide-y-transition" mode="horizontal"> |
||||||
<v-menu open-on-hover bottom offset-y transition="slide-y-transition"> |
<a-sub-menu key="sub1"> |
||||||
<template #activator="{ on }"> |
<template #icon> |
||||||
<v-btn |
<setting-outlined /> |
||||||
v-t="['c:actions']" |
</template> |
||||||
outlined |
<template #title> |
||||||
class="nc-actions-menu-btn caption px-2 nc-remove-border font-weight-medium" |
<span class="flex items-center gap-2"> |
||||||
small |
<MdiFlashOutlineIcon /> |
||||||
text |
|
||||||
v-on="on" |
|
||||||
> |
|
||||||
<v-icon small color="#777"> mdi-flash-outline </v-icon> |
|
||||||
<!-- More --> |
|
||||||
{{ $t('general.more') }} |
{{ $t('general.more') }} |
||||||
|
</span> |
||||||
<v-icon small color="#777"> mdi-menu-down </v-icon> |
|
||||||
</v-btn> |
|
||||||
</template> |
</template> |
||||||
|
<a-menu-item key="action:downloadCSV" v-t="['c:actions']" @click="exportCsv"> |
||||||
<v-list dense> |
<span class="flex items-center gap-2"> |
||||||
<v-list-item v-t="['a:actions:download-csv']" dense @click="exportCsv"> |
<MdiDownloadOutlineIcon class="text-primary" /> |
||||||
<v-list-item-title> |
<!-- Download as CSV --> |
||||||
<v-icon small class="mr-1"> mdi-download-outline </v-icon> |
{{ $t('activity.downloadCSV') }} |
||||||
<span class="caption"> |
</span> |
||||||
<!-- Download as CSV --> |
</a-menu-item> |
||||||
{{ $t('activity.downloadCSV') }} |
|
||||||
</span> |
<a-menu-item |
||||||
</v-list-item-title> |
v-if="isUIAllowed('csvImport') && !isView" |
||||||
</v-list-item> |
key="action:uploadCSV" |
||||||
<v-list-item v-if="_isUIAllowed('csvImport') && !isView" v-t="['a:actions:upload-csv']" dense @click="importModal = true"> |
v-t="['a:actions:upload-csv']" |
||||||
<v-list-item-title> |
@click="importCsv" |
||||||
<v-icon small class="mr-1" color=""> mdi-upload-outline </v-icon> |
> |
||||||
<span class="caption"> |
<span class="flex items-center gap-2"> |
||||||
<!-- Upload CSV --> |
<MdiUploadOutlineIcon class="text-primary" /> |
||||||
{{ $t('activity.uploadCSV') }} |
<!-- Upload CSV --> |
||||||
</span> |
{{ $t('activity.uploadCSV') }} |
||||||
|
</span> |
||||||
<span class="caption grey--text">(<x-icon small color="grey lighten-2"> mdi-alpha </x-icon> version)</span> |
</a-menu-item> |
||||||
</v-list-item-title> |
|
||||||
</v-list-item> |
<a-menu-item |
||||||
<v-list-item |
v-if="isUIAllowed('SharedViewList') && !isView" |
||||||
v-if="_isUIAllowed('SharedViewList') && !isView" |
key="action:listSharedView" |
||||||
v-t="['a:actions:shared-view-list']" |
v-t="['a:actions:shared-view-list']" |
||||||
dense |
@click="openSharedViewModal" |
||||||
@click="$emit('showAdditionalFeatOverlay', 'shared-views')" |
> |
||||||
> |
<span class="flex items-center gap-2"> |
||||||
<v-list-item-title> |
<MdiViewListOutlineIcon class="text-primary" /> |
||||||
<v-icon small class="mr-1" color=""> mdi-view-list-outline </v-icon> |
<!-- Shared View List --> |
||||||
<span class="caption"> |
{{ $t('activity.listSharedView') }} |
||||||
<!-- Shared View List --> |
</span> |
||||||
{{ $t('activity.listSharedView') }} |
</a-menu-item> |
||||||
</span> |
|
||||||
</v-list-item-title> |
<a-menu-item |
||||||
</v-list-item> |
v-if="isUIAllowed('webhook') && !isView" |
||||||
<v-list-item v-if="_isUIAllowed('webhook') && !isView" v-t="['c:actions:webhook']" dense @click="webhookModal = true"> |
key="action:webhooks" |
||||||
<v-list-item-title> |
v-t="['c:actions:webhook']" |
||||||
<v-icon small class="mr-1" color=""> mdi-hook </v-icon> |
@click="openWebhookModal" |
||||||
<span class="caption"> Webhooks </span> |
> |
||||||
</v-list-item-title> |
<span class="flex items-center gap-2"> |
||||||
</v-list-item> |
<MdiHookIcon class="text-primary" /> |
||||||
</v-list> |
<!-- TODO: i18n --> |
||||||
</v-menu> |
Webhooks |
||||||
<DropOrSelectFileModal v-model="importModal" accept=".csv" text="CSV" @file="onCsvFileSelection" /> |
</span> |
||||||
<ColumnMappingModal |
</a-menu-item> |
||||||
v-if="columnMappingModal && meta" |
</a-sub-menu> |
||||||
v-model="columnMappingModal" |
</a-menu> |
||||||
:meta="meta" |
|
||||||
:import-data-columns="parsedCsv.columns" |
|
||||||
:parsed-csv="parsedCsv" |
|
||||||
@import="importData" |
|
||||||
/> |
|
||||||
<!-- <webhook-modal v-if="webhookModal" v-model="webhookModal" :meta="meta" /> --> |
|
||||||
<WebhookSlider v-model="webhookModal" :meta="meta" /> |
|
||||||
</div> |
|
||||||
</template> |
</template> |
||||||
|
|
||||||
<style scoped></style> |
<style scoped></style> |
||||||
|
Loading…
Reference in new issue