From 6be9663a6ca6861421971cb78dfc5cae65ee8b6e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 28 Apr 2022 19:35:49 +0800 Subject: [PATCH 01/26] fix: key naming for column name --- .../components/import/templateParsers/ExcelTemplateAdapter.js | 4 ++-- .../components/templates/createProjectFromTemplateBtn.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js index 37dfd37bae..469bca5ca9 100644 --- a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js +++ b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js @@ -79,8 +79,8 @@ export default class ExcelTemplateAdapter extends TemplateGenerator { columnNamePrefixRef[cn] = 0 const column = { - cn, - refCn: cn + column_name: cn, + ref_column_name: cn } table.columns.push(column) diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue index 9d3c0542c0..709c844936 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -198,7 +198,7 @@ export default { remapColNames(batchData, columns) { return batchData.map(data => (columns || []).reduce((aggObj, col) => ({ ...aggObj, - [col.column_name]: data[col.refCn] + [col.column_name]: data[col.ref_column_name] }), {}) ) } From f91f46b0c99dc3832af655e2c75b792de90bc03f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 28 Apr 2022 19:36:03 +0800 Subject: [PATCH 02/26] chore: enable back excel import option --- packages/nc-gui/pages/projects/index.vue | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/nc-gui/pages/projects/index.vue b/packages/nc-gui/pages/projects/index.vue index 86e52191e6..9a6553a5ca 100644 --- a/packages/nc-gui/pages/projects/index.vue +++ b/packages/nc-gui/pages/projects/index.vue @@ -135,6 +135,26 @@ {{ $t("tooltip.extDB") }} + + + + + mdi-file-excel-outline + + + + + + From 6873bcb0e6b2dce65a2e62a2e0b0e5c6976da402 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 28 Apr 2022 20:03:31 +0800 Subject: [PATCH 03/26] fix: key naming for table name --- .../components/import/templateParsers/ExcelTemplateAdapter.js | 2 +- .../components/templates/createProjectFromTemplateBtn.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js index 469bca5ca9..b1964ae5a6 100644 --- a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js +++ b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js @@ -42,7 +42,7 @@ export default class ExcelTemplateAdapter extends TemplateGenerator { } tableNamePrefixRef[tn] = 0 - const table = { tn, refTn: tn, columns: [] } + const table = { table_name: tn, ref_table_name: tn, columns: [] } this.data[tn] = [] const ws = this.wb.Sheets[sheet] const range = XLSX.utils.decode_range(ws['!ref']) diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue index 709c844936..0b53645aa4 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -173,7 +173,7 @@ export default { await Promise.all(this.templateData.tables.map(v => (async(tableMeta) => { const table = tableMeta.table_name - const data = this.importData[tableMeta.refTn] + const data = this.importData[tableMeta.ref_table_name] await this.$store.dispatch('meta/ActLoadMeta', { tn: `${prefix}${table}`, project_id: projectId From 21cd83be9628b62d63a9c728aa6ae6b6d3a09dbc Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 29 Apr 2022 12:45:26 +0800 Subject: [PATCH 04/26] chore: point to file:../nocodb-sdk --- packages/nc-gui/package.json | 2 +- packages/nocodb/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/package.json b/packages/nc-gui/package.json index ab53a44056..67e5eb682c 100644 --- a/packages/nc-gui/package.json +++ b/packages/nc-gui/package.json @@ -29,7 +29,7 @@ "monaco-editor": "^0.19.3", "monaco-themes": "^0.2.5", "nano-assign": "^1.0.1", - "nocodb-sdk": "0.90.8", + "nocodb-sdk": "file:../nocodb-sdk", "nuxt": "^2.14.0", "odometer": "^0.4.8", "papaparse": "^5.3.1", diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index faee031447..bdd0f5efd4 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -154,7 +154,7 @@ "nc-lib-gui": "0.90.8", "nc-plugin": "^0.1.1", "ncp": "^2.0.0", - "nocodb-sdk": "0.90.8", + "nocodb-sdk": "file:../nocodb-sdk", "nodemailer": "^6.4.10", "ora": "^4.0.4", "os-locale": "^5.0.0", From 183a4fb659b35f96a249ef954913fea8ea7ff026 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 29 Apr 2022 12:59:12 +0800 Subject: [PATCH 05/26] feat: add createByExcel API --- packages/nocodb-sdk/src/lib/Api.ts | 33 +++++++++++++++ scripts/sdk/swagger.json | 68 ++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index ee6bb65487..3ca7adba17 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -1204,6 +1204,39 @@ export class Api< ...params, }), + /** + * @description Create Project by Importing Excel File + * + * @tags Project + * @name CreateByExcel + * @summary Project create + * @request POST:/api/v1/db/meta/projects/import/excel + * @response `200` `ProjectType` OK + */ + createByExcel: ( + data: { + title?: string; + projectType?: string; + template?: { + title?: string; + tables?: { + table_name?: string; + ref_table_name?: string; + columns?: object[]; + }[]; + }; + }, + params: RequestParams = {} + ) => + this.request({ + path: `/api/v1/db/meta/projects/import/excel`, + method: 'POST', + body: data, + type: ContentType.Json, + format: 'json', + ...params, + }), + /** * @description Read project details * diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index 091505e9a9..8ce379b025 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -801,6 +801,74 @@ ] } }, + "/api/v1/db/meta/projects/import/excel": { + "parameters": [], + "post": { + "summary": "Project create", + "operationId": "project-create-by-excel", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "projectType": { + "type": "string" + }, + "template": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "tables": { + "type": "array", + "items": { + "type": "object", + "properties": { + "table_name": { + "type": "string" + }, + "ref_table_name": { + "type": "string" + }, + "columns": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [ + "Project" + ], + "description": "Create Project by Importing Excel File" + } + }, "/api/v1/db/meta/projects/{projectId}": { "parameters": [ { From 53c6aade153934daba4137aed557f4cffd21f542 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 29 Apr 2022 19:53:32 +0800 Subject: [PATCH 06/26] chore: mark it optional --- packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts index 5ec1d5afa8..1b5b04dff0 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts @@ -1476,7 +1476,7 @@ class BaseModelSqlv2 { const response = await this.dbDriver .batchInsert(this.model.table_name, insertDatas, 50) - .returning(this.model.primaryKey.column_name); + .returning(this.model.primaryKey?.column_name); // await this.afterInsertb(insertDatas, null); From f34c7aae161e03e509f7dfefe35b2eccd07669c0 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 29 Apr 2022 19:54:20 +0800 Subject: [PATCH 07/26] fix: integrate export import with new api design --- .../createProjectFromTemplateBtn.vue | 110 +++++++++--------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue index 0b53645aa4..23aa25515b 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -107,33 +107,57 @@ export default { projectId = this.$route.params.project_id prefix = this.$store.getters['project/GtrProjectPrefix'] } else { - const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', { - title: this.templateData.title, - projectType, - template: this.templateData, - excelImport: this.excelImport - }]) - projectId = result.id - prefix = result.prefix - await this.$store.dispatch('project/ActLoadProjectInfo') + try { + // Create an empty project + const result = await this.$api.project.create({ + title: this.templateData.title, + external: false + }) + + prefix = result.prefix + + clearInterval(interv) + + await this.$store.dispatch('project/ActLoadProjectInfo') + + this.projectReloading = false + + if (!this.edit && !this.allSchemas) { + this.$router.push({ + path: `/nc/${result.id}`, + query: { + new: 1 + } + }) + } + + this.projectCreated = true + + // Create tables + for (var t of this.templateData.tables) { + const table = await this.$api.dbTable.create(result.id, { + table_name: t.ref_table_name, + title: '', + columns: t.columns, + }); + console.log(table) + t.table_title = table.title + } + } catch (e) { + this.$toast + .error(await this._extractSdkResponseErrorMsg(e)) + .goAway(3000) + } } clearInterval(interv) + + // Bulk import data if (this.importData) { this.$store.commit('loader/MutMessage', 'Importing excel data to project') - await this.importDataToProject({ projectId, projectType, prefix }) + await this.importDataToProject(this.templateData.title, prefix) } this.$store.commit('loader/MutMessage', null) - this.projectReloading = false - if (!this.importToProject) { - await this.$router.push({ - path: `/nc/${projectId}`, - query: { - new: 1 - } - }) - } - this.$emit('success') } catch (e) { console.log(e) @@ -143,53 +167,23 @@ export default { } this.projectCreation = false }, - async importDataToProject({ projectId, projectType, prefix = '' }) { - // this.$store.commit('project/MutProjectId', projectId) - this.$ncApis.setProjectId(projectId) - + async importDataToProject(projectName, prefix) { let total = 0 let progress = 0 - - /* await Promise.all(Object.entries(this.importData).map(v => (async([table, data]) => { - await this.$store.dispatch('meta/ActLoadMeta', { - tn: `${prefix}${table}`, project_id: projectId - }) - - // todo: get table name properly - const api = this.$ncApis.get({ - table: `${prefix}${table}`, - type: projectType - }) - total += data.length - for (let i = 0; i < data.length; i += 500) { - this.$store.commit('loader/MutMessage', `Importing data : ${progress}/${total}`) - this.$store.commit('loader/MutProgress', Math.round(progress && 100 * progress / total)) - const batchData = data.slice(i, i + 500) - await api.insertBulk(batchData) - progress += batchData.length - } - this.$store.commit('loader/MutClear') - })(v))) */ - await Promise.all(this.templateData.tables.map(v => (async(tableMeta) => { - const table = tableMeta.table_name + const table = tableMeta.table_title const data = this.importData[tableMeta.ref_table_name] - - await this.$store.dispatch('meta/ActLoadMeta', { - tn: `${prefix}${table}`, project_id: projectId - }) - - // todo: get table name properly - const api = this.$ncApis.get({ - table: `${prefix}${table}`, - type: projectType - }) total += data.length for (let i = 0; i < data.length; i += 500) { this.$store.commit('loader/MutMessage', `Importing data : ${progress}/${total}`) this.$store.commit('loader/MutProgress', Math.round(progress && 100 * progress / total)) const batchData = this.remapColNames(data.slice(i, i + 500), tableMeta.columns) - await api.insertBulk(batchData) + await this.$api.dbTableRow.bulkCreate( + 'noco', + projectName, + table, + batchData + ) progress += batchData.length } this.$store.commit('loader/MutClear') From 253a3b1fecab3108d4b8c8cc3c3b571b93b48c9b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 29 Apr 2022 19:57:05 +0800 Subject: [PATCH 08/26] chore: rm CreateByExcel API --- packages/nocodb-sdk/src/lib/Api.ts | 33 --------------- scripts/sdk/swagger.json | 68 ------------------------------ 2 files changed, 101 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index 3ca7adba17..ee6bb65487 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -1204,39 +1204,6 @@ export class Api< ...params, }), - /** - * @description Create Project by Importing Excel File - * - * @tags Project - * @name CreateByExcel - * @summary Project create - * @request POST:/api/v1/db/meta/projects/import/excel - * @response `200` `ProjectType` OK - */ - createByExcel: ( - data: { - title?: string; - projectType?: string; - template?: { - title?: string; - tables?: { - table_name?: string; - ref_table_name?: string; - columns?: object[]; - }[]; - }; - }, - params: RequestParams = {} - ) => - this.request({ - path: `/api/v1/db/meta/projects/import/excel`, - method: 'POST', - body: data, - type: ContentType.Json, - format: 'json', - ...params, - }), - /** * @description Read project details * diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index 8ce379b025..091505e9a9 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -801,74 +801,6 @@ ] } }, - "/api/v1/db/meta/projects/import/excel": { - "parameters": [], - "post": { - "summary": "Project create", - "operationId": "project-create-by-excel", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "projectType": { - "type": "string" - }, - "template": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "tables": { - "type": "array", - "items": { - "type": "object", - "properties": { - "table_name": { - "type": "string" - }, - "ref_table_name": { - "type": "string" - }, - "columns": { - "type": "array", - "items": { - "type": "object" - } - } - } - } - } - } - } - } - } - } - } - }, - "tags": [ - "Project" - ], - "description": "Create Project by Importing Excel File" - } - }, "/api/v1/db/meta/projects/{projectId}": { "parameters": [ { From 26205c1d9a6706fd1212e3b24aec0561a7caba0b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 13:19:53 +0800 Subject: [PATCH 09/26] enhancement: add tooltip --- .../nc-gui/components/templates/editor.vue | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index 30f4840b45..b9e353c93d 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -118,15 +118,24 @@ - - mdi-delete-outline - + + + Delete Table + + Delete Column + - - - - - From 387fa87a3ca442cd2f649a1de1f1b7261cc39f06 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 13:28:34 +0800 Subject: [PATCH 10/26] enhancement: add tooltips to bottom options --- .../nc-gui/components/templates/editor.vue | 195 +++++++++++------- 1 file changed, 122 insertions(+), 73 deletions(-) diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index b9e353c93d..0022d00f6f 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -118,10 +118,8 @@ - - + Delete Table @@ -495,24 +493,22 @@ " /> - - + Delete Column @@ -522,57 +518,110 @@
- - {{ getIcon("Number") }} - - - {{ getIcon("SingleLineText") }} - - - {{ getIcon("LongText") }} - - - {{ getIcon("LinkToAnotherRecord") }} - - - {{ getIcon("Lookup") }} - - - {{ getIcon("Rollup") }} - - - + column - + + + + Add Number Column + + + + + + Add SingleLineText Column + + + + + + Add LongText Column + + + + + + Add LinkToAnotherRecord Column + + + + + + Add Lookup Column + + + + + + Add Rollup Column + + + + + + Add Other Column +
From ab15302850a3e842b74b5b341169e5a87671d10e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 13:31:33 +0800 Subject: [PATCH 11/26] chore: lint fix --- .../nc-gui/components/templates/editor.vue | 774 +++++++++--------- 1 file changed, 387 insertions(+), 387 deletions(-) diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index 0022d00f6f..787809a2d2 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -761,75 +761,75 @@ import { uiTypes, getUIDTIcon, - UITypes, -} from "~/components/project/spreadsheet/helpers/uiTypes"; -import GradientGenerator from "~/components/templates/gradientGenerator"; -import Help from "~/components/templates/help"; + UITypes +} from '~/components/project/spreadsheet/helpers/uiTypes' +import GradientGenerator from '~/components/templates/gradientGenerator' +import Help from '~/components/templates/help' -const LinkToAnotherRecord = "LinkToAnotherRecord"; -const Lookup = "Lookup"; -const Rollup = "Rollup"; -const defaultColProp = {}; +const LinkToAnotherRecord = 'LinkToAnotherRecord' +const Lookup = 'Lookup' +const Rollup = 'Rollup' +const defaultColProp = {} export default { - name: "TemplateEditor", + name: 'TemplateEditor', components: { Help, GradientGenerator }, props: { id: [Number, String], viewMode: Boolean, projectTemplate: Object, - excelImport: Boolean, + excelImport: Boolean }, data: () => ({ loading: false, localId: null, valid: false, - url: "", + url: '', githubConfigForm: false, helpModal: false, editableTn: {}, expansionPanel: 0, project: { - name: "Project name", - tables: [], + name: 'Project name', + tables: [] }, - tableNamesInput: "", - columnNamesInput: "", + tableNamesInput: '', + columnNamesInput: '', createTablesDialog: false, createTableColumnsDialog: false, selectedTable: null, uiTypes: uiTypes.filter( - (t) => ![UITypes.Formula, UITypes.SpecificDBType].includes(t.name) + t => ![UITypes.Formula, UITypes.SpecificDBType].includes(t.name) ), rollupFnList: [ - { text: "count", value: "count" }, - { text: "min", value: "min" }, - { text: "max", value: "max" }, - { text: "avg", value: "avg" }, - { text: "min", value: "min" }, - { text: "sum", value: "sum" }, - { text: "countDistinct", value: "countDistinct" }, - { text: "sumDistinct", value: "sumDistinct" }, - { text: "avgDistinct", value: "avgDistinct" }, + { text: 'count', value: 'count' }, + { text: 'min', value: 'min' }, + { text: 'max', value: 'max' }, + { text: 'avg', value: 'avg' }, + { text: 'min', value: 'min' }, + { text: 'sum', value: 'sum' }, + { text: 'countDistinct', value: 'countDistinct' }, + { text: 'sumDistinct', value: 'sumDistinct' }, + { text: 'avgDistinct', value: 'avgDistinct' } ], colors: { - LinkToAnotherRecord: "blue lighten-5", - Rollup: "pink lighten-5", - Lookup: "green lighten-5", - }, + LinkToAnotherRecord: 'blue lighten-5', + Rollup: 'pink lighten-5', + Lookup: 'green lighten-5' + } }), computed: { counter: { get() { - return this.$store.state.templateC; + return this.$store.state.templateC }, set(c) { - this.$store.commit("mutTemplateC", c); - }, + this.$store.commit('mutTemplateC', c) + } }, updateFilename() { - return this.url && this.url.split("/").pop(); - }, + return this.url && this.url.split('/').pop() + } }, watch: { project: { @@ -844,26 +844,26 @@ export default { hasMany: [], manyToMany: [], belongsTo: [], - v: [], - }; + v: [] + } for (const column of t.columns || []) { if (this.isRelation(column)) { - if (column.type === "hm") { + if (column.type === 'hm') { table.hasMany.push({ tn: column.rtn, - _cn: column.column_name, - }); - } else if (column.type === "mm") { + _cn: column.column_name + }) + } else if (column.type === 'mm') { table.manyToMany.push({ rtn: column.rtn, - _cn: column.column_name, - }); + _cn: column.column_name + }) } else if (column.uidt === UITypes.ForeignKey) { table.belongsTo.push({ tn: column.rtn, - _cn: column.column_name, - }); + _cn: column.column_name + }) } } else if (this.isLookup(column)) { if (column.rtn) { @@ -872,9 +872,9 @@ export default { lk: { ltn: column.rtn.table_name, type: column.rtn.type, - lcn: column.rcn, - }, - }); + lcn: column.rcn + } + }) } } else if (this.isRollup(column)) { if (column.rtn) { @@ -884,267 +884,267 @@ export default { rltn: column.rtn.table_name, rlcn: column.rcn, type: column.rtn.type, - fn: column.fn, - }, - }); + fn: column.fn + } + }) } } else { - table.columns.push(column); + table.columns.push(column) } } - return table; - }), - }; - this.$emit("update:projectTemplate", template); - }, - }, + return table + }) + } + this.$emit('update:projectTemplate', template) + } + } }, created() { - document.addEventListener("keydown", this.handleKeyDown); + document.addEventListener('keydown', this.handleKeyDown) }, destroyed() { - document.removeEventListener("keydown", this.handleKeyDown); + document.removeEventListener('keydown', this.handleKeyDown) }, mounted() { - this.parseAndLoadTemplate(); + this.parseAndLoadTemplate() const input = - this.$refs.projec && this.$refs.project.$el.querySelector("input"); + this.$refs.projec && this.$refs.project.$el.querySelector('input') if (input) { - input.focus(); - input.select(); + input.focus() + input.select() } }, methods: { createTableClick() { - this.createTablesDialog = true; - this.$e("c:table:create:navdraw"); + this.createTablesDialog = true + this.$e('c:table:create:navdraw') }, parseAndLoadTemplate() { if (this.projectTemplate) { - this.parseTemplate(this.projectTemplate); + this.parseTemplate(this.projectTemplate) this.expansionPanel = Array.from( { length: this.project.tables.length }, (_, i) => i - ); + ) } }, getIcon(type) { - return getUIDTIcon(type); + return getUIDTIcon(type) }, getRelatedTables(tableName, rollup = false) { - const tables = []; + const tables = [] for (const t of this.projectTemplate.tables) { if (tableName === t.table_name) { for (const hm of t.hasMany) { const rTable = this.project.tables.find( - (t1) => t1.table_name === hm.table_name - ); + t1 => t1.table_name === hm.table_name + ) tables.push({ ...rTable, - type: "hm", - }); + type: 'hm' + }) } for (const mm of t.manyToMany) { const rTable = this.project.tables.find( - (t1) => t1.table_name === mm.rtn - ); + t1 => t1.table_name === mm.rtn + ) tables.push({ ...rTable, - type: "mm", - }); + type: 'mm' + }) } } else { for (const hm of t.hasMany) { if (hm.table_name === tableName && !rollup) { tables.push({ ...t, - type: "bt", - }); + type: 'bt' + }) } } for (const mm of t.manyToMany) { if (mm.rtn === tableName) { tables.push({ ...t, - type: "mm", - }); + type: 'mm' + }) } } } } - return tables; + return tables }, validateAndFocus() { if (!this.$refs.form.validate()) { - const input = this.$el.querySelector(".v-input.error--text"); + const input = this.$el.querySelector('.v-input.error--text') this.expansionPanel = input && input.parentElement && input.parentElement.parentElement && - +input.parentElement.parentElement.dataset.exp; + +input.parentElement.parentElement.dataset.exp setTimeout(() => { - input.querySelector("input,select").focus(); - }, 500); - return false; + input.querySelector('input,select').focus() + }, 500) + return false } - return true; + return true }, deleteTable(i) { - const deleteTable = this.project.tables[i]; + const deleteTable = this.project.tables[i] for (const table of this.project.tables) { if (table === deleteTable) { - continue; + continue } table.columns = table.columns.filter( - (c) => c.rtn !== deleteTable.table_name - ); + c => c.rtn !== deleteTable.table_name + ) } - this.project.tables.splice(i, 1); + this.project.tables.splice(i, 1) }, deleteTableColumn(i, j, col, table) { - const deleteTable = this.project.tables[i]; - const deleteColumn = deleteTable.columns[j]; + const deleteTable = this.project.tables[i] + const deleteColumn = deleteTable.columns[j] - let rTable, index; + let rTable, index // if relation column, delete the corresponding relation from other table if (col.uidt === UITypes.LinkToAnotherRecord) { - if (col.type === "hm") { - rTable = this.project.tables.find((t) => t.table_name === col.rtn); + if (col.type === 'hm') { + rTable = this.project.tables.find(t => t.table_name === col.rtn) index = rTable && rTable.columns.findIndex( - (c) => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name - ); - } else if (col.type === "mm") { - rTable = this.project.tables.find((t) => t.table_name === col.rtn); + c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + ) + } else if (col.type === 'mm') { + rTable = this.project.tables.find(t => t.table_name === col.rtn) index = rTable && rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "mm" - ); + c.type === 'mm' + ) } } else if (col.uidt === UITypes.ForeignKey) { - rTable = this.project.tables.find((t) => t.table_name === col.rtn); + rTable = this.project.tables.find(t => t.table_name === col.rtn) index = rTable && rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "hm" - ); + c.type === 'hm' + ) } if (rTable && index > -1) { - rTable.columns.splice(index, 1); + rTable.columns.splice(index, 1) } for (const table of this.project.tables) { if (table === deleteTable) { - continue; + continue } table.columns = table.columns.filter( - (c) => + c => c.rtn !== deleteTable.table_name || c.rcn !== deleteColumn.column_name - ); + ) } - deleteTable.columns.splice(j, 1); + deleteTable.columns.splice(j, 1) }, addTables() { if (!this.tableNamesInput) { - return; + return } // todo: fix const re = - /(?:^|,\s*)(\w+)(?:\(((\w+)(?:\s*,\s*\w+)?)?\)){0,1}(?=\s*,|\s*$)/g; - let m; + /(?:^|,\s*)(\w+)(?:\(((\w+)(?:\s*,\s*\w+)?)?\)){0,1}(?=\s*,|\s*$)/g + let m // eslint-disable-next-line no-cond-assign while ((m = re.exec(this.tableNamesInput))) { - if (this.project.tables.some((t) => t.table_name === m[1])) { - this.$toast.info(`Table '${m[1]}' is already exist`).goAway(1000); - continue; + if (this.project.tables.some(t => t.table_name === m[1])) { + this.$toast.info(`Table '${m[1]}' is already exist`).goAway(1000) + continue } this.project.tables.push({ tn: m[1], columns: (m[2] ? m[2].split(/\s*,\s*/) : []) - .map((col) => ({ + .map(col => ({ cn: col, - ...defaultColProp, + ...defaultColProp })) .filter( (v, i, arr) => - i === arr.findIndex((c) => c.column_name === v.column_name) - ), - }); + i === arr.findIndex(c => c.column_name === v.column_name) + ) + }) } - this.createTablesDialog = false; - this.tableNamesInput = ""; + this.createTablesDialog = false + this.tableNamesInput = '' }, compareRel(a, b) { return ( ((a && a.table_name) || a) === ((b && b.table_name) || b) && (a && a.type) === (b && b.type) - ); + ) }, addColumns() { if (!this.columnNamesInput) { - return; + return } - const table = this.project.tables[this.expansionPanel]; + const table = this.project.tables[this.expansionPanel] for (const col of this.columnNamesInput.split(/\s*,\s*/)) { - if (table.columns.some((c) => c.column_name === col)) { - this.$toast.info(`Column '${col}' is already exist`).goAway(1000); - continue; + if (table.columns.some(c => c.column_name === col)) { + this.$toast.info(`Column '${col}' is already exist`).goAway(1000) + continue } table.columns.push({ cn: col, - ...defaultColProp, - }); + ...defaultColProp + }) } - this.columnNamesInput = ""; - this.createTableColumnsDialog = false; + this.columnNamesInput = '' + this.createTableColumnsDialog = false this.$nextTick(() => { const input = this.$refs[ `uidt_${table.table_name}_${table.columns.length - 1}` - ][0].$el.querySelector("input"); - input.focus(); + ][0].$el.querySelector('input') + input.focus() this.$nextTick(() => { - input.select(); - }); - }); + input.select() + }) + }) }, showColCreateDialog(table) { - this.createTableColumnsDialog = true; - this.selectedTable = table; + this.createTableColumnsDialog = true + this.selectedTable = table }, isRelation(col) { - return col.uidt === "LinkToAnotherRecord" || col.uidt === "ForeignKey"; + return col.uidt === 'LinkToAnotherRecord' || col.uidt === 'ForeignKey' }, isLookup(col) { - return col.uidt === "Lookup"; + return col.uidt === 'Lookup' }, isRollup(col) { - return col.uidt === "Rollup"; + return col.uidt === 'Rollup' }, isVirtual(col) { - return col && uiTypes.some((ut) => ut.name === col.uidt && ut.virtual); + return col && uiTypes.some(ut => ut.name === col.uidt && ut.virtual) }, isLookupOrRollup(col) { - return this.isLookup(col) || this.isRollup(col); + return this.isLookup(col) || this.isRollup(col) }, isSelect(col) { - return col.uidt === "MultiSelect" || col.uidt === "SingleSelect"; + return col.uidt === 'MultiSelect' || col.uidt === 'SingleSelect' }, addNewColumnRow(table, uidt) { table.columns.push({ @@ -1153,120 +1153,120 @@ export default { uidt, ...(uidt === LinkToAnotherRecord ? { - type: "mm", + type: 'mm' } - : {}), - }); + : {}) + }) this.$nextTick(() => { const input = this.$refs[ `cn_${table.table_name}_${table.columns.length - 1}` - ][0].$el.querySelector("input"); - input.focus(); - input.select(); - }); + ][0].$el.querySelector('input') + input.focus() + input.select() + }) }, async handleKeyDown({ metaKey, key, altKey, shiftKey, ctrlKey }) { if (!(metaKey && ctrlKey) && !(altKey && shiftKey)) { - return; + return } switch (key && key.toLowerCase()) { - case "t": - this.createTablesDialog = true; - break; - case "c": - this.createTableColumnsDialog = true; - break; - case "a": - this.addNewColumnRow(this.project.tables[this.expansionPanel]); - break; - case "j": - this.copyJSON(); - break; - case "s": - await this.saveTemplate(); - break; - case "arrowup": + case 't': + this.createTablesDialog = true + break + case 'c': + this.createTableColumnsDialog = true + break + case 'a': + this.addNewColumnRow(this.project.tables[this.expansionPanel]) + break + case 'j': + this.copyJSON() + break + case 's': + await this.saveTemplate() + break + case 'arrowup': this.expansionPanel = this.expansionPanel ? --this.expansionPanel - : this.project.tables.length - 1; - break; - case "arrowdown": + : this.project.tables.length - 1 + break + case 'arrowdown': this.expansionPanel = - ++this.expansionPanel % this.project.tables.length; - break; + ++this.expansionPanel % this.project.tables.length + break - case "1": + case '1': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "Number" - ); - break; - case "2": + 'Number' + ) + break + case '2': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "SingleLineText" - ); - break; - case "3": + 'SingleLineText' + ) + break + case '3': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "LongText" - ); - break; - case "4": + 'LongText' + ) + break + case '4': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "LinkToAnotherRecord" - ); - break; - case "5": + 'LinkToAnotherRecord' + ) + break + case '5': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "Lookup" - ); - break; - case "6": + 'Lookup' + ) + break + case '6': this.addNewColumnRow( this.project.tables[this.expansionPanel], - "Rollup" - ); - break; + 'Rollup' + ) + break } }, copyJSON() { if (!this.validateAndFocus()) { - this.$toast.info("Please fill all the required column!").goAway(5000); - return; + this.$toast.info('Please fill all the required column!').goAway(5000) + return } - const el = document.createElement("textarea"); - el.addEventListener("focusin", (e) => e.stopPropagation()); - el.value = JSON.stringify(this.projectTemplate, null, 2); - el.style = { position: "absolute", left: "-9999px" }; - document.body.appendChild(el); - el.select(); - document.execCommand("copy"); - document.body.removeChild(el); + const el = document.createElement('textarea') + el.addEventListener('focusin', e => e.stopPropagation()) + el.value = JSON.stringify(this.projectTemplate, null, 2) + el.style = { position: 'absolute', left: '-9999px' } + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) this.$toast - .success("Successfully copied JSON data to clipboard!") - .goAway(3000); - return true; + .success('Successfully copied JSON data to clipboard!') + .goAway(3000) + return true }, openUrl() { - window.open(this.url, "_blank"); + window.open(this.url, '_blank') }, async loadUrl() { try { - let template = (await this.$axios.get(this.url)).data; + let template = (await this.$axios.get(this.url)).data - if (typeof template === "string") { - template = JSON.parse(template); + if (typeof template === 'string') { + template = JSON.parse(template) } - this.parseTemplate(template); + this.parseTemplate(template) } catch (e) { - this.$toast.error(e.message).goAway(5000); + this.$toast.error(e.message).goAway(5000) } }, @@ -1285,62 +1285,62 @@ export default { ...rest, columns: [ ...columns, - ...manyToMany.map((mm) => ({ + ...manyToMany.map(mm => ({ cn: mm.title || `${rest.table_name} <=> ${mm.rtn}`, uidt: LinkToAnotherRecord, - type: "mm", - ...mm, + type: 'mm', + ...mm })), - ...hasMany.map((hm) => ({ + ...hasMany.map(hm => ({ cn: hm.title || `${rest.table_name} => ${hm.table_name}`, uidt: LinkToAnotherRecord, - type: "hm", + type: 'hm', rtn: hm.table_name, - ...hm, + ...hm })), - ...belongsTo.map((bt) => ({ + ...belongsTo.map(bt => ({ cn: bt.title || `${rest.table_name} => ${bt.rtn}`, uidt: UITypes.ForeignKey, rtn: bt.table_name, - ...bt, + ...bt })), ...v.map((v) => { const res = { cn: v.title, rtn: { - ...v, - }, - }; + ...v + } + } if (v.lk) { - res.uidt = Lookup; - res.rtn.table_name = v.lk.ltn; - res.rcn = v.lk.lcn; - res.rtn.type = v.lk.type; + res.uidt = Lookup + res.rtn.table_name = v.lk.ltn + res.rcn = v.lk.lcn + res.rtn.type = v.lk.type } else if (v.rl) { - res.uidt = Rollup; - res.rtn.table_name = v.rl.rltn; - res.rcn = v.rl.rlcn; - res.rtn.type = v.rl.type; - res.fn = v.rl.fn; + res.uidt = Rollup + res.rtn.table_name = v.rl.rltn + res.rcn = v.rl.rlcn + res.rtn.type = v.rl.type + res.fn = v.rl.fn } - return res; - }), - ], + return res + }) + ] }) - ), - }; + ) + } - this.project = parsedTemplate; + this.project = parsedTemplate }, async projectTemplateCreate() { if (!this.validateAndFocus()) { - this.$toast.info("Please fill all the required column!").goAway(5000); - return; + this.$toast.info('Please fill all the required column!').goAway(5000) + return } try { - const githubConfig = this.$store.state.github; + const githubConfig = this.$store.state.github // const token = await models.store.where({ key: 'GITHUB_TOKEN' }).first() // const branch = await models.store.where({ key: 'GITHUB_BRANCH' }).first() @@ -1348,75 +1348,75 @@ export default { // const templateRepo = await models.store.where({ key: 'PROJECT_TEMPLATES_REPO' }).first() if (!githubConfig.token || !githubConfig.repo) { - throw new Error("Missing token or template path"); + throw new Error('Missing token or template path') } - const data = JSON.stringify(this.projectTemplate, 0, 2); + const data = JSON.stringify(this.projectTemplate, 0, 2) const filename = this.updateFilename || - `${this.projectTemplate.name}_${Date.now()}.json`; + `${this.projectTemplate.name}_${Date.now()}.json` const filePath = `${ - githubConfig.filePath ? githubConfig.filePath + "/" : "" - }${filename}`; - const apiPath = `https://api.github.com/repos/${githubConfig.repo}/contents/${filePath}`; + githubConfig.filePath ? githubConfig.filePath + '/' : '' + }${filename}` + const apiPath = `https://api.github.com/repos/${githubConfig.repo}/contents/${filePath}` - let sha; + let sha if (this.updateFilename) { const { - data: { sha: _sha }, + data: { sha: _sha } } = await this.$axios({ url: `https://api.github.com/repos/${githubConfig.repo}/contents/${filePath}`, - method: "get", + method: 'get', headers: { - Authorization: "token " + githubConfig.token, - }, - }); - sha = _sha; + Authorization: 'token ' + githubConfig.token + } + }) + sha = _sha } await this.$axios({ url: apiPath, - method: "put", + method: 'put', headers: { - "Content-Type": "application/json", - Authorization: "token " + githubConfig.token, + 'Content-Type': 'application/json', + Authorization: 'token ' + githubConfig.token }, data: { message: `templates : init template ${filename}`, content: Base64.encode(data), sha, - branch: githubConfig.branch, - }, - }); + branch: githubConfig.branch + } + }) - this.url = `https://raw.githubusercontent.com/${githubConfig.repo}/${githubConfig.branch}/${filePath}`; + this.url = `https://raw.githubusercontent.com/${githubConfig.repo}/${githubConfig.branch}/${filePath}` this.$toast - .success("Template generated and saved successfully!") - .goAway(4000); + .success('Template generated and saved successfully!') + .goAway(4000) } catch (e) { - this.$toast.error(e.message).goAway(5000); + this.$toast.error(e.message).goAway(5000) } }, navigateToTable(tn) { const index = this.projectTemplate.tables.findIndex( - (t) => t.table_name === tn - ); + t => t.table_name === tn + ) if (Array.isArray(this.expansionPanel)) { - this.expansionPanel.push(index); + this.expansionPanel.push(index) } else { - this.expansionPanel = index; + this.expansionPanel = index } this.$nextTick(() => { - const accord = this.$el.querySelector(`#tn_${tn}`); - accord.focus(); - accord.scrollIntoView(); - }); + const accord = this.$el.querySelector(`#tn_${tn}`) + accord.focus() + accord.scrollIntoView() + }) }, async saveTemplate() { - this.loading = true; + this.loading = true try { if (this.id || this.localId) { await this.$axios.put( @@ -1426,281 +1426,281 @@ export default { this.projectTemplate, { params: { - token: this.$store.state.template, - }, + token: this.$store.state.template + } } - ); - this.$toast.success("Template updated successfully").goAway(3000); + ) + this.$toast.success('Template updated successfully').goAway(3000) } else if (!this.$store.state.template) { if (!this.copyJSON()) { - return; + return } - this.$toast.info("Initiating Github for template").goAway(3000); + this.$toast.info('Initiating Github for template').goAway(3000) const res = await this.$axios.post( `${process.env.NC_API_URL}/api/v1/projectTemplateCreate`, this.projectTemplate - ); - this.$toast.success("Initiated Github successfully").goAway(3000); - window.open(res.data.path, "_blank"); + ) + this.$toast.success('Initiated Github successfully').goAway(3000) + window.open(res.data.path, '_blank') } else { const res = await this.$axios.post( `${process.env.NC_API_URL}/api/v1/nc/templates`, this.projectTemplate, { params: { - token: this.$store.state.template, - }, + token: this.$store.state.template + } } - ); - this.localId = res.data.id; - this.$toast.success("Template updated successfully").goAway(3000); + ) + this.localId = res.data.id + this.$toast.success('Template updated successfully').goAway(3000) } - this.$emit("saved"); + this.$emit('saved') } catch (e) { - this.$toast.error(e.message).goAway(3000); + this.$toast.error(e.message).goAway(3000) } finally { - this.loading = false; + this.loading = false } }, getRules(col, table) { - return (v) => + return v => col.uidt !== UITypes.LinkToAnotherRecord || !table.columns.some( - (c) => + c => c !== col && c.uidt === UITypes.LinkToAnotherRecord && c.type === col.type && c.rtn === col.rtn ) || - "Duplicate relation is not allowed"; + 'Duplicate relation is not allowed' }, onTableNameUpdate(oldTable, newVal) { - const oldVal = oldTable.table_name; - this.$set(oldTable, "tn", newVal); + const oldVal = oldTable.table_name + this.$set(oldTable, 'tn', newVal) for (const table of this.project.tables) { for (const col of table.columns) { if (col.uidt === UITypes.LinkToAnotherRecord) { if (col.rtn === oldVal) { - this.$set(col, "rtn", newVal); + this.$set(col, 'rtn', newVal) } } else if ( col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup ) { if (col.rtn && col.rtn.table_name === oldVal) { - this.$set(col.rtn, "tn", newVal); + this.$set(col.rtn, 'tn', newVal) } } } } }, onColumnNameUpdate(oldCol, newVal, tn) { - const oldVal = oldCol.column_name; - this.$set(oldCol, "cn", newVal); + const oldVal = oldCol.column_name + this.$set(oldCol, 'cn', newVal) for (const table of this.project.tables) { for (const col of table.columns) { if (col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup) { if (col.rtn && col.rcn === oldVal && col.rtn.table_name === tn) { - this.$set(col, "rcn", newVal); + this.$set(col, 'rcn', newVal) } } } } }, async onRtnChange(oldVal, newVal, col, table) { - this.$set(col, "rtn", newVal); + this.$set(col, 'rtn', newVal) - await this.$nextTick(); + await this.$nextTick() if ( col.uidt !== UITypes.LinkToAnotherRecord && col.uidt !== UITypes.ForeignKey ) { - return; + return } if (oldVal) { - const rTable = this.project.tables.find((t) => t.table_name === oldVal); + const rTable = this.project.tables.find(t => t.table_name === oldVal) // delete relation from other table if exist - let index = -1; - if (col.uidt === UITypes.LinkToAnotherRecord && col.type === "mm") { + let index = -1 + if (col.uidt === UITypes.LinkToAnotherRecord && col.type === 'mm') { index = rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "mm" - ); + c.type === 'mm' + ) } else if ( col.uidt === UITypes.LinkToAnotherRecord && - col.type === "hm" + col.type === 'hm' ) { index = rTable.columns.findIndex( - (c) => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name - ); + c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + ) } else if (col.uidt === UITypes.ForeignKey) { index = rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "hm" - ); + c.type === 'hm' + ) } if (index > -1) { - rTable.columns.splice(index, 1); + rTable.columns.splice(index, 1) } } if (newVal) { - const rTable = this.project.tables.find((t) => t.table_name === newVal); + const rTable = this.project.tables.find(t => t.table_name === newVal) // check relation relation exist in other table // if not create a relation - if (col.uidt === UITypes.LinkToAnotherRecord && col.type === "mm") { + if (col.uidt === UITypes.LinkToAnotherRecord && col.type === 'mm') { if ( !rTable.columns.find( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "mm" + c.type === 'mm' ) ) { rTable.columns.push({ cn: `title${rTable.columns.length + 1}`, uidt: UITypes.LinkToAnotherRecord, - type: "mm", - rtn: table.table_name, - }); + type: 'mm', + rtn: table.table_name + }) } } else if ( col.uidt === UITypes.LinkToAnotherRecord && - col.type === "hm" + col.type === 'hm' ) { if ( !rTable.columns.find( - (c) => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name ) ) { rTable.columns.push({ cn: `title${rTable.columns.length + 1}`, uidt: UITypes.ForeignKey, - rtn: table.table_name, - }); + rtn: table.table_name + }) } } else if (col.uidt === UITypes.ForeignKey) { if ( !rTable.columns.find( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "hm" + c.type === 'hm' ) ) { rTable.columns.push({ cn: `title${rTable.columns.length + 1}`, uidt: UITypes.LinkToAnotherRecord, - type: "hm", - rtn: table.table_name, - }); + type: 'hm', + rtn: table.table_name + }) } } } }, onRTypeChange(oldType, newType, col, table) { - this.$set(col, "type", newType); + this.$set(col, 'type', newType) - const rTable = this.project.tables.find((t) => t.table_name === col.rtn); + const rTable = this.project.tables.find(t => t.table_name === col.rtn) - let index = -1; + let index = -1 // find column and update relation // or create a new column - if (oldType === "hm") { + if (oldType === 'hm') { index = rTable.columns.findIndex( - (c) => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name - ); - } else if (oldType === "mm") { + c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + ) + } else if (oldType === 'mm') { index = rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "mm" - ); + c.type === 'mm' + ) } const rCol = index === -1 ? { cn: `title${rTable.columns.length + 1}` } - : { ...rTable.columns[index] }; - index = index === -1 ? rTable.columns.length : index; - - if (newType === "mm") { - rCol.type = "mm"; - rCol.uidt = UITypes.LinkToAnotherRecord; - } else if (newType === "hm") { - rCol.type = "bt"; - rCol.uidt = UITypes.ForeignKey; + : { ...rTable.columns[index] } + index = index === -1 ? rTable.columns.length : index + + if (newType === 'mm') { + rCol.type = 'mm' + rCol.uidt = UITypes.LinkToAnotherRecord + } else if (newType === 'hm') { + rCol.type = 'bt' + rCol.uidt = UITypes.ForeignKey } - rCol.rtn = table.table_name; + rCol.rtn = table.table_name - this.$set(rTable.columns, index, rCol); + this.$set(rTable.columns, index, rCol) }, onUidtChange(oldVal, newVal, col, table) { - this.$set(col, "uidt", newVal); - this.$set(col, "dtxp", undefined); + this.$set(col, 'uidt', newVal) + this.$set(col, 'dtxp', undefined) // delete relation column from other table // if previous type is relation - let index = -1; - let rTable; + let index = -1 + let rTable if (oldVal === UITypes.LinkToAnotherRecord) { - rTable = this.project.tables.find((t) => t.table_name === col.rtn); + rTable = this.project.tables.find(t => t.table_name === col.rtn) if (rTable) { - if (col.type === "hm") { + if (col.type === 'hm') { index = rTable.columns.findIndex( - (c) => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name - ); - } else if (col.type === "mm") { + c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + ) + } else if (col.type === 'mm') { index = rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "mm" - ); + c.type === 'mm' + ) } } } else if (oldVal === UITypes.ForeignKey) { - rTable = this.project.tables.find((t) => t.table_name === col.rtn); + rTable = this.project.tables.find(t => t.table_name === col.rtn) if (rTable) { index = rTable.columns.findIndex( - (c) => + c => c.uidt === UITypes.LinkToAnotherRecord && c.rtn === table.table_name && - c.type === "hm" - ); + c.type === 'hm' + ) } } if (rTable && index > -1) { - rTable.columns.splice(index, 1); + rTable.columns.splice(index, 1) } - col.rtn = undefined; - col.type = undefined; - col.rcn = undefined; + col.rtn = undefined + col.type = undefined + col.rcn = undefined if (col.uidt === LinkToAnotherRecord) { - col.type = col.type || "mm"; + col.type = col.type || 'mm' } - }, - }, -}; + } + } +} + \ No newline at end of file From ffa0ee23b09653cd3312e1b93949aad05e4d4a47 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 18:40:46 +0800 Subject: [PATCH 15/26] fix: empty data after editing column name --- .../components/templates/createProjectFromTemplateBtn.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue index 489575b7e1..7f796a0c2d 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -162,8 +162,9 @@ export default { // mark updated column_name t.columns.map((c) => { if (c.cn) { + // update column_name if users change it + // the original one will be kept in ref_column_name c.column_name = c.cn - c.ref_column_name = c.column_name } return c }) From 2666d3cd891f98320f17bb733e954d0a0a8b2b52 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 19:42:11 +0800 Subject: [PATCH 16/26] refactor: createProjectFromTemplateBtn.vue --- .../createProjectFromTemplateBtn.vue | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue index 7f796a0c2d..4cab3288d6 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue @@ -101,25 +101,26 @@ export default { let project + // Not available now if (this.importToProject) { - this.$store.commit('loader/MutMessage', 'Importing excel template') - - const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ - // todo: extract based on active - dbAlias: 'db', // this.nodes.dbAlias, - env: '_noco' - }, 'xcModelsCreateFromTemplate', { - template: this.templateData - }]) - - if (res && res.tables && res.tables.length) { - this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000) - } else { - this.$toast.success('Template imported successfully').goAway(3000) - } + // this.$store.commit('loader/MutMessage', 'Importing excel template') + + // const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ + // // todo: extract based on active + // dbAlias: 'db', // this.nodes.dbAlias, + // env: '_noco' + // }, 'xcModelsCreateFromTemplate', { + // template: this.templateData + // }]) + + // if (res && res.tables && res.tables.length) { + // this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000) + // } else { + // this.$toast.success('Template imported successfully').goAway(3000) + // } - projectId = this.$route.params.project_id - prefix = this.$store.getters['project/GtrProjectPrefix'] + // projectId = this.$route.params.project_id + // prefix = this.$store.getters['project/GtrProjectPrefix'] } else { // Create an empty project try { @@ -159,17 +160,9 @@ export default { .create({ client: 'sqlite3' }) .getNewTableColumns() .filter(c => c.column_name != 'title') - // mark updated column_name - t.columns.map((c) => { - if (c.cn) { - // update column_name if users change it - // the original one will be kept in ref_column_name - c.column_name = c.cn - } - return c - }) + const table = await this.$api.dbTable.create(project.id, { - table_name: t.ref_table_name, + table_name: t.table_name, title: '', columns: [...t.columns, ...systemColumns] }) @@ -186,26 +179,30 @@ export default { } } - if (this.tableCreation) { - // Bulk import data - if (this.importData) { - this.$store.commit('loader/MutMessage', 'Importing excel data to project') - await this.importDataToProject(this.templateData.title, project.prefix) - } - this.$store.commit('loader/MutMessage', null) - this.projectReloading = false - this.$emit('success') + if (!this.tableCreation) { + // failed to create table + return + } + + // Bulk import data + if (this.importData) { + this.$store.commit('loader/MutMessage', 'Importing excel data to project') + await this.importDataToProject(this.templateData.title) } + this.projectReloading = false + this.$emit('success') } catch (e) { console.log(e) this.$toast.error(e.message).goAway(3000) - this.$store.commit('loader/MutMessage', null) + } finally { clearInterval(interv) + this.$store.commit('loader/MutMessage', null) + this.projectCreation = false + this.tableCreation = false + this.projectReloading = false } - this.projectCreation = false - this.tableCreation = false }, - async importDataToProject(projectName, prefix) { + async importDataToProject(projectName) { let total = 0 let progress = 0 await Promise.all(this.localTemplateData.tables.map(v => (async(tableMeta) => { @@ -241,4 +238,4 @@ export default { \ No newline at end of file + From b57d5d9c33d317879b5c14fb1005ab3e0f07a4f1 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 30 Apr 2022 19:42:32 +0800 Subject: [PATCH 17/26] fix: using new naming convention --- .../nc-gui/components/templates/editor.vue | 225 +++++++++--------- 1 file changed, 116 insertions(+), 109 deletions(-) diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index 787809a2d2..06504392b3 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -10,7 +10,9 @@ v-on="on" @click="$toast.info('Happy hacking!').goAway(3000)" > - mdi-file-excel-outline + + mdi-file-excel-outline + Import @@ -24,11 +26,15 @@ - mdi-close + + mdi-close + Reset - mdi-plus + + mdi-plus + New table - {{ col.rtn }} + {{ col.ref_table_name }} @@ -288,13 +294,13 @@ :items=" col.uidt === 'ForeignKey' ? [ - ...uiTypes, - { - name: 'ForeignKey', - icon: 'mdi-link-variant', - virtual: 1, - }, - ] + ...uiTypes, + { + name: 'ForeignKey', + icon: 'mdi-link-variant', + virtual: 1, + }, + ] : uiTypes " item-text="name" @@ -339,7 +345,7 @@ > @@ -413,12 +419,12 @@ - {{ col.rcn }} + {{ col.ref_column_name }} t.table_name === - ((col.rtn && - col.rtn.table_name) || - col.rtn) + ((col.ref_table_name && + col.ref_table_name.table_name) || + col.ref_table_name) ) || { columns: [] } ).columns.filter((v) => !isVirtual(v)) " @@ -484,8 +490,8 @@ v-if="!isRollup(col)" :colspan=" isLookupOrRollup(col) || - isRelation(col) || - isSelect(col) + isRelation(col) || + isSelect(col) ? isRollup(col) ? 0 : 1 @@ -501,8 +507,8 @@ small color="grey" @click.stop=" - deleteTableColumn(i, j, col, table) - " + deleteTableColumn(i, j, col, table) + " v-on="on" > mdi-delete-outline @@ -848,42 +854,43 @@ export default { } for (const column of t.columns || []) { + console.log(column) if (this.isRelation(column)) { if (column.type === 'hm') { table.hasMany.push({ - tn: column.rtn, - _cn: column.column_name + table_name: column.ref_table_name, + title: column.column_name }) } else if (column.type === 'mm') { table.manyToMany.push({ - rtn: column.rtn, - _cn: column.column_name + ref_table_name: column.ref_table_name, + title: column.column_name }) } else if (column.uidt === UITypes.ForeignKey) { table.belongsTo.push({ - tn: column.rtn, - _cn: column.column_name + table_name: column.ref_table_name, + title: column.column_name }) } } else if (this.isLookup(column)) { - if (column.rtn) { + if (column.ref_table_name) { table.v.push({ - _cn: column.column_name, + title: column.column_name, lk: { - ltn: column.rtn.table_name, - type: column.rtn.type, - lcn: column.rcn + ltn: column.ref_table_name.table_name, + type: column.ref_table_name.type, + lcn: column.ref_column_name } }) } } else if (this.isRollup(column)) { - if (column.rtn) { + if (column.ref_table_name) { table.v.push({ - _cn: column.column_name, + title: column.column_name, rl: { - rltn: column.rtn.table_name, - rlcn: column.rcn, - type: column.rtn.type, + rltn: column.ref_table_name.table_name, + rlcn: column.ref_column_name, + type: column.ref_table_name.type, fn: column.fn } }) @@ -947,7 +954,7 @@ export default { } for (const mm of t.manyToMany) { const rTable = this.project.tables.find( - t1 => t1.table_name === mm.rtn + t1 => t1.table_name === mm.ref_table_name ) tables.push({ ...rTable, @@ -964,7 +971,7 @@ export default { } } for (const mm of t.manyToMany) { - if (mm.rtn === tableName) { + if (mm.ref_table_name === tableName) { tables.push({ ...t, type: 'mm' @@ -998,7 +1005,7 @@ export default { continue } table.columns = table.columns.filter( - c => c.rtn !== deleteTable.table_name + c => c.ref_table_name !== deleteTable.table_name ) } this.project.tables.splice(i, 1) @@ -1011,31 +1018,31 @@ export default { // if relation column, delete the corresponding relation from other table if (col.uidt === UITypes.LinkToAnotherRecord) { if (col.type === 'hm') { - rTable = this.project.tables.find(t => t.table_name === col.rtn) + rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) index = rTable && rTable.columns.findIndex( - c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.ref_table_name === table.table_name ) } else if (col.type === 'mm') { - rTable = this.project.tables.find(t => t.table_name === col.rtn) + rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) index = rTable && rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'mm' ) } } else if (col.uidt === UITypes.ForeignKey) { - rTable = this.project.tables.find(t => t.table_name === col.rtn) + rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) index = rTable && rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'hm' ) } @@ -1050,8 +1057,8 @@ export default { } table.columns = table.columns.filter( c => - c.rtn !== deleteTable.table_name || - c.rcn !== deleteColumn.column_name + c.ref_table_name !== deleteTable.table_name || + c.ref_column_name !== deleteColumn.column_name ) } deleteTable.columns.splice(j, 1) @@ -1072,10 +1079,10 @@ export default { } this.project.tables.push({ - tn: m[1], + table_name: m[1], columns: (m[2] ? m[2].split(/\s*,\s*/) : []) .map(col => ({ - cn: col, + column_name: col, ...defaultColProp })) .filter( @@ -1105,7 +1112,7 @@ export default { } table.columns.push({ - cn: col, + column_name: col, ...defaultColProp }) } @@ -1148,7 +1155,7 @@ export default { }, addNewColumnRow(table, uidt) { table.columns.push({ - cn: `title${table.columns.length + 1}`, + column_name: `title${table.columns.length + 1}`, ...defaultColProp, uidt, ...(uidt === LinkToAnotherRecord @@ -1286,41 +1293,41 @@ export default { columns: [ ...columns, ...manyToMany.map(mm => ({ - cn: mm.title || `${rest.table_name} <=> ${mm.rtn}`, + column_name: mm.title || `${rest.table_name} <=> ${mm.ref_table_name}`, uidt: LinkToAnotherRecord, type: 'mm', ...mm })), ...hasMany.map(hm => ({ - cn: hm.title || `${rest.table_name} => ${hm.table_name}`, + column_name: hm.title || `${rest.table_name} => ${hm.table_name}`, uidt: LinkToAnotherRecord, type: 'hm', - rtn: hm.table_name, + ref_table_name: hm.table_name, ...hm })), ...belongsTo.map(bt => ({ - cn: bt.title || `${rest.table_name} => ${bt.rtn}`, + column_name: bt.title || `${rest.table_name} => ${bt.ref_table_name}`, uidt: UITypes.ForeignKey, - rtn: bt.table_name, + ref_table_name: bt.table_name, ...bt })), ...v.map((v) => { const res = { - cn: v.title, - rtn: { + column_name: v.title, + ref_table_name: { ...v } } if (v.lk) { res.uidt = Lookup - res.rtn.table_name = v.lk.ltn - res.rcn = v.lk.lcn - res.rtn.type = v.lk.type + res.ref_table_name.table_name = v.lk.ltn + res.ref_column_name = v.lk.lcn + res.ref_table_name.type = v.lk.type } else if (v.rl) { res.uidt = Rollup - res.rtn.table_name = v.rl.rltn - res.rcn = v.rl.rlcn - res.rtn.type = v.rl.type + res.ref_table_name.table_name = v.rl.rltn + res.ref_column_name = v.rl.rlcn + res.ref_table_name.type = v.rl.type res.fn = v.rl.fn } return res @@ -1472,26 +1479,26 @@ export default { c !== col && c.uidt === UITypes.LinkToAnotherRecord && c.type === col.type && - c.rtn === col.rtn + c.ref_table_name === col.ref_table_name ) || 'Duplicate relation is not allowed' }, onTableNameUpdate(oldTable, newVal) { const oldVal = oldTable.table_name - this.$set(oldTable, 'tn', newVal) + this.$set(oldTable, 'table_name', newVal) for (const table of this.project.tables) { for (const col of table.columns) { if (col.uidt === UITypes.LinkToAnotherRecord) { - if (col.rtn === oldVal) { - this.$set(col, 'rtn', newVal) + if (col.ref_table_name === oldVal) { + this.$set(col, 'ref_table_name', newVal) } } else if ( col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup ) { - if (col.rtn && col.rtn.table_name === oldVal) { - this.$set(col.rtn, 'tn', newVal) + if (col.ref_table_name && col.ref_table_name.table_name === oldVal) { + this.$set(col.ref_table_name, 'table_name', newVal) } } } @@ -1499,20 +1506,20 @@ export default { }, onColumnNameUpdate(oldCol, newVal, tn) { const oldVal = oldCol.column_name - this.$set(oldCol, 'cn', newVal) + this.$set(oldCol, 'column_name', newVal) for (const table of this.project.tables) { for (const col of table.columns) { if (col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup) { - if (col.rtn && col.rcn === oldVal && col.rtn.table_name === tn) { - this.$set(col, 'rcn', newVal) + if (col.ref_table_name && col.ref_column_name === oldVal && col.ref_table_name.table_name === tn) { + this.$set(col, 'ref_column_name', newVal) } } } } }, async onRtnChange(oldVal, newVal, col, table) { - this.$set(col, 'rtn', newVal) + this.$set(col, 'ref_table_name', newVal) await this.$nextTick() @@ -1532,7 +1539,7 @@ export default { index = rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'mm' ) } else if ( @@ -1540,13 +1547,13 @@ export default { col.type === 'hm' ) { index = rTable.columns.findIndex( - c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.ref_table_name === table.table_name ) } else if (col.uidt === UITypes.ForeignKey) { index = rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'hm' ) } @@ -1565,15 +1572,15 @@ export default { !rTable.columns.find( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'mm' ) ) { rTable.columns.push({ - cn: `title${rTable.columns.length + 1}`, + column_name: `title${rTable.columns.length + 1}`, uidt: UITypes.LinkToAnotherRecord, type: 'mm', - rtn: table.table_name + ref_table_name: table.table_name }) } } else if ( @@ -1582,13 +1589,13 @@ export default { ) { if ( !rTable.columns.find( - c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.ref_table_name === table.table_name ) ) { rTable.columns.push({ - cn: `title${rTable.columns.length + 1}`, + column_name: `title${rTable.columns.length + 1}`, uidt: UITypes.ForeignKey, - rtn: table.table_name + ref_table_name: table.table_name }) } } else if (col.uidt === UITypes.ForeignKey) { @@ -1596,15 +1603,15 @@ export default { !rTable.columns.find( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'hm' ) ) { rTable.columns.push({ - cn: `title${rTable.columns.length + 1}`, + column_name: `title${rTable.columns.length + 1}`, uidt: UITypes.LinkToAnotherRecord, type: 'hm', - rtn: table.table_name + ref_table_name: table.table_name }) } } @@ -1613,7 +1620,7 @@ export default { onRTypeChange(oldType, newType, col, table) { this.$set(col, 'type', newType) - const rTable = this.project.tables.find(t => t.table_name === col.rtn) + const rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) let index = -1 @@ -1622,20 +1629,20 @@ export default { if (oldType === 'hm') { index = rTable.columns.findIndex( - c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.ref_table_name === table.table_name ) } else if (oldType === 'mm') { index = rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'mm' ) } const rCol = index === -1 - ? { cn: `title${rTable.columns.length + 1}` } + ? { column_name: `title${rTable.columns.length + 1}` } : { ...rTable.columns[index] } index = index === -1 ? rTable.columns.length : index @@ -1646,7 +1653,7 @@ export default { rCol.type = 'bt' rCol.uidt = UITypes.ForeignKey } - rCol.rtn = table.table_name + rCol.ref_table_name = table.table_name this.$set(rTable.columns, index, rCol) }, @@ -1661,28 +1668,28 @@ export default { let rTable if (oldVal === UITypes.LinkToAnotherRecord) { - rTable = this.project.tables.find(t => t.table_name === col.rtn) + rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) if (rTable) { if (col.type === 'hm') { index = rTable.columns.findIndex( - c => c.uidt === UITypes.ForeignKey && c.rtn === table.table_name + c => c.uidt === UITypes.ForeignKey && c.ref_table_name === table.table_name ) } else if (col.type === 'mm') { index = rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'mm' ) } } } else if (oldVal === UITypes.ForeignKey) { - rTable = this.project.tables.find(t => t.table_name === col.rtn) + rTable = this.project.tables.find(t => t.table_name === col.ref_table_name) if (rTable) { index = rTable.columns.findIndex( c => c.uidt === UITypes.LinkToAnotherRecord && - c.rtn === table.table_name && + c.ref_table_name === table.table_name && c.type === 'hm' ) } @@ -1691,9 +1698,9 @@ export default { rTable.columns.splice(index, 1) } - col.rtn = undefined + col.ref_table_name = undefined col.type = undefined - col.rcn = undefined + col.ref_column_name = undefined if (col.uidt === LinkToAnotherRecord) { col.type = col.type || 'mm' From c4a9f11fb1b254795137b559d5d67d2344993cac Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 4 May 2022 12:48:49 +0800 Subject: [PATCH 18/26] chore: hide virtual columns for time being --- packages/nc-gui/components/templates/editor.vue | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/components/templates/editor.vue b/packages/nc-gui/components/templates/editor.vue index 06504392b3..d229c185b3 100644 --- a/packages/nc-gui/components/templates/editor.vue +++ b/packages/nc-gui/components/templates/editor.vue @@ -569,7 +569,7 @@ Add LongText Column - + Add LinkToAnotherRecord Column @@ -595,7 +594,6 @@ {{ getIcon("Lookup") }} - Add Lookup Column @@ -610,9 +608,8 @@ {{ getIcon("Rollup") }} - Add Rollup Column - + -->