Browse Source

Merge pull request #8281 from nocodb/nc-feat/meta-v3-apis

Nc feat/meta v3 apis
pull/8292/head
Pranav C 7 months ago committed by GitHub
parent
commit
5c6f934472
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      packages/nocodb-sdk/src/lib/enums.ts
  2. 53
      packages/nocodb/src/controllers/view-columns.controller.ts
  3. 4
      packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
  4. 64
      packages/nocodb/src/meta/migrations/v2/nc_044_view_column_index.ts
  5. 7
      packages/nocodb/src/models/CalendarViewColumn.ts
  6. 30
      packages/nocodb/src/models/GalleryViewColumn.ts
  7. 31
      packages/nocodb/src/models/KanbanViewColumn.ts
  8. 39
      packages/nocodb/src/models/MapViewColumn.ts
  9. 6
      packages/nocodb/src/models/View.ts
  10. 184
      packages/nocodb/src/schema/swagger.json
  11. 238
      packages/nocodb/src/services/view-columns.service.ts
  12. 4
      packages/nocodb/tests/unit/factory/row.ts
  13. 47
      packages/nocodb/tests/unit/factory/viewColumns.ts
  14. 93
      packages/nocodb/tests/unit/rest/tests/viewRow.test.ts

6
packages/nocodb-sdk/src/lib/enums.ts

@ -296,3 +296,9 @@ export enum PlanLimitTypes {
FILTER_LIMIT = 'FILTER_LIMIT', FILTER_LIMIT = 'FILTER_LIMIT',
SORT_LIMIT = 'SORT_LIMIT', SORT_LIMIT = 'SORT_LIMIT',
} }
export enum APIContext {
VIEW_COLUMNS = 'fields',
FILTERS = 'filters',
SORTS = 'sorts',
}

53
packages/nocodb/src/controllers/view-columns.controller.ts

@ -9,7 +9,14 @@ import {
Req, Req,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ViewColumnReqType } from 'nocodb-sdk'; import { APIContext, ViewColumnReqType } from 'nocodb-sdk';
import type {
CalendarColumnReqType,
FormColumnReqType,
GalleryColumnReqType,
GridColumnReqType,
KanbanColumnReqType,
} from 'nocodb-sdk';
import { GlobalGuard } from '~/guards/global/global.guard'; import { GlobalGuard } from '~/guards/global/global.guard';
import { PagedResponseImpl } from '~/helpers/PagedResponse'; import { PagedResponseImpl } from '~/helpers/PagedResponse';
import { ViewColumnsService } from '~/services/view-columns.service'; import { ViewColumnsService } from '~/services/view-columns.service';
@ -73,4 +80,48 @@ export class ViewColumnsController {
}); });
return result; return result;
} }
@Patch('/api/v3/meta/views/:viewId/columns')
@Acl('columnUpdate')
async viewColumnUpdateV3(
@Req() req,
@Param('viewId') viewId: string,
@Body()
body:
| GridColumnReqType
| GalleryColumnReqType
| KanbanColumnReqType
| FormColumnReqType
| CalendarColumnReqType[]
| Record<
APIContext.VIEW_COLUMNS,
Record<
string,
| GridColumnReqType
| GalleryColumnReqType
| KanbanColumnReqType
| FormColumnReqType
| CalendarColumnReqType
>
>,
) {
return new PagedResponseImpl(
await this.viewColumnsService.columnsUpdate({
viewId,
columns: body,
req,
}),
);
}
@Get('/api/v3/meta/views/:viewId/columns')
@Acl('columnList')
async viewColumnListV3(@Req() req, @Param('viewId') viewId: string) {
return {
[APIContext.VIEW_COLUMNS]: await this.viewColumnsService.viewColumnList({
viewId,
req,
}),
};
}
} }

4
packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts

