pinkmine/libs/event-emitter/src/issue-enhancers/calendar-enhancer.ts
2023-11-10 07:45:41 +07:00

195 lines
5.4 KiB
TypeScript

import { IssueEnhancerInterface } from '@app/event-emitter/issue-enhancers/issue-enhancer-interface';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { Injectable, Logger } from '@nestjs/common';
import * as Luxon from 'luxon';
import { CalendarEvent } from '../models/calendar-event';
import * as DatesParser from '../utils/string-with-dates-parser';
export type DescriptionParserParams = {
title: string;
lineRegexp: string;
};
export type CustomFieldParserParams = {
dateFormat: string;
customFieldName: string;
/** Время в минутах */
interval?: number;
fullDay?: boolean;
alias?: string;
};
export const UNKNOWN_CALENDAR_EVENT = 'Unknown calendar event';
@Injectable()
export class CalendarEnhancer implements IssueEnhancerInterface {
private logger = new Logger(CalendarEnhancer.name);
name = 'calendar';
constructor(
public useForProjects: string[],
public customFields: CustomFieldParserParams[],
public descriptionCalendarParams: DescriptionParserParams,
public calendarEventsKey: string,
) {
const initParams = {
useForProjects,
customFields,
descriptionCalendarParams,
calendarEventsKey,
};
this.logger.debug(
`Calendar enhancer init with ${JSON.stringify(initParams)}`,
);
}
async enhance(
issue: RedmineTypes.ExtendedIssue,
): Promise<RedmineTypes.ExtendedIssue> {
const res: RedmineTypes.ExtendedIssue = { ...issue };
if (!this.checkProject(res)) {
return res;
}
try {
res[this.calendarEventsKey] = this.getCalendarEvents(res);
} catch (ex) {
this.logger.error(
`Error at parsing calendar events, message - ${ex}: ${
(ex as Error)?.stack
}`,
);
return res;
}
this.logger.debug(
`Calendar events for #${issue.id}: issue.${
this.calendarEventsKey
} = ${JSON.stringify(res[this.calendarEventsKey])}`,
);
return res;
}
private checkProject(issue: RedmineTypes.ExtendedIssue): boolean {
if (this.useForProjects.indexOf('*') >= 0) {
return true;
}
if (
this.useForProjects.length > 0 &&
this.useForProjects.indexOf(issue.project.name) >= 0
) {
return true;
}
return false;
}
private getCalendarEvents(
issue: RedmineTypes.ExtendedIssue,
): CalendarEvent[] {
return [
...this.getCalendarEventsFromCustomFields(issue),
...this.getCalendarEventsFromSubject(issue),
...this.getCalendarEventsFromDescription(issue),
];
}
private getCalendarEventsFromCustomFields(
issue: RedmineTypes.ExtendedIssue,
): CalendarEvent[] {
const res: CalendarEvent[] = [];
for (let i = 0; i < this.customFields.length; i++) {
const cfParam = this.customFields[i];
const event = this.getCalendarEventFromCustomField(issue, cfParam);
if (event) res.push(event);
}
return res;
}
private getCalendarEventFromCustomField(
issue: RedmineTypes.ExtendedIssue,
params: CustomFieldParserParams,
): CalendarEvent | null {
if (typeof params.interval !== 'number' && params.fullDay !== true)
return null;
const cf = issue.custom_fields.find((issueCf) => {
return issueCf.name === params.customFieldName;
});
if (!cf) return null;
const from = Luxon.DateTime.fromFormat(cf.value, params.dateFormat);
if (!from.isValid) return null;
let to: Luxon.DateTime;
if (typeof params.interval === 'number') {
const interval = Luxon.Duration.fromObject({ minutes: params.interval });
to = from.plus(interval);
} else if (params.fullDay) {
from.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
to = from.plus(Luxon.Duration.fromObject({ day: 1 }));
} else {
return null;
}
if (!to.isValid) return null;
return {
from: from.toISO(),
fromTimestamp: from.toMillis(),
to: to.toISO(),
toTimestamp: to.toMillis(),
fullDay: Boolean(params.fullDay),
description: params.alias || cf.name || UNKNOWN_CALENDAR_EVENT,
};
}
private getCalendarEventsFromSubject(
issue: RedmineTypes.ExtendedIssue,
): CalendarEvent[] {
const matches = issue.subject.matchAll(/(?<=\()[^()]*(?=\))/g);
const items = [...matches].map((i) => i[0]).filter((i) => !!i);
return items
.map((item) => DatesParser.parseToCalendarEvent(item))
.filter((i) => !!i);
}
private getCalendarEventsFromDescription(
issue: RedmineTypes.ExtendedIssue,
): CalendarEvent[] {
const text = issue.description;
const lines = text.split('\n').map((line) => line.trim());
const calendarStartIndex = lines.indexOf(
this.descriptionCalendarParams.title,
);
if (calendarStartIndex < 0) return [];
let index = calendarStartIndex + 1;
let line = this.extractFromList(lines[index]);
if (!line) {
index++;
line = this.extractFromList(lines[index]);
}
if (!line) return [];
const res: string[] = [];
do {
res.push(line);
index++;
line = this.extractFromList(lines[index]);
} while (line);
return res.map((line) => DatesParser.parseToCalendarEvent(line));
}
private extractFromList(line: string): string | null {
if (!line || line.length <= 0) return null;
const regexp = new RegExp(this.descriptionCalendarParams.lineRegexp);
const match = line.match(regexp);
return match && match[0] ? match[0] : null;
}
}