Browse Source

feat(nocodb): calendar datas service

pull/7716/head
DarkPhoenix2704 7 months ago
parent
commit
8b008b0b87
  1. 45
      packages/nocodb/src/controllers/calendars-datas.controller.ts
  2. 25
      packages/nocodb/src/controllers/data-alias.controller.ts
  3. 1
      packages/nocodb/src/controllers/data-table.controller.ts
  4. 5
      packages/nocodb/src/modules/datas/datas.module.ts
  5. 199
      packages/nocodb/src/schema/swagger.json
  6. 205
      packages/nocodb/src/services/calendar-datas.service.ts
  7. 50
      packages/nocodb/src/services/datas.service.ts
  8. 79
      packages/nocodb/src/services/public-datas.service.ts

45
packages/nocodb/src/controllers/calendars-datas.controller.ts

@ -0,0 +1,45 @@
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { GlobalGuard } from '~/guards/global/global.guard';
import { DataApiLimiterGuard } from '~/guards/data-api-limiter.guard';
import { CalendarDatasService } from '~/services/calendar-datas.service';
import Acl from '~/utils/acl';
@Controller()
@UseGuards(DataApiLimiterGuard, GlobalGuard)
export class CalendarDatasController {
constructor(private readonly calendarDatasService: CalendarDatasService) {}
@Get(['/api/v1/db/calendar-data/:orgs/:baseName/:tableName/views/:viewName'])
@Acl('dataList')
async dataList(@Req() req: Request, @Param('viewName') viewId: string) {
return await this.calendarDatasService.getCalendarDataList({
viewId: viewId,
query: req.query,
});
}
@Get([
'/api/v1/db/calendar-data/:orgs/:baseName/:tableName/countByDate/',
'/api/v1/db/calendar-data/:orgs/:baseName/:tableName/views/:viewName/countByDate/',
])
@Acl('dataList')
async calendarDataCount(
@Req() req: Request,
@Res() res: Response,
@Param('baseName') baseName: string,
@Param('tableName') tableName: string,
@Param('viewName') viewName: string,
) {
const startTime = process.hrtime();
const data = await this.calendarDatasService.getCalendarRecordCount({
query: req.query,
viewId: viewName,
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);
res.json(data);
}
}

25
packages/nocodb/src/controllers/data-alias.controller.ts

@ -45,7 +45,6 @@ export class DataAliasController {
tableName: tableName,
viewName: viewName,
disableOptimization: opt === 'false',
ignorePagination: req.headers?.['xc-ignore-pagination'] === 'true',
});
const elapsedMilliSeconds = parseHrtimeToMilliSeconds(
process.hrtime(startTime),
@ -118,30 +117,6 @@ export class DataAliasController {
res.json(countResult);
}
@Get([
'/api/v1/db/data/:orgs/:baseName/:tableName/countByDate/',
'/api/v1/db/data/:orgs/:baseName/:tableName/views/:viewName/countByDate/',
])
@Acl('dataList')
async calendarDataCount(
@Req() req: Request,
@Res() res: Response,
@Param('baseName') baseName: string,
@Param('tableName') tableName: string,
@Param('viewName') viewName: string,
) {
const startTime = process.hrtime();
const data = await this.datasService.getCalendarRecordCount({
query: req.query,
viewId: viewName,
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);
res.json(data);
}
@Post([
'/api/v1/db/data/:orgs/:baseName/:tableName',
'/api/v1/db/data/:orgs/:baseName/:tableName/views/:viewName',

1
packages/nocodb/src/controllers/data-table.controller.ts

@ -38,7 +38,6 @@ export class DataTableController {
query: req.query,
modelId: modelId,
viewId: viewId,
ignorePagination: req.headers?.['xc-ignore-pagination'] === 'true',
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);

5
packages/nocodb/src/modules/datas/datas.module.ts

@ -18,6 +18,8 @@ import { DataAliasNestedService } from '~/services/data-alias-nested.service';
import { OldDatasService } from '~/controllers/old-datas/old-datas.service';
import { PublicDatasExportService } from '~/services/public-datas-export.service';
import { PublicDatasService } from '~/services/public-datas.service';
import { CalendarDatasController } from '~/controllers/calendars-datas.controller';
import { CalendarDatasService } from '~/services/calendar-datas.service';
export const dataModuleMetadata = {
imports: [
@ -33,6 +35,7 @@ export const dataModuleMetadata = {
? [
DataTableController,
DatasController,
CalendarDatasController,
BulkDataAliasController,
DataAliasController,
DataAliasNestedController,
@ -48,6 +51,7 @@ export const dataModuleMetadata = {
DatasService,
BulkDataAliasService,
DataAliasNestedService,
CalendarDatasService,
OldDatasService,
PublicDatasService,
PublicDatasExportService,
@ -55,6 +59,7 @@ export const dataModuleMetadata = {
exports: [
DatasService,
BulkDataAliasService,
CalendarDatasService,
DataAliasNestedService,
OldDatasService,
PublicDatasService,

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

@ -9716,6 +9716,205 @@
}
}
},
"/api/v1/db/calendar-data/{orgs}/{baseName}/{tableName}/views/{viewName}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "orgs",
"in": "path",
"required": true,
"description": "Organisation Name. Currently `noco` will be used."
},
{
"schema": {
"type": "string"
},
"name": "baseName",
"in": "path",
"required": true,
"description": "Base Name"
},
{
"schema": {
"type": "string"
},
"name": "tableName",
"in": "path",
"required": true,
"description": "Table Name"
},
{
"schema": {
"type": "string"
},
"name": "viewName",
"in": "path",
"required": true
}
],
"get": {
"summary": "List rows in Calendar View of a Table",
"operationId": "db-calendar-view-row-list",
"description": "List all rows in Calendar View of a Table",
"tags": [
"DB Calendar View Row"
],
"parameters": [
{
"schema": {
"type": "string"
},
"name": "from_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "to_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields"
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {},
"in": "query",
"name": "nested",
"description": "Query params for nested data"
},
{
"schema": {
"type": "number"
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"list": {
"type": "array",
"x-stoplight": {
"id": "okd8utzm9xqet"
},
"description": "List of calendar view rows",
"items": {
"x-stoplight": {
"id": "j758lsjv53o4q"
},
"type": "object"
}
},
"pageInfo": {
"$ref": "#/components/schemas/Paginated",
"x-stoplight": {
"id": "hylgqzgm8yhye"
},
"description": "Paginated Info"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"/api/v1/db/data/{orgs}/{baseName}/{tableName}/views/{viewName}": {
"parameters": [
{

205
packages/nocodb/src/services/calendar-datas.service.ts

@ -0,0 +1,205 @@
import { Injectable, Logger } from '@nestjs/common';
import { ViewTypes } from 'nocodb-sdk';
import { nocoExecute } from 'nc-help';
import dayjs from 'dayjs';
import type { CalendarRangeType, FilterType } from 'nocodb-sdk';
import { CalendarRange, Model, Source, View } from '~/models';
import { NcBaseError, NcError } from '~/helpers/catchError';
import getAst from '~/helpers/getAst';
import { PagedResponseImpl } from '~/helpers/PagedResponse';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
import { DatasService } from '~/services/datas.service';
@Injectable()
export class CalendarDatasService {
protected logger = new Logger(CalendarDatasService.name);
constructor(protected datasService: DatasService) {}
async getCalendarDataList(param: { viewId: string; query: any }) {
const { viewId, query } = param;
const from_date = query.from_date;
const to_date = query.to_date;
if (!from_date || !to_date)
NcError.badRequest('from_date and to_date are required');
const view = await View.get(viewId);
if (!view) NcError.notFound('View not found');
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const calendarRange = await CalendarRange.read(view.id);
if (!calendarRange?.ranges?.length) NcError.badRequest('No ranges found');
const filterArr = await this.buildFilterArr({
viewId,
from_date,
to_date,
});
query.filterArr = [...(query.filterArr ? query.filterArr : []), filterArr];
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const source = await Source.get(model.source_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: await NcConnectionMgrv2.get(source),
});
const { ast, dependencyFields } = await getAst({
model,
query,
view,
});
const listArgs: any = dependencyFields;
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const [count, data] = await Promise.all([
baseModel.count(listArgs, false),
(async () => {
let data = [];
try {
data = await nocoExecute(
ast,
await baseModel.list(listArgs, {
ignoreViewFilterAndSort: false,
}),
{},
listArgs,
);
} catch (e) {
if (e instanceof NcBaseError) throw e;
this.logger.error(e);
NcError.internalServerError(
'Please check server log for more details',
);
}
return data;
})(),
]);
return new PagedResponseImpl(data, {
...query,
count,
});
}
async getCalendarRecordCount(param: { viewId: string; query: any }) {
const { viewId, query } = param;
const from_date = query.from_date;
const to_date = query.to_date;
if (!from_date || !to_date)
NcError.badRequest('from_date and to_date are required');
const view = await View.get(viewId);
if (!view) NcError.notFound('View not found');
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const { ranges } = await CalendarRange.read(view.id);
if (!ranges.length) NcError.badRequest('No ranges found');
const filterArr = await this.buildFilterArr({
viewId,
from_date,
to_date,
});
query.filterArr = [...(query.filterArr ? query.filterArr : []), filterArr];
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const data = await this.datasService.getDataList({
model,
view,
query,
});
if (!data) NcError.notFound('Data not found');
const dates: Array<string> = [];
ranges.forEach((range: CalendarRangeType) => {
const fromCol = model.columns.find(
(c) => c.id === range.fk_from_column_id,
)?.title;
data.list.forEach((date) => {
const fromDt = dayjs(date[fromCol]);
if (fromCol && fromDt.isValid()) {
dates.push(fromDt.format('YYYY-MM-DD HH:mm:ssZ'));
}
});
});
return {
count: dates.length,
dates: Array.from(new Set(dates)),
};
}
async buildFilterArr({
viewId,
from_date,
to_date,
}: {
viewId: string;
from_date: string;
to_date: string;
}) {
const calendarRange = await CalendarRange.read(viewId);
if (!calendarRange?.ranges?.length) NcError.badRequest('No ranges found');
const filterArr: FilterType = {
is_group: true,
logical_op: 'and',
children: [],
};
calendarRange.ranges.forEach((range: CalendarRange) => {
const fromColumn = range.fk_from_column_id;
let rangeFilter: any = [];
if (fromColumn) {
rangeFilter = [
{
fk_column_id: fromColumn,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: to_date as string,
},
{
fk_column_id: fromColumn,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: from_date as string,
},
];
}
if (rangeFilter.length > 0) filterArr.children.push(rangeFilter);
});
return filterArr;
}
}

50
packages/nocodb/src/services/datas.service.ts

@ -180,16 +180,6 @@ export class DatasService {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
let options = {};
if (view && view.type === ViewTypes.CALENDAR && param.ignorePagination) {
{
options = {
ignorePagination: true,
};
}
}
const [count, data] = await Promise.all([
baseModel.count(listArgs, false, param.throwErrorIfInvalidParams),
(async () => {
@ -200,7 +190,6 @@ export class DatasService {
await baseModel.list(listArgs, {
ignoreViewFilterAndSort,
throwErrorIfInvalidParams: param.throwErrorIfInvalidParams,
...options,
}),
{},
listArgs,
@ -1054,43 +1043,4 @@ export class DatasService {
return column;
}
async getDataAggregateBy(param: {
viewId: string;
query?: any;
aggregateColumnName: string;
aggregateFunction: string;
groupByColumnName?: string;
ignoreFilters?: boolean;
sort?: {
column_name: string;
direction: 'asc' | 'desc';
};
}) {
const { viewId, query = {} } = param;
const view = await View.get(viewId);
const source = await Source.get(view.source_id);
const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id,
viewId: view?.id,
dbDriver: await NcConnectionMgrv2.get(source),
});
const data = await baseModel.groupByAndAggregate(
param.aggregateColumnName,
param.aggregateFunction,
{
groupByColumnName: param.groupByColumnName,
sortBy: param.sort,
...query,
},
);
return new PagedResponseImpl(data, {
...query,
});
}
}

79
packages/nocodb/src/services/public-datas.service.ts

@ -10,9 +10,8 @@ import {
import slash from 'slash';
import { nocoExecute } from 'nc-help';
import dayjs from 'dayjs';
import type { LinkToAnotherRecordColumn } from '~/models';
import { CalendarRange, Column, Model, Source, View } from '~/models';
import { Column, Model, Source, View } from '~/models';
import { NcError } from '~/helpers/catchError';
import getAst from '~/helpers/getAst';
import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2';
@ -80,17 +79,10 @@ export class PublicDatasService {
let data = [];
let count = 0;
let option = {};
if (view && view.type === ViewTypes.CALENDAR) {
option = {
ignorePagination: true,
};
}
try {
data = await nocoExecute(
ast,
await baseModel.list(listArgs, option),
await baseModel.list(listArgs),
{},
listArgs,
);
@ -103,73 +95,6 @@ export class PublicDatasService {
return new PagedResponseImpl(data, { ...param.query, count });
}
async getCalendarRecordCount(param: {
sharedViewUuid: string;
password?: string;
query: any;
}) {
const { sharedViewUuid, password, query = {} } = param;
const view = await View.getByUUID(sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.password && view.password !== password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const calendarRange = await CalendarRange.read(view.id);
if (!calendarRange?.ranges?.length)
NcError.notFound('Calendar ranges are required in a calendar view');
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const columns = await model.getColumns();
const data: any = await this.dataList({
sharedViewUuid,
password,
query,
});
if (!data) NcError.notFound('Data not found');
const dates: Array<string> = [];
calendarRange.ranges.forEach((range: any) => {
data.list.forEach((date) => {
const from =
date[columns.find((c) => c.id === range.fk_from_column_id).title];
let to;
if (range.fk_to_column_id) {
to = date[columns.find((c) => c.id === range.fk_to_column_id).title];
}
if (from && to) {
const fromDt = dayjs(from);
const toDt = dayjs(to);
let current = fromDt;
while (current.isSameOrBefore(toDt)) {
dates.push(current.format('YYYY-MM-DD HH:mm:ssZ'));
current = current.add(1, 'day');
}
} else if (from) {
dates.push(dayjs(from).format('YYYY-MM-DD HH:mm:ssZ'));
}
});
});
return dates;
}
// todo: Handle the error case where view doesnt belong to model
async groupedDataList(param: {
sharedViewUuid: string;

Loading…
Cancel
Save