@ -30,6 +30,7 @@ import * as nc_040_form_view_alter_column_types from '~/meta/migrations/v2/nc_04
import * as nc_041_calendar_view from '~/meta/migrations/v2/nc_041_calendar_view'; import * as nc_041_calendar_view from '~/meta/migrations/v2/nc_041_calendar_view';
import * as nc_042_user_block from '~/meta/migrations/v2/nc_042_user_block'; import * as nc_042_user_block from '~/meta/migrations/v2/nc_042_user_block';
import * as nc_043_user_refresh_token from '~/meta/migrations/v2/nc_043_user_refresh_token'; import * as nc_043_user_refresh_token from '~/meta/migrations/v2/nc_043_user_refresh_token';
import * as nc_044_view_column_index from '~/meta/migrations/v2/nc_044_view_column_index';
// Create a custom migration source class // Create a custom migration source class
export default class XcMigrationSourcev2 { export default class XcMigrationSourcev2 {
@ -71,6 +72,7 @@ export default class XcMigrationSourcev2 {
'nc_041_calendar_view', 'nc_041_calendar_view',
'nc_042_user_block', 'nc_042_user_block',
'nc_043_user_refresh_token', 'nc_043_user_refresh_token',
'nc_044_view_column_index',
]); ]);
} }
@ -144,6 +146,8 @@ export default class XcMigrationSourcev2 {
return nc_042_user_block; return nc_042_user_block;
case 'nc_043_user_refresh_token': case 'nc_043_user_refresh_token':
return nc_043_user_refresh_token; return nc_043_user_refresh_token;
case 'nc_044_view_column_index':
return nc_044_view_column_index;
} }
} }
} }

64
packages/nocodb/src/meta/migrations/v2/nc_044_view_column_index.ts

@ -0,0 +1,64 @@
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
console.log('Adding index to view column tables...');
console.time('Added index to Grid view columns');
await knex.schema.alterTable(MetaTable.GRID_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Grid view columns');
console.time('Added index to Gallery view columns');
await knex.schema.alterTable(MetaTable.GALLERY_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Gallery view columns');
console.time('Added index to Kanban view columns');
await knex.schema.alterTable(MetaTable.KANBAN_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Kanban view columns');
console.time('Added index to Form view columns');
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Form view columns');
console.time('Added index to Calendar view columns');
await knex.schema.alterTable(MetaTable.CALENDAR_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Calendar view columns');
console.time('Added index to Map view columns');
await knex.schema.alterTable(MetaTable.MAP_VIEW_COLUMNS, (table) => {
table.index(['fk_view_id', 'fk_column_id']);
});
console.timeEnd('Added index to Map view columns');
};
const down = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.GRID_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
await knex.schema.alterTable(MetaTable.GALLERY_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
await knex.schema.alterTable(MetaTable.KANBAN_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
await knex.schema.alterTable(MetaTable.CALENDAR_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
await knex.schema.alterTable(MetaTable.MAP_VIEW_COLUMNS, (table) => {
table.dropIndex(['fk_view_id', 'fk_column_id']);
});
};
export { up, down };

7
packages/nocodb/src/models/CalendarViewColumn.ts

@ -171,6 +171,13 @@ export default class CalendarViewColumn {
updateObj, updateObj,
); );
// on view column update, delete any optimised single query cache
{
const viewCol = await this.get(columnId, ncMeta);
const view = await View.get(viewCol.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
}
return res; return res;
} }
} }

30
packages/nocodb/src/models/GalleryViewColumn.ts

@ -121,4 +121,34 @@ export default class GalleryViewColumn {
); );
return views?.map((v) => new GalleryViewColumn(v)); return views?.map((v) => new GalleryViewColumn(v));
} }
static async update(
columnId: string,
body: Partial<GalleryViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, ['order', 'show']);
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.GALLERY_VIEW_COLUMNS,
updateObj,
columnId,
);
// get existing cache
const key = `${CacheScope.GALLERY_VIEW_COLUMN}:${columnId}`;
await NocoCache.update(key, updateObj);
// on view column update, delete any optimised single query cache
{
const viewCol = await this.get(columnId, ncMeta);
const view = await View.get(viewCol.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
}
return res;
}
} }

31
packages/nocodb/src/models/KanbanViewColumn.ts

