Добавлен виджет kanban доски с группировкой задач по тегам

This commit is contained in:
Pavel Gnedov 2023-02-17 01:35:12 +07:00
parent 7b74f07b43
commit d031105c9f
3 changed files with 149 additions and 0 deletions

View file

@ -37,6 +37,7 @@ import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handler
import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service'; import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service';
import { SimpleKanbanBoardController } from './dashboards/simple-kanban-board.controller'; import { SimpleKanbanBoardController } from './dashboards/simple-kanban-board.controller';
import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer';
import { IssuesByTagsWidgetService } from './dashboards/widgets/issues-by-tags.widget.service';
@Module({ @Module({
imports: [ imports: [
@ -79,6 +80,7 @@ import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-e
DailyEccmUserCommentsService, DailyEccmUserCommentsService,
SetDailyEccmUserCommentBotHandlerService, SetDailyEccmUserCommentBotHandlerService,
DailyEccmWithExtraDataService, DailyEccmWithExtraDataService,
IssuesByTagsWidgetService,
], ],
}) })
export class AppModule implements OnModuleInit { export class AppModule implements OnModuleInit {

View file

@ -5,6 +5,7 @@ import { RootIssueSubTreesWidgetService } from '@app/event-emitter/project-dashb
import { Controller, Get, Param, Render } from '@nestjs/common'; import { Controller, Get, Param, Render } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { parse } from 'jsonc-parser'; import { parse } from 'jsonc-parser';
import { IssuesByTagsWidgetService } from './widgets/issues-by-tags.widget.service';
@Controller('simple-kanban-board') @Controller('simple-kanban-board')
export class SimpleKanbanBoardController { export class SimpleKanbanBoardController {
@ -16,6 +17,7 @@ export class SimpleKanbanBoardController {
private configService: ConfigService, private configService: ConfigService,
private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService, private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService,
private listIssuesByUsersLikeJiraWidgetService: ListIssuesByUsersLikeJiraWidgetService, private listIssuesByUsersLikeJiraWidgetService: ListIssuesByUsersLikeJiraWidgetService,
private issuesByTagsWidgetService: IssuesByTagsWidgetService,
) { ) {
this.path = this.configService.get<string>('simpleKanbanBoard.path'); this.path = this.configService.get<string>('simpleKanbanBoard.path');
} }
@ -67,4 +69,20 @@ export class SimpleKanbanBoardController {
async getByUsersLikeJira(@Param('name') name: string): Promise<any> { async getByUsersLikeJira(@Param('name') name: string): Promise<any> {
return await this.getByUsersLikeJiraRawData(name); return await this.getByUsersLikeJiraRawData(name);
} }
@Get('/by-tags/:name/raw')
async getByTagsRawData(@Param('name') name: string): Promise<any> {
const cfg = this.dynamicLoader.load(name, {
path: this.path,
ext: 'jsonc',
parser: parse,
});
return await this.issuesByTagsWidgetService.render(cfg);
}
@Get('/by-tags/:name')
@Render('simple-kanban-board')
async getByTags(@Param('name') name: string): Promise<any> {
return await this.getByTagsRawData(name);
}
} }

View file

@ -0,0 +1,129 @@
/* 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 { WidgetInterface } from '@app/event-emitter/project-dashboard/widget-interface';
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';
export namespace IssuesByTagsWidgetNs {
export type Params = {
fromRootIssueId?: number;
fromQuery?: nano.MangoQuery;
tagsSort?: boolean;
showEmptyTags?: string;
statuses: string[];
} & TagStyledEnhancerNs.ConfigWithTagsStyles;
}
type Params = IssuesByTagsWidgetNs.Params;
@Injectable()
export class IssuesByTagsWidgetService
implements WidgetInterface<Params, any, any>
{
private logger = new Logger(IssuesByTagsWidgetService.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.groupByStatusWithExtraToMultipleStories((issue) => {
if (!issue || !widgetParams.tags || !widgetParams.tags.tagsKeyName) {
return [];
}
const tagsResult = GetValueFromObjectByKey(
issue,
widgetParams.tags.tagsKeyName,
);
if (
(tagsResult.error == 'NOT_FOUND' ||
(tagsResult.result && tagsResult.result.length <= 0)) &&
widgetParams.showEmptyTags
) {
return [widgetParams.showEmptyTags];
}
if (
typeof tagsResult.result !== 'object' ||
tagsResult.result.length <= 0
) {
return [];
}
return tagsResult.result;
}, widgetParams.statuses);
let res = [] as any[];
for (const tag in grouped) {
if (Object.prototype.hasOwnProperty.call(grouped, tag)) {
const data = grouped[tag];
res.push({
data: data,
metainfo: this.createMetaInfo(tag),
});
}
}
if (widgetParams.tagsSort) {
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 createMetaInfo(tag: string): Record<string, any> {
return {
title: tag,
};
}
}