diff --git a/packages/nc-gui/composables/useCalendarViewStore.ts b/packages/nc-gui/composables/useCalendarViewStore.ts index bd10f03ae5..50f02c8bf3 100644 --- a/packages/nc-gui/composables/useCalendarViewStore.ts +++ b/packages/nc-gui/composables/useCalendarViewStore.ts @@ -90,7 +90,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() - const { sharedView, fetchSharedViewData } = useSharedView() + const { sharedView, fetchSharedViewData, fetchSharedViewActiveDate } = useSharedView() const calendarMetaData = ref({}) @@ -452,7 +452,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( prevDate = prevDate!.format('YYYY-MM-DD HH:mm:ssZ') nextDate = nextDate!.format('YYYY-MM-DD HH:mm:ssZ') - fromDate = pageDate.value.format('YYYY-MM-DD HH:mm:ssZ') + fromDate = fromDate!.format('YYYY-MM-DD HH:mm:ssZ') const activeDateFilter: Array = [] @@ -510,14 +510,21 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( }) if (!base?.value?.id || !meta.value?.id || !viewMeta.value?.id) return - const res = await api.dbViewRow.calendarCount('noco', base.value.id!, meta.value!.id!, viewMeta.value.id, { - ...queryParams.value, - ...{}, - ...{}, - ...{ filterArrJson: JSON.stringify([...filterJSON.value, ...activeDateFilter]) }, - }) + const res = !isPublic.value + ? await api.dbViewRow.calendarCount('noco', base.value.id!, meta.value!.id!, viewMeta.value.id, { + ...queryParams.value, + ...{}, + ...{}, + ...{ filterArrJson: JSON.stringify([...filterJSON.value, ...activeDateFilter]) }, + }) + : await fetchSharedViewActiveDate({ + sortsArr: sorts.value, + filtersArr: activeDateFilter, + }) if (res) { - activeDates.value = res.map((dateObj: unknown) => dayjs(dateObj.date)) + activeDates.value = res.map((dateObj: unknown) => dayjs(dateObj)) + } else { + activeDates.value = [] } } @@ -730,8 +737,14 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( if (sideBarFilterOption.value === 'selectedDate') { await loadSidebarData() } + } else if (activeCalendarView.value === 'year') { + if (value.year() !== oldValue.year()) { + await Promise.all([loadCalendarData(), loadSidebarData(), await fetchActiveDates()]) + } else if (sideBarFilterOption.value === 'selectedDate') { + await loadSidebarData() + } } else { - await Promise.all([loadCalendarData(), loadSidebarData()]) + await Promise.all([loadSidebarData()]) } if (activeCalendarView.value === 'year' && value.year() !== oldValue.year()) { @@ -748,7 +761,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( watch(selectedMonth, async (value, oldValue) => { if (activeCalendarView.value !== 'month') return if (value.month() !== oldValue.month()) { - await Promise.all([loadCalendarData(), loadSidebarData()]) + await Promise.all([loadCalendarData(), loadSidebarData(), fetchActiveDates()]) } }) diff --git a/packages/nc-gui/composables/useSharedView.ts b/packages/nc-gui/composables/useSharedView.ts index acbefa54df..32d5241271 100644 --- a/packages/nc-gui/composables/useSharedView.ts +++ b/packages/nc-gui/composables/useSharedView.ts @@ -148,6 +148,33 @@ export function useSharedView() { ) } + const fetchSharedViewActiveDate = async (param: { + sortsArr: SortType[] + filtersArr: FilterType[] + sort?: any[] + where?: string + }) => { + if (!sharedView.value) + return { + list: [], + pageInfo: {}, + } + + return await $api.public.calendarCount( + sharedView.value.uuid!, + { + ...param, + filterArrJson: JSON.stringify(param.filtersArr ?? nestedFilters.value), + sortArrJson: JSON.stringify(param.sortsArr ?? sorts.value), + } as any, + { + headers: { + 'xc-password': password.value, + }, + }, + ) + } + const fetchSharedViewGroupedData = async ( columnId: string, { sortsArr, filtersArr }: { sortsArr: SortType[]; filtersArr: FilterType[] }, @@ -200,6 +227,7 @@ export function useSharedView() { meta, nestedFilters, fetchSharedViewData, + fetchSharedViewActiveDate, fetchSharedViewGroupedData, paginationData, sorts, diff --git a/packages/nocodb/src/controllers/public-datas.controller.ts b/packages/nocodb/src/controllers/public-datas.controller.ts index 187e8ecbdd..94e4cba39e 100644 --- a/packages/nocodb/src/controllers/public-datas.controller.ts +++ b/packages/nocodb/src/controllers/public-datas.controller.ts @@ -34,6 +34,21 @@ export class PublicDatasController { return pagedResponse; } + @Get([ + '/api/v1/db/public/shared-view/:sharedViewUuid/countByDate', + '/api/v2/public/shared-view/:sharedViewUuid/countByDate', + ]) + async countByDate( + @Req() req: Request, + @Param('sharedViewUuid') sharedViewUuid: string, + ) { + return await this.publicDatasService.getCalendarRecordCount({ + query: req.query, + password: req.headers?.['xc-password'] as string, + sharedViewUuid, + }); + } + @Get([ '/api/v1/db/public/shared-view/:sharedViewUuid/groupby', '/api/v2/public/shared-view/:sharedViewUuid/groupby', diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 5df1fff1ec..6bac3a9673 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -21,7 +21,7 @@ import { customAlphabet } from 'nanoid'; import DOMPurify from 'isomorphic-dompurify'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from '@nestjs/common'; -import type { CalendarRangeType, SortType } from 'nocodb-sdk'; +import type { SortType } from 'nocodb-sdk'; import type { Knex } from 'knex'; import type LookupColumn from '~/models/LookupColumn'; import type { XKnex } from '~/db/CustomKnex'; @@ -251,76 +251,6 @@ class BaseModelSqlv2 { return !!(await this.execAndParse(qb, null, { raw: true, first: true })); } - public async countByRanges({ - ranges, - model, - filterArr, - }: { - ranges: Partial[]; - model: Model; - filterArr?: Filter[]; - }) { - const columns = await model.getColumns(); - - const queryRanges = []; - - for (const range of ranges) { - let query; - if (range?.fk_from_column_id && range?.fk_to_column_id) { - query = this.dbDriver( - this.dbDriver.raw( - `(SELECT generate_series( - ??, - ??, - '1 day' - )::timestamptz AS date, ??::timestamptz, ??::timestamptz FROM ??) AS ??`, - [ - columns.find((c) => c.id === range.fk_from_column_id).column_name, - columns.find((c) => c.id === range.fk_to_column_id).column_name, - columns.find((c) => c.id === range.fk_from_column_id).column_name, - columns.find((c) => c.id === range.fk_to_column_id).column_name, - this.tnPath, - range.id, - ], - ), - ); - } else if (range.fk_from_column_id) { - query = this.dbDriver( - this.dbDriver.raw( - `(SELECT ??::timestamptz AS date, ??::timestamptz FROM ??) AS ?? `, - [ - columns.find((c) => c.id === range.fk_from_column_id).column_name, - columns.find((c) => c.id === range.fk_from_column_id).column_name, - this.tnPath, - range.id, - ], - ), - ); - } - - if (query) { - await conditionV2(this, filterArr, query); - queryRanges.push(query); - } - } - - const unionQuery = this.dbDriver.raw( - queryRanges.reduce( - (acc, range) => - acc ? `${acc} UNION ALL ${range.toQuery()}` : range.toQuery(), - '', - ), - ); - - const qb = this.dbDriver(this.dbDriver.raw(`(${unionQuery}) AS ??`, ['nc'])) - .select('date') - .count('* as count') - .groupBy('date') - .orderBy('date'); - - return await this.execAndParse(qb); - } - // todo: add support for sortArrJson public async findOne( args: { diff --git a/packages/nocodb/src/schema/swagger-v2.json b/packages/nocodb/src/schema/swagger-v2.json index ba122a9864..b85ee22884 100644 --- a/packages/nocodb/src/schema/swagger-v2.json +++ b/packages/nocodb/src/schema/swagger-v2.json @@ -7273,6 +7273,83 @@ "description": "List Shared View Grouped Data" } }, + "/api/v2/public/shared-view/{sharedViewUuid}/countByDate": { + "parameters": [ + { + "schema": { + "type": "string", + "example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f" + }, + "name": "sharedViewUuid", + "in": "path", + "required": true, + "description": "Shared View UUID" + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "xc-password", + "description": "Shared view password" + } + ], + "get": { + "summary": "Count of Records in Dates in Calendar View", + "operationId": "public-calendar-count", + "parameters": [ + { + "schema": { + "type": "array" + }, + "in": "query", + "name": "sort" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "where" + }, + { + "schema": { + "type": "integer", + "minimum": 1 + }, + "in": "query", + "name": "limit" + }, + { + "schema": { + "type": "integer", + "minimum": 0 + }, + "in": "query", + "name": "offset" + }, + { + "$ref": "#/components/parameters/xc-auth" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + } + }, + "tags": [ + "Public" + ] + } + }, "/api/v2/public/shared-view/{sharedViewUuid}/rows": { "parameters": [ { diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index a46bd8a9da..6bfa6045d3 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -12118,6 +12118,83 @@ "description": "List Shared View Grouped Data" } }, + "/api/v1/db/public/shared-view/{sharedViewUuid}/countByDate": { + "parameters": [ + { + "schema": { + "type": "string", + "example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f" + }, + "name": "sharedViewUuid", + "in": "path", + "required": true, + "description": "Shared View UUID" + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "xc-password", + "description": "Shared view password" + } + ], + "get": { + "summary": "Count of Records in Dates in Calendar View", + "operationId": "public-calendar-count", + "parameters": [ + { + "schema": { + "type": "array" + }, + "in": "query", + "name": "sort" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "where" + }, + { + "schema": { + "type": "integer", + "minimum": 1 + }, + "in": "query", + "name": "limit" + }, + { + "schema": { + "type": "integer", + "minimum": 0 + }, + "in": "query", + "name": "offset" + }, + { + "$ref": "#/components/parameters/xc-auth" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + } + }, + "tags": [ + "Public" + ] + } + }, "/api/v1/db/public/shared-view/{sharedViewUuid}/rows": { "parameters": [ { diff --git a/packages/nocodb/src/services/datas.service.ts b/packages/nocodb/src/services/datas.service.ts index a4499933b2..5902f79e28 100644 --- a/packages/nocodb/src/services/datas.service.ts +++ b/packages/nocodb/src/services/datas.service.ts @@ -3,6 +3,7 @@ import { isSystemColumn, ViewTypes } from 'nocodb-sdk'; import * as XLSX from 'xlsx'; import papaparse from 'papaparse'; import { nocoExecute } from 'nc-help'; +import dayjs from 'dayjs'; import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; import type { PathParams } from '~/modules/datas/helpers'; import { getDbRows, getViewAndModelByAliasOrId } from '~/modules/datas/helpers'; @@ -214,8 +215,6 @@ export class DatasService { if (view.type !== ViewTypes.CALENDAR) NcError.badRequest('View is not a calendar view'); - const source = await Source.get(view.source_id); - const { ranges } = await CalendarRange.read(view.id); if (!ranges.length) NcError.badRequest('No ranges found'); @@ -224,32 +223,48 @@ export class DatasService { id: view.fk_model_id, }); - const baseModel = await Model.getBaseModelSQL({ - id: view.fk_model_id, - viewId: view?.id, - dbDriver: await NcConnectionMgrv2.get(source), - }); - - const { dependencyFields } = await getAst({ + const data = await this.getDataList({ model, - query, view, - extractOnlyRangeFields: true, + query, }); - const listArgs: any = dependencyFields; - try { - listArgs.filterArr = JSON.parse(listArgs.filterArrJson); - } catch (e) {} - try { - listArgs.sortArr = JSON.parse(listArgs.sortArrJson); - } catch (e) {} + if (!data) NcError.notFound('Data not found'); - return await baseModel.countByRanges({ - model, - ranges, - ...listArgs, + const dates: Array = []; + + ranges.forEach((range) => { + data.list.forEach((date) => { + const from = + date[ + model.columns.find((c) => c.id === range.fk_from_column_id).title + ]; + + let to; + if (range.fk_to_column_id) { + to = + date[ + model.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; } async getFindOne(param: { model: Model; view: View; query: any }) { diff --git a/packages/nocodb/src/services/public-datas.service.ts b/packages/nocodb/src/services/public-datas.service.ts index b93e989b5d..013d2d4e53 100644 --- a/packages/nocodb/src/services/public-datas.service.ts +++ b/packages/nocodb/src/services/public-datas.service.ts @@ -5,7 +5,9 @@ import { ErrorMessages, UITypes, ViewTypes } from 'nocodb-sdk'; import slash from 'slash'; import { nocoExecute } from 'nc-help'; +import dayjs from 'dayjs'; import type { LinkToAnotherRecordColumn } from '~/models'; +import { CalendarRange } from '~/models'; import { NcError } from '~/helpers/catchError'; import getAst from '~/helpers/getAst'; import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2'; @@ -97,6 +99,70 @@ 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 { ranges } = await CalendarRange.read(view.id); + + 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 = []; + + ranges.forEach((range) => { + 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;