diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index 8c4a97ae29..2d076385ba 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -2330,7 +2330,12 @@ class BaseModelSqlv2 { }); } - public async groupedList(args: { groupColumnId: string }) { + public async groupedList(args: { groupColumnId: string }): Promise< + { + key: string; + value: Record[]; + }[] + > { const column = await this.model .getColumns() .then((cols) => cols?.find((col) => col.id === args.groupColumnId)); @@ -2352,15 +2357,17 @@ class BaseModelSqlv2 { const qb = this.dbDriver(this.model.table_name); await this.selectObject({ qb }); - const nullQb = qb.clone().whereNull(column.title); - nullQb.limit(20); - nullQb.offset(0); + const nullListQb = qb.clone().whereNull(column.title); + nullListQb.limit(20); + nullListQb.offset(0); const groupedQb = this.dbDriver.from( this.dbDriver .unionAll( [ - this.isSqlite ? this.dbDriver.select().from(nullQb) : nullQb, + this.isSqlite + ? this.dbDriver.select().from(nullListQb) + : nullListQb, ...groupingValues.map((r) => { const query = qb.clone().where(column.title, r); query.limit(20); @@ -2376,7 +2383,18 @@ class BaseModelSqlv2 { const result = await groupedQb; - return _.groupBy(result, column.title); + // todo: handle null values + const groupedResult: Record[]> = _.groupBy( + result, + column.title + ); + + const r = Object.entries(groupedResult).map(([key, value]) => ({ + key, + value, + })); + + return r; } public async groupedListCount(args: { groupColumnId: string }) { diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts index b843af5d08..4434f63bd7 100644 --- a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts +++ b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts @@ -222,6 +222,59 @@ async function dataExist(req: Request, res: Response) { res.json(await baseModel.exist(req.params.rowId)); } + + +// todo: Handle the error case where view doesnt belong to model +async function groupedDataList(req: Request, res: Response) { + const { model, view } = await getViewAndModelFromRequestByAliasOrId(req); + res.json(await getGroupedDataList(model, view, req)); +} + + + +async function getGroupedDataList(model, view: View, req) { + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + viewId: view?.id, + dbDriver: NcConnectionMgrv2.get(base), + }); + + const requestObj = await getAst({ model, query: req.query, view }); + + const listArgs: any = { ...req.query }; + try { + listArgs.filterArr = JSON.parse(listArgs.filterArrJson); + } catch (e) {} + try { + listArgs.sortArr = JSON.parse(listArgs.sortArrJson); + } catch (e) {} + + let data = []; + let count = 0; + try { + data = await nocoExecute( + requestObj, + await baseModel.list(listArgs), + {}, + listArgs + ); + count = await baseModel.count(listArgs); + } catch (e) { + // show empty result instead of throwing error here + // e.g. search some text in a numeric field + } + + return new PagedResponseImpl(data, { + ...req.query, + count, + }); +} + + + + const router = Router({ mergeParams: true }); // table data crud apis @@ -304,6 +357,13 @@ router.get( ncMetaAclMw(dataGroupBy, 'dataGroupBy') ); + +router.get( + '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/grouped', + apiMetrics, + ncMetaAclMw(groupedDataList, 'groupedDataList') +); + router.get( '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId/exist', apiMetrics,