diff --git a/packages/nc-gui/components/import/ExcelTemplateAdapter.js b/packages/nc-gui/components/import/ExcelTemplateAdapter.js new file mode 100644 index 0000000000..0a16b2fe67 --- /dev/null +++ b/packages/nc-gui/components/import/ExcelTemplateAdapter.js @@ -0,0 +1,90 @@ +import XLSX from 'xlsx' +import TemplateGenerator from '~/components/import/TemplateGenerator' +import { UITypes } from '~/components/project/spreadsheet/helpers/uiTypes' + +const excelTypeToUidt = { + d: UITypes.DateTime, + b: UITypes.Checkbox, + n: UITypes.Number, + s: UITypes.SingleLineText +} + +export default class ExcelTemplateAdapter extends TemplateGenerator { + constructor(name, ab) { + super() + this.name = name + this.wb = XLSX.read(new Uint8Array(ab), { type: 'array' }) + this.project = { + title: this.name, + tables: [] + } + this.data = {} + } + + parse() { + for (let i = 0; i < this.wb.SheetNames.length; i++) { + const sheet = this.wb.SheetNames[i] + const table = { tn: sheet, columns: [] } + this.data[sheet] = [] + const ws = this.wb.Sheets[sheet] + const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) + + for (let col = 0; col < rows[0].length; col++) { + const column = { + cn: rows[0][col] + } + + const cellProps = ws[`${col.toString(26).split('').map(s => (parseInt(s, 26) + 10).toString(36).toUpperCase())}2`] + + column.uidt = excelTypeToUidt[cellProps.t] || UITypes.SingleLineText + + // todo: optimize + if (column.uidt === UITypes.SingleLineText) { + // check for long text + if (rows.some(r => (r[col] || '').toString().length > 255)) { + column.uidt = UITypes.LongText + } else { + const vals = rows.slice(1).map(r => r[col]).filter(v => v !== null && v !== undefined) + + // check column is multi or single select by comparing unique values + if (vals.some(v => v && v.includes(','))) { + const flattenedVals = vals.flatMap(v => v ? v.split(',') : []) + const uniqueVals = new Set(flattenedVals) + if (flattenedVals.length > uniqueVals.size && uniqueVals.size <= flattenedVals.length / 10) { + column.uidt = UITypes.MultiSelect + column.dtxp = [...uniqueVals].join(',') + } + } else { + const uniqueVals = new Set(vals) + if (vals.length > uniqueVals.size && uniqueVals.size <= vals.length / 10) { + column.uidt = UITypes.SingleSelect + column.dtxp = [...uniqueVals].join(',') + } + } + } + } + + table.columns.push(column) + } + + for (const row of rows.slice(1)) { + const rowData = {} + for (let i = 0; i < table.columns.length; i++) { + // toto: do parsing if necessary based on type + rowData[table.columns[i].cn] = row[i] + } + this.data[sheet].push(rowData) + } + + this.project.tables.push(table) + } + } + + getTemplate() { + return this.project + } + + getData() { + return this.data + } +} diff --git a/packages/nc-gui/components/import/TemplateGenerator.js b/packages/nc-gui/components/import/TemplateGenerator.js new file mode 100644 index 0000000000..8561541020 --- /dev/null +++ b/packages/nc-gui/components/import/TemplateGenerator.js @@ -0,0 +1,13 @@ +export default class TemplateGenerator { + parse() { + throw new Error('\'parse\' method is not implemented') + } + + getTemplate() { + throw new Error('\'getTemplate\' method is not implemented') + } + + getData() { + throw new Error('\'getData\' method is not implemented') + } +} diff --git a/packages/nc-gui/components/import/excelImport.vue b/packages/nc-gui/components/import/excelImport.vue index 4e7f597992..c7ab326c91 100644 --- a/packages/nc-gui/components/import/excelImport.vue +++ b/packages/nc-gui/components/import/excelImport.vue @@ -1,55 +1,170 @@ diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue new file mode 100644 index 0000000000..3adae03b9a --- /dev/null +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index 6151142417..f325dfef93 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -1,88 +1,90 @@ @@ -686,6 +707,7 @@ :dialog-show="dialogShow" :heading="confirmMessage" /> + @@ -696,9 +718,11 @@ import ShareIcons from '../../components/share-icons' import SponsorMini from '@/components/sponsorMini' import colors from '~/mixins/colors' import TemplatesModal from '~/components/templates/templatesModal' +import ExcelImport from '~/components/import/excelImport' export default { components: { + ExcelImport, TemplatesModal, ShareIcons, SponsorMini, @@ -711,6 +735,7 @@ export default { }, data() { return { + excelImportModal: false, templatesModal: false, overlayVisible: true, showCommunity: false, @@ -979,6 +1004,10 @@ export default { onCreateProjectFromTemplate() { this.templatesModal = true }, + onCreateProjectFromExcel() { + // this.$refs.excelImport.selectFile() + this.excelImportModal = true + }, async importProjectFromJSON() { }, onTourCompletion() { diff --git a/packages/nc-gui/plugins/ncApis/index.js b/packages/nc-gui/plugins/ncApis/index.js index fcdacfa2a8..4ced54aa1e 100644 --- a/packages/nc-gui/plugins/ncApis/index.js +++ b/packages/nc-gui/plugins/ncApis/index.js @@ -5,7 +5,7 @@ export default function({ store: $store, $axios, ...rest }, inject) { let projectId = null inject('ncApis', { - get: ({ table, dbAlias = 'db', env = '_noco' }) => { + get: ({ table, dbAlias = 'db', env = '_noco', type }) => { if (!$store.state.meta.metas[table]) { return } @@ -19,7 +19,7 @@ export default function({ store: $store, $axios, ...rest }, inject) { instanceRefs[env][dbAlias][table] = ApiFactory.create( table, - $store.getters['project/GtrProjectType'], + type || $store.getters['project/GtrProjectType'], { $store, $axios, projectId, dbAlias, env, table } ) diff --git a/packages/nc-gui/plugins/ncApis/restApi.js b/packages/nc-gui/plugins/ncApis/restApi.js index 722a3e0749..b26c9bf13b 100644 --- a/packages/nc-gui/plugins/ncApis/restApi.js +++ b/packages/nc-gui/plugins/ncApis/restApi.js @@ -93,6 +93,17 @@ export default class RestApi { })).data } + async insertBulk(data, { + params = {} + } = {}) { + return (await this.$axios({ + method: 'post', + url: `/nc/${this.$ctx.projectId}/api/v1/${this.table}/bulk`, + data, + params + })).data + } + async delete(id) { return this.$axios({ method: 'delete', diff --git a/packages/nc-gui/store/meta.js b/packages/nc-gui/store/meta.js index 8bc833cdd2..8869501f31 100644 --- a/packages/nc-gui/store/meta.js +++ b/packages/nc-gui/store/meta.js @@ -16,7 +16,7 @@ export const mutations = { } export const actions = { - async ActLoadMeta({ state, commit, dispatch }, { tn, env, dbAlias, force }) { + async ActLoadMeta({ state, commit, dispatch }, { tn, env = '_noco', dbAlias = 'db', force }) { if (!force && state.loading[tn]) { return await new Promise((resolve) => { const unsubscribe = this.app.store.subscribe((s) => { diff --git a/packages/nc-gui/store/sqlMgr.js b/packages/nc-gui/store/sqlMgr.js index 66b7a7fba4..adccc9a68f 100644 --- a/packages/nc-gui/store/sqlMgr.js +++ b/packages/nc-gui/store/sqlMgr.js @@ -361,6 +361,8 @@ export const actions = { dispatch }, [args, op, opArgs, cusHeaders, cusAxiosOptions, queryParams, returnResponse]) { const params = {} + params.project_id = rootState.project.projectId + if (this.$router.currentRoute && this.$router.currentRoute.params) { if (this.$router.currentRoute.params.project_id) { params.project_id = this.$router.currentRoute.params.project_id @@ -419,6 +421,7 @@ export const actions = { return data } catch (e) { + console.log(e) const err = new Error(e.response.data.msg) err.response = e.response throw err diff --git a/packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts b/packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts index 69d2b2a0df..057c590bff 100644 --- a/packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts +++ b/packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts @@ -353,7 +353,7 @@ export default class NcMetaIOImpl extends NcMetaIO { // } } config.id = id; - const project = { + const project: any = { id, title: projectName, description, @@ -368,6 +368,8 @@ export default class NcMetaIOImpl extends NcMetaIO { created_at: this.knexConnection?.fn?.now(), updated_at: this.knexConnection?.fn?.now() }); + + project.prefix = config.prefix; return project; } catch (e) { console.log(e);