diff --git a/packages/nocodb/src/db/functionMappings/pg.ts b/packages/nocodb/src/db/functionMappings/pg.ts index 3e1c95a820..3d877855c3 100644 --- a/packages/nocodb/src/db/functionMappings/pg.ts +++ b/packages/nocodb/src/db/functionMappings/pg.ts @@ -210,7 +210,9 @@ const pg = { SUBSTR: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { const str = (await fn(pt.arguments[0])).builder; const positionFrom = (await fn(pt.arguments[1] ?? 1)).builder; - const numberOfCharacters = pt.arguments[2] ? (await fn(pt.arguments[2])).builder : null; + const numberOfCharacters = pt.arguments[2] + ? (await fn(pt.arguments[2])).builder + : null; return { builder: knex.raw( `SUBSTR(${str}::TEXT, ${positionFrom}${ diff --git a/packages/nocodb/src/meta/meta.service.ts b/packages/nocodb/src/meta/meta.service.ts index 40548db54d..5074883b7d 100644 --- a/packages/nocodb/src/meta/meta.service.ts +++ b/packages/nocodb/src/meta/meta.service.ts @@ -118,26 +118,35 @@ export class MetaService { }); return insertObj; } - public async bulkMetaInsert( base_id: string, source_id: string, target: string, - data: any[], + data: any | any[], ignoreIdGeneration?: boolean, ): Promise { + if (Array.isArray(data) ? !data.length : !data) { + return []; + } + const insertObj = []; const at = this.now(); - for (const d of data) { + + const commonProps: Record = { + created_at: at, + updated_at: at, + }; + + if (source_id !== null) commonProps.source_id = source_id; + if (base_id !== null) commonProps.base_id = base_id; + + for (const d of Array.isArray(data) ? data : [data]) { const id = d?.id || (await this.genNanoid(target)); const tempObj = { ...d, ...(ignoreIdGeneration ? {} : { id }), - created_at: at, - updated_at: at, + ...commonProps, }; - if (source_id !== null) tempObj.source_id = source_id; - if (base_id !== null) tempObj.base_id = base_id; insertObj.push(tempObj); } diff --git a/packages/nocodb/src/models/Column.ts b/packages/nocodb/src/models/Column.ts index 35c2a49f1f..fbce1f91a0 100644 --- a/packages/nocodb/src/models/Column.ts +++ b/packages/nocodb/src/models/Column.ts @@ -1330,4 +1330,302 @@ export default class Column implements ColumnType { colId, ); } + + static async bulkInsert( + param: { + columns: Column[]; + fk_model_id: any; + source_id: string; + base_id: string; + }, + ncMeta = Noco.ncMeta, + ) { + const extractedColumnMetas = []; + const columns = []; + + // add fk_model_id + for (const column of param.columns) { + // pre-populate column meta to use while inserting colOptions + const id = await ncMeta.genNanoid(MetaTable.COLUMNS); + const colWithId = { + ...column, + id, + base_id: param.base_id, + source_id: param.source_id, + fk_model_id: param.fk_model_id, + }; + + const insertObj = extractProps(colWithId as any, [ + 'id', + 'fk_model_id', + 'column_name', + 'title', + 'uidt', + 'dt', + 'np', + 'ns', + 'clen', + 'cop', + 'pk', + 'rqd', + 'un', + 'ct', + 'ai', + 'unique', + 'cdf', + 'cc', + 'csn', + 'dtx', + 'dtxp', + 'dtxs', + 'au', + 'pv', + 'order', + 'base_id', + 'source_id', + 'system', + 'meta', + ]); + + if (column.meta && typeof column.meta === 'object') { + insertObj.meta = JSON.stringify(column.meta); + } + + if (column.validate) { + if (typeof column.validate === 'string') + insertObj.validate = column.validate; + else insertObj.validate = JSON.stringify(column.validate); + } + extractedColumnMetas.push(insertObj); + + columns.push(colWithId); + } + + // bulk insert columns + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COLUMNS, + extractedColumnMetas, + true, + ); + + // insert column options if any + // for (const column of columns) { + await Column.bulkInsertColOption(columns, ncMeta); + // } + + return columns; + } + + private static async bulkInsertColOption( + columns: (Partial & { source_id?: string; [p: string]: any })[], + ncMeta = Noco.ncMeta, + ) { + const insertGroups = new Map[]>(); + + for (const column of columns) { + let insertArr = insertGroups.get( + column.uidt === UITypes.MultiSelect + ? UITypes.SingleSelect + : column.uidt, + ); + if (!insertArr) { + insertGroups.set(column.uidt, (insertArr = [])); + } + switch (column.uidt || column.ui_data_type) { + case UITypes.Lookup: + // LookupColumn.insert() + insertArr.push({ + fk_column_id: column.id, + fk_relation_column_id: column.fk_relation_column_id, + fk_lookup_column_id: column.fk_lookup_column_id, + }); + break; + + case UITypes.Rollup: { + insertArr.push({ + fk_column_id: column.id, + fk_relation_column_id: column.fk_relation_column_id, + + fk_rollup_column_id: column.fk_rollup_column_id, + rollup_function: column.rollup_function, + }); + break; + } + case UITypes.Links: + case UITypes.LinkToAnotherRecord: { + insertArr.push({ + fk_column_id: column.id, + type: column.type, + + fk_child_column_id: column.fk_child_column_id, + fk_parent_column_id: column.fk_parent_column_id, + + fk_mm_model_id: column.fk_mm_model_id, + fk_mm_child_column_id: column.fk_mm_child_column_id, + fk_mm_parent_column_id: column.fk_mm_parent_column_id, + + ur: column.ur, + dr: column.dr, + + fk_index_name: column.fk_index_name, + fk_related_model_id: column.fk_related_model_id, + + virtual: column.virtual, + }); + break; + } + case UITypes.QrCode: { + insertArr.push( + { + fk_column_id: column.id, + fk_qr_value_column_id: column.fk_qr_value_column_id, + }, + ncMeta, + ); + break; + } + case UITypes.Barcode: { + insertArr.push({ + fk_column_id: column.id, + fk_barcode_value_column_id: column.fk_barcode_value_column_id, + barcode_format: column.barcode_format, + }); + break; + } + case UITypes.Formula: { + insertArr.push({ + fk_column_id: column.id, + formula: column.formula, + formula_raw: column.formula_raw, + parsed_tree: column.parsed_tree, + }); + break; + } + case UITypes.MultiSelect: { + if (!column.colOptions?.options) { + for (const [i, option] of column.dtxp?.split(',').entries() || + [].entries()) { + insertArr.push({ + fk_column_id: column.id, + title: option.replace(/^'/, '').replace(/'$/, ''), + order: i + 1, + color: selectColors[i % selectColors.length], + }); + } + } else { + for (const [i, option] of column.colOptions.options.entries() || + [].entries()) { + // Trim end of enum/set + if (column.dt === 'enum' || column.dt === 'set') { + option.title = option.title.trimEnd(); + } + insertArr.push({ + color: selectColors[i % selectColors.length], // in case color is not provided + ...extractProps(option, ['title', 'fk_column_id', 'color']), + fk_column_id: column.id, + order: i + 1, + }); + } + } + break; + } + case UITypes.SingleSelect: { + if (!column.colOptions?.options) { + for (const [i, option] of column.dtxp?.split(',').entries() || + [].entries()) { + insertArr.push({ + fk_column_id: column.id, + title: option.replace(/^'/, '').replace(/'$/, ''), + order: i + 1, + color: selectColors[i % selectColors.length], + }); + } + } else { + for (const [i, option] of column.colOptions.options.entries() || + [].entries()) { + // Trim end of enum/set + if (column.dt === 'enum' || column.dt === 'set') { + option.title = option.title.trimEnd(); + } + insertArr.push({ + color: selectColors[i % selectColors.length], // in case color is not provided + ...extractProps(option, ['title', 'fk_column_id', 'color']), + fk_column_id: column.id, + order: i + 1, + }); + } + } + break; + } + } + } + + // bulk insert column options + for (const group of insertGroups.keys()) { + switch (group) { + case UITypes.SingleSelect: + case UITypes.MultiSelect: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_SELECT_OPTIONS, + insertGroups.get(group), + ); + break; + + case UITypes.Lookup: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_LOOKUP, + insertGroups.get(group), + ); + break; + + case UITypes.Rollup: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_ROLLUP, + insertGroups.get(group), + ); + break; + case UITypes.Links: + case UITypes.LinkToAnotherRecord: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_RELATIONS, + insertGroups.get(group), + ); + break; + case UITypes.QrCode: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_QRCODE, + insertGroups.get(group), + ); + break; + case UITypes.Barcode: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_BARCODE, + insertGroups.get(group), + ); + break; + case UITypes.Formula: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.COL_FORMULA, + insertGroups.get(group), + ); + break; + } + } + } } diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index 5f4243f6ae..f21100562c 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -147,37 +147,50 @@ export default class Model implements TableType { insertObj, ); - const view = await View.insert( + const insertedColumns = await Column.bulkInsert( + { + columns: (model?.columns || []) as Column[], + fk_model_id: id, + source_id: sourceId, + base_id: baseId, + }, + ncMeta, + ); + + await View.insertMetaOnly( { fk_model_id: id, title: model.title || model.table_name, is_default: true, type: ViewTypes.GRID, + base_id: baseId, + source_id: sourceId, + }, + { + getColumns: async () => insertedColumns, }, ncMeta, ); - for (const column of model?.columns || []) { - await Column.insert({ ...column, fk_model_id: id, view } as any, ncMeta); - } + const modelRes = await this.getWithInfo({ id }, ncMeta); - return this.getWithInfo({ id }, ncMeta).then(async (model) => { - if (sourceId) { - await NocoCache.appendToList( - CacheScope.MODEL, - [baseId, sourceId], - `${CacheScope.MODEL}:${id}`, - ); - } - // cater cases where sourceId is not required - // e.g. xcVisibilityMetaGet + // append to model list since model list cache will be there already + if (sourceId) { await NocoCache.appendToList( CacheScope.MODEL, - [baseId], + [baseId, sourceId], `${CacheScope.MODEL}:${id}`, ); - return model; - }); + } + // cater cases where sourceId is not required + // e.g. xcVisibilityMetaGet + await NocoCache.appendToList( + CacheScope.MODEL, + [baseId], + `${CacheScope.MODEL}:${id}`, + ); + + return modelRes; } public static async list( diff --git a/packages/nocodb/src/models/View.ts b/packages/nocodb/src/models/View.ts index 1aa879cb7b..ea3e242a5c 100644 --- a/packages/nocodb/src/models/View.ts +++ b/packages/nocodb/src/models/View.ts @@ -388,7 +388,13 @@ export default class View implements ViewType { for (const sort of sorts) { await Sort.insert( { - ...sort, + ...extractProps(sort, [ + 'fk_column_id', + 'direction', + 'base_id', + 'source_id', + 'order', + ]), fk_view_id: view_id, id: null, }, @@ -399,7 +405,19 @@ export default class View implements ViewType { for (const filter of filters.children) { await Filter.insert( { - ...filter, + ...extractProps(filter, [ + 'id', + 'fk_column_id', + 'comparison_op', + 'comparison_sub_op', + 'value', + 'fk_parent_id', + 'is_group', + 'logical_op', + 'base_id', + 'source_id', + 'order', + ]), fk_view_id: view_id, id: null, }, @@ -1539,4 +1557,382 @@ export default class View implements ViewType { await NocoCache.del(deleteKeys); } + + static async bulkColumnInsertToViews( + { + columns, + viewColumns, + copyFromView, + }: { + copyFromView?: View; + columns?: ({ + order?: number; + show?; + } & Column)[]; + viewColumns?: ( + | GridViewColumn + | GalleryViewColumn + | FormViewColumn + | KanbanViewColumn + | MapViewColumn + )[]; + }, + view: View, + ncMeta = Noco.ncMeta, + ) { + const insertObjs = []; + + if (viewColumns) { + for (let i = 0; i < viewColumns.length; i++) { + const column = viewColumns[i]; + + insertObjs.push({ + ...extractProps(column, [ + 'fk_view_id', + 'fk_column_id', + 'show', + 'base_id', + 'source_id', + 'order', + 'width', + 'group_by', + 'group_by_order', + 'group_by_sort', + ]), + fk_view_id: view.id, + base_id: view.base_id, + source_id: view.source_id, + }); + } + } else { + if (!columns) { + columns = await Column.list({ fk_model_id: view.fk_model_id }); + } + + // todo: avoid duplicate code + if (view.type === ViewTypes.KANBAN && !copyFromView) { + // sort by display value & attachment first, then by singleLineText & Number + // so that later we can handle control `show` easily + columns.sort((a, b) => { + const displayValueOrder = +b.pv - +a.pv; + const attachmentOrder = + +(b.uidt === UITypes.Attachment) - +(a.uidt === UITypes.Attachment); + const singleLineTextOrder = + +(b.uidt === UITypes.SingleLineText) - + +(a.uidt === UITypes.SingleLineText); + const numberOrder = + +(b.uidt === UITypes.Number) - +(a.uidt === UITypes.Number); + const defaultOrder = b.order - a.order; + return ( + displayValueOrder || + attachmentOrder || + singleLineTextOrder || + numberOrder || + defaultOrder + ); + }); + } + + let order = 1; + let galleryShowLimit = 0; + let kanbanShowLimit = 0; + + for (let i = 0; i < columns.length; i++) { + const column = columns[i]; + + let show = 'show' in column ? column.show : true; + + if (view.type === ViewTypes.GALLERY) { + const galleryView = await GalleryView.get(view.id, ncMeta); + if ( + column.id === galleryView.fk_cover_image_col_id || + column.pv || + galleryShowLimit < 3 + ) { + show = true; + galleryShowLimit++; + } else { + show = false; + } + } else if (view.type === ViewTypes.KANBAN && !copyFromView) { + const kanbanView = await KanbanView.get(view.id, ncMeta); + if (column.id === kanbanView?.fk_grp_col_id) { + // include grouping field if it exists + show = true; + } else if ( + column.id === kanbanView.fk_cover_image_col_id || + column.pv + ) { + // Show cover image or primary key + show = true; + kanbanShowLimit++; + } else if (kanbanShowLimit < 3 && !isSystemColumn(column)) { + // show at most 3 non-system columns + show = true; + kanbanShowLimit++; + } else { + // other columns will be hidden + show = false; + } + } else if (view.type === ViewTypes.MAP && !copyFromView) { + const mapView = await MapView.get(view.id, ncMeta); + if (column.id === mapView?.fk_geo_data_col_id) { + show = true; + } + } else if (view.type === ViewTypes.FORM && isSystemColumn(column)) { + show = false; + } + + insertObjs.push({ + fk_column_id: column.id, + order: order++, + show, + fk_view_id: view.id, + base_id: view.base_id, + source_id: view.source_id, + }); + } + } + + switch (view.type) { + case ViewTypes.GRID: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.GRID_VIEW_COLUMNS, + insertObjs, + ); + break; + case ViewTypes.GALLERY: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.GALLERY_VIEW_COLUMNS, + insertObjs, + ); + break; + case ViewTypes.MAP: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.MAP_VIEW_COLUMNS, + insertObjs, + ); + break; + case ViewTypes.KANBAN: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.KANBAN_VIEW_COLUMNS, + insertObjs, + ); + break; + case ViewTypes.FORM: + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.FORM_VIEW_COLUMNS, + insertObjs, + ); + break; + } + } + + static async insertMetaOnly( + view: Partial & + Partial & { + copy_from_id?: string; + fk_grp_col_id?: string; + }, + model: { + getColumns: () => Promise; + }, + ncMeta = Noco.ncMeta, + ) { + const insertObj = extractProps(view, [ + 'id', + 'title', + 'is_default', + 'type', + 'fk_model_id', + 'base_id', + 'source_id', + 'meta', + ]); + + if (!insertObj.order) { + // get order value + insertObj.order = await ncMeta.metaGetNextOrder(MetaTable.VIEWS, { + fk_model_id: view.fk_model_id, + }); + } + + insertObj.show = true; + + if (!insertObj.meta) { + insertObj.meta = {}; + } + + insertObj.meta = stringifyMetaProp(insertObj); + + const copyFromView = + view.copy_from_id && (await View.get(view.copy_from_id, ncMeta)); + + // get base and base id if missing + if (!(view.base_id && view.source_id)) { + const model = await Model.getByIdOrName({ id: view.fk_model_id }, ncMeta); + insertObj.base_id = model.base_id; + insertObj.source_id = model.source_id; + } + + const insertedView = await ncMeta.metaInsert2( + null, + null, + MetaTable.VIEWS, + insertObj, + ); + + const { id: view_id } = insertedView; + + // insert view metadata based on view type + switch (view.type) { + case ViewTypes.GRID: + await GridView.insert( + { + ...((copyFromView?.view as GridView) || {}), + ...(view as GridView), + fk_view_id: view_id, + }, + ncMeta, + ); + break; + case ViewTypes.MAP: + await MapView.insert( + { + ...(view as MapView), + fk_view_id: view_id, + }, + ncMeta, + ); + break; + case ViewTypes.GALLERY: + await GalleryView.insert( + { + ...(copyFromView?.view || {}), + ...view, + fk_view_id: view_id, + }, + ncMeta, + ); + break; + case ViewTypes.FORM: + await FormView.insert( + { + heading: view.title, + ...(copyFromView?.view || {}), + ...view, + fk_view_id: view_id, + }, + ncMeta, + ); + break; + case ViewTypes.KANBAN: + // set grouping field + (view as KanbanView).fk_grp_col_id = view.fk_grp_col_id; + + await KanbanView.insert( + { + ...(copyFromView?.view || {}), + ...view, + fk_view_id: view_id, + }, + ncMeta, + ); + break; + } + + // copy from view + if (copyFromView) { + const sorts = await copyFromView.getSorts(ncMeta); + const filters = await Filter.rootFilterList( + { viewId: copyFromView.id }, + ncMeta, + ); + const viewColumns = await copyFromView.getColumns(ncMeta); + + const sortInsertObjs = []; + const filterInsertObjs = []; + + for (const sort of sorts) { + sortInsertObjs.push({ + ...extractProps(sort, [ + 'fk_column_id', + 'direction', + 'base_id', + 'source_id', + ]), + fk_view_id: view_id, + id: undefined, + }); + } + + for (const filter of filters) { + const fn = async (filter, parentId: string = null) => { + const generatedId = await ncMeta.genNanoid(MetaTable.FILTER_EXP); + + filterInsertObjs.push({ + ...extractProps(filter, [ + 'fk_column_id', + 'comparison_op', + 'comparison_sub_op', + 'value', + 'fk_parent_id', + 'is_group', + 'logical_op', + ]), + fk_view_id: view_id, + id: generatedId, + fk_parent_id: parentId, + }); + if (filter.is_group) + await Promise.all( + ((await filter.getChildren()) || []).map(async (child) => { + await fn(child, generatedId); + }), + ); + }; + + await fn(filter); + } + + await ncMeta.bulkMetaInsert(null, null, MetaTable.SORT, sortInsertObjs); + + await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.FILTER_EXP, + filterInsertObjs, + true, + ); + + // populate view columns + await View.bulkColumnInsertToViews( + { viewColumns, copyFromView }, + insertedView, + ); + } else { + // populate view columns + await View.bulkColumnInsertToViews( + { columns: (await model.getColumns()) as any[] }, + insertedView, + ); + } + + await Model.getNonDefaultViewsCountAndReset( + { modelId: view.fk_model_id }, + ncMeta, + ); + + return insertedView; + } } diff --git a/packages/nocodb/src/services/forms.service.ts b/packages/nocodb/src/services/forms.service.ts index 31cdb7ec21..08db6056b3 100644 --- a/packages/nocodb/src/services/forms.service.ts +++ b/packages/nocodb/src/services/forms.service.ts @@ -9,7 +9,9 @@ import type { NcRequest } from '~/interface/config'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { validatePayload } from '~/helpers'; import { NcError } from '~/helpers/catchError'; -import { FormView, View } from '~/models'; +import { FormView, Model, View } from '~/models'; +import NocoCache from '~/cache/NocoCache'; +import { CacheScope } from '~/utils/globals'; @Injectable() export class FormsService { @@ -31,22 +33,32 @@ export class FormsService { param.body, ); - const view = await View.insert({ - ...param.body, - // todo: sanitize - fk_model_id: param.tableId, - type: ViewTypes.FORM, - }); + const model = await Model.get(param.tableId); - this.appHooksService.emit(AppEvents.VIEW_CREATE, { - view, - showAs: 'form', - req: param.req, - }); + const { id } = await View.insertMetaOnly( + { + ...param.body, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.FORM, + base_id: model.base_id, + source_id: model.source_id, + }, + model, + ); + + // populate cache and add to list since the list cache already exist + const view = await View.get(id); + await NocoCache.appendToList( + CacheScope.VIEW, + [view.fk_model_id], + `${CacheScope.VIEW}:${id}`, + ); this.appHooksService.emit(AppEvents.VIEW_CREATE, { user: param.user, view, + showAs: 'form', req: param.req, }); diff --git a/packages/nocodb/src/services/galleries.service.ts b/packages/nocodb/src/services/galleries.service.ts index 32406ca4c4..6afb4eb361 100644 --- a/packages/nocodb/src/services/galleries.service.ts +++ b/packages/nocodb/src/services/galleries.service.ts @@ -9,7 +9,9 @@ import type { NcRequest } from '~/interface/config'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { validatePayload } from '~/helpers'; import { NcError } from '~/helpers/catchError'; -import { GalleryView, View } from '~/models'; +import { GalleryView, Model, View } from '~/models'; +import NocoCache from '~/cache/NocoCache'; +import { CacheScope } from '~/utils/globals'; @Injectable() export class GalleriesService { @@ -31,12 +33,27 @@ export class GalleriesService { param.gallery, ); - const view = await View.insert({ - ...param.gallery, - // todo: sanitize - fk_model_id: param.tableId, - type: ViewTypes.GALLERY, - }); + const model = await Model.get(param.tableId); + + const { id } = await View.insertMetaOnly( + { + ...param.gallery, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.GALLERY, + base_id: model.base_id, + source_id: model.source_id, + }, + model, + ); + + // populate cache and add to list since the list cache already exist + const view = await View.get(id); + await NocoCache.appendToList( + CacheScope.VIEW, + [view.fk_model_id], + `${CacheScope.VIEW}:${id}`, + ); this.appHooksService.emit(AppEvents.VIEW_CREATE, { view, diff --git a/packages/nocodb/src/services/grids.service.ts b/packages/nocodb/src/services/grids.service.ts index 9c616e209d..1e027f1849 100644 --- a/packages/nocodb/src/services/grids.service.ts +++ b/packages/nocodb/src/services/grids.service.ts @@ -5,7 +5,9 @@ import type { NcRequest } from '~/interface/config'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { validatePayload } from '~/helpers'; import { NcError } from '~/helpers/catchError'; -import { GridView, View } from '~/models'; +import { GridView, Model, View } from '~/models'; +import NocoCache from '~/cache/NocoCache'; +import { CacheScope } from '~/utils/globals'; @Injectable() export class GridsService { @@ -21,15 +23,30 @@ export class GridsService { param.grid, ); - const view = await View.insert({ - ...param.grid, - // todo: sanitize - fk_model_id: param.tableId, - type: ViewTypes.GRID, - }); + const model = await Model.get(param.tableId); + + const { id } = await View.insertMetaOnly( + { + ...param.grid, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.GRID, + base_id: model.base_id, + source_id: model.source_id, + }, + model, + ); + // populate cache and add to list since the list cache already exist + const view = await View.get(id); + await NocoCache.appendToList( + CacheScope.VIEW, + [view.fk_model_id], + `${CacheScope.VIEW}:${id}`, + ); this.appHooksService.emit(AppEvents.VIEW_CREATE, { view, + showAs: 'grid', req: param.req, }); diff --git a/packages/nocodb/src/services/kanbans.service.ts b/packages/nocodb/src/services/kanbans.service.ts index c21880792d..900ca2464a 100644 --- a/packages/nocodb/src/services/kanbans.service.ts +++ b/packages/nocodb/src/services/kanbans.service.ts @@ -9,7 +9,9 @@ import type { NcRequest } from '~/interface/config'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { validatePayload } from '~/helpers'; import { NcError } from '~/helpers/catchError'; -import { KanbanView, View } from '~/models'; +import { KanbanView, Model, View } from '~/models'; +import NocoCache from '~/cache/NocoCache'; +import { CacheScope } from '~/utils/globals'; @Injectable() export class KanbansService { @@ -30,12 +32,27 @@ export class KanbansService { param.kanban, ); - const view = await View.insert({ - ...param.kanban, - // todo: sanitize - fk_model_id: param.tableId, - type: ViewTypes.KANBAN, - }); + const model = await Model.get(param.tableId); + + const { id } = await View.insertMetaOnly( + { + ...param.kanban, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.KANBAN, + base_id: model.base_id, + source_id: model.source_id, + }, + model, + ); + + // populate cache and add to list since the list cache already exist + const view = await View.get(id); + await NocoCache.appendToList( + CacheScope.VIEW, + [view.fk_model_id], + `${CacheScope.VIEW}:${id}`, + ); this.appHooksService.emit(AppEvents.VIEW_CREATE, { view, diff --git a/packages/nocodb/src/services/maps.service.ts b/packages/nocodb/src/services/maps.service.ts index 1ea51a41f3..0069268385 100644 --- a/packages/nocodb/src/services/maps.service.ts +++ b/packages/nocodb/src/services/maps.service.ts @@ -5,7 +5,9 @@ import type { NcRequest } from '~/interface/config'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { validatePayload } from '~/helpers'; import { NcError } from '~/helpers/catchError'; -import { MapView, View } from '~/models'; +import { MapView, Model, View } from '~/models'; +import { CacheScope } from '~/utils/globals'; +import NocoCache from '~/cache/NocoCache'; @Injectable() export class MapsService { @@ -25,12 +27,28 @@ export class MapsService { 'swagger.json#/components/schemas/ViewCreateReq', param.map, ); - const view = await View.insert({ - ...param.map, - // todo: sanitize - fk_model_id: param.tableId, - type: ViewTypes.MAP, - }); + + const model = await Model.get(param.tableId); + + const { id } = await View.insertMetaOnly( + { + ...param.map, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.MAP, + base_id: model.base_id, + source_id: model.source_id, + }, + model, + ); + + // populate cache and add to list since the list cache already exist + const view = await View.get(id); + await NocoCache.appendToList( + CacheScope.VIEW, + [view.fk_model_id], + `${CacheScope.VIEW}:${id}`, + ); this.appHooksService.emit(AppEvents.VIEW_CREATE, { view, diff --git a/packages/nocodb/src/services/tables.service.ts b/packages/nocodb/src/services/tables.service.ts index f2e2846387..976d392d0b 100644 --- a/packages/nocodb/src/services/tables.service.ts +++ b/packages/nocodb/src/services/tables.service.ts @@ -601,6 +601,7 @@ export class TablesService { column_name: c.column_name, })), ); + await sqlMgr.sqlOpPlus(source, 'tableCreate', { ...tableCreatePayLoad, tn: tableCreatePayLoad.table_name, @@ -612,6 +613,7 @@ export class TablesService { system?: boolean; } >; + if (!source.isMeta()) { columns = ( await sqlMgr.sqlOpPlus(source, 'columnList', { @@ -620,6 +622,7 @@ export class TablesService { }) )?.data?.list; } + const tables = await Model.list({ base_id: base.id, source_id: source.id,