From 7f272abf4df4063ce6a71468694c6e5bc5792a0c Mon Sep 17 00:00:00 2001 From: DarkPhoenix2704 Date: Tue, 20 Feb 2024 07:15:46 +0000 Subject: [PATCH] feat(nocodb): calendar view apis --- packages/nocodb/src/models/CalendarRange.ts | 145 ++++++++++++++++++ packages/nocodb/src/models/CalendarView.ts | 15 +- .../nocodb/src/models/CalendarViewColumn.ts | 22 +-- packages/nocodb/src/models/View.ts | 99 ++++++++++-- packages/nocodb/src/models/index.ts | 2 + .../nocodb/src/modules/metas/metas.module.ts | 3 + packages/nocodb/src/schema/swagger.json | 36 ++++- .../nocodb/src/services/calendars.service.ts | 75 +++++++++ 8 files changed, 352 insertions(+), 45 deletions(-) create mode 100644 packages/nocodb/src/models/CalendarRange.ts create mode 100644 packages/nocodb/src/services/calendars.service.ts diff --git a/packages/nocodb/src/models/CalendarRange.ts b/packages/nocodb/src/models/CalendarRange.ts new file mode 100644 index 0000000000..c4226728d3 --- /dev/null +++ b/packages/nocodb/src/models/CalendarRange.ts @@ -0,0 +1,145 @@ +import type {CalendarRangeType} from 'nocodb-sdk'; +import Noco from '~/Noco'; +import NocoCache from '~/cache/NocoCache'; +import {extractProps} from '~/helpers/extractProps'; +import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals'; + +export default class CalendarRange implements CalendarRangeType { + id: string; + fk_from_column_id: string; + fk_to_column_id: string | null; + fk_view_id: string; + + constructor(data: Partial) { + Object.assign(this, data); + } + + public static async insert( + data: Partial, + ncMeta = Noco.ncMeta, + ) { + const insertObj = extractProps(data, [ + 'fk_from_column_id', + 'fk_to_column_id', + 'fk_view_id', + ]); + + const { id } = await ncMeta.metaInsert2( + null, + null, + MetaTable.CALENDAR_VIEW_RANGE, + insertObj, + ); + + await NocoCache.appendToList( + CacheScope.CALENDAR_VIEW_RANGE, + [data.fk_view_id], + `${CacheScope.CALENDAR_VIEW_RANGE}:${id}`, + ); + + return this.get(id, ncMeta); + } + + public static async bulkInsert( + data: Partial[], + ncMeta = Noco.ncMeta, + ) { + const insertObj = []; + + for (const d of data) { + const tempObj = extractProps(d, [ + 'fk_from_column_id', + 'fk_to_column_id', + 'fk_view_id', + ]); + insertObj.push(tempObj); + } + + const bulkData = await ncMeta.bulkMetaInsert( + null, + null, + MetaTable.CALENDAR_VIEW_RANGE, + insertObj, + ); + + for (const d of bulkData) { + await NocoCache.appendToList( + CacheScope.CALENDAR_VIEW_RANGE, + [d.fk_view_id], + `${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`, + ); + await NocoCache.set(`${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`, d); + } + + return true; + } + + public static async get( + calendarRangeId: string, + ncMeta = Noco.ncMeta, + ): Promise { + let data = + calendarRangeId && + (await NocoCache.get( + `${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`, + CacheGetType.TYPE_OBJECT, + )); + if (!data) { + data = await ncMeta.metaGet2( + null, + null, + MetaTable.CALENDAR_VIEW_RANGE, + calendarRangeId, + ); + await NocoCache.set( + `${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`, + data, + ); + } + return data && new CalendarRange(data); + } + + public static async read(fk_view_id: string, ncMeta = Noco.ncMeta) { + const cachedList = await NocoCache.getList(CacheScope.CALENDAR_VIEW, [ + fk_view_id, + ]); + let { list: ranges } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !ranges.length) { + ranges = await ncMeta.metaList2( + null, //, + null, //model.db_alias, + MetaTable.CALENDAR_VIEW_RANGE, + { condition: { fk_view_id } }, + ); + await NocoCache.setList( + CacheScope.CALENDAR_VIEW_RANGE, + [fk_view_id], + ranges.map(({ created_at, updated_at, ...others }) => others), + ); + } + + return ranges?.length + ? { + ranges: ranges + .map(({ created_at, updated_at, ...c }) => new CalendarRange(c)) + } + : null; + } + + public static async find( + fk_view_id: string, + ncMeta = Noco.ncMeta, + ): Promise { + const data = await ncMeta.metaGet2( + null, + null, + MetaTable.CALENDAR_VIEW_RANGE, + { + fk_view_id, + }, + ); + + return data && new CalendarRange(data); + } +} diff --git a/packages/nocodb/src/models/CalendarView.ts b/packages/nocodb/src/models/CalendarView.ts index c1bc255e1f..4914406e0c 100644 --- a/packages/nocodb/src/models/CalendarView.ts +++ b/packages/nocodb/src/models/CalendarView.ts @@ -1,10 +1,10 @@ -import type { MetaType } from 'nocodb-sdk'; -import type { CalendarType } from 'nocodb-sdk'; +import type {CalendarType} from 'nocodb-sdk'; +import {BoolType, MetaType} from 'nocodb-sdk'; import View from '~/models/View'; -import { extractProps } from '~/helpers/extractProps'; +import {extractProps} from '~/helpers/extractProps'; import NocoCache from '~/cache/NocoCache'; import Noco from '~/Noco'; -import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals'; +import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals'; export default class CalendarView implements CalendarType { fk_view_id: string; @@ -13,12 +13,13 @@ export default class CalendarView implements CalendarType { source_id?: string; meta?: MetaType; + fk_cover_image_col_id?: string; // below fields are not in use at this moment // keep them for time being - show?: boolean; - public?: boolean; + show?: BoolType; + public?: BoolType; password?: string; - show_all_fields?: boolean; + show_all_fields?: BoolType; constructor(data: CalendarView) { Object.assign(this, data); diff --git a/packages/nocodb/src/models/CalendarViewColumn.ts b/packages/nocodb/src/models/CalendarViewColumn.ts index d5c5c28b2f..2ed07340f3 100644 --- a/packages/nocodb/src/models/CalendarViewColumn.ts +++ b/packages/nocodb/src/models/CalendarViewColumn.ts @@ -1,13 +1,10 @@ -import type { - BoolType, - MetaType, -} from 'nocodb-sdk'; +import type {BoolType, MetaType,} from 'nocodb-sdk'; import View from '~/models/View'; import Noco from '~/Noco'; import NocoCache from '~/cache/NocoCache'; -import { extractProps } from '~/helpers/extractProps'; -import { deserializeJSON, serializeJSON } from '~/utils/serialize'; -import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals'; +import {extractProps} from '~/helpers/extractProps'; +import {deserializeJSON} from '~/utils/serialize'; +import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals'; export default class CalendarViewColumn { id?: string; @@ -63,7 +60,6 @@ export default class CalendarViewColumn { 'underline', 'bold', 'italic', - 'meta', ]); insertObj.order = await ncMeta.metaGetNextOrder( @@ -73,10 +69,6 @@ export default class CalendarViewColumn { }, ); - if (insertObj.meta) { - insertObj.meta = serializeJSON(insertObj.meta); - } - if (!(insertObj.base_id && insertObj.source_id)) { const viewRef = await View.get(insertObj.fk_view_id, ncMeta); insertObj.base_id = viewRef.base_id; @@ -156,7 +148,6 @@ export default class CalendarViewColumn { const updateObj = extractProps(body, [ 'show', 'order', - 'meta', 'underline', 'bold', 'italic', @@ -170,11 +161,6 @@ export default class CalendarViewColumn { // set cache await NocoCache.set(key, o); } - - if (updateObj.meta) { - updateObj.meta = serializeJSON(updateObj.meta); - } - // update meta return await ncMeta.metaUpdate( null, diff --git a/packages/nocodb/src/models/View.ts b/packages/nocodb/src/models/View.ts index d989095be4..59d7c3e56f 100644 --- a/packages/nocodb/src/models/View.ts +++ b/packages/nocodb/src/models/View.ts @@ -1,11 +1,14 @@ -import { isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk'; -import type { BoolType, ColumnReqType, ViewType } from 'nocodb-sdk'; +import type {BoolType, ColumnReqType, ViewType} from 'nocodb-sdk'; +import {isSystemColumn, UITypes, ViewTypes} from 'nocodb-sdk'; import Model from '~/models/Model'; import FormView from '~/models/FormView'; import GridView from '~/models/GridView'; import KanbanView from '~/models/KanbanView'; import GalleryView from '~/models/GalleryView'; +import CalendarView from "~/models/CalendarView"; import GridViewColumn from '~/models/GridViewColumn'; +import CalendarViewColumn from '~/models/CalendarViewColumn'; +import CalendarRange from "~/models/CalendarRange"; import Sort from '~/models/Sort'; import Filter from '~/models/Filter'; import GalleryViewColumn from '~/models/GalleryViewColumn'; @@ -14,16 +17,11 @@ import KanbanViewColumn from '~/models/KanbanViewColumn'; import Column from '~/models/Column'; import MapView from '~/models/MapView'; import MapViewColumn from '~/models/MapViewColumn'; -import { extractProps } from '~/helpers/extractProps'; +import {extractProps} from '~/helpers/extractProps'; import NocoCache from '~/cache/NocoCache'; -import { - CacheDelDirection, - CacheGetType, - CacheScope, - MetaTable, -} from '~/utils/globals'; +import {CacheDelDirection, CacheGetType, CacheScope, MetaTable,} from '~/utils/globals'; import Noco from '~/Noco'; -import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils'; +import {parseMetaProp, stringifyMetaProp} from '~/utils/modelUtils'; const { v4: uuidv4 } = require('uuid'); @@ -33,6 +31,7 @@ type ViewColumn = | FormViewColumn | GalleryViewColumn | KanbanViewColumn + | CalendarViewColumn | MapViewColumn; type ViewColumnEnrichedWithTitleAndName = ViewColumn & { @@ -55,13 +54,14 @@ export default class View implements ViewType { fk_model_id: string; model?: Model; - view?: FormView | GridView | KanbanView | GalleryView | MapView; + view?: FormView | GridView | KanbanView | GalleryView | MapView | CalendarView; columns?: Array< | FormViewColumn | GridViewColumn | GalleryViewColumn | KanbanViewColumn | MapViewColumn + | CalendarViewColumn >; sorts: Sort[]; @@ -106,6 +106,9 @@ export default class View implements ViewType { case ViewTypes.FORM: this.view = await FormView.get(this.id); break; + case ViewTypes.CALENDAR: + this.view = await CalendarView.get(this.id); + break; } return this.view; } @@ -129,6 +132,9 @@ export default class View implements ViewType { case ViewTypes.FORM: this.view = await FormView.get(this.id, ncMeta); break; + case ViewTypes.CALENDAR: + this.view = await CalendarView.get(this.id, ncMeta); + break; } return this.view; } @@ -271,9 +277,10 @@ export default class View implements ViewType { static async insert( view: Partial & - Partial & { + Partial & { copy_from_id?: string; fk_grp_col_id?: string; + calendar_range?: Partial[]; }, ncMeta = Noco.ncMeta, ) { @@ -378,6 +385,21 @@ export default class View implements ViewType { ncMeta, ); break; + case ViewTypes.CALENDAR: + const obj = extractProps(view, ["calendar_range"]) + if (!obj.calendar_range) break; + const calendarRange = obj.calendar_range as Partial[]; + calendarRange.forEach((range) => { + range.fk_view_id = view_id; + }) + + await CalendarView.insert({ + ...(copyFromView?.view || {}), + ...view, + fk_view_id: view_id, + }, ncMeta,) + + await CalendarRange.bulkInsert(calendarRange, ncMeta); } if (copyFromView) { @@ -429,6 +451,20 @@ export default class View implements ViewType { let order = 1; let galleryShowLimit = 0; let kanbanShowLimit = 0; + let calendarRanges: Array | null = null; + + if (view.type === ViewTypes.CALENDAR) { + const calRange = await CalendarRange.read(view_id, ncMeta); + if (calRange) { + const calIds: Set = new Set() + calRange.ranges.forEach((range) => { + calIds.add(range.fk_from_column_id); + if (!range.fk_to_column_id) return; + calIds.add(range.fk_to_column_id); + }) + calendarRanges = Array.from(calIds) as Array; + } + } if (view.type === ViewTypes.KANBAN && !copyFromView) { // sort by display value & attachment first, then by singleLineText & Number @@ -455,6 +491,9 @@ export default class View implements ViewType { for (const vCol of columns) { let show = 'show' in vCol ? vCol.show : true; + let underline = false; + let bold = false; + let italic = false; if (view.type === ViewTypes.GALLERY) { const galleryView = await GalleryView.get(view_id, ncMeta); @@ -485,6 +524,12 @@ export default class View implements ViewType { // other columns will be hidden show = false; } + } else if (view.type === ViewTypes.CALENDAR && !copyFromView) { + const calendarView = await CalendarView.get(view_id, ncMeta); + if (calendarRanges && calendarRanges.includes(vCol.id)) { + show = true; + } else show = vCol.id === calendarView?.fk_cover_image_col_id; + // Show all Fields in Ranges } else if (view.type === ViewTypes.MAP && !copyFromView) { const mapView = await MapView.get(view_id, ncMeta); if (vCol.id === mapView?.fk_geo_data_col_id) { @@ -506,6 +551,9 @@ export default class View implements ViewType { view_id, fk_column_id: vCol.fk_column_id || vCol.id, show, + underline, + bold, + italic, id: null, }, ncMeta, @@ -591,9 +639,12 @@ export default class View implements ViewType { view_id: any; order; show; + underline?; + bold?; + italic?; fk_column_id; id?: string; - } & Partial, + } & Partial & Partial, ncMeta = Noco.ncMeta, ) { const view = await this.get(param.view_id, ncMeta); @@ -655,6 +706,16 @@ export default class View implements ViewType { ); } break; + case ViewTypes.CALENDAR: { + col = await CalendarViewColumn.insert( + { + ...param, + fk_view_id: view.id, + }, + ncMeta, + ); + } + break; } return col; @@ -678,6 +739,7 @@ export default class View implements ViewType { | GalleryViewColumn | KanbanViewColumn | MapViewColumn + | CalendarViewColumn > > { let columns: Array = []; @@ -700,6 +762,9 @@ export default class View implements ViewType { case ViewTypes.KANBAN: columns = await KanbanViewColumn.list(viewId, ncMeta); break; + case ViewTypes.CALENDAR: + columns = await CalendarViewColumn.list(viewId, ncMeta); + break; } return columns; @@ -749,6 +814,11 @@ export default class View implements ViewType { tableName = MetaTable.KANBAN_VIEW_COLUMNS; cacheScope = CacheScope.KANBAN_VIEW_COLUMN; + break; + case ViewTypes.CALENDAR: + tableName = MetaTable.CALENDAR_VIEW_COLUMNS; + cacheScope = CacheScope.CALENDAR_VIEW_COLUMN; + break; } @@ -800,6 +870,9 @@ export default class View implements ViewType { table = MetaTable.FORM_VIEW_COLUMNS; cacheScope = CacheScope.FORM_VIEW_COLUMN; break; + case ViewTypes.CALENDAR: + table = MetaTable.CALENDAR_VIEW_COLUMNS; + cacheScope = CacheScope.CALENDAR_VIEW_COLUMN; } const updateObj = extractProps(colData, ['order', 'show']); diff --git a/packages/nocodb/src/models/index.ts b/packages/nocodb/src/models/index.ts index a6e64a1e28..7d398cf46d 100644 --- a/packages/nocodb/src/models/index.ts +++ b/packages/nocodb/src/models/index.ts @@ -3,6 +3,8 @@ export { default as Audit } from './Audit'; export { default as BarcodeColumn } from './BarcodeColumn'; export { default as Source } from './Source'; export { default as Column } from './Column'; +export { default as CalendarView } from './CalendarView'; +export { default as CalendarViewColumn } from './CalendarViewColumn'; export { default as Filter } from './Filter'; export { default as FormulaColumn } from './FormulaColumn'; export { default as FormView } from './FormView'; diff --git a/packages/nocodb/src/modules/metas/metas.module.ts b/packages/nocodb/src/modules/metas/metas.module.ts index 92a038dd36..59b5b194a4 100644 --- a/packages/nocodb/src/modules/metas/metas.module.ts +++ b/packages/nocodb/src/modules/metas/metas.module.ts @@ -10,6 +10,7 @@ import { AttachmentsSecureController } from '~/controllers/attachments-secure.co import { AuditsController } from '~/controllers/audits.controller'; import { SourcesController } from '~/controllers/sources.controller'; import { CachesController } from '~/controllers/caches.controller'; +import { CalendarsController } from '~/controllers/calendars.controller'; import { ColumnsController } from '~/controllers/columns.controller'; import { FiltersController } from '~/controllers/filters.controller'; import { FormColumnsController } from '~/controllers/form-columns.controller'; @@ -41,6 +42,7 @@ import { AuditsService } from '~/services/audits.service'; import { SourcesService } from '~/services/sources.service'; import { BulkDataAliasService } from '~/services/bulk-data-alias.service'; import { CachesService } from '~/services/caches.service'; +import { CalendarsService } from '~/services/calendars.service'; import { ColumnsService } from '~/services/columns.service'; import { FiltersService } from '~/services/filters.service'; import { FormColumnsService } from '~/services/form-columns.service'; @@ -137,6 +139,7 @@ export const metaModuleMetadata = { AuditsService, SourcesService, CachesService, + CalendarsService, ColumnsService, FiltersService, FormColumnsService, diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index c2818b3000..c60e89419c 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -20443,7 +20443,7 @@ "id": "psbv6c6y9qvbu" } }, - "CalendarDateRange": { + "CalendarRange": { "description": "Model for Calendar Date Range", "examples": [ { @@ -20458,7 +20458,7 @@ "type": "object", "properties": { "fk_from_column_id": { - "$ref": "#/components/schemas/StringOrNull", + "$ref": "#/components/schemas/Id", "description": "Foreign Key to Column" }, "fk_to_column_id": { @@ -22586,6 +22586,31 @@ ], "title": "TextOrNull Model" }, + "CalendarRangeOrNull": { + "description": "Model for CalendarRangeOrNull", + "example": [{ + "id": "kvc_2skkg5mi1eb37f", + "fk_from_column_id": "cl_hzos4ghyncqi4k", + "fk_to_column_id": "cl_hzos4ghyncqi4k", + "fk_view_id": "vw_wqs4zheuo5lgdy", + "label": "string" + }], + "oneOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/CalendarRange" + } + } + ], + "title": "CalendarRangeOrNull Model", + "x-stoplight": { + "id": "p1g7xrgdsn540" + } + }, "StringOrNull": { "description": "Model for StringOrNull", "examples": [ @@ -23887,11 +23912,8 @@ "description": "Foreign Key to Geo Data Column. Used in creating Map View." }, "calendar_range": { - "type": "array", - "description": "Calendar Range", - "items": { - "$ref": "#/components/schemas/CalendarDateRange" - } + "description": "Calendar Range or Null", + "$ref": "#/components/schemas/CalendarRangeOrNull" } }, "required": [ diff --git a/packages/nocodb/src/services/calendars.service.ts b/packages/nocodb/src/services/calendars.service.ts new file mode 100644 index 0000000000..f2a41d45ec --- /dev/null +++ b/packages/nocodb/src/services/calendars.service.ts @@ -0,0 +1,75 @@ +import {Injectable} from '@nestjs/common'; +import type {CalendarUpdateReqType, UserType, ViewCreateReqType,} from 'nocodb-sdk'; +import {AppEvents, ViewTypes} from 'nocodb-sdk'; +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 {CalendarView, View} from '~/models'; + +@Injectable() +export class CalendarsService { + constructor(private readonly appHooksService: AppHooksService) { + } + + async calendarViewGet(param: { calendarViewId: string }) { + return await CalendarView.get(param.calendarViewId); + } + + async calendarViewCreate(param: { + tableId: string; + calendar: ViewCreateReqType; + user: UserType; + req: NcRequest; + }) { + - + validatePayload( + 'swagger.json#/components/schemas/ViewCreateReq', + param.calendar, + ); + + const view = await View.insert({ + ...param.calendar, + // todo: sanitize + fk_model_id: param.tableId, + type: ViewTypes.CALENDAR, + }); + + this.appHooksService.emit(AppEvents.VIEW_CREATE, { + view, + showAs: 'calendar', + user: param.user, + + req: param.req, + }); + + return view; + } + + async calendarViewUpdate(param: { + calendarViewId: string; + calendar: CalendarUpdateReqType; + req: NcRequest; + }) { + validatePayload( + 'swagger.json#/components/schemas/CalendarUpdateReq', + param.calendar, + ); + + const view = await View.get(param.calendarViewId); + + if (!view) { + NcError.badRequest('View not found'); + } + + const res = await CalendarView.update(param.calendarViewId, param.calendar); + + this.appHooksService.emit(AppEvents.VIEW_UPDATE, { + view, + showAs: 'calendar', + req: param.req, + }); + + return res; + } +}