Browse Source

feat: unit test and swagger doc

pull/8281/head
Pranav C 7 months ago
parent
commit
f33c19c285
  1. 24
      packages/nocodb/src/controllers/view-columns.controller.ts
  2. 34
      packages/nocodb/src/models/GalleryViewColumn.ts
  3. 36
      packages/nocodb/src/models/KanbanViewColumn.ts
  4. 36
      packages/nocodb/src/models/MapViewColumn.ts
  5. 92
      packages/nocodb/src/models/View.ts
  6. 1238
      packages/nocodb/src/schema/swagger-v3.json
  7. 102
      packages/nocodb/src/services/view-columns.service.ts
  8. 4
      packages/nocodb/tests/unit/factory/row.ts
  9. 46
      packages/nocodb/tests/unit/factory/viewColumns.ts
  10. 85
      packages/nocodb/tests/unit/rest/tests/viewRow.test.ts

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

@ -74,20 +74,28 @@ export class ViewColumnsController {
return result; return result;
} }
@Patch('/api/v3/meta/views/:viewId/columns')
@Acl('columnUpdate')
@Patch( async viewColumnUpdate(
'/api/v3/meta/views/:viewId/columns', @Req() req,
) @Param('viewId') viewId: string,
@Acl('columnList') @Body() body: ViewColumnReqType[] | Record<string, ViewColumnReqType>,
async columnUpdate(@Param('viewId') viewId: string, @Body() body: ViewColumnReqType[] | Record<string, ViewColumnReqType>) { ) {
return new PagedResponseImpl( return new PagedResponseImpl(
await this.viewColumnsService.columnsUpdate({ await this.viewColumnsService.columnsUpdate({
viewId, viewId,
columns: body, columns: body,
req req,
}), }),
); );
} }
@Get('/api/v3/meta/views/:viewId/columns')
@Acl('columnList')
async viewColumnList(@Req() req, @Param('viewId') viewId: string) {
return await this.viewColumnsService.viewColumnList({
viewId,
req,
});
}
} }

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

@ -122,6 +122,38 @@ export default class GalleryViewColumn {
return views?.map((v) => new GalleryViewColumn(v)); return views?.map((v) => new GalleryViewColumn(v));
} }
// todo: update method // todo: update prop names
static async update(
columnId: string,
body: Partial<GalleryViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, [
'order',
'show',
'width',
'group_by',
'group_by_order',
'group_by_sort',
]);
// get existing cache
const key = `${CacheScope.GALLERY_VIEW_COLUMN}:${columnId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
// update data
o = { ...o, ...updateObj };
// set cache
await NocoCache.set(key, o);
}
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.GALLERY_VIEW_COLUMNS,
updateObj,
columnId,
);
return res;
}
} }

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

@ -112,4 +112,40 @@ 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',
'width',
'group_by',
'group_by_order',
'group_by_sort',
]);
// get existing cache
const key = `${CacheScope.KANBAN_VIEW_COLUMN}:${columnId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
// update data
o = { ...o, ...updateObj };
// set cache
await NocoCache.set(key, o);
}
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.KANBAN_VIEW_COLUMNS,
updateObj,
columnId,
);
return res;
}
} }

