faloker/purify

View on GitHub
api/src/issues/issues.service.ts

Summary

Maintainability
C
1 day
Test Coverage
C
72%
/* eslint-disable @typescript-eslint/camelcase */
import {
  Injectable,
  NotFoundException,
  NotImplementedException,
  BadRequestException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Issue } from './interfaces/issue.interface';
import { Unit } from 'src/units/interfaces/unit.interface';
import { Ticket } from './interfaces/ticket.interface';
import { Comment } from './interfaces/comment.interface';
import { SaveCommentBodyDto, GetIssuesQueryDto } from './dto/issues.dto';
import { JiraService } from 'src/plugins/jira/jira.service';
import { matchPattern } from 'src/utils/converter';
import { Project } from 'src/projects/interfaces/project.interface';
import { Template } from 'src/templates/interfaces/template.interface';
import { sub } from 'date-fns';

@Injectable()
export class IssuesService {
  constructor(
    @InjectModel('Issue') private readonly issueModel: Model<Issue>,
    @InjectModel('Unit') private readonly unitModel: Model<Unit>,
    @InjectModel('Comment') private readonly commentModel: Model<Comment>,
    @InjectModel('Ticket') private readonly ticketModel: Model<Ticket>,
    @InjectModel('Project') private readonly projectModel: Model<Project>,
    private jiraService: JiraService
  ) {}

  async getIssues(params: GetIssuesQueryDto, allowedProjects?: string[]) {
    const conditions: any = {};
    const options: any = { sort: { createdAt: -1 } };

    if (params.limit) {
      options.limit = parseInt(params.limit);
    }

    if (params.days) {
      const startDate = sub(new Date(), { days: parseInt(params.days) });
      conditions.createdAt = { $gte: startDate };
    }

    if (params.status && params.status.split(',').length < 2) {
      conditions.status = params.status;
    }

    if (params.ticket && params.ticket.split(',').length < 2) {
      conditions.ticket = { $exists: params.ticket === 'true' ? true : false };
    }

    if (params.risk && params.risk.split(',').length < 5) {
      conditions.risk = {
        $in: params.risk.split(',').map((r) => r.toLowerCase()),
      };
    }

    if (params.resolution && params.resolution.split(',').length < 4) {
      conditions.resolution = {
        $in: params.resolution.split(',').map((r) => r.toLowerCase()),
      };
    }

    if (params.unitName) {
      const unit = await this.unitModel
        .findOne({ name: params.unitName }, '_id')
        .lean();
      if (unit) {
        conditions.unit = unit._id;
      } else {
        throw new NotFoundException('Unit not found');
      }
    }
    if (params.projectName) {
      const project = await this.projectModel
        .findOne({ name: params.projectName }, '_id')
        .lean();
      if (project) {
        conditions.project = project._id;
      } else {
        throw new NotFoundException('Project not found');
      }
    } else if (allowedProjects) {
      conditions.project = { $in: allowedProjects };
    }

    const issues: any = [];

    const rawIssues = await this.issueModel
      .find(conditions, null, options)
      .lean()
      .populate('template', ['titlePattern', 'subtitlePattern', 'displayName'])
      .populate('project', 'name')
      .populate('unit', 'name')
      .populate('ticket')
      .populate('comments');

    for (const issue of rawIssues) {
      const fieldsAsObject = JSON.parse(issue.fields);
      if (issue.template) {
        if (
          !params.template ||
          (params.template &&
            params.template
              .toLowerCase()
              .split(',')
              .includes((issue.template as Template).displayName.toLowerCase()))
        ) {
          issues.push({
            _id: issue._id,
            fields: fieldsAsObject,
            status: issue.status,
            resolution: issue.resolution,
            title: matchPattern(
              fieldsAsObject,
              (issue.template as Template).titlePattern
            ),
            subtitle: matchPattern(
              fieldsAsObject,
              (issue.template as Template).subtitlePattern
            ),
            template: (issue.template as Template).displayName,
            risk: issue.risk,
            createdAt: issue.createdAt,
            updatedAt: issue.updatedAt,
            ticket: issue.ticket,
            totalComments: issue.comments.length,
            closedAt: issue.closedAt,
            project: (issue.project as Project).name,
            unit: (issue.unit as Unit).name,
            report: issue.report,
          });
        }
      }
    }

    return issues;
  }

  async updateMany(ids: string[], change: any, allowedProjects?: string[]) {
    const conditions: any = { _id: { $in: ids } };
    const payload: any = { $set: change };

    if (allowedProjects) {
      conditions.project = { $in: allowedProjects };
    }

    if (change.status === 'closed') {
      payload.$set.closedAt = new Date();
    } else if (change.status === 'open') {
      payload.$unset = {};
      payload.$unset.closedAt = '';
    }

    return this.issueModel.updateMany(conditions, payload);
  }

  async createJiraTicket(issueId: string, jiraIssue: any) {
    const settings = await this.jiraService.getSettings();
    if (settings) {
      const jiraTicket = await this.jiraService
        .createIssue(jiraIssue)
        .catch((err) => {
          throw new BadRequestException(JSON.stringify(JSON.parse(err).body));
        });

      const ticket = await new this.ticketModel({
        type: 'jira',
        link: `https://${settings.host}/browse/${jiraTicket.key}`,
        key: jiraTicket.key,
      }).save();

      await this.issueModel.updateOne(
        { _id: issueId },
        { $set: { ticket: ticket._id } }
      );

      return ticket;
    } else {
      throw new NotImplementedException('Jira is not configured');
    }
  }

  async saveComment(issueId: string, saveCommentBodyDto: SaveCommentBodyDto) {
    const comment = await new this.commentModel(saveCommentBodyDto).save();

    await this.issueModel.updateOne(
      { _id: issueId },
      { $push: { comments: comment._id } }
    );

    return this.commentModel
      .findOne({ _id: comment._id })
      .lean()
      .populate('author', ['name', 'image']);
  }

  async getComments(issueId: string, allowedProjects?: string[]) {
    const issue = await this.issueModel
      .findOne({ _id: issueId })
      .lean()
      .populate({
        path: 'comments',
        populate: { path: 'author', select: 'name image' },
      });
    return issue.comments;
  }

  async findOne(issueId: string) {
    return this.issueModel.findOne({ _id: issueId }).lean();
  }

  async enrichOne(issueId: string) {
    const issue = await this.issueModel
      .findOne({ _id: issueId }, '_id fields risk resolution')
      .populate('template', ['titlePattern'])
      .populate('project', 'name _id')
      .populate('unit', 'name')
      .lean();

    if (issue) {
      return {
        _id: issue._id,
        risk: issue.risk,
        resolution: issue.resolution,
        project: issue.project,
        unit: (issue.unit as Unit).name,
        title: matchPattern(
          JSON.parse(issue.fields),
          (issue.template as Template).titlePattern
        ),
      };
    } else {
      return null;
    }
  }
}