From 232686a11d7a8be16cc29615f1f028fcb960fde2 Mon Sep 17 00:00:00 2001 From: mertmit Date: Sun, 22 May 2022 11:15:34 +0300 Subject: [PATCH] feat: Select column/model changes Signed-off-by: mertmit --- .../project/spreadsheet/components/Cell.vue | 10 +- .../spreadsheet/components/EditColumn.vue | 35 ++- .../spreadsheet/components/EditableCell.vue | 24 +- .../components/cell/MultiSelectCell.vue | 74 ++++++ .../components/cell/SingleSelectCell.vue | 59 +++++ .../editColumn/CustomSelectOptions.vue | 211 +++++++++++++----- ...leCell.vue => MultiSelectEditableCell.vue} | 96 +++++--- ...eCell.vue => SingleSelectEditableCell.vue} | 55 +++-- packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts | 6 +- .../nocodb/src/lib/meta/api/columnApis.ts | 19 +- .../lib/meta/helpers/populateSamplePayload.ts | 7 +- packages/nocodb/src/lib/models/Column.ts | 104 +++++++-- packages/nocodb/src/lib/models/Model.ts | 21 +- .../{MultiSelectColumn.ts => SelectOption.ts} | 48 ++-- .../src/lib/models/SingleSelectColumn.ts | 87 -------- 15 files changed, 575 insertions(+), 281 deletions(-) create mode 100644 packages/nc-gui/components/project/spreadsheet/components/cell/MultiSelectCell.vue create mode 100644 packages/nc-gui/components/project/spreadsheet/components/cell/SingleSelectCell.vue rename packages/nc-gui/components/project/spreadsheet/components/editableCell/{SetListEditableCell.vue => MultiSelectEditableCell.vue} (53%) rename packages/nc-gui/components/project/spreadsheet/components/editableCell/{EnumListEditableCell.vue => SingleSelectEditableCell.vue} (65%) rename packages/nocodb/src/lib/models/{MultiSelectColumn.ts => SelectOption.ts} (64%) delete mode 100644 packages/nocodb/src/lib/models/SingleSelectColumn.ts diff --git a/packages/nc-gui/components/project/spreadsheet/components/Cell.vue b/packages/nc-gui/components/project/spreadsheet/components/Cell.vue index 5dd090cedc..f7d3544df6 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/Cell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/Cell.vue @@ -7,9 +7,9 @@ :column="column" @click.stop="$emit('enableedit')" /> - + - + @@ -30,8 +30,8 @@ import TimeCell from './cell/TimeCell' import JsonCell from '~/components/project/spreadsheet/components/cell/JsonCell' import UrlCell from '~/components/project/spreadsheet/components/cell/UrlCell' import cell from '@/components/project/spreadsheet/mixins/cell' -import SetListCell from '~/components/project/spreadsheet/components/cell/SetListCell' -import EnumCell from '~/components/project/spreadsheet/components/cell/EnumCell' +import MultiSelectCell from '~/components/project/spreadsheet/components/cell/MultiSelectCell' +import SingleSelectCell from '~/components/project/spreadsheet/components/cell/SingleSelectCell' import EditableAttachmentCell from '~/components/project/spreadsheet/components/editableCell/EditableAttachmentCell' import BooleanCell from '~/components/project/spreadsheet/components/cell/BooleanCell' import EmailCell from '~/components/project/spreadsheet/components/cell/EmailCell' @@ -40,7 +40,7 @@ import CurrencyCell from '@/components/project/spreadsheet/components/cell/Curre export default { name: 'TableCell', - components: { RatingCell, EmailCell, TimeCell, DateTimeCell, DateCell, JsonCell, UrlCell, EditableAttachmentCell, EnumCell, SetListCell, BooleanCell, CurrencyCell }, + components: { RatingCell, EmailCell, TimeCell, DateTimeCell, DateCell, JsonCell, UrlCell, EditableAttachmentCell, SingleSelectCell, MultiSelectCell, BooleanCell, CurrencyCell }, mixins: [cell], props: ['value', 'dbAlias', 'isLocked', 'selected', 'column'], computed: { diff --git a/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue b/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue index 15d7c07ac7..5a238750a3 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue @@ -148,8 +148,10 @@ @@ -746,10 +748,19 @@ export default { if (this.newColumn.uidt === 'Formula' && this.$refs.formula) { return await this.$refs.formula.save() } - this.newColumn.table_name = this.nodes.table_name this.newColumn.title = this.newColumn.column_name + if (this.isSelect && this.$refs.customselect) { + if (this.column) { + await this.$refs.customselect.update() + } else { + await this.$refs.customselect.save() + } + await this.$emit('saved') + return this.$emit('close') + } + if (this.editColumn) { await this.$api.dbTableColumn.update(this.column.id, this.newColumn) } else { @@ -798,15 +809,6 @@ export default { this.newColumn.dtx = 'specificType' - const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect] - if ( - this.column && - selectTypes.includes(this.newColumn.uidt) && - selectTypes.includes(this.column.uidt) - ) { - this.newColumn.dtxp = this.column.dtxp - } - if (this.isCurrency) { if (this.column?.uidt === UITypes.Currency) { this.newColumn.dtxp = this.column.dtxp @@ -842,15 +844,6 @@ export default { this.newColumn.dt ) - const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect] - if ( - this.column && - selectTypes.includes(this.newColumn.uidt) && - selectTypes.includes(this.column.uidt) - ) { - this.newColumn.dtxp = this.column.dtxp - } - if (columnToValidate.includes(this.newColumn.uidt)) { this.newColumn.meta = { validate: this.newColumn.meta && this.newColumn.meta.validate diff --git a/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue b/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue index 5ce6c7e428..979743fdba 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue @@ -74,13 +74,13 @@ v-on="parentListeners" /> - - - - +
+ {{ op ? migrate(op.title) || op : '' }} +
+ + + + + + diff --git a/packages/nc-gui/components/project/spreadsheet/components/cell/SingleSelectCell.vue b/packages/nc-gui/components/project/spreadsheet/components/cell/SingleSelectCell.vue new file mode 100644 index 0000000000..934646cac9 --- /dev/null +++ b/packages/nc-gui/components/project/spreadsheet/components/cell/SingleSelectCell.vue @@ -0,0 +1,59 @@ + + + + + + diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/CustomSelectOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/CustomSelectOptions.vue index 25824a0521..3693fc0e5c 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editColumn/CustomSelectOptions.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/CustomSelectOptions.vue @@ -1,80 +1,168 @@ - diff --git a/packages/nc-gui/components/project/spreadsheet/components/editableCell/EnumListEditableCell.vue b/packages/nc-gui/components/project/spreadsheet/components/editableCell/SingleSelectEditableCell.vue similarity index 65% rename from packages/nc-gui/components/project/spreadsheet/components/editableCell/EnumListEditableCell.vue rename to packages/nc-gui/components/project/spreadsheet/components/editableCell/SingleSelectEditableCell.vue index 1d37eba688..42e2940c89 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editableCell/EnumListEditableCell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editableCell/SingleSelectEditableCell.vue @@ -1,10 +1,12 @@ diff --git a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts index ab1e268818..1f02652850 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts @@ -254,7 +254,7 @@ export class MysqlUi { return ''; case 'enum': - return "'a','b'"; + return ''; case 'set': return "'a','b'"; @@ -1135,10 +1135,10 @@ export class MysqlUi { ]; case 'MultiSelect': - return ['set', 'text', 'tinytext', 'mediumtext', 'longtext']; + return ['varchar', 'text', 'tinytext', 'mediumtext', 'longtext']; case 'SingleSelect': - return ['enum', 'text', 'tinytext', 'mediumtext', 'longtext']; + return ['varchar', 'text', 'tinytext', 'mediumtext', 'longtext']; case 'Year': return ['year']; diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts index 2e942107f8..fd26cd12e2 100644 --- a/packages/nocodb/src/lib/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/meta/api/columnApis.ts @@ -698,12 +698,21 @@ export async function columnUpdate(req: Request, res: Response) { ) }; - const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id }); - await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody); + if ([UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)) { + await Column.update(req.params.columnId, { + ...colBody + }); - await Column.update(req.params.columnId, { - ...colBody - }); + const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id }); + await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody); + } else { + const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id }); + await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody); + + await Column.update(req.params.columnId, { + ...colBody + }); + } } Audit.insert({ project_id: base.project_id, diff --git a/packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts b/packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts index eff2fe6a8c..04d5e1e831 100644 --- a/packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts +++ b/packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts @@ -4,8 +4,7 @@ import { RelationTypes, UITypes } from 'nocodb-sdk'; import Model from '../../models/Model'; import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn'; import LookupColumn from '../../models/LookupColumn'; -import MultiSelectColumn from '../../models/MultiSelectColumn'; -import SingleSelectColumn from '../../models/SingleSelectColumn'; +import SelectOption from '../../models/SelectOption'; export default async function populateSamplePayload( viewOrModel: View | Model, @@ -105,7 +104,7 @@ async function getSampleColumnValue(column: Column): Promise { break; case UITypes.MultiSelect: { - const colOpt = await column.getColOptions(); + const colOpt = await column.getColOptions(); return ( colOpt?.[0]?.title || column?.dtxp?.split(',')?.[0]?.replace(/^['"]|['"]$/g, '') @@ -114,7 +113,7 @@ async function getSampleColumnValue(column: Column): Promise { break; case UITypes.SingleSelect: { - const colOpt = await column.getColOptions(); + const colOpt = await column.getColOptions(); return ( colOpt?.[0]?.title || column?.dtxp?.split(',')?.[0]?.replace(/^['"]|['"]$/g, '') diff --git a/packages/nocodb/src/lib/models/Column.ts b/packages/nocodb/src/lib/models/Column.ts index 431e4061c3..4bd56facf4 100644 --- a/packages/nocodb/src/lib/models/Column.ts +++ b/packages/nocodb/src/lib/models/Column.ts @@ -2,10 +2,11 @@ import FormulaColumn from './FormulaColumn'; import LinkToAnotherRecordColumn from './LinkToAnotherRecordColumn'; import LookupColumn from './LookupColumn'; import RollupColumn from './RollupColumn'; -import SingleSelectColumn from './SingleSelectColumn'; -import MultiSelectColumn from './MultiSelectColumn'; +import SelectOption from './SelectOption'; +import Base from './Base'; import Model from './Model'; import NocoCache from '../cache/NocoCache'; +import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; import { ColumnType, UITypes } from 'nocodb-sdk'; import { CacheDelDirection, @@ -232,26 +233,54 @@ export default class Column implements ColumnType { break; } case UITypes.MultiSelect: { - for (const option of column.dtxp?.split(',') || []) { - await MultiSelectColumn.insert( - { - fk_column_id: colId, - title: option - }, - ncMeta - ); + if (column.dt === 'set' && !column.altered) { + for (const [i, option] of column.dtxp?.split(',').entries() || [].entries()) { + await SelectOption.insert( + { + fk_column_id: colId, + title: option, + order: i + 1 + }, + ncMeta + ); + } + } else { + for (const [i, option] of column.options.entries() || [].entries()) { + await SelectOption.insert( + { + ...option, + fk_column_id: colId, + order: i + 1 + }, + ncMeta + ); + } } break; } case UITypes.SingleSelect: { - for (const option of column.dtxp?.split(',') || []) { - await SingleSelectColumn.insert( - { - fk_column_id: colId, - title: option - }, - ncMeta - ); + if (column.dt === 'enum' && !column.altered) { + for (const [i, option] of column.dtxp?.split(',').entries() || [].entries()) { + await SelectOption.insert( + { + fk_column_id: colId, + title: option, + order: i + 1 + }, + ncMeta + ); + } + } else { + for (const [i, option] of column.options.entries() || [].entries()) { + await SelectOption.insert( + { + ...option, + fk_column_id: colId, + order: i + 1 + }, + ncMeta + ); + } } break; } @@ -322,10 +351,10 @@ export default class Column implements ColumnType { res = await LinkToAnotherRecordColumn.read(this.id, ncMeta); break; case UITypes.MultiSelect: - res = await MultiSelectColumn.get(this.id, ncMeta); + res = await SelectOption.read(this.id, ncMeta); break; case UITypes.SingleSelect: - res = await SingleSelectColumn.get(this.id, ncMeta); + res = await SelectOption.read(this.id, ncMeta); break; case UITypes.Formula: res = await FormulaColumn.read(this.id, ncMeta); @@ -769,13 +798,43 @@ export default class Column implements ColumnType { case UITypes.MultiSelect: case UITypes.SingleSelect: { + + const model = await oldCol.getModel(); + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: NcConnectionMgrv2.get(base) + }); + + // Handle option delete + if (oldCol.colOptions?.options) { + for (const option of oldCol.colOptions.options.filter(oldOp => column.options.find(newOp => newOp.id === oldOp.id) ? false : true)) { + if (column.uidt === UITypes.SingleSelect) { + if (column.dt === 'enum') { + await baseModel.bulkUpdateAll({ where: `(${oldCol.title},eq,${option.title.replace(/'/g, '')})` }, { [oldCol.title]: null }); + } else { + await baseModel.bulkUpdateAll({ where: `(${oldCol.title},eq,${option.id})` }, { [oldCol.title]: null }); + } + } else if (column.uidt === UITypes.MultiSelect) { + const dbDriver = NcConnectionMgrv2.get(base); + if (column.dt === 'set') { + await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ','))`, [model.table_name, oldCol.title, oldCol.title, option.title.replace(/'/g, '')]); + } else { + await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ','))`, [model.table_name, oldCol.title, oldCol.title, option.id]); + } + } + } + } + await ncMeta.metaDelete(null, null, MetaTable.COL_SELECT_OPTIONS, { fk_column_id: colId }); + await NocoCache.deepDel( CacheScope.COL_SELECT_OPTION, - `${CacheScope.COL_SELECT_OPTION}:${colId}`, - CacheDelDirection.CHILD_TO_PARENT + `${CacheScope.COL_SELECT_OPTION}:${colId}:list`, + CacheDelDirection.PARENT_TO_CHILD ); break; } @@ -822,6 +881,7 @@ export default class Column implements ColumnType { // set cache await NocoCache.set(key, o); } + // set meta await ncMeta.metaUpdate( null, diff --git a/packages/nocodb/src/lib/models/Model.ts b/packages/nocodb/src/lib/models/Model.ts index 5f2e111f81..af3fc10d98 100644 --- a/packages/nocodb/src/lib/models/Model.ts +++ b/packages/nocodb/src/lib/models/Model.ts @@ -1,5 +1,6 @@ import Noco from '../Noco'; import Column from './Column'; +import SelectOption from './SelectOption' import NocoCache from '../cache/NocoCache'; import { XKnex } from '../db/sql-data-mapper'; import { BaseModelSqlv2 } from '../db/sql-data-mapper/lib/sql/BaseModelSqlv2'; @@ -403,9 +404,25 @@ export default class Model implements TableType { const insertObj = {}; for (const col of await this.getColumns()) { if (isVirtualCol(col)) continue; - const val = + if ([UITypes.SingleSelect, UITypes.MultiSelect].includes(col.uidt) && !['enum', 'set'].includes(col.dt)) { + let val = data?.[sanitize(col.column_name)] ?? data?.[sanitize(col.title)]; - if (val !== undefined) insertObj[sanitize(col.column_name)] = val; + if (val !== undefined) { + let selection = []; + if (val !== null) { + for (const opt of val.split(',')) { + let tmp = await SelectOption.get(opt) ?? await SelectOption.find(col.id, opt) + if (tmp) selection.push(tmp.id); + } + val = selection.join(','); + } + insertObj[sanitize(col.column_name)] = val; + } + } else { + const val = + data?.[sanitize(col.column_name)] ?? data?.[sanitize(col.title)]; + if (val !== undefined) insertObj[sanitize(col.column_name)] = val; + } } return insertObj; } diff --git a/packages/nocodb/src/lib/models/MultiSelectColumn.ts b/packages/nocodb/src/lib/models/SelectOption.ts similarity index 64% rename from packages/nocodb/src/lib/models/MultiSelectColumn.ts rename to packages/nocodb/src/lib/models/SelectOption.ts index 05c48f644c..53a74800e5 100644 --- a/packages/nocodb/src/lib/models/MultiSelectColumn.ts +++ b/packages/nocodb/src/lib/models/SelectOption.ts @@ -2,26 +2,25 @@ import Noco from '../Noco'; import NocoCache from '../cache/NocoCache'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; -export default class MultiSelectColumn { +export default class SelectOption { title: string; fk_column_id: string; + color: string; + order: number; - constructor(data: Partial) { + constructor(data: Partial) { Object.assign(this, data); } public static async insert( - data: Partial, + data: Partial, ncMeta = Noco.ncMeta ) { const { id } = await ncMeta.metaInsert2( null, null, MetaTable.COL_SELECT_OPTIONS, - { - fk_column_id: data.fk_column_id, - title: data.title - } + data ); await NocoCache.appendToList( @@ -36,7 +35,7 @@ export default class MultiSelectColumn { public static async get( selectOptionId: string, ncMeta = Noco.ncMeta - ): Promise { + ): Promise { let data = selectOptionId && (await NocoCache.get( @@ -55,33 +54,52 @@ export default class MultiSelectColumn { data ); } - return data && new MultiSelectColumn(data); + return data && new SelectOption(data); } - public static async read(columnId: string, ncMeta = Noco.ncMeta) { + public static async read(fk_column_id: string, ncMeta = Noco.ncMeta) { let options = await NocoCache.getList(CacheScope.COL_SELECT_OPTION, [ - columnId + fk_column_id ]); if (!options.length) { options = await ncMeta.metaList2( null, //, null, //model.db_alias, MetaTable.COL_SELECT_OPTIONS, - { condition: { fk_column_id: columnId } } + { condition: { fk_column_id } } ); await NocoCache.setList( CacheScope.COL_SELECT_OPTION, - [columnId], - options + [fk_column_id], + options.map(({created_at, updated_at, ...others}) => others) ); } return options?.length ? { - options: options.map(c => new MultiSelectColumn(c)) + options: options.map(({created_at, updated_at, ...c}) => new SelectOption(c)) } : null; } + public static async find( + fk_column_id: string, + title: string, + ncMeta = Noco.ncMeta + ): Promise { + + let data = await ncMeta.metaGet2( + null, + null, + MetaTable.COL_SELECT_OPTIONS, + { + fk_column_id, + title + } + ); + + return data && new SelectOption(data); + } + id: string; } diff --git a/packages/nocodb/src/lib/models/SingleSelectColumn.ts b/packages/nocodb/src/lib/models/SingleSelectColumn.ts deleted file mode 100644 index 193fed6a16..0000000000 --- a/packages/nocodb/src/lib/models/SingleSelectColumn.ts +++ /dev/null @@ -1,87 +0,0 @@ -import Noco from '../Noco'; -import NocoCache from '../cache/NocoCache'; -import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; - -export default class SingleSelectColumn { - title: string; - fk_column_id: string; - - constructor(data: Partial) { - Object.assign(this, data); - } - - public static async insert( - data: Partial, - ncMeta = Noco.ncMeta - ) { - const { id } = await ncMeta.metaInsert2( - null, - null, - MetaTable.COL_SELECT_OPTIONS, - { - fk_column_id: data.fk_column_id, - title: data.title - } - ); - - await NocoCache.appendToList( - CacheScope.COL_SELECT_OPTION, - [data.fk_column_id], - `${CacheScope.COL_SELECT_OPTION}:${id}` - ); - - return this.get(id, ncMeta); - } - - public static async get( - selectOptionId: string, - ncMeta = Noco.ncMeta - ): Promise { - let data = - selectOptionId && - (await NocoCache.get( - `${CacheScope.COL_SELECT_OPTION}:${selectOptionId}`, - CacheGetType.TYPE_OBJECT - )); - if (!data) { - data = await ncMeta.metaGet2( - null, - null, - MetaTable.COL_SELECT_OPTIONS, - selectOptionId - ); - await NocoCache.set( - `${CacheScope.COL_SELECT_OPTION}:${selectOptionId}`, - data - ); - } - return data && new SingleSelectColumn(data); - } - - public static async read(columnId: string, ncMeta = Noco.ncMeta) { - let options = await NocoCache.getList(CacheScope.COL_SELECT_OPTION, [ - columnId - ]); - if (!options.length) { - options = await ncMeta.metaList2( - null, //, - null, //model.db_alias, - MetaTable.COL_SELECT_OPTIONS, - { condition: { fk_column_id: columnId } } - ); - await NocoCache.setList( - CacheScope.COL_SELECT_OPTION, - [columnId], - options - ); - } - - return options?.length - ? { - options: options.map(c => new SingleSelectColumn(c)) - } - : null; - } - - id: string; -}