36
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,39 @@ 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',
]);
// get existing cache
const key = `${CacheScope.MAP_VIEW_COLUMN}:${columnId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
// update data
o = { ...o, ...updateObj };
// set cache
await NocoCache.set(key, o);
}
// set meta
const res = await ncMeta.metaUpdate(
null,
null,
MetaTable.MAP_VIEW_COLUMNS,
updateObj,
columnId,
);
return res;
}
} }

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

@ -1196,94 +1196,6 @@ export default class View implements ViewType {
); );
} }
private static extractViewColumnsTableName(view: View) {
let table;
switch (view.type) {
case ViewTypes.GRID:
table = MetaTable.GRID_VIEW_COLUMNS;
break;
case ViewTypes.GALLERY:
table = MetaTable.GALLERY_VIEW_COLUMNS;
break;
case ViewTypes.KANBAN:
table = MetaTable.KANBAN_VIEW_COLUMNS;
break;
case ViewTypes.FORM:
table = MetaTable.FORM_VIEW_COLUMNS;
break;
case ViewTypes.MAP:
table = MetaTable.MAP_VIEW_COLUMNS;
break;
}
return table;
}
private static extractViewTableName(view: View) {
let table;
switch (view.type) {
case ViewTypes.GRID:
table = MetaTable.GRID_VIEW;
break;
case ViewTypes.GALLERY:
table = MetaTable.GALLERY_VIEW;
break;
case ViewTypes.KANBAN:
table = MetaTable.KANBAN_VIEW;
break;
case ViewTypes.FORM:
table = MetaTable.FORM_VIEW;
break;
case ViewTypes.MAP:
table = MetaTable.MAP_VIEW;
break;
}
return table;
}
private static extractViewColumnsTableNameScope(view: View) {
let scope;
switch (view.type) {
case ViewTypes.GRID:
scope = CacheScope.GRID_VIEW_COLUMN;
break;
case ViewTypes.GALLERY:
scope = CacheScope.GALLERY_VIEW_COLUMN;
break;
case ViewTypes.MAP:
scope = CacheScope.MAP_VIEW_COLUMN;
break;
case ViewTypes.KANBAN:
scope = CacheScope.KANBAN_VIEW_COLUMN;
break;
case ViewTypes.FORM:
scope = CacheScope.FORM_VIEW_COLUMN;
break;
}
return scope;
}
private static extractViewTableNameScope(view: View) {
let scope;
switch (view.type) {
case ViewTypes.GRID:
scope = CacheScope.GRID_VIEW;
break;
case ViewTypes.GALLERY:
scope = CacheScope.GALLERY_VIEW;
break;
case ViewTypes.MAP:
scope = CacheScope.MAP_VIEW;
break;
case ViewTypes.KANBAN:
scope = CacheScope.KANBAN_VIEW;
break;
case ViewTypes.FORM:
scope = CacheScope.FORM_VIEW;
break;
}
return scope;
}
static async showAllColumns( static async showAllColumns(
viewId, viewId,
ignoreColdIds = [], ignoreColdIds = [],
@ -2036,7 +1948,7 @@ export default class View implements ViewType {
return insertedView; return insertedView;
} }
protected 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:
@ -2086,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:

1238
packages/nocodb/src/schema/swagger-v3.json

File diff suppressed because it is too large Load Diff

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

@ -9,7 +9,7 @@ import type { 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 { NcError } from '~/helpers/catchError';
import Noco from '~/Noco'; import Noco from '~/Noco';
@ -81,7 +81,6 @@ export class ViewColumnsService {
const view = await View.get(viewId); const view = await View.get(viewId);
const updateOrInsertOptions: Promise<any>[] = []; const updateOrInsertOptions: Promise<any>[] = [];
let result: any; let result: any;
@ -96,7 +95,7 @@ export class ViewColumnsService {
// iterate over view columns and update/insert accordingly // iterate over view columns and update/insert accordingly
for (const [indexOrId, column] of Object.entries(columns)) { for (const [indexOrId, column] of Object.entries(columns)) {
const columnId = const columnId =
typeof param.columns === 'object' ? indexOrId : column.id; typeof param.columns === 'object' ? indexOrId : column['id'];
const existingCol = await ncMeta.metaGet2(null, null, table, { const existingCol = await ncMeta.metaGet2(null, null, table, {
fk_view_id: viewId, fk_view_id: viewId,
@ -111,13 +110,17 @@ export class ViewColumnsService {
); );
} else { } else {
updateOrInsertOptions.push( updateOrInsertOptions.push(
GridViewColumn.insert({ GridViewColumn.insert(
fk_view_id: viewId, {
fk_column_id: columnId, fk_view_id: viewId,
...column, fk_column_id: columnId,
}, ncMeta), ...column,
},
ncMeta,
),
); );
} }
break
case ViewTypes.GALLERY: case ViewTypes.GALLERY:
if (existingCol) { if (existingCol) {
updateOrInsertOptions.push( updateOrInsertOptions.push(
@ -125,13 +128,17 @@ export class ViewColumnsService {
); );
} else { } else {
updateOrInsertOptions.push( updateOrInsertOptions.push(
GalleryViewColumn.insert({ GalleryViewColumn.insert(
fk_view_id: viewId, {
fk_column_id: columnId, fk_view_id: viewId,
...column, fk_column_id: columnId,
}, ncMeta), ...column,
},
ncMeta,
),
); );
} }
break;
case ViewTypes.KANBAN: case ViewTypes.KANBAN:
if (existingCol) { if (existingCol) {
updateOrInsertOptions.push( updateOrInsertOptions.push(
@ -139,11 +146,14 @@ export class ViewColumnsService {
); );
} else { } else {
updateOrInsertOptions.push( updateOrInsertOptions.push(
KanbanViewColumn.insert({ KanbanViewColumn.insert(
fk_view_id: viewId, {
fk_column_id: columnId, fk_view_id: viewId,
...column, fk_column_id: columnId,
}, ncMeta), ...column,
},
ncMeta,
),
); );
} }
break; break;
@ -154,11 +164,14 @@ export class ViewColumnsService {
); );
} else { } else {
updateOrInsertOptions.push( updateOrInsertOptions.push(
MapViewColumn.insert({ MapViewColumn.insert(
fk_view_id: viewId, {
fk_column_id: columnId, fk_view_id: viewId,
...column, fk_column_id: columnId,
}, ncMeta), ...column,
},
ncMeta,
),
); );
} }
break; break;
@ -169,11 +182,32 @@ export class ViewColumnsService {
); );
} else { } else {
updateOrInsertOptions.push( updateOrInsertOptions.push(
FormViewColumn.insert({ FormViewColumn.insert(
fk_view_id: viewId, {
fk_column_id: columnId, fk_view_id: viewId,
...column, fk_column_id: columnId,
}, ncMeta), ...column,
},
ncMeta,
),
);
}
break;
case ViewTypes.CALENDAR:
if (existingCol) {
updateOrInsertOptions.push(
CalendarViewColumn.update(existingCol.id, column, ncMeta),
);
} else {
updateOrInsertOptions.push(
CalendarViewColumn.insert(
{
fk_view_id: viewId,
fk_column_id: columnId,
...column,
},
ncMeta,
),
); );
} }
break; break;
@ -192,4 +226,16 @@ export class ViewColumnsService {
throw e; 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;

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

@ -0,0 +1,46 @@
import request from 'supertest';
import type View from '../../../src/models/View';
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(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 };

85
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 { 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,81 @@ 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 viewColumnsViaApi: any = await getViewColumns(context, {
view,
});
for (const colId of Object.keys(viewColumnsViaApi)) {
const column = columns.find((c) => c.id === colId);
if (columnsToHide.includes(column.title))
expect(!!viewColumnsViaApi[colId]).to.have.property('show', false);
}
});
} }
export default function () { export default function () {

Loading…
Cancel
Save