diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue index e274b4e37e..b456e3396a 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue @@ -104,6 +104,20 @@ - + mdi-pencil @@ -108,37 +111,69 @@ export default { }), computed: { type() { - if (this.column.bt) { return 'bt' } - if (this.column.hm) { return 'hm' } - if (this.column.mm) { return 'mm' } + if (this.column.bt) { + return 'bt' + } + if (this.column.hm) { + return 'hm' + } + if (this.column.mm) { + return 'mm' + } return '' }, childColumn() { - if (this.column.bt) { return this.column.bt.cn } - if (this.column.hm) { return this.column.hm.cn } - if (this.column.mm) { return this.column.mm.rcn } + if (this.column.bt) { + return this.column.bt.cn + } + if (this.column.hm) { + return this.column.hm.cn + } + if (this.column.mm) { + return this.column.mm.rcn + } return '' }, childTable() { - if (this.column.bt) { return this.column.bt.tn } - if (this.column.hm) { return this.column.hm.tn } - if (this.column.mm) { return this.column.mm.rtn } + if (this.column.bt) { + return this.column.bt.tn + } + if (this.column.hm) { + return this.column.hm.tn + } + if (this.column.mm) { + return this.column.mm.rtn + } return '' }, parentTable() { - if (this.column.bt) { return this.column.bt.rtn } - if (this.column.hm) { return this.column.hm.rtn } - if (this.column.mm) { return this.column.mm.tn } + if (this.column.bt) { + return this.column.bt.rtn + } + if (this.column.hm) { + return this.column.hm.rtn + } + if (this.column.mm) { + return this.column.mm.tn + } return '' }, parentColumn() { - if (this.column.bt) { return this.column.bt.rcn } - if (this.column.hm) { return this.column.hm.rcn } - if (this.column.mm) { return this.column.mm.cn } + if (this.column.bt) { + return this.column.bt.rcn + } + if (this.column.hm) { + return this.column.hm.rcn + } + if (this.column.mm) { + return this.column.mm.cn + } return '' }, tooltipMsg() { - if (!this.column) { return '' } + if (!this.column) { + return '' + } if (this.column.hm) { return `'${this.column.hm._rtn}' has many '${this.column.hm._tn}'` } else if (this.column.mm) { @@ -150,9 +185,8 @@ export default { } }, methods: { - async deleteColumn() { + async deleteRelation() { try { - // const column = { ...this.column, cno: this.column.cn } await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{ env: this.nodes.env, dbAlias: this.nodes.dbAlias @@ -169,6 +203,42 @@ export default { } catch (e) { console.log(e) } + }, + async deleteLookupColumn() { + try { + await this.$store.dispatch('meta/ActLoadMeta', { + dbAlias: this.nodes.dbAlias, + env: this.nodes.env, + tn: this.meta.tn, + force: true + }) + const meta = JSON.parse(JSON.stringify(this.$store.state.meta.metas[this.meta.tn])) + + // remove lookup from virtual columns + meta.v = meta.v.filter(cl => cl.cn !== this.column.cn || + cl.type !== this.column.type || + cl._cn !== this.column._cn || + cl.tn !== this.column.tn) + + await this.$store.dispatch('sqlMgr/ActSqlOp', [{ + env: this.nodes.env, + dbAlias: this.nodes.dbAlias + }, 'xcModelSet', { + tn: this.nodes.tn, + meta + }]) + this.$emit('saved') + this.columnDeleteDialog = false + } catch (e) { + console.log(e) + } + }, + async deleteColumn() { + if (this.column.lookup) { + await this.deleteLookupColumn() + } else { + await this.deleteRelation() + } } } } diff --git a/packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js b/packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js index 1d23598080..1733907bf1 100644 --- a/packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js +++ b/packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js @@ -12,6 +12,10 @@ const uiTypes = [ name: 'ForeignKey', icon: 'mdi-link-variant' }, + { + name: 'Lookup', + icon: 'mdi-link-variant' + }, { name: 'SingleLineText', icon: 'mdi-format-color-text' diff --git a/packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js b/packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js index 37a6b2f212..270dc9de6b 100644 --- a/packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js +++ b/packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js @@ -23,23 +23,33 @@ export default { // not implemented }, onKeyDown(e) { - if (this.selected.col === null || this.selected.row === null) { return } + if (this.selected.col === null || this.selected.row === null) { + return + } switch (e.keyCode) { // left case 37: - if (this.selected.col > 0) { this.selected.col-- } + if (this.selected.col > 0) { + this.selected.col-- + } break // right case 39: - if (this.selected.col < this.colLength - 1) { this.selected.col++ } + if (this.selected.col < this.colLength - 1) { + this.selected.col++ + } break // up case 38: - if (this.selected.row > 0) { this.selected.row-- } + if (this.selected.row > 0) { + this.selected.row-- + } break // down case 40: - if (this.selected.row < this.rowLength - 1) { this.selected.row++ } + if (this.selected.row < this.rowLength - 1) { + this.selected.row++ + } break // enter case 13: @@ -111,6 +121,59 @@ export default { return this.xWhere ? where + `~and(${this.xWhere})` : where }, + nestedAndRollupColumnParams() { + // generate params for nested columns + const nestedFields = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => { + if (vc.hm) { + obj.hm.push(vc.hm.tn) + } else if (vc.bt) { + obj.bt.push(vc.bt.rtn) + } else if (vc.mm) { + obj.mm.push(vc.mm.rtn) + } + return obj + }, { hm: [], bt: [], mm: [] }) + + // todo: handle if virtual column missing + // construct fields args based on lookup columns + const fieldsObj = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => { + if (!vc.lookup) { + return obj + } + + let key + let index + let column + + switch (vc.type) { + case 'mm': + index = nestedFields.mm.indexOf(vc.tn) + 1 + key = `mfields${index}` + column = vc.cn + break + case 'hm': + index = nestedFields.hm.indexOf(vc.tn) + 1 + key = `hfields${index}` + column = vc.cn + break + case 'bt': + index = nestedFields.bt.indexOf(vc.tn) + 1 + key = `bfields${index}` + column = vc.cn + break + } + + if (index && column) { + obj[key] = `${obj[key] ? `${obj[key]},` : ''}${column}` + } + + return obj + }, {}) + return { + ...Object.entries(nestedFields).reduce((ro, [k, a]) => ({ ...ro, [k]: a.join(',') }), {}), + ...fieldsObj + } + }, queryParams() { return { limit: this.size, @@ -118,10 +181,9 @@ export default { // condition: this.condition, where: this.concatenatedXWhere, sort: this.sort, - // todo: use reduce - hm: (this.meta && this.meta.v && this.meta.v.filter(v => v.hm).map(({ hm }) => hm.tn).join()) || '', - bt: (this.meta && this.meta.v && this.meta.v.filter(v => v.bt).map(({ bt }) => bt.rtn).join()) || '', - mm: (this.meta && this.meta.v && this.meta.v.filter(v => v.mm).map(({ mm }) => mm.rtn).join()) || '' + ...this.nestedAndRollupColumnParams + // ...Object.entries(nestedFields).reduce((ro, [k, a]) => ({ ...ro, [k]: a.join(',') })), + // ...fieldsObj } }, colLength() { @@ -169,23 +231,31 @@ export default { return this.nodes.tn || this.nodes.view_name }, primaryValueColumn() { - if (!this.meta || !this.availableColumns || !this.availableColumns.length) { return '' } + if (!this.meta || !this.availableColumns || !this.availableColumns.length) { + return '' + } return (this.availableColumns.find(col => col.pv) || { _cn: '' })._cn } }, watch: { 'viewStatus.type'() { - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, showFields: { handler(v) { - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, deep: true }, fieldsOrder: { handler(v) { - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, deep: true }, @@ -243,7 +313,9 @@ export default { // if (!this.progress) { // await this.loadTableData(); // } - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, deep: true }, @@ -256,12 +328,16 @@ export default { // if (!this.progress) { // await this.loadTableData(); // } - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, deep: true }, columnsWidth() { - if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } + if (!this.loadingMeta || !this.loadingData) { + this.syncDataDebounce(this) + } }, sort(n, o) { if (o !== n) { diff --git a/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue b/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue index 98fcffb325..f6252298cb 100644 --- a/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue +++ b/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue @@ -681,7 +681,8 @@ export default { } await this.sqlOp({ dbAlias: this.nodes.dbAlias }, 'xcVirtualTableUpdate', { id: this.selectedViewId, - query_params: queryParams + query_params: queryParams, + tn: this.meta.tn }) } catch (e) { // this.$toast.error(e.message).goAway(3000); @@ -918,15 +919,8 @@ export default { break } }, - async loadMeta(updateShowFields = true) { + async loadMeta(updateShowFields = true, col) { this.loadingMeta = true - // const tableMeta = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ - // env: this.nodes.env, - // dbAlias: this.nodes.dbAlias - // }, 'tableXcModelGet', { - // tn: this.table - // }]); - // this.meta = JSON.parse(tableMeta.meta); const tableMeta = await this.$store.dispatch('meta/ActLoadMeta', { env: this.nodes.env, dbAlias: this.nodes.dbAlias, @@ -938,6 +932,9 @@ export default { try { const qp = JSON.parse(tableMeta.query_params) this.showFields = qp.showFields ? qp.showFields : this.showFields + if (col) { + this.$set(this.showFields, col, true) + } } catch (e) { } } @@ -983,8 +980,8 @@ export default { this.selectedExpandRowIndex = row this.selectedExpandRowMeta = rowMeta }, - async onNewColCreation() { - await this.loadMeta(true) + async onNewColCreation(col) { + await this.loadMeta(true, col) this.$nextTick(async() => { await this.loadTableData() // this.mapFieldsAndShowFields(); diff --git a/packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue b/packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue index 1dc1bd4c38..b7563e18b8 100644 --- a/packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue +++ b/packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue @@ -431,10 +431,10 @@ export default { this.selected.col = null this.selected.row = null }, - onNewColCreation() { + onNewColCreation(col) { this.addNewColMenu = false this.addNewColModal = false - this.$emit('onNewColCreation') + this.$emit('onNewColCreation', col) }, expandRow(...args) { this.$emit('expandRow', ...args) diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts index 823cc737ea..311d7483d4 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts @@ -1101,7 +1101,7 @@ class BaseModelSql extends BaseModel { * @private */ async _getChildListInParent({parent, child}, rest = {}, index) { - const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child); + const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child, 'h'); let {fields} = restArgs; const {cn} = this.hasManyRelations.find(({tn}) => tn === child) || {}; const _cn = this.dbModels[child].columnToAlias?.[cn]; @@ -1155,7 +1155,7 @@ class BaseModelSql extends BaseModel { } public async _getGroupedManyToManyList({rest = {}, index = 0, child, parentIds}) { - const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child); + const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child, 'm'); let {fields} = restArgs; const {tn, cn, vtn, vcn, vrcn, rtn, rcn} = this.manyToManyRelations.find(({rtn}) => rtn === child) || {}; // @ts-ignore @@ -1445,7 +1445,7 @@ class BaseModelSql extends BaseModel { * @private */ async _belongsTo({parent, rcn, parentIds, childs, cn, ...rest}, index) { - let {fields} = this._getChildListArgs(rest, index, parent); + let {fields} = this._getChildListArgs(rest, index, parent, 'b'); if (fields !== '*' && fields.split(',').indexOf(rcn) === -1) { fields += ',' + rcn; } @@ -1642,18 +1642,21 @@ class BaseModelSql extends BaseModel { * @returns {Object} consisting of fields*,where*,limit*,offset*,sort* * @private */ - _getChildListArgs(args: any, index?: number, child?: string) { + _getChildListArgs(args: any, index?: number, child?: string, prefix = '') { index++; const obj: XcFilter = {}; - obj.where = args[`where${index}`] || args[`w${index}`] || ''; - obj.limit = Math.max(Math.min(args[`limit${index}`] || args[`l${index}`] || this.config.limitDefault, this.config.limitMax), this.config.limitMin); - obj.offset = args[`offset${index}`] || args[`o${index}`] || 0; - obj.fields = args[`fields${index}`] || args[`f${index}`] || this.getPKandPV(child); - obj.sort = args[`sort${index}`] || args[`s${index}`]; + obj.where = args[`${prefix}where${index}`] || args[`w${index}`] || ''; + obj.limit = Math.max(Math.min(args[`${prefix}limit${index}`] || args[`${prefix}l${index}`] || this.config.limitDefault, this.config.limitMax), this.config.limitMin); + obj.offset = args[`${prefix}offset${index}`] || args[`${prefix}o${index}`] || 0; + obj.fields = args[`${prefix}fields${index}`] || args[`f${index}`]; + obj.sort = args[`${prefix}sort${index}`] || args[`${prefix}s${index}`]; + + obj.fields = obj.fields ? `${obj.fields},${this.getTablePKandPVFields(child)}` : this.getTablePKandPVFields(child); + return obj; } - private getPKandPV(child: string) { + private getTablePKandPVFields(child: string) { return child ? (this.dbModels[child]?.columns?.filter(col => col.pk || col.pv).map(col => col.cn) || ['*']).join(',') : '*'; diff --git a/packages/nocodb/src/lib/noco/NcProjectBuilder.ts b/packages/nocodb/src/lib/noco/NcProjectBuilder.ts index d549e71f1e..ba834b2a77 100644 --- a/packages/nocodb/src/lib/noco/NcProjectBuilder.ts +++ b/packages/nocodb/src/lib/noco/NcProjectBuilder.ts @@ -146,6 +146,10 @@ export default class NcProjectBuilder { break; + case 'xcVirtualTableUpdate': + await curBuilder.onVirtualTableUpdate(data.req.args); + break; + case 'tableCreate': await curBuilder.onTableCreate(data.req.args.tn, data.req.args); diff --git a/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts b/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts index 7572cec2f1..456d0d0296 100644 --- a/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts +++ b/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts @@ -470,9 +470,19 @@ export default abstract class BaseApiBuilder implements XcDynami } } } + + + // update lookup columns + this.metas[bt.rtn].v?.forEach(v => { + if (v.tn === tn && v.cn === column.cno) { + relationTableMetas.add(this.metas[bt.rtn]) + v.cn = column.cn; + } + }) } } + // update column name in has many if (newMeta.hasMany?.length) { for (const hm of newMeta.hasMany) { @@ -489,6 +499,44 @@ export default abstract class BaseApiBuilder implements XcDynami } } } + + // update lookup columns + this.metas[hm.tn].v?.forEach(v => { + if (v.tn === tn && v.cn === column.cno) { + relationTableMetas.add(this.metas[hm.tn]) + v.cn = column.cn; + } + }) + + } + } + + // update column name in many to many + if (newMeta.manyToMany?.length) { + for (const mm of newMeta.manyToMany) { + if (mm.cn === column.cno) { + mm.cn = column.cn; + mm._cn = column._cn; + + // update column name in child table metadata + relationTableMetas.add(this.metas[mm.rtn]) + for (const cMm of this.metas[mm.rtn]?.manyToMany) { + if (cMm.rcn === column.cno && cMm.rtn === tn) { + cMm.rcn = column.cn; + cMm._rcn = column._cn; + } + } + } + + + // update lookup columns + this.metas[mm.rtn].v?.forEach(v => { + if (v.tn === tn && v.cn === column.cno) { + relationTableMetas.add(this.metas[mm.tn]) + v.cn = column.cn; + } + }) + } } } @@ -605,6 +653,8 @@ export default abstract class BaseApiBuilder implements XcDynami }, { title: relMeta.tn }); + this.models[relMeta.tn] = this.getBaseModel(relMeta); + XcCache.del([this.projectId, this.dbAlias, 'table', relMeta.tn].join('::')); } } @@ -1757,6 +1807,14 @@ export default abstract class BaseApiBuilder implements XcDynami public async onTableCreate(_tn: string, _args?: any) { Tele.emit('evt', {evt_type: 'table:created'}) } + + public onVirtualTableUpdate(args: any) { + const meta = XcCache.get([this.projectId, this.dbAlias, 'table', args.tn].join('::')); + if(meta && meta.id === args.id) { + XcCache.del([this.projectId, this.dbAlias, 'table', args.tn].join('::')); + // todo: update meta and model + } + } } export {IGNORE_TABLES}; diff --git a/packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts b/packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts index 3e0b9553d5..32f114ded0 100644 --- a/packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts +++ b/packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts @@ -1728,9 +1728,6 @@ export default class NcMetaMgr { // NOTE: updated protected async tableXcModelGet(req, args): Promise { - - console.time('tableXcModelGet') - const roles = req.session?.passport?.user?.roles; const dbAlias = await this.getDbAlias(args); @@ -1745,7 +1742,8 @@ export default class NcMetaMgr { 'meta', 'parent_model_title', 'title', - 'query_params' + 'query_params', + 'id' ]); this.cacheModelSet(args.project_id, dbAlias, 'table', args.args.tn, meta); } @@ -1819,7 +1817,6 @@ export default class NcMetaMgr { meta.meta = JSON.stringify(parsedTableMeta); - console.timeEnd('tableXcModelGet') return meta; }