Добавлена доска для группировки по полям

This commit is contained in:
Pavel Gnedov 2023-02-20 01:54:44 +07:00
parent fccdee5cbc
commit e9d808a19d
4 changed files with 155 additions and 1 deletions

View file

@ -26,6 +26,7 @@ import { IssueUrlEnhancer } from './issue-enhancers/issue-url-enhancer';
import { ListIssuesByUsersWidgetService } from './project-dashboard/widgets/list-issues-by-users.widget.service';
import { ListIssuesByUsersLikeJiraWidgetService } from './project-dashboard/widgets/list-issues-by-users-like-jira.widget.service';
import { TimePassedHighlightEnhancer } from './issue-enhancers/time-passed-highlight-enhancer';
import { ListIssuesByFieldsWidgetService } from './project-dashboard/widgets/list-issues-by-fields.widget.service';
@Module({})
export class EventEmitterModule implements OnModuleInit {
@ -56,6 +57,7 @@ export class EventEmitterModule implements OnModuleInit {
ListIssuesByUsersWidgetService,
ListIssuesByUsersLikeJiraWidgetService,
TimePassedHighlightEnhancer,
ListIssuesByFieldsWidgetService,
],
exports: [
EventEmitterService,
@ -78,6 +80,7 @@ export class EventEmitterModule implements OnModuleInit {
ListIssuesByUsersWidgetService,
ListIssuesByUsersLikeJiraWidgetService,
TimePassedHighlightEnhancer,
ListIssuesByFieldsWidgetService,
],
controllers: [MainController, UsersController, IssuesController],
};

View file

@ -44,7 +44,7 @@ export module RedmineTypes {
author: IdAndName;
assigned_to?: IdAndName;
category: IdAndName;
fixed_version: IdAndName;
fixed_version?: IdAndName;
subject: string;
description: string;
start_date: string;

View file

@ -0,0 +1,133 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer';
import { TagStyledEnhancerNs } from '@app/event-emitter/issue-enhancers/tag-styled-enhancer';
import { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer';
import {
IssuesService,
IssuesServiceNs,
} from '@app/event-emitter/issues/issues.service';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { FlatIssuesStore } from '@app/event-emitter/utils/flat-issues-store';
import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from-object-by-key';
import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store';
import { Injectable, Logger } from '@nestjs/common';
import nano from 'nano';
import { WidgetInterface } from '../widget-interface';
export namespace ListIssuesByFieldsWidgetNs {
export type Params = {
fromRootIssueId?: number;
fromQuery?: nano.MangoQuery;
fields: Field[];
delimiter: string;
sort?: boolean;
statuses: string[];
} & TagStyledEnhancerNs.ConfigWithTagsStyles;
export type Field = {
path: string;
default: string;
};
}
type Params = ListIssuesByFieldsWidgetNs.Params;
@Injectable()
export class ListIssuesByFieldsWidgetService
implements WidgetInterface<Params, any, any>
{
private logger = new Logger(ListIssuesByFieldsWidgetService.name);
private issuesLoader: IssuesServiceNs.IssuesLoader;
constructor(
private issuesService: IssuesService,
private timePassedHighlightEnhancer: TimePassedHighlightEnhancer,
private issueUrlEnhancer: IssueUrlEnhancer,
) {
this.issuesLoader = this.issuesService.createDynamicIssuesLoader();
}
isMyConfig(): boolean {
return true;
}
async render(widgetParams: Params): Promise<any> {
let store: FlatIssuesStore;
if (widgetParams.fromRootIssueId) {
store = await this.getListFromRoot(widgetParams.fromRootIssueId);
} else if (widgetParams.fromQuery) {
store = await this.getListByQuery(widgetParams.fromQuery);
} else {
const errMsg = `Wrong widgetParams value`;
this.logger.error(errMsg);
throw new Error(errMsg);
}
await store.enhanceIssues([
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
]);
const grouped = store.groupByStatusWithExtra((issue) => {
return this.getGroupFromIssue(issue, widgetParams);
}, widgetParams.statuses);
let res = [] as any[];
for (const key in grouped) {
if (Object.prototype.hasOwnProperty.call(grouped, key)) {
const data = grouped[key];
res.push({
data: data,
metainfo: this.createMetaInfo(key),
});
}
}
if (widgetParams.sort) {
res = res.sort((a, b) => {
return a.metainfo.title.localeCompare(b.metainfo.title);
});
}
return res;
}
private async getListFromRoot(issueId: number): Promise<FlatIssuesStore> {
const treeStore = new TreeIssuesStore();
const rootIssue = await this.issuesService.getIssue(issueId);
treeStore.setRootIssue(rootIssue);
await treeStore.fillData(this.issuesLoader);
return treeStore.getFlatStore();
}
private async getListByQuery(
query: nano.MangoQuery,
): Promise<FlatIssuesStore> {
const rawData = await this.issuesService.find(query);
const store = new FlatIssuesStore();
for (let i = 0; i < rawData.length; i++) {
const issue = rawData[i];
store.push(issue);
}
return store;
}
private getGroupFromIssue(
issue: RedmineTypes.ExtendedIssue,
params: Params,
): string | null {
if (!issue) return null;
const values = [] as string[];
for (let i = 0; i < params.fields.length; i++) {
const field = params.fields[i];
const valueResult = GetValueFromObjectByKey(issue, field.path);
const value: string = valueResult.result
? valueResult.result
: field.default;
values.push(value);
}
return values.join(params.delimiter);
}
private createMetaInfo(title: string): Record<string, any> {
return {
title: title,
};
}
}

View file

@ -1,5 +1,6 @@
import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader';
import { RedmineEventsGateway } from '@app/event-emitter/events/redmine-events.gateway';
import { ListIssuesByFieldsWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-fields.widget.service';
import { ListIssuesByUsersLikeJiraWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service';
import { ListIssuesByUsersWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users.widget.service';
import {
@ -23,6 +24,7 @@ export class SimpleKanbanBoardController {
private listIssuesByUsersLikeJiraWidgetService: ListIssuesByUsersLikeJiraWidgetService,
private issuesByTagsWidgetService: IssuesByTagsWidgetService,
private redmineEventsGateway: RedmineEventsGateway,
private listIssuesByFieldsWidgetService: ListIssuesByFieldsWidgetService,
) {
this.path = this.configService.get<string>('simpleKanbanBoard.path');
}
@ -109,4 +111,20 @@ export class SimpleKanbanBoardController {
async getByTags(@Param('name') name: string): Promise<any> {
return await this.getByTagsRawData(name);
}
@Get('/by-fields/:name/raw')
async getByFieldsRawData(@Param('name') name: string): Promise<any> {
const cfg = this.dynamicLoader.load(name, {
path: this.path,
ext: 'jsonc',
parser: parse,
});
return await this.listIssuesByFieldsWidgetService.render(cfg);
}
@Get('/by-fields/:name')
@Render('simple-kanban-board')
async getByFields(@Param('name') name: string): Promise<any> {
return await this.getByFieldsRawData(name);
}
}