Browse Source

feat: add project creation from excel

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/765/head
Pranav C 3 years ago
parent
commit
39c644ad65
  1. 90
      packages/nc-gui/components/import/ExcelTemplateAdapter.js
  2. 13
      packages/nc-gui/components/import/TemplateGenerator.js
  3. 177
      packages/nc-gui/components/import/excelImport.vue
  4. 148
      packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue
  5. 172
      packages/nc-gui/components/templates/editor.vue
  6. 3
      packages/nc-gui/lang/en.json
  7. 29
      packages/nc-gui/pages/projects/index.vue
  8. 4
      packages/nc-gui/plugins/ncApis/index.js
  9. 11
      packages/nc-gui/plugins/ncApis/restApi.js
  10. 2
      packages/nc-gui/store/meta.js
  11. 3
      packages/nc-gui/store/sqlMgr.js
  12. 4
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

90
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
}
}

13
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')
}
}

177
packages/nc-gui/components/import/excelImport.vue

@ -1,55 +1,170 @@
<template> <template>
<v-tooltip bottom> <div>
<template #activator="{on}"> <v-dialog max-width="600" :value="dropOrUpload">
<input ref="file" type="file" style="display: none" accept=".xlsx, .xls" @change="_change($event)"> <v-card max-width="600">
<v-btn <div class="pa-4">
small <div
outlined class="nc-droppable d-flex align-center justify-center"
v-on="on" :style="{
@click="$refs.file.click()" background : dragOver ? '#7774' : ''
> }"
<v-icon small class="mr-1"> @click="$refs.file.click()"
mdi-file-excel-outline @drop.prevent="dropHandler"
</v-icon> @dragover.prevent="dragOver = true"
Import @dragenter.prevent="dragOver = true"
</v-btn> @dragexit="dragOver = false"
</template> @dragleave="dragOver = false"
<span class="caption">Create template from Excel</span> @dragend="dragOver = false"
</v-tooltip> >
<v-icon size="50" color="grey">
mdi-upload
</v-icon>
</div>
</div>
</v-card>
</v-dialog>
<v-tooltip bottom>
<template #activator="{on}">
<input ref="file" type="file" style="display: none" accept=".xlsx, .xls" @change="_change($event)">
<v-btn
v-if="!hideLabel"
small
outlined
v-on="on"
@click="$refs.file.click()"
>
<v-icon small class="mr-1">
mdi-file-excel-outline
</v-icon>
Import
</v-btn>
</template>
<span class="caption">Create template from Excel</span>
</v-tooltip>
<v-dialog v-if="templateData" :value="true">
<v-card>
<template-editor :template-data.sync="templateData">
<template #toolbar>
<v-spacer />
<create-project-from-template-btn
:loader-message.sync="loaderMessage"
:template-data="templateData"
:import-data="importData"
/>
</template>
</template-editor>
</v-card>
</v-dialog>
<v-overlay :value="loaderMessage" z-index="99999" opacity=".9">
<div class="d-flex flex-column align-center">
<v-progress-circular indeterminate size="100" width="15" class="mb-10" />
<span class="title">{{ loaderMessage }}</span>
</div>
</v-overlay>
</div>
</template> </template>
<script> <script>
import XLSX from 'xlsx' // import XLSX from 'xlsx'
import ExcelTemplateAdapter from '~/components/import/ExcelTemplateAdapter'
import TemplateEditor from '~/components/templates/editor'
import CreateProjectFromTemplateBtn from '~/components/templates/createProjectFromTemplateBtn'
export default { export default {
name: 'ExcelImport', name: 'ExcelImport',
components: { CreateProjectFromTemplateBtn, TemplateEditor },
props: {
hideLabel: Boolean,
value: Boolean
},
data() {
return {
templateData: null,
importData: null,
dragOver: false,
loaderMessage: null
}
},
computed: {
dropOrUpload: {
set(v) {
this.$emit('input', v)
},
get() {
return this.value
}
}
},
mounted() {
},
methods: { methods: {
selectFile() {
this.$refs.file.files = null
this.$refs.file.click()
},
_change(file) { _change(file) {
const files = file.target.files const files = file.target.files
if (files && files[0]) { this._file(files[0]) } if (files && files[0]) {
this._file(files[0])
}
}, },
_file(file) { _file(file) {
this.loaderMessage = 'Loading excel file...'
this.dropOrUpload = false
console.time('excelImport')
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = (e) => {
const ab = e.target.result const ab = e.target.result
const wb = XLSX.read(new Uint8Array(ab), { type: 'array' }) const templateGenerator = new ExcelTemplateAdapter(file.name, ab)
templateGenerator.parse()
// const res = {} this.templateData = templateGenerator.getTemplate()
// iterate each sheet this.importData = templateGenerator.getData()
// each sheet repensents each table console.timeEnd('excelImport')
for (let i = 0; i < wb.SheetNames.length; i++) { this.loaderMessage = null
const sheet = wb.SheetNames[i] }
const ws = wb.Sheets[sheet] reader.onerror = () => {
const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) this.loaderMessage = null
console.log(rows)
}
} }
reader.readAsArrayBuffer(file) reader.readAsArrayBuffer(file)
},
dropHandler(ev) {
console.log('File(s) dropped')
let file
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
if (ev.dataTransfer.items.length && ev.dataTransfer.items[0].kind === 'file') {
file = ev.dataTransfer.items[0].getAsFile()
}
} else if (ev.dataTransfer.files.length) {
file = ev.dataTransfer.files[0]
}
if (file) {
this._file(file)
}
},
dragOverHandler(ev) {
console.log('File(s) in drop zone')
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault()
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.nc-droppable {
width: 100%;
min-height: 200px;
border-radius: 4px;
border: 2px dashed var(--v-textColor-lighten5);
}
</style> </style>

148
packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue

@ -0,0 +1,148 @@
<template>
<div>
<v-menu bottom offset-y>
<template #activator="{on}">
<v-btn
:loading="projectCreation"
:disabled="projectCreation"
class="primary"
x-large
v-on="on"
>
Use template
<v-icon>mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item dense class="py-2" @click="useTemplate('rest')">
<v-list-item-title>
<v-icon class="mr-1" :color="textColors[7]">
mdi-code-json
</v-icon>
Create REST Project
</v-list-item-title>
</v-list-item>
<v-list-item dense class="py-2" @click="useTemplate('graphql')">
<v-list-item-title>
<v-icon class="mr-1" :color="textColors[3]">
mdi-graphql
</v-icon>
Create GQL Project
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</template>
<script>
import colors from '~/mixins/colors'
export default {
name: 'CreateProjectFromTemplateBtn',
mixins: [colors],
props: {
loading: Boolean,
templateData: Object,
importData: Object,
loaderMessage: String
},
data() {
return {
projectCreation: false,
loaderMessagesIndex: 0,
loaderMessages: [
'Setting up new database configs',
'Inferring database schema',
'Generating APIs.',
'Generating APIs..',
'Generating APIs...',
'Generating APIs....',
'Please wait',
'Please wait.',
'Please wait..',
'Please wait...',
'Please wait..',
'Please wait.',
'Please wait',
'Please wait.',
'Please wait..',
'Please wait...',
'Please wait..',
'Please wait.',
'Please wait..',
'Please wait...'
]
}
},
methods: {
async useTemplate(projectType) {
// this.$emit('useTemplate', type)
this.projectCreation = true
try {
const interv = setInterval(() => {
debugger
this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6
this.$emit('update:loaderMessage', this.loaderMessages[this.loaderMessagesIndex])
}, 1000)
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', {
title: this.templateData.title,
projectType,
template: this.templateData
}])
await this.$store.dispatch('project/ActLoadProjectInfo')
clearInterval(interv)
if (this.importData) {
this.$emit('update:loaderMessage', 'Importing excel data to project')
await this.importDataToProject({ projectId: result.id, projectType, prefix: result.prefix })
}
this.$emit('update:loaderMessage', null)
this.projectReloading = false
this.$router.push({
path: `/nc/${result.id}`,
query: {
new: 1
}
})
} catch (e) {
console.log(e)
this.$toast.error(e.message).goAway(3000)
}
this.projectCreation = false
},
async importDataToProject({ projectId, projectType, prefix = '' }) {
this.$store.commit('project/MutProjectId', projectId)
this.$ncApis.setProjectId(projectId)
await Promise.all(Object.entries(this.importData).map(async([table, data]) => {
await this.$store.dispatch('meta/ActLoadMeta', {
tn: `${prefix}${table}`
})
// todo: get table name properly
const api = this.$ncApis.get({
table: `${prefix}${table}`,
type: projectType
})
for (let i = 0; i < data.length; i += 500) {
console.log(data[i])
await api.insertBulk(data.slice(i, i + 500))
}
}))
}
}
}
</script>
<style scoped>
</style>

