From 46bb559257a3821081240c55fe5656a4b00011fd Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Tue, 12 Nov 2024 14:56:14 +0200 Subject: [PATCH 01/13] feat: get tasks by start and due dates filters --- .env.local | 4 +-- .../common/src/utils/find-by-date-between.ts | 14 +++++++++ packages/common/src/utils/index.ts | 1 + packages/contracts/src/task.model.ts | 7 +++++ .../tasks/dto/get-task-by-date-filter.dto.ts | 30 ++++++++++++++++++ packages/core/src/tasks/dto/index.ts | 1 + packages/core/src/tasks/task.controller.ts | 18 ++++++++++- packages/core/src/tasks/task.service.ts | 31 +++++++++++++++++-- 8 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 packages/common/src/utils/find-by-date-between.ts create mode 100644 packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts diff --git a/.env.local b/.env.local index f218550195f..4b2ebcdbe9b 100644 --- a/.env.local +++ b/.env.local @@ -26,7 +26,7 @@ PORT=4200 API_HOST=localhost # API Port -API_PORT=3000 +API_PORT=5500 # WEB UI Host WEB_HOST=localhost @@ -56,7 +56,7 @@ PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads DB_ORM=typeorm # DB_TYPE: sqlite | postgres | better-sqlite3 | mysql -DB_TYPE=better-sqlite3 +DB_TYPE=postgres DB_SYNCHRONIZE=false # DB Connection Parameters diff --git a/packages/common/src/utils/find-by-date-between.ts b/packages/common/src/utils/find-by-date-between.ts new file mode 100644 index 00000000000..04eaea8b4da --- /dev/null +++ b/packages/common/src/utils/find-by-date-between.ts @@ -0,0 +1,14 @@ +import { SelectQueryBuilder } from 'typeorm'; +import { prepareSQLQuery as p } from './../../../core/'; + +export function addBetween( + query: SelectQueryBuilder, + field: string, + from?: Date, + to?: Date +): SelectQueryBuilder { + if (from && to) { + query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from, to }); + } + return query; +} diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index 4d4978a7115..677df10e644 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './deep-merge'; export * from './shared-utils'; export * from './to-hex-string'; export * from './mixins'; +export * from './find-by-date-between'; diff --git a/packages/contracts/src/task.model.ts b/packages/contracts/src/task.model.ts index 11cd0c92dfa..2bc316c8017 100644 --- a/packages/contracts/src/task.model.ts +++ b/packages/contracts/src/task.model.ts @@ -104,3 +104,10 @@ export interface IGetTasksByViewFilters extends IBasePerTenantAndOrganizationEnt // Relations relations?: string[]; } + +export interface ITaskDateFilterInput { + startDateFrom?: Date; + startDateTo?: Date; + dueDateFrom?: Date; + dueDateTo?: Date; +} diff --git a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts new file mode 100644 index 00000000000..b70f8b9abc1 --- /dev/null +++ b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts @@ -0,0 +1,30 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDate, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ITaskDateFilterInput } from '@gauzy/contracts'; + +export class TaskDateFilterInputDTO implements ITaskDateFilterInput { + @ApiPropertyOptional({ type: () => Date }) + @Type(() => Date) + @IsOptional() + @IsDate() + startDateFrom?: Date; + + @ApiPropertyOptional({ type: () => Date }) + @Type(() => Date) + @IsOptional() + @IsDate() + startDateTo?: Date; + + @ApiPropertyOptional({ type: () => Date }) + @Type(() => Date) + @IsOptional() + @IsDate() + dueDateFrom?: Date; + + @ApiPropertyOptional({ type: () => Date }) + @Type(() => Date) + @IsOptional() + @IsDate() + dueDateTo?: Date; +} diff --git a/packages/core/src/tasks/dto/index.ts b/packages/core/src/tasks/dto/index.ts index c2595de73c6..2b3f44d04d1 100644 --- a/packages/core/src/tasks/dto/index.ts +++ b/packages/core/src/tasks/dto/index.ts @@ -2,3 +2,4 @@ export * from './create-task.dto'; export * from './task-max-number-query.dto'; export * from './update-task.dto'; export * from './get-task-by-id.dto'; +export * from './get-task-by-date-filter.dto'; diff --git a/packages/core/src/tasks/task.controller.ts b/packages/core/src/tasks/task.controller.ts index 6696377cde1..cdc284638ce 100644 --- a/packages/core/src/tasks/task.controller.ts +++ b/packages/core/src/tasks/task.controller.ts @@ -23,7 +23,7 @@ import { CrudController, PaginationParams } from './../core/crud'; import { Task } from './task.entity'; import { TaskService } from './task.service'; import { TaskCreateCommand, TaskUpdateCommand } from './commands'; -import { CreateTaskDTO, GetTaskByIdDTO, TaskMaxNumberQueryDTO, UpdateTaskDTO } from './dto'; +import { CreateTaskDTO, GetTaskByIdDTO, TaskDateFilterInputDTO, TaskMaxNumberQueryDTO, UpdateTaskDTO } from './dto'; @ApiTags('Tasks') @UseGuards(TenantPermissionGuard, PermissionGuard) @@ -146,6 +146,22 @@ export class TaskController extends CrudController { return this.taskService.findModuleTasks(params); } + /** + * Retrieves tasks based on the provided date filters for startDate and dueDate. + * + * @function getTasksByDateFilters + * @param {TaskDateFilterInputDTO} params - The DTO containing the date filters for the tasks. + */ + @ApiOperation({ summary: 'Get tasks by start and due dates filters.' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Tasks retrieved successfully.' }) + @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'No records found.' }) + @Permissions(PermissionsEnum.ALL_ORG_VIEW, PermissionsEnum.ORG_TASK_VIEW) + @Get('/filter-by-date') + @UseValidationPipe({ transform: true }) + async getTasksByDateFilters(@Query() params: TaskDateFilterInputDTO): Promise { + return this.taskService.getTasksByDateFilters(params); + } + /** * GET view tasks * diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index a4f2f4b31a9..249c389f0be 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -22,9 +22,10 @@ import { ITask, ITaskUpdateInput, PermissionsEnum, - ActionTypeEnum + ActionTypeEnum, + ITaskDateFilterInput } from '@gauzy/contracts'; -import { isEmpty, isNotEmpty } from '@gauzy/common'; +import { addBetween, isEmpty, isNotEmpty } from '@gauzy/common'; import { isPostgres, isSqlite } from '@gauzy/config'; import { PaginationParams, TenantAwareCrudService } from './../core/crud'; import { RequestContext } from '../core/context'; @@ -837,4 +838,30 @@ export class TaskService extends TenantAwareCrudService { throw new HttpException({ message: error?.message, error }, HttpStatus.BAD_REQUEST); } } + + /** + * Retrieves tasks based on the provided date filters for startDate and dueDate. + * + * @function getTasksByDateFilters + * @param {ITaskDateFilterInput} dateFilterDto - The DTO containing the date filters for the tasks. + * + * @returns {Promise} A promise that resolves to an array of tasks filtered by the provided dates. + * + * @throws {Error} Will throw an error if there is a problem with the database query. + */ + async getTasksByDateFilters(dateFilterDto: ITaskDateFilterInput): Promise { + try { + const { startDateFrom, startDateTo, dueDateFrom, dueDateTo } = dateFilterDto; + + let query = this.typeOrmRepository.createQueryBuilder(this.tableName); + + // Apply the filters on startDate and dueDate + query = addBetween(query, 'startDate', startDateFrom, startDateTo); + query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo); + + return await query.getMany(); + } catch (error) { + throw new BadRequestException(error); + } + } } From ca7e48a96e711f38d2e0c2cd1c2aad19eb662dbf Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 06:19:51 +0200 Subject: [PATCH 02/13] fix: build issue and find relations --- packages/common/src/utils/find-by-date-between.ts | 4 ++-- packages/contracts/src/task.model.ts | 4 +++- packages/core/src/tasks/task.service.ts | 15 ++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/common/src/utils/find-by-date-between.ts b/packages/common/src/utils/find-by-date-between.ts index 04eaea8b4da..51b007eda5f 100644 --- a/packages/common/src/utils/find-by-date-between.ts +++ b/packages/common/src/utils/find-by-date-between.ts @@ -1,11 +1,11 @@ import { SelectQueryBuilder } from 'typeorm'; -import { prepareSQLQuery as p } from './../../../core/'; export function addBetween( query: SelectQueryBuilder, field: string, from?: Date, - to?: Date + to?: Date, + p?: (queryStr: string) => string ): SelectQueryBuilder { if (from && to) { query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from, to }); diff --git a/packages/contracts/src/task.model.ts b/packages/contracts/src/task.model.ts index 2bc316c8017..69cd0daaea4 100644 --- a/packages/contracts/src/task.model.ts +++ b/packages/contracts/src/task.model.ts @@ -105,7 +105,9 @@ export interface IGetTasksByViewFilters extends IBasePerTenantAndOrganizationEnt relations?: string[]; } -export interface ITaskDateFilterInput { +export interface ITaskDateFilterInput + extends IBasePerTenantAndOrganizationEntityModel, + Pick { startDateFrom?: Date; startDateTo?: Date; dueDateFrom?: Date; diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 249c389f0be..1d853009c8b 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -843,21 +843,26 @@ export class TaskService extends TenantAwareCrudService { * Retrieves tasks based on the provided date filters for startDate and dueDate. * * @function getTasksByDateFilters - * @param {ITaskDateFilterInput} dateFilterDto - The DTO containing the date filters for the tasks. + * @param {ITaskDateFilterInput} params - The query params containing the date filters for the tasks. * * @returns {Promise} A promise that resolves to an array of tasks filtered by the provided dates. * * @throws {Error} Will throw an error if there is a problem with the database query. */ - async getTasksByDateFilters(dateFilterDto: ITaskDateFilterInput): Promise { + async getTasksByDateFilters(params: ITaskDateFilterInput): Promise { try { - const { startDateFrom, startDateTo, dueDateFrom, dueDateTo } = dateFilterDto; + const { startDateFrom, startDateTo, dueDateFrom, dueDateTo, relations } = params; let query = this.typeOrmRepository.createQueryBuilder(this.tableName); // Apply the filters on startDate and dueDate - query = addBetween(query, 'startDate', startDateFrom, startDateTo); - query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo); + query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); + query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); + + // Check if relations were provided and include them + query.setFindOptions({ + ...(relations ? { relations } : {}) + }); return await query.getMany(); } catch (error) { From 5cdc3ac906c00af227f984e6691241a8e5b35598 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 06:22:40 +0200 Subject: [PATCH 03/13] fix: .env.local use previous values --- .env.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.local b/.env.local index 4b2ebcdbe9b..f218550195f 100644 --- a/.env.local +++ b/.env.local @@ -26,7 +26,7 @@ PORT=4200 API_HOST=localhost # API Port -API_PORT=5500 +API_PORT=3000 # WEB UI Host WEB_HOST=localhost @@ -56,7 +56,7 @@ PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads DB_ORM=typeorm # DB_TYPE: sqlite | postgres | better-sqlite3 | mysql -DB_TYPE=postgres +DB_TYPE=better-sqlite3 DB_SYNCHRONIZE=false # DB Connection Parameters From 42b2b05e4ba41431d2b8fb006d1ac983a6ba8914 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 07:01:20 +0200 Subject: [PATCH 04/13] fix: validate from and to dates --- .env.local | 4 +-- .../common/src/utils/find-by-date-between.ts | 27 ++++++++++++++++++- packages/core/src/tasks/task.service.ts | 4 +-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.env.local b/.env.local index f218550195f..4b2ebcdbe9b 100644 --- a/.env.local +++ b/.env.local @@ -26,7 +26,7 @@ PORT=4200 API_HOST=localhost # API Port -API_PORT=3000 +API_PORT=5500 # WEB UI Host WEB_HOST=localhost @@ -56,7 +56,7 @@ PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads DB_ORM=typeorm # DB_TYPE: sqlite | postgres | better-sqlite3 | mysql -DB_TYPE=better-sqlite3 +DB_TYPE=postgres DB_SYNCHRONIZE=false # DB Connection Parameters diff --git a/packages/common/src/utils/find-by-date-between.ts b/packages/common/src/utils/find-by-date-between.ts index 51b007eda5f..948cc050217 100644 --- a/packages/common/src/utils/find-by-date-between.ts +++ b/packages/common/src/utils/find-by-date-between.ts @@ -1,5 +1,22 @@ +import { BadRequestException } from '@nestjs/common'; import { SelectQueryBuilder } from 'typeorm'; +type AllowedFields = 'startDate' | 'dueDate' | 'createdAt'; + +function isValidField(field: string): field is AllowedFields { + return ['startDate', 'dueDate', 'endDate', 'createdAt'].includes(field); +} + +/** + * Adds a date range condition to a TypeORM query builder + * @param query - The TypeORM SelectQueryBuilder instance + * @param field - The date field to filter on + * @param from - Start date (inclusive, UTC) + * @param to - End date (inclusive, UTC) + * @param p - Optional transform function for the query string + * @returns Modified query builder instance + * @throws a BadRequestException if from date is after to date + */ export function addBetween( query: SelectQueryBuilder, field: string, @@ -8,7 +25,15 @@ export function addBetween( p?: (queryStr: string) => string ): SelectQueryBuilder { if (from && to) { - query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from, to }); + if (from > to) { + throw new BadRequestException('From date must not be after to date'); + } + + // Convert dates to UTC for consistent comparison + const utcFrom = from.toISOString(); + const utcTo = to.toISOString(); + + query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from: utcFrom, to: utcTo }); } return query; } diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 1d853009c8b..50bd17c0dc8 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -856,8 +856,8 @@ export class TaskService extends TenantAwareCrudService { let query = this.typeOrmRepository.createQueryBuilder(this.tableName); // Apply the filters on startDate and dueDate - query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); - query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); + query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); + query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); // Check if relations were provided and include them query.setFindOptions({ From 2844b461eb5c21dcddc46fd41483c686a3353d2a Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 07:32:53 +0200 Subject: [PATCH 05/13] fix: coderabbit suggestions --- .../common/src/utils/find-by-date-between.ts | 6 +++++- .../tasks/dto/get-task-by-date-filter.dto.ts | 4 +++- packages/core/src/tasks/task.controller.ts | 2 +- packages/core/src/tasks/task.service.ts | 19 +++++++++++++++---- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/common/src/utils/find-by-date-between.ts b/packages/common/src/utils/find-by-date-between.ts index 948cc050217..4a9f8b3bc91 100644 --- a/packages/common/src/utils/find-by-date-between.ts +++ b/packages/common/src/utils/find-by-date-between.ts @@ -24,9 +24,13 @@ export function addBetween( to?: Date, p?: (queryStr: string) => string ): SelectQueryBuilder { + if (!isValidField(field)) { + throw new BadRequestException(`Invalid field name: ${field}`); + } + if (from && to) { if (from > to) { - throw new BadRequestException('From date must not be after to date'); + throw new BadRequestException('"From" date must not be after "to" date'); } // Convert dates to UTC for consistent comparison diff --git a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts index b70f8b9abc1..bb35345869e 100644 --- a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts +++ b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts @@ -1,5 +1,5 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsDate, IsOptional } from 'class-validator'; +import { IsDate, IsOptional, ValidateIf } from 'class-validator'; import { Type } from 'class-transformer'; import { ITaskDateFilterInput } from '@gauzy/contracts'; @@ -14,6 +14,7 @@ export class TaskDateFilterInputDTO implements ITaskDateFilterInput { @Type(() => Date) @IsOptional() @IsDate() + @ValidateIf((o) => o.startDateFrom != null) startDateTo?: Date; @ApiPropertyOptional({ type: () => Date }) @@ -26,5 +27,6 @@ export class TaskDateFilterInputDTO implements ITaskDateFilterInput { @Type(() => Date) @IsOptional() @IsDate() + @ValidateIf((o) => o.dueDateFrom != null) dueDateTo?: Date; } diff --git a/packages/core/src/tasks/task.controller.ts b/packages/core/src/tasks/task.controller.ts index cdc284638ce..07f9dfd7163 100644 --- a/packages/core/src/tasks/task.controller.ts +++ b/packages/core/src/tasks/task.controller.ts @@ -158,7 +158,7 @@ export class TaskController extends CrudController { @Permissions(PermissionsEnum.ALL_ORG_VIEW, PermissionsEnum.ORG_TASK_VIEW) @Get('/filter-by-date') @UseValidationPipe({ transform: true }) - async getTasksByDateFilters(@Query() params: TaskDateFilterInputDTO): Promise { + async getTasksByDateFilters(@Query() params: TaskDateFilterInputDTO): Promise> { return this.taskService.getTasksByDateFilters(params); } diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 50bd17c0dc8..9910eeb18b1 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -845,16 +845,25 @@ export class TaskService extends TenantAwareCrudService { * @function getTasksByDateFilters * @param {ITaskDateFilterInput} params - The query params containing the date filters for the tasks. * - * @returns {Promise} A promise that resolves to an array of tasks filtered by the provided dates. + * @returns {Promise>} A promise that resolves to an paginated tasks filtered by the provided dates. * * @throws {Error} Will throw an error if there is a problem with the database query. */ - async getTasksByDateFilters(params: ITaskDateFilterInput): Promise { + async getTasksByDateFilters(params: ITaskDateFilterInput): Promise> { + const tenantId = RequestContext.currentTenantId() || params.tenantId; + try { - const { startDateFrom, startDateTo, dueDateFrom, dueDateTo, relations } = params; + const { startDateFrom, startDateTo, dueDateFrom, dueDateTo, organizationId, relations } = params; let query = this.typeOrmRepository.createQueryBuilder(this.tableName); + query.andWhere( + new Brackets((qb: WhereExpressionBuilder) => { + qb.andWhere(p(`"${query.alias}"."tenantId" = :tenantId`), { tenantId }); + qb.andWhere(p(`"${query.alias}"."organizationId" = :organizationId`), { organizationId }); + }) + ); + // Apply the filters on startDate and dueDate query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); @@ -864,7 +873,9 @@ export class TaskService extends TenantAwareCrudService { ...(relations ? { relations } : {}) }); - return await query.getMany(); + const [items, total] = await query.getManyAndCount(); + + return { items, total }; } catch (error) { throw new BadRequestException(error); } From 2d88153cb271e0bc3ecc1fc66f1b44d4e9c12f36 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 07:36:14 +0200 Subject: [PATCH 06/13] fix: .env.local use previous values --- .env.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.local b/.env.local index 4b2ebcdbe9b..f218550195f 100644 --- a/.env.local +++ b/.env.local @@ -26,7 +26,7 @@ PORT=4200 API_HOST=localhost # API Port -API_PORT=5500 +API_PORT=3000 # WEB UI Host WEB_HOST=localhost @@ -56,7 +56,7 @@ PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads DB_ORM=typeorm # DB_TYPE: sqlite | postgres | better-sqlite3 | mysql -DB_TYPE=postgres +DB_TYPE=better-sqlite3 DB_SYNCHRONIZE=false # DB Connection Parameters From 07f1676986322ddfce6c66fd677effd7cf5de55a Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 11:51:52 +0200 Subject: [PATCH 07/13] feat: improve by filtering by employee, project, etc. --- packages/contracts/src/task.model.ts | 8 +++- .../tasks/dto/get-task-by-date-filter.dto.ts | 3 +- packages/core/src/tasks/task.service.ts | 47 ++++++++++++++++++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/contracts/src/task.model.ts b/packages/contracts/src/task.model.ts index 69cd0daaea4..876508dee13 100644 --- a/packages/contracts/src/task.model.ts +++ b/packages/contracts/src/task.model.ts @@ -1,5 +1,5 @@ import { IBasePerTenantAndOrganizationEntityModel, IBaseRelationsEntityModel, ID } from './base-entity.model'; -import { IEmployee } from './employee.model'; +import { IEmployee, IEmployeeEntityInput } from './employee.model'; import { IInvoiceItem } from './invoice-item.model'; import { IRelationalOrganizationProject } from './organization-projects.model'; import { @@ -7,7 +7,7 @@ import { IRelationalOrganizationSprint, IOrganizationSprintTaskHistory } from './organization-sprint.model'; -import { IOrganizationTeam } from './organization-team.model'; +import { IOrganizationTeam, IRelationalOrganizationTeam } from './organization-team.model'; import { ITag } from './tag.model'; import { IUser } from './user.model'; import { ITaskStatus, TaskStatusEnum } from './task-status.model'; @@ -107,6 +107,10 @@ export interface IGetTasksByViewFilters extends IBasePerTenantAndOrganizationEnt export interface ITaskDateFilterInput extends IBasePerTenantAndOrganizationEntityModel, + IEmployeeEntityInput, + IRelationalOrganizationProject, + IRelationalOrganizationTeam, + IRelationalOrganizationSprint, Pick { startDateFrom?: Date; startDateTo?: Date; diff --git a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts index bb35345869e..427a03a472e 100644 --- a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts +++ b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts @@ -2,8 +2,9 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsDate, IsOptional, ValidateIf } from 'class-validator'; import { Type } from 'class-transformer'; import { ITaskDateFilterInput } from '@gauzy/contracts'; +import { TenantOrganizationBaseDTO } from '../../core/dto'; -export class TaskDateFilterInputDTO implements ITaskDateFilterInput { +export class TaskDateFilterInputDTO extends TenantOrganizationBaseDTO implements ITaskDateFilterInput { @ApiPropertyOptional({ type: () => Date }) @Type(() => Date) @IsOptional() diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 9910eeb18b1..5ae4b66e9d7 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -853,7 +853,18 @@ export class TaskService extends TenantAwareCrudService { const tenantId = RequestContext.currentTenantId() || params.tenantId; try { - const { startDateFrom, startDateTo, dueDateFrom, dueDateTo, organizationId, relations } = params; + const { + startDateFrom, + startDateTo, + dueDateFrom, + dueDateTo, + organizationId, + employeeId, + projectId, + organizationTeamId, + organizationSprintId, + relations + } = params; let query = this.typeOrmRepository.createQueryBuilder(this.tableName); @@ -868,6 +879,40 @@ export class TaskService extends TenantAwareCrudService { query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); + // Add Optional additonal filters by + query.andWhere( + new Brackets((web: WhereExpressionBuilder) => { + if (isNotEmpty(employeeId)) { + query.leftJoin(`${query.alias}.members`, 'members'); + web.andWhere((qb: SelectQueryBuilder) => { + const subQuery = qb.subQuery(); + subQuery.select(p('"task_employee"."taskId"')).from(p('task_employee'), p('task_employee')); + subQuery.andWhere(p('"task_employee"."employeeId" = :employeeId'), { employeeId }); + return p(`"task_members"."taskId" IN (${subQuery.distinct(true).getQuery()})`); + }); + } + if (isNotEmpty(organizationTeamId)) { + query.leftJoin(`${query.alias}.teams`, 'teams'); + web.andWhere((qb: SelectQueryBuilder) => { + const subQuery = qb.subQuery(); + subQuery.select(p('"task_team"."taskId"')).from(p('task_team'), p('task_team')); + subQuery.andWhere(p('"task_team"."organizationTeamId" = :organizationTeamId'), { + organizationTeamId + }); + return p(`"task_teams"."taskId" IN (${subQuery.distinct(true).getQuery()})`); + }); + } + if (isNotEmpty(projectId)) { + web.andWhere(p(`"${query.alias}"."projectId" = :projectId`), { projectId }); + } + if (isNotEmpty(organizationSprintId)) { + web.andWhere(p(`"${query.alias}"."organizationSprintId" = :organizationSprintId`), { + organizationSprintId + }); + } + }) + ); + // Check if relations were provided and include them query.setFindOptions({ ...(relations ? { relations } : {}) From 7c7bc0cce5ca0c8b3919d4876f46e6ef3a7ad790 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 11:54:00 +0200 Subject: [PATCH 08/13] fix: typos errors --- packages/core/src/tasks/task.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 5ae4b66e9d7..f407fbd79f2 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -879,7 +879,7 @@ export class TaskService extends TenantAwareCrudService { query = addBetween(query, 'startDate', startDateFrom, startDateTo, p); query = addBetween(query, 'dueDate', dueDateFrom, dueDateTo, p); - // Add Optional additonal filters by + // Add Optional additional filters by query.andWhere( new Brackets((web: WhereExpressionBuilder) => { if (isNotEmpty(employeeId)) { From c5ffa4e50020fa9ffd3f9bcd67a570ad97d7f9a6 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Wed, 13 Nov 2024 16:36:10 +0200 Subject: [PATCH 09/13] fix: add creatorId on filter options --- packages/contracts/src/task.model.ts | 1 + packages/core/src/tasks/task.service.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/contracts/src/task.model.ts b/packages/contracts/src/task.model.ts index 876508dee13..a4bfd0b5361 100644 --- a/packages/contracts/src/task.model.ts +++ b/packages/contracts/src/task.model.ts @@ -116,4 +116,5 @@ export interface ITaskDateFilterInput startDateTo?: Date; dueDateFrom?: Date; dueDateTo?: Date; + creatorId?: Date; } diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index f407fbd79f2..7c74bbb35d1 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -858,6 +858,7 @@ export class TaskService extends TenantAwareCrudService { startDateTo, dueDateFrom, dueDateTo, + creatorId, organizationId, employeeId, projectId, @@ -882,6 +883,9 @@ export class TaskService extends TenantAwareCrudService { // Add Optional additional filters by query.andWhere( new Brackets((web: WhereExpressionBuilder) => { + if (isNotEmpty(creatorId)) { + web.andWhere(p(`"${query.alias}"."creatorId" = :creatorId`), { creatorId }); + } if (isNotEmpty(employeeId)) { query.leftJoin(`${query.alias}.members`, 'members'); web.andWhere((qb: SelectQueryBuilder) => { From 7bc5e294aa2514b345c6404b4418b6c9220583e4 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Thu, 14 Nov 2024 13:20:03 +0200 Subject: [PATCH 10/13] fix: date finder model --- packages/contracts/src/task.model.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/contracts/src/task.model.ts b/packages/contracts/src/task.model.ts index a4bfd0b5361..a379597cde4 100644 --- a/packages/contracts/src/task.model.ts +++ b/packages/contracts/src/task.model.ts @@ -107,14 +107,12 @@ export interface IGetTasksByViewFilters extends IBasePerTenantAndOrganizationEnt export interface ITaskDateFilterInput extends IBasePerTenantAndOrganizationEntityModel, + Pick, IEmployeeEntityInput, - IRelationalOrganizationProject, IRelationalOrganizationTeam, - IRelationalOrganizationSprint, Pick { startDateFrom?: Date; startDateTo?: Date; dueDateFrom?: Date; dueDateTo?: Date; - creatorId?: Date; } From 4069dd7f15916746fa2b1c7d37922efef9cef125 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Thu, 21 Nov 2024 08:43:25 +0200 Subject: [PATCH 11/13] fix: move addBetween function in core package --- packages/common/src/utils/index.ts | 1 - packages/core/src/core/index.ts | 1 + .../src/utils => core/src/core/util}/find-by-date-between.ts | 0 packages/core/src/core/util/index.ts | 2 ++ packages/core/src/tasks/task.service.ts | 3 ++- 5 files changed, 5 insertions(+), 2 deletions(-) rename packages/{common/src/utils => core/src/core/util}/find-by-date-between.ts (100%) create mode 100644 packages/core/src/core/util/index.ts diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index 677df10e644..4d4978a7115 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -3,4 +3,3 @@ export * from './deep-merge'; export * from './shared-utils'; export * from './to-hex-string'; export * from './mixins'; -export * from './find-by-date-between'; diff --git a/packages/core/src/core/index.ts b/packages/core/src/core/index.ts index 461a0c71d34..678db207148 100644 --- a/packages/core/src/core/index.ts +++ b/packages/core/src/core/index.ts @@ -10,3 +10,4 @@ export * from './decorators'; export * from './dto'; export * from './orm-type'; export * from './plugin-common.module'; +export * from './util'; diff --git a/packages/common/src/utils/find-by-date-between.ts b/packages/core/src/core/util/find-by-date-between.ts similarity index 100% rename from packages/common/src/utils/find-by-date-between.ts rename to packages/core/src/core/util/find-by-date-between.ts diff --git a/packages/core/src/core/util/index.ts b/packages/core/src/core/util/index.ts new file mode 100644 index 00000000000..f24ea14ca0e --- /dev/null +++ b/packages/core/src/core/util/index.ts @@ -0,0 +1,2 @@ +export * from './find-by-date-between'; +export * from './object-utils'; diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 7c74bbb35d1..e34de4467b8 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -25,9 +25,10 @@ import { ActionTypeEnum, ITaskDateFilterInput } from '@gauzy/contracts'; -import { addBetween, isEmpty, isNotEmpty } from '@gauzy/common'; +import { isEmpty, isNotEmpty } from '@gauzy/common'; import { isPostgres, isSqlite } from '@gauzy/config'; import { PaginationParams, TenantAwareCrudService } from './../core/crud'; +import { addBetween } from './../core/util'; import { RequestContext } from '../core/context'; import { TaskViewService } from './views/view.service'; import { ActivityLogService } from '../activity-log/activity-log.service'; From 7308eb43631969cc99aa6554854ec327ddbf1c4d Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Thu, 21 Nov 2024 08:54:57 +0200 Subject: [PATCH 12/13] fix: use IsBeforeDate validator for ranges --- packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts index 427a03a472e..65c997b4024 100644 --- a/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts +++ b/packages/core/src/tasks/dto/get-task-by-date-filter.dto.ts @@ -3,12 +3,16 @@ import { IsDate, IsOptional, ValidateIf } from 'class-validator'; import { Type } from 'class-transformer'; import { ITaskDateFilterInput } from '@gauzy/contracts'; import { TenantOrganizationBaseDTO } from '../../core/dto'; +import { IsBeforeDate } from './../../shared/validators'; export class TaskDateFilterInputDTO extends TenantOrganizationBaseDTO implements ITaskDateFilterInput { @ApiPropertyOptional({ type: () => Date }) @Type(() => Date) @IsOptional() @IsDate() + @IsBeforeDate(TaskDateFilterInputDTO, (it) => it.startDateTo, { + message: 'Start date from must be before the start date to' + }) startDateFrom?: Date; @ApiPropertyOptional({ type: () => Date }) @@ -22,6 +26,9 @@ export class TaskDateFilterInputDTO extends TenantOrganizationBaseDTO implements @Type(() => Date) @IsOptional() @IsDate() + @IsBeforeDate(TaskDateFilterInputDTO, (it) => it.dueDateTo, { + message: 'Due date from must be before the due date to' + }) dueDateFrom?: Date; @ApiPropertyOptional({ type: () => Date }) From d0be38af3b2fc4ea3db1da365122112e586b3b60 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Thu, 21 Nov 2024 09:05:01 +0200 Subject: [PATCH 13/13] fix: improve by coderabbit --- .../src/core/util/find-by-date-between.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/core/src/core/util/find-by-date-between.ts b/packages/core/src/core/util/find-by-date-between.ts index 4a9f8b3bc91..f6a1604c4c0 100644 --- a/packages/core/src/core/util/find-by-date-between.ts +++ b/packages/core/src/core/util/find-by-date-between.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { SelectQueryBuilder } from 'typeorm'; -type AllowedFields = 'startDate' | 'dueDate' | 'createdAt'; +type AllowedFields = 'startDate' | 'dueDate' | 'endDate' | 'createdAt'; function isValidField(field: string): field is AllowedFields { return ['startDate', 'dueDate', 'endDate', 'createdAt'].includes(field); @@ -28,16 +28,20 @@ export function addBetween( throw new BadRequestException(`Invalid field name: ${field}`); } - if (from && to) { - if (from > to) { + if (from || to) { + if (from && to && from > to) { throw new BadRequestException('"From" date must not be after "to" date'); } - // Convert dates to UTC for consistent comparison - const utcFrom = from.toISOString(); - const utcTo = to.toISOString(); - - query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from: utcFrom, to: utcTo }); + const utcFrom = from?.toISOString(); + const utcTo = to?.toISOString(); + if (from && to) { + query.andWhere(p(`"${query.alias}"."${field}" BETWEEN :from AND :to`), { from: utcFrom, to: utcTo }); + } else if (from) { + query.andWhere(p(`"${query.alias}"."${field}" >= :from`), { from: utcFrom }); + } else if (to) { + query.andWhere(p(`"${query.alias}"."${field}" <= :to`), { to: utcTo }); + } } return query; }