@ -112,4 +112,35 @@ export default class KanbanViewColumn implements KanbanColumnType {
); );
return views?.map((v) => new KanbanViewColumn(v)); return views?.map((v) => new KanbanViewColumn(v));
} }
// todo: update prop names
static async update(
columnId: string,
body: Partial<KanbanViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, ['order', 'show']);
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.KANBAN_VIEW_COLUMNS,
updateObj,
columnId,
);
// get existing cache
const key = `${CacheScope.KANBAN_VIEW_COLUMN}:${columnId}`;
await NocoCache.update(key, updateObj);
// on view column update, delete any optimised single query cache
{
const viewCol = await this.get(columnId, ncMeta);
const view = await View.get(viewCol.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
}
return res;
}
} }

39
packages/nocodb/src/models/MapViewColumn.ts

@ -3,6 +3,7 @@ import View from '~/models/View';
import Noco from '~/Noco'; import Noco from '~/Noco';
import NocoCache from '~/cache/NocoCache'; import NocoCache from '~/cache/NocoCache';
import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
import { extractProps } from '~/helpers/extractProps';
export default class MapViewColumn { export default class MapViewColumn {
id: string; id: string;
@ -101,4 +102,42 @@ export default class MapViewColumn {
); );
return views?.map((v) => new MapViewColumn(v)); return views?.map((v) => new MapViewColumn(v));
} }
// todo: update prop names
static async update(
columnId: string,
body: Partial<MapViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, [
'order',
'show',
'width',
'group_by',
'group_by_order',
'group_by_sort',
]);
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.MAP_VIEW_COLUMNS,
updateObj,
columnId,
);
// get existing cache
const key = `${CacheScope.MAP_VIEW_COLUMN}:${columnId}`;
await NocoCache.update(key, updateObj);
// on view column update, delete any optimised single query cache
{
const viewCol = await this.get(columnId, ncMeta);
const view = await View.get(viewCol.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
}
return res;
}
} }

6
packages/nocodb/src/models/View.ts