172
packages/nc-gui/components/templates/editor.vue

@ -1,88 +1,90 @@
<template> <template>
<div class="h-100"> <div class="h-100">
<v-toolbar v-if="!viewMode" class="elevation-0"> <v-toolbar v-if="!viewMode" class="elevation-0">
<!-- <v-text-field <slot name="toolbar">
v-model="url" <!-- <v-text-field
clearable v-model="url"
placeholder="Enter template url" clearable
outlined placeholder="Enter template url"
hide-details outlined
dense hide-details
@keydown.enter="loadUrl" dense
/>--> @keydown.enter="loadUrl"
<!-- <v-btn outlined class='ml-1' @click='loadUrl'> Load URL</v-btn>--> />-->
<!-- <v-btn outlined class='ml-1' @click='loadUrl'> Load URL</v-btn>-->
<v-tooltip bottom>
<template #activator="{on}"> <v-tooltip bottom>
<v-btn <template #activator="{on}">
small <v-btn
outlined small
v-on="on" outlined
@click="$toast.info('Happy hacking!').goAway(3000)" v-on="on"
> @click="$toast.info('Happy hacking!').goAway(3000)"
<v-icon small class="mr-1"> >
mdi-file-excel-outline <v-icon small class="mr-1">
mdi-file-excel-outline
</v-icon>
Import
</v-btn>
</template>
<span class="caption">Create template from Excel</span>
</v-tooltip>
<v-spacer />
<v-icon class="mr-3" @click="helpModal=true">
mdi-information-outline
</v-icon>
<!-- <v-icon class="mr-3" @click="openUrl">
mdi-web
</v-icon>-->
<!-- <v-tooltip bottom>
<template #activator="{on}">
<v-icon
class="mr-3"
v-on="on"
@click="url = '',project.tables= []"
>
mdi-close-circle-outline
</v-icon> </v-icon>
Import </template>
</v-btn> <span class="caption">Reset template</span>
</template> </v-tooltip>-->
<span class="caption">Create template from Excel</span>
</v-tooltip> <v-btn small outlined class="mr-1" @click="project = {tables : []}">
<v-icon small>
<v-spacer /> mdi-close
<v-icon class="mr-3" @click="helpModal=true">
mdi-information-outline
</v-icon>
<!-- <v-icon class="mr-3" @click="openUrl">
mdi-web
</v-icon>-->
<!-- <v-tooltip bottom>
<template #activator="{on}">
<v-icon
class="mr-3"
v-on="on"
@click="url = '',project.tables= []"
>
mdi-close-circle-outline
</v-icon> </v-icon>
</template> Reset
<span class="caption">Reset template</span> </v-btn>
</v-tooltip>--> <!-- <v-icon
:color="$store.getters['github/isAuthorized'] ? '' : 'error'"
class="mr-3"
@click="githubConfigForm = !githubConfigForm"
>
mdi-github
</v-icon>-->
<v-btn small outlined class="mr-1" @click="createTablesDialog = true">
<v-icon small>
mdi-plus
</v-icon>
New table
</v-btn>
<v-btn small outlined class="mr-1" @click="project = {tables : []}"> <!-- <v-btn outlined small class='mr-1' @click='submitTemplate'> Submit Template</v-btn>-->
<v-icon small> <v-btn
mdi-close color="primary"
</v-icon> outlined
Reset small
</v-btn> class="mr-1"
<!-- <v-icon :loading="loading"
:color="$store.getters['github/isAuthorized'] ? '' : 'error'" :disabled="loading"
class="mr-3" @click="saveTemplate"
@click="githubConfigForm = !githubConfigForm" >
> {{ id || localId ? 'Update in' : 'Submit to' }} NocoDB
mdi-github </v-btn>
</v-icon>--> </slot>
<v-btn small outlined class="mr-1" @click="createTablesDialog = true">
<v-icon small>
mdi-plus
</v-icon>
New table
</v-btn>
<!-- <v-btn outlined small class='mr-1' @click='submitTemplate'> Submit Template</v-btn>-->
<v-btn
color="primary"
outlined
small
class="mr-1"
:loading="loading"
:disabled="loading"
@click="saveTemplate"
>
{{ id || localId ? 'Update in' : 'Submit to' }} NocoDB
</v-btn>
</v-toolbar> </v-toolbar>
<v-container class="text-center" style="height:calc(100% - 64px);overflow-y: auto"> <v-container class="text-center" style="height:calc(100% - 64px);overflow-y: auto">
<v-form ref="form"> <v-form ref="form">
@ -620,7 +622,7 @@ export default {
return this.url && this.url.split('/').pop() return this.url && this.url.split('/').pop()
}, },
projectTemplate() { projectTemplate() {
return { const template = {
...this.project, ...this.project,
tables: (this.project.tables || []).map((t) => { tables: (this.project.tables || []).map((t) => {
const table = { tn: t.tn, columns: [], hasMany: [], manyToMany: [], belongsTo: [], v: [] } const table = { tn: t.tn, columns: [], hasMany: [], manyToMany: [], belongsTo: [], v: [] }
@ -673,6 +675,10 @@ export default {
return table return table
}) })
} }
this.$emit('update:templateData', template)
return template
} }
}, },
@ -851,7 +857,7 @@ export default {
isRelation(col) { isRelation(col) {
return col.uidt === 'LinkToAnotherRecord' || return col.uidt === 'LinkToAnotherRecord' ||
col.uidt === 'ForeignKey' col.uidt === 'ForeignKey'
}, },
isLookup(col) { isLookup(col) {
return col.uidt === 'Lookup' return col.uidt === 'Lookup'
@ -864,11 +870,11 @@ export default {
}, },
isLookupOrRollup(col) { isLookupOrRollup(col) {
return this.isLookup(col) || return this.isLookup(col) ||
this.isRollup(col) this.isRollup(col)
}, },
isSelect(col) { isSelect(col) {
return col.uidt === 'MultiSelect' || return col.uidt === 'MultiSelect' ||
col.uidt === 'SingleSelect' col.uidt === 'SingleSelect'
}, },
addNewColumnRow(table, uidt) { addNewColumnRow(table, uidt) {
table.columns.push({ table.columns.push({

3
packages/nc-gui/lang/en.json

@ -8,7 +8,8 @@
"subtext_1_tooltip": "Create a new project", "subtext_1_tooltip": "Create a new project",
"subtext_2": "Create By Connecting <br>To An External Database", "subtext_2": "Create By Connecting <br>To An External Database",
"subtext_2_tooltip": "Supports MySQL, PostgreSQL, SQL Server & SQLite", "subtext_2_tooltip": "Supports MySQL, PostgreSQL, SQL Server & SQLite",
"from_template": "Create Project from template" "from_template": "Create Project from template",
"from_excel": "Create Project from excel"
}, },
"search_project": "Search Project", "search_project": "Search Project",
"import_metadata": "Import Metadata", "import_metadata": "Import Metadata",

29
packages/nc-gui/pages/projects/index.vue

@ -211,6 +211,27 @@
/> />
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-divider />
<v-list-item
title
class="pt-2 nc-create-project-from-excel"
@click="onCreateProjectFromExcel()"
>
<v-list-item-icon class="mr-2">
<v-icon small class="">
mdi-file-excel-outline
</v-icon>
</v-list-item-icon>
<v-list-item-title>
<!-- Create By Connecting <br>To An External Database -->
<span
class="caption font-weight-regular"
v-html="
$t('projects.create_new_project_button.from_excel')
"
/>
</v-list-item-title>
</v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
</template> </template>
@ -686,6 +707,7 @@
:dialog-show="dialogShow" :dialog-show="dialogShow"
:heading="confirmMessage" :heading="confirmMessage"
/> />
<excel-import ref="excelImport" v-model="excelImportModal" hide-label />
<templates-modal v-model="templatesModal" hide-label create-project /> <templates-modal v-model="templatesModal" hide-label create-project />
</v-container> </v-container>
</template> </template>
@ -696,9 +718,11 @@ import ShareIcons from '../../components/share-icons'
import SponsorMini from '@/components/sponsorMini' import SponsorMini from '@/components/sponsorMini'
import colors from '~/mixins/colors' import colors from '~/mixins/colors'
import TemplatesModal from '~/components/templates/templatesModal' import TemplatesModal from '~/components/templates/templatesModal'
import ExcelImport from '~/components/import/excelImport'
export default { export default {
components: { components: {
ExcelImport,
TemplatesModal, TemplatesModal,
ShareIcons, ShareIcons,
SponsorMini, SponsorMini,
@ -711,6 +735,7 @@ export default {
}, },
data() { data() {
return { return {
excelImportModal: false,
templatesModal: false, templatesModal: false,
overlayVisible: true, overlayVisible: true,
showCommunity: false, showCommunity: false,
@ -979,6 +1004,10 @@ export default {
onCreateProjectFromTemplate() { onCreateProjectFromTemplate() {
this.templatesModal = true this.templatesModal = true
}, },
onCreateProjectFromExcel() {
// this.$refs.excelImport.selectFile()
this.excelImportModal = true
},
async importProjectFromJSON() { async importProjectFromJSON() {
}, },
onTourCompletion() { onTourCompletion() {

4
packages/nc-gui/plugins/ncApis/index.js

@ -5,7 +5,7 @@ export default function({ store: $store, $axios, ...rest }, inject) {
let projectId = null let projectId = null
inject('ncApis', { inject('ncApis', {
get: ({ table, dbAlias = 'db', env = '_noco' }) => { get: ({ table, dbAlias = 'db', env = '_noco', type }) => {
if (!$store.state.meta.metas[table]) { if (!$store.state.meta.metas[table]) {
return return
} }
@ -19,7 +19,7 @@ export default function({ store: $store, $axios, ...rest }, inject) {
instanceRefs[env][dbAlias][table] = ApiFactory.create( instanceRefs[env][dbAlias][table] = ApiFactory.create(
table, table,
$store.getters['project/GtrProjectType'], type || $store.getters['project/GtrProjectType'],
{ $store, $axios, projectId, dbAlias, env, table } { $store, $axios, projectId, dbAlias, env, table }
) )

11
packages/nc-gui/plugins/ncApis/restApi.js

@ -93,6 +93,17 @@ export default class RestApi {
})).data })).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) { async delete(id) {
return this.$axios({ return this.$axios({
method: 'delete', method: 'delete',

2
packages/nc-gui/store/meta.js

@ -16,7 +16,7 @@ export const mutations = {
} }
export const actions = { 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]) { if (!force && state.loading[tn]) {
return await new Promise((resolve) => { return await new Promise((resolve) => {
const unsubscribe = this.app.store.subscribe((s) => { const unsubscribe = this.app.store.subscribe((s) => {

3
packages/nc-gui/store/sqlMgr.js

@ -361,6 +361,8 @@ export const actions = {
dispatch dispatch
}, [args, op, opArgs, cusHeaders, cusAxiosOptions, queryParams, returnResponse]) { }, [args, op, opArgs, cusHeaders, cusAxiosOptions, queryParams, returnResponse]) {
const params = {} const params = {}
params.project_id = rootState.project.projectId
if (this.$router.currentRoute && this.$router.currentRoute.params) { if (this.$router.currentRoute && this.$router.currentRoute.params) {
if (this.$router.currentRoute.params.project_id) { if (this.$router.currentRoute.params.project_id) {
params.project_id = this.$router.currentRoute.params.project_id params.project_id = this.$router.currentRoute.params.project_id
@ -419,6 +421,7 @@ export const actions = {
return data return data
} catch (e) { } catch (e) {
console.log(e)
const err = new Error(e.response.data.msg) const err = new Error(e.response.data.msg)
err.response = e.response err.response = e.response
throw err throw err

4
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -353,7 +353,7 @@ export default class NcMetaIOImpl extends NcMetaIO {
// } // }
} }
config.id = id; config.id = id;
const project = { const project: any = {
id, id,
title: projectName, title: projectName,
description, description,
@ -368,6 +368,8 @@ export default class NcMetaIOImpl extends NcMetaIO {
created_at: this.knexConnection?.fn?.now(), created_at: this.knexConnection?.fn?.now(),
updated_at: this.knexConnection?.fn?.now() updated_at: this.knexConnection?.fn?.now()
}); });
project.prefix = config.prefix;
return project; return project;
} catch (e) { } catch (e) {
console.log(e); console.log(e);

Loading…
Cancel
Save