Skip to content

Commit

Permalink
Merge pull request #8373 from ever-co/feat/get-tasks-by-view-query-pa…
Browse files Browse the repository at this point in the history
…rams

[Feat] Get tasks by view query params
  • Loading branch information
rahul-rocket authored Oct 11, 2024
2 parents 1d0475a + 6534365 commit 03d0382
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 7 deletions.
7 changes: 7 additions & 0 deletions packages/contracts/src/issue-type.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ export interface IIssueTypeUpdateInput extends Partial<IIssueTypeCreateInput> {
export interface IIssueTypeFindInput
extends IBasePerTenantAndOrganizationEntityModel,
Pick<IIssueType, 'projectId' | 'organizationTeamId'> {}

export enum TaskTypeEnum {
EPIC = 'epic',
STORY = 'story',
TASK = 'task',
BUG = 'bug'
}
23 changes: 23 additions & 0 deletions packages/contracts/src/task.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ITaskStatus, TaskStatusEnum } from './task-status.model';
import { ITaskPriority, TaskPriorityEnum } from './task-priority.model';
import { ITaskSize, TaskSizeEnum } from './task-size.model';
import { IOrganizationProjectModule } from './organization-project-module.model';
import { TaskTypeEnum } from './issue-type.model';

export interface ITask
extends IBasePerTenantAndOrganizationEntityModel,
Expand Down Expand Up @@ -79,3 +80,25 @@ export interface ITaskUpdateInput extends ITaskCreateInput {
export interface IGetTaskById {
includeRootEpic?: boolean;
}

export interface IGetTasksByViewFilters extends IBasePerTenantAndOrganizationEntityModel {
projects?: ID[];
teams?: ID[];
modules?: ID[];
sprints?: ID[];
members?: ID[];
tags?: ID[];
statusIds?: ID[];
statuses?: TaskStatusEnum[];
priorityIds?: ID[];
priorities?: TaskPriorityEnum[];
sizeIds?: ID[];
sizes?: TaskSizeEnum[];
types?: TaskTypeEnum[];
startDates?: Date[];
dueDates?: Date[];
creators?: ID[];

// Relations
relations?: string[];
}
10 changes: 5 additions & 5 deletions packages/core/src/tasks/issue-type/default-global-issue-types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { IIssueType } from '@gauzy/contracts';
import { IIssueType, TaskTypeEnum } from '@gauzy/contracts';

export const DEFAULT_GLOBAL_ISSUE_TYPES: IIssueType[] = [
{
name: 'Bug',
value: 'bug',
value: TaskTypeEnum.BUG,
description:
'A "bug type issue" typically refers to a specific type of technical issue that occurs in software development',
icon: 'task-issue-types/bug.svg',
Expand All @@ -13,7 +13,7 @@ export const DEFAULT_GLOBAL_ISSUE_TYPES: IIssueType[] = [
},
{
name: 'Story',
value: 'story',
value: TaskTypeEnum.STORY,
description:
'A "story (or user story) type issue" typically refers to an issue related to a user story in software development.',
icon: 'task-issue-types/note.svg',
Expand All @@ -23,7 +23,7 @@ export const DEFAULT_GLOBAL_ISSUE_TYPES: IIssueType[] = [
},
{
name: 'Task',
value: 'task',
value: TaskTypeEnum.TASK,
description: 'A "task type issue" typically refers to an issue related to a specific task within a project.',
icon: 'task-issue-types/task-square.svg',
color: '#5483BA',
Expand All @@ -32,7 +32,7 @@ export const DEFAULT_GLOBAL_ISSUE_TYPES: IIssueType[] = [
},
{
name: 'Epic',
value: 'epic',
value: TaskTypeEnum.EPIC,
description: 'An "epic type issue" typically refers to an issue related to an Epic in software development.',
icon: 'task-issue-types/category.svg',
color: '#8154BA',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tasks/issue-type/issue-type.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp
name: string;

@ApiProperty({ type: () => String })
@IsString()
@ColumnIndex()
@MultiORMColumn()
value: string;
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/tasks/task.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ export class TaskController extends CrudController<Task> {
return this.taskService.findModuleTasks(params);
}

/**
* GET view tasks
*
* @param params The filter options for retrieving view tasks.
* @returns A paginated list of tasks by view filters.
*/
@ApiOperation({ summary: 'Get tasks by view query filter.' })
@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('/view/:id')
@UseValidationPipe({ transform: true })
async findTasksByViewQuery(@Param('id', UUIDValidationPipe) viewId: ID): Promise<IPagination<ITask>> {
return this.taskService.findTasksByViewQuery(viewId);
}

/**
* GET task by ID
*
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tasks/task.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RoleModule } from './../role/role.module';
import { EmployeeModule } from './../employee/employee.module';
import { OrganizationProjectModule } from './../organization-project/organization-project.module';
import { OrganizationSprintModule } from './../organization-sprint/organization-sprint.module';
import { TaskViewModule } from './views/view.module';
import { Task } from './task.entity';
import { TaskService } from './task.service';
import { TaskController } from './task.controller';
Expand All @@ -26,6 +27,7 @@ import { TypeOrmTaskRepository } from './repository';
EmployeeModule,
OrganizationProjectModule,
OrganizationSprintModule,
TaskViewModule,
CqrsModule,
EventBusModule
],
Expand Down
123 changes: 121 additions & 2 deletions packages/core/src/tasks/task.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { Injectable, BadRequestException, HttpStatus, HttpException } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { IsNull, SelectQueryBuilder, Brackets, WhereExpressionBuilder, Raw, In } from 'typeorm';
import {
IsNull,
SelectQueryBuilder,
Brackets,
WhereExpressionBuilder,
Raw,
In,
FindOptionsWhere,
FindManyOptions,
Between
} from 'typeorm';
import { isBoolean, isUUID } from 'class-validator';
import {
ActionTypeEnum,
Expand All @@ -9,17 +19,19 @@ import {
ID,
IEmployee,
IGetTaskOptions,
IGetTasksByViewFilters,
IPagination,
ITask,
ITaskUpdateInput,
PermissionsEnum
} from '@gauzy/contracts';
import { isEmpty, isNotEmpty } from '@gauzy/common';
import { isPostgres } from '@gauzy/config';
import { isPostgres, isSqlite } from '@gauzy/config';
import { PaginationParams, TenantAwareCrudService } from './../core/crud';
import { RequestContext } from '../core/context';
import { ActivityLogEvent } from '../activity-log/events';
import { activityLogUpdatedFieldsAndValues, generateActivityLogDescription } from '../activity-log/activity-log.helper';
import { TaskViewService } from './views/view.service';
import { Task } from './task.entity';
import { TypeOrmOrganizationSprintTaskHistoryRepository } from './../organization-sprint/repository/type-orm-organization-sprint-task-history.repository';
import { GetTaskByIdDTO } from './dto';
Expand All @@ -33,6 +45,7 @@ export class TaskService extends TenantAwareCrudService<Task> {
readonly typeOrmTaskRepository: TypeOrmTaskRepository,
readonly mikroOrmTaskRepository: MikroOrmTaskRepository,
readonly typeOrmOrganizationSprintTaskHistoryRepository: TypeOrmOrganizationSprintTaskHistoryRepository,
private readonly taskViewService: TaskViewService,
private readonly _eventBus: EventBus
) {
super(typeOrmTaskRepository, mikroOrmTaskRepository);
Expand Down Expand Up @@ -729,4 +742,110 @@ export class TaskService extends TenantAwareCrudService<Task> {
throw new BadRequestException(error);
}
}

/**
* @description Get tasks by views query
* @param {ID} viewId - View ID
* @returns {Promise<IPagination<ITask>>} A Promise resolved to paginated found tasks and total matching query filters
* @memberof TaskService
*/
async findTasksByViewQuery(viewId: ID): Promise<IPagination<ITask>> {
const tenantId = RequestContext.currentTenantId();
try {
// Retrieve Task View by ID for getting their pre-defined query params
const taskView = await this.taskViewService.findOneByWhereOptions({ id: viewId, tenantId });
if (!taskView) {
throw new HttpException('View not found', HttpStatus.NOT_FOUND);
}

// Extract `queryParams` from the view
const queryParams = taskView.queryParams;
let viewFilters: IGetTasksByViewFilters = {};

try {
viewFilters = isSqlite()
? (JSON.parse(queryParams as string) as IGetTasksByViewFilters)
: (queryParams as IGetTasksByViewFilters) || {};
} catch (error) {
throw new HttpException('Invalid query parameters in task view', HttpStatus.BAD_REQUEST);
}

// Extract filters
const {
projects = [],
teams = [],
members = [],
modules = [],
sprints = [],
statusIds = [],
statuses = [],
priorityIds = [],
priorities = [],
sizeIds = [],
sizes = [],
tags = [],
types = [],
creators = [],
startDates = [],
dueDates = [],
organizationId,
relations = []
} = viewFilters;

// Calculate min and max dates only if arrays are not empty
const getMinMaxDates = (dates: Date[]) =>
dates.length
? [
new Date(
Math.min(
...dates
.filter((date) => !Number.isNaN(new Date(date).getTime()))
.map((date) => new Date(date).getTime())
)
),
new Date(
Math.max(
...dates
.filter((date) => !Number.isNaN(new Date(date).getTime()))
.map((date) => new Date(date).getTime())
)
)
]
: [undefined, undefined];

const [minStartDate, maxStartDate] = getMinMaxDates(startDates);
const [minDueDate, maxDueDate] = getMinMaxDates(dueDates);

// Build the 'where' condition
const where: FindOptionsWhere<Task> = {
...(projects.length && { projectId: In(projects) }),
...(teams.length && { teams: { id: In(teams) } }),
...(members.length && { members: { id: In(members) } }),
...(modules.length && { modules: { id: In(modules) } }),
...(sprints.length && { organizationSprintId: In(sprints) }),
...(statusIds.length && { taskStatusId: In(statusIds) }),
...(statuses.length && { status: In(statuses) }),
...(priorityIds.length && { taskPriorityId: In(priorityIds) }),
...(priorities.length && { priority: In(priorities) }),
...(sizeIds.length && { taskSizeId: In(sizeIds) }),
...(sizes.length && { size: In(sizes) }),
...(tags.length && { tags: { id: In(tags) } }),
...(types.length && { issueType: In(types) }),
...(creators.length && { creatorId: In(creators) }),
...(minStartDate && maxStartDate && { startDate: Between(minStartDate, maxStartDate) }),
...(minDueDate && maxDueDate && { dueDate: Between(minDueDate, maxDueDate) }),
organizationId: taskView.organizationId || organizationId,
tenantId
};

// Define find options
const findOptions: FindManyOptions<Task> = { where, ...(relations && { relations }) };

// Retrieve tasks using base class method
return await super.findAll(findOptions);
} catch (error) {
console.error(`Error while retrieve view tasks: ${error.message}`, error.message);
throw new HttpException({ message: error?.message, error }, HttpStatus.BAD_REQUEST);
}
}
}

0 comments on commit 03d0382

Please sign in to comment.