@ -1948,7 +1948,7 @@ export default class View implements ViewType {
return insertedView; return insertedView;
} }
private static extractViewColumnsTableName(view: View) { public static extractViewColumnsTableName(view: View) {
let table; let table;
switch (view.type) { switch (view.type) {
case ViewTypes.GRID: case ViewTypes.GRID:
@ -1973,7 +1973,7 @@ export default class View implements ViewType {
return table; return table;
} }
private static extractViewTableName(view: View) { protected static extractViewTableName(view: View) {
let table; let table;
switch (view.type) { switch (view.type) {
case ViewTypes.GRID: case ViewTypes.GRID:
@ -1998,7 +1998,7 @@ export default class View implements ViewType {
return table; return table;
} }
private static extractViewColumnsTableNameScope(view: View) { protected static extractViewColumnsTableNameScope(view: View) {
let scope; let scope;
switch (view.type) { switch (view.type) {
case ViewTypes.GRID: case ViewTypes.GRID:

184
packages/nocodb/src/schema/swagger.json

@ -15848,9 +15848,9 @@
"content": { "content": {
"multipart/form-data": { "multipart/form-data": {
"schema": { "schema": {
"type":"object", "type": "object",
"properties":{ "properties": {
"files":{ "files": {
"type": "array", "type": "array",
"required": true, "required": true,
"items": { "items": {
@ -15865,9 +15865,9 @@
"files": [ "files": [
{ {
"mimetype": "image/jpeg", "mimetype": "image/jpeg",
"fieldname":"files", "fieldname": "files",
"originalname": "22bc-kavypmq4869759 (1).jpg", "originalname": "22bc-kavypmq4869759 (1).jpg",
"encoding":"7bit", "encoding": "7bit",
"size": 13052, "size": 13052,
"buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >" "buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >"
} }
@ -17187,19 +17187,31 @@
"schema": {} "schema": {}
}, },
"examples": { "examples": {
"Example 1" : { "Example 1": {
"value": { "value": {
"link": [ "link": [
{"Id": 1}, {
{"Id": 2}, "Id": 1
{"Id": 3}, },
{"Id": 5}, {
{"Id": 6} "Id": 2
},
{
"Id": 3
},
{
"Id": 5
},
{
"Id": 6
}
], ],
"unlink": [ "unlink": [
{"Id": 4} {
"Id": 4
}
] ]
} }
} }
} }
} }
@ -17208,7 +17220,9 @@
"$ref": "#/components/responses/BadRequest" "$ref": "#/components/responses/BadRequest"
} }
}, },
"tags": ["DB Data Table Row"], "tags": [
"DB Data Table Row"
],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
@ -17536,7 +17550,7 @@
"type": "string", "type": "string",
"description": "Attachment URL to be uploaded via upload-by-url" "description": "Attachment URL to be uploaded via upload-by-url"
}, },
"fileName":{ "fileName": {
"type": "string", "type": "string",
"description": "The name of the attachment file name" "description": "The name of the attachment file name"
} }
@ -17609,9 +17623,9 @@
"x-examples": { "x-examples": {
"Example 1": { "Example 1": {
"mimetype": "image/jpeg", "mimetype": "image/jpeg",
"fieldname":"files", "fieldname": "files",
"originalname": "22bc-kavypmq4869759 (1).jpg", "originalname": "22bc-kavypmq4869759 (1).jpg",
"encoding":"7bit", "encoding": "7bit",
"size": 13052, "size": 13052,
"buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >" "buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >"
} }
@ -23308,13 +23322,15 @@
}, },
"CalendarRangeOrNull": { "CalendarRangeOrNull": {
"description": "Model for CalendarRangeOrNull", "description": "Model for CalendarRangeOrNull",
"example": [{ "example": [
"id": "kvc_2skkg5mi1eb37f", {
"fk_from_column_id": "cl_hzos4ghyncqi4k", "id": "kvc_2skkg5mi1eb37f",
"fk_to_column_id": "cl_hzos4ghyncqi4k", "fk_from_column_id": "cl_hzos4ghyncqi4k",
"fk_view_id": "vw_wqs4zheuo5lgdy", "fk_to_column_id": "cl_hzos4ghyncqi4k",
"label": "string" "fk_view_id": "vw_wqs4zheuo5lgdy",
}], "label": "string"
}
],
"oneOf": [ "oneOf": [
{ {
"type": "null" "type": "null"
@ -25282,19 +25298,131 @@
"properties": { "properties": {
"operation": { "operation": {
"type": "string", "type": "string",
"enum": ["copy", "paste", "deleteAll"] "enum": [
"copy",
"paste",
"deleteAll"
]
}, },
"rowId": { "rowId": {
"type": "string" "type": "string"
}, },
"columnId":{ "columnId": {
"type": "string" "type": "string"
}, },
"fk_related_model_id":{ "fk_related_model_id": {
"type": "string" "type": "string"
} }
}, },
"required": ["operation", "rowId", "columnId", "fk_related_model_id"] "required": [
"operation",
"rowId",
"columnId",
"fk_related_model_id"
]
}
},
"KanbanColumnReq": {
"description": "Model for Kanban Column Request",
"examples": [
{
"title": "string",
"show": 0,
"order": "1"
}
],
"title": "Kanban Column Model Request",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Title"
},
"show": {
"$ref": "#/components/schemas/Bool",
"description": "Is this column shown?"
},
"order": {
"type": "number",
"example": 1,
"description": "Column Order"
}
}
},
"GalleryColumnReq": {
"description": "Model for Gallery Column Request",
"examples": [
{
"label": "My Column",
"width": "200px"
}
],
"properties": {
"show": {
"$ref": "#/components/schemas/Bool",
"description": "Show"
},
"order": {
"type": "number",
"description": "Order",
"example": 1
}
}
},
"CalendarColumnReq": {
"description": "Model for Calendar Column Request",
"examples": [
{
"title": "string",
"show": 0,
"bold": 0,
"italic": 0,
"underline": 0,
"order": "1"
}
],
"title": "Calendar Column Model",
"type": "object",
"properties": {
"show": {
"$ref": "#/components/schemas/Bool",
"x-stoplight": {
"id": "uqq8xmyz97t1u"
},
"description": "Is this column shown?"
},
"bold": {
"$ref": "#/components/schemas/Bool",
"x-stoplight": {
"id": "uqq8xmyz97t1u"
},
"description": "Is this column shown as bold?"
},
"italic": {
"$ref": "#/components/schemas/Bool",
"x-stoplight": {
"id": "uqq8xmyz97t1u"
},
"description": "Is this column shown as italic?"
},
"underline": {
"$ref": "#/components/schemas/Bool",
"x-stoplight": {
"id": "uqq8xmyz97t1u"
},
"description": "Is this column shown underlines?"
},
"order": {
"type": "number",
"x-stoplight": {
"id": "pbnchzgci5dwa"
},
"example": 1,
"description": "Column Order"
}
},
"x-stoplight": {
"id": "psbv6c6y9qvbu"
} }
} }
}, },

238
packages/nocodb/src/services/view-columns.service.ts

@ -1,10 +1,25 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { AppEvents } from 'nocodb-sdk'; import { APIContext, AppEvents, ViewTypes } from 'nocodb-sdk';
import type { ViewColumnReqType, ViewColumnUpdateReqType } from 'nocodb-sdk'; import GridViewColumn from '../models/GridViewColumn';
import GalleryViewColumn from '../models/GalleryViewColumn';
import KanbanViewColumn from '../models/KanbanViewColumn';
import MapViewColumn from '../models/MapViewColumn';
import FormViewColumn from '../models/FormViewColumn';
import type {
CalendarColumnReqType,
FormColumnReqType,
GalleryColumnReqType,
GridColumnReqType,
KanbanColumnReqType,
ViewColumnReqType,
ViewColumnUpdateReqType,
} from 'nocodb-sdk';
import type { NcRequest } from '~/interface/config'; import type { NcRequest } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers'; import { validatePayload } from '~/helpers';
import { View } from '~/models'; import { CalendarViewColumn, View } from '~/models';
import { NcError } from '~/helpers/catchError';
import Noco from '~/Noco';
@Injectable() @Injectable()
export class ViewColumnsService { export class ViewColumnsService {
@ -13,6 +28,7 @@ export class ViewColumnsService {
async columnList(param: { viewId: string }) { async columnList(param: { viewId: string }) {
return await View.getColumns(param.viewId, undefined); return await View.getColumns(param.viewId, undefined);
} }
async columnAdd(param: { async columnAdd(param: {
viewId: string; viewId: string;
column: ViewColumnReqType; column: ViewColumnReqType;
@ -63,4 +79,220 @@ export class ViewColumnsService {
return result; return result;
} }
async columnsUpdate(param: {
viewId: string;
columns:
| GridColumnReqType
| GalleryColumnReqType
| KanbanColumnReqType
| FormColumnReqType
| CalendarColumnReqType[]
| Record<
APIContext.VIEW_COLUMNS,
Record<
string,
| GridColumnReqType
| GalleryColumnReqType
| KanbanColumnReqType
| FormColumnReqType
| CalendarColumnReqType
>
>;
req: any;
}) {
const { viewId } = param;
const columns = Array.isArray(param.columns)
? param.columns
: param.columns?.[APIContext.VIEW_COLUMNS];
if (!columns) {
NcError.badRequest('Invalid request - fields not found');
}
const view = await View.get(viewId);
const updateOrInsertOptions: Promise<any>[] = [];
let result: any;
const ncMeta = await Noco.ncMeta.startTransaction();
if (!view) {
NcError.notFound('View not found');
}
try {
const table = View.extractViewColumnsTableName(view);
// iterate over view columns and update/insert accordingly
for (const [indexOrId, column] of Object.entries(columns)) {
const columnId =
typeof param.columns === 'object' ? indexOrId : column['id'];
const existingCol = await ncMeta.metaGet2(null, null, table, {
fk_view_id: viewId,
fk_column_id: columnId,
});
switch (view.type) {
case ViewTypes.GRID:
validatePayload(
'swagger.json#/components/schemas/GridColumnReq',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
GridViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
GridViewColumn.insert(
{
...(column as GridColumnReqType),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
case ViewTypes.GALLERY:
validatePayload(
'swagger.json#/components/schemas/GalleryColumnReq',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
GalleryViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
GalleryViewColumn.insert(
{
...(column as GalleryColumnReqType),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
case ViewTypes.KANBAN:
validatePayload(
'swagger.json#/components/schemas/KanbanColumnReq',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
KanbanViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
KanbanViewColumn.insert(
{
...(column as KanbanColumnReqType),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
case ViewTypes.MAP:
validatePayload(
'swagger.json#/components/schemas/MapColumn',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
MapViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
MapViewColumn.insert(
{
...(column as MapViewColumn),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
case ViewTypes.FORM:
validatePayload(
'swagger.json#/components/schemas/FormColumnReq',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
FormViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
FormViewColumn.insert(
{
...(column as FormColumnReqType),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
case ViewTypes.CALENDAR:
validatePayload(
'swagger.json#/components/schemas/CalendarColumnReq',
column,
);
if (existingCol) {
updateOrInsertOptions.push(
CalendarViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
CalendarViewColumn.insert(
{
...(column as CalendarColumnReqType),
fk_view_id: viewId,
fk_column_id: columnId,
},
ncMeta,
),
);
}
break;
}
}
await Promise.all(updateOrInsertOptions);
await ncMeta.commit();
await View.clearSingleQueryCache(view.fk_model_id, [view]);
return result;
} catch (e) {
await ncMeta.rollback();
throw e;
}
}
async viewColumnList(param: { viewId: string; req: any }) {
const columnList = await View.getColumns(param.viewId, undefined);
// generate key-value pair of column id and column
const columnMap = columnList.reduce((acc, column) => {
acc[column.fk_column_id] = column;
return acc;
}, {});
return columnMap;
}
} }

4
packages/nocodb/tests/unit/factory/row.ts

@ -11,6 +11,7 @@ import type Column from '../../../src/models/Column';
import type Filter from '../../../src/models/Filter'; import type Filter from '../../../src/models/Filter';
import type Base from '~/models/Base'; import type Base from '~/models/Base';
import type Sort from '../../../src/models/Sort'; import type Sort from '../../../src/models/Sort';
import {View} from "~/models";
const rowValue = (column: ColumnType, index: number) => { const rowValue = (column: ColumnType, index: number) => {
switch (column.uidt) { switch (column.uidt) {
@ -218,9 +219,11 @@ const listRow = async ({
base, base,
table, table,
options, options,
view
}: { }: {
base: Base; base: Base;
table: Model; table: Model;
view?: View;
options?: { options?: {
limit?: any; limit?: any;
offset?: any; offset?: any;
@ -232,6 +235,7 @@ const listRow = async ({
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: table.id, id: table.id,
dbDriver: await NcConnectionMgrv2.get(sources[0]!), dbDriver: await NcConnectionMgrv2.get(sources[0]!),
viewId: view?.id,
}); });
const ignorePagination = !options; const ignorePagination = !options;

47
packages/nocodb/tests/unit/factory/viewColumns.ts

@ -0,0 +1,47 @@
import request from 'supertest';
import type View from '../../../src/models/View';
import { APIContext } from 'nocodb-sdk'
const updateViewColumns = async (
context,
{
view,
viewColumns,
}: {
view: View;
viewColumns: Record<string, any>[] | Record<string, Record<string, any>[]>;
},
) => {
// generate key-value pair of column id and column
const fields = Array.isArray(viewColumns)
? viewColumns.reduce((acc, column) => {
acc[column.fk_column_id] = column;
return acc;
}, {})
: viewColumns;
// configure view to hide selected fields
await request(context.app)
.patch(`/api/v3/meta/views/${view.id}/columns`)
.set('xc-auth', context.token)
.send({ [APIContext.VIEW_COLUMNS]: fields })
.expect(200);
};
const getViewColumns = async (
context,
{
view,
}: {
view: View;
},
) => {
return (
await request(context.app)
.get(`/api/v3/meta/views/${view.id}/columns`)
.set('xc-auth', context.token)
.expect(200)
).body;
};
export { updateViewColumns, getViewColumns };

93
packages/nocodb/tests/unit/rest/tests/viewRow.test.ts

@ -1,17 +1,19 @@
import 'mocha'; import 'mocha';
// @ts-ignore // @ts-ignore
import assert from 'assert';
import request from 'supertest'; import request from 'supertest';
import { UITypes, ViewTypes } from 'nocodb-sdk'; import { APIContext, UITypes, ViewTypes } from 'nocodb-sdk';
import { expect } from 'chai'; import { expect } from 'chai';
import init from '../../init'; import init from '../../init';
import { createProject, createSakilaProject } from '../../factory/base'; import { createProject, createSakilaProject } from '../../factory/base';
import { createTable, getTable } from '../../factory/table'; import { createTable, getAllTables, getTable } from '../../factory/table';
import { createView } from '../../factory/view'; import { createView, getView } from '../../factory/view';
import { import {
createColumn, createColumn,
createLookupColumn, createLookupColumn,
createLtarColumn, createLtarColumn,
createRollupColumn, createRollupColumn,
defaultColumns,
updateViewColumn, updateViewColumn,
} from '../../factory/column'; } from '../../factory/column';
import { import {
@ -19,10 +21,12 @@ import {
createRow, createRow,
getOneRow, getOneRow,
getRow, getRow,
listRow,
} from '../../factory/row'; } from '../../factory/row';
import Model from '../../../../src/models/Model';
import { getViewColumns, updateViewColumns } from '../../factory/viewColumns';
import type { ColumnType } from 'nocodb-sdk'; import type { ColumnType } from 'nocodb-sdk';
import type View from '../../../../src/models/View'; import type View from '../../../../src/models/View';
import type Model from '../../../../src/models/Model';
import type Base from '~/models/Base'; import type Base from '~/models/Base';
// Test case list // Test case list
@ -1697,6 +1701,87 @@ function viewRowTests() {
throw new Error('Wrong export'); throw new Error('Wrong export');
} }
}); });
it('Test view column v3 apis', async function () {
const table = new Model(
await getTable({
base: sakilaProject,
name: 'film',
}),
);
const view = await getView(context, {
table,
name: 'Film',
});
const columns = await table.getColumns();
// get rows
const rows = await listRow({
base: sakilaProject,
table: table,
view,
options: {
limit: 1,
},
});
// verify fields in response
// hide few columns using update view column API
// const view = await createView(context, {
const columnsToHide = ['Rating', 'Description', 'ReleaseYear'];
// generate key value pair of column id and object with hidden as true
const viewColumnsObj: any = columnsToHide.reduce((acc, columnTitle) => {
const column = columns.find((c) => c.title === columnTitle);
if (column) {
acc[column.id] = {
show: false,
};
}
return acc;
}, {});
await updateViewColumns(context, {
view,
viewColumns: viewColumnsObj,
});
// get rows after update
const rowsAfterUpdate = await listRow({
base: sakilaProject,
table: table,
view,
options: {
limit: 1,
},
});
// verify column visible in old and hidden in new
for (const title of columnsToHide) {
expect(rows[0]).to.have.property(title);
expect(rowsAfterUpdate[0]).to.not.have.property(title);
}
// get view columns and verify hidden columns
const viewColApiRes: any = await getViewColumns(context, {
view,
});
for (const colId of Object.keys(viewColApiRes[APIContext.VIEW_COLUMNS])) {
const column = columns.find((c) => c.id === colId);
if (columnsToHide.includes(column.title)) {
expect(viewColApiRes[APIContext.VIEW_COLUMNS][colId]).to.have.property(
'show',
);
expect(!!viewColApiRes[APIContext.VIEW_COLUMNS][colId].show).to.be.eq(
false,
);
}
}
});
} }
export default function () { export default function () {

Loading…
Cancel
Save