Добавлена доска задач сгруппированная по работникам
This commit is contained in:
parent
a51b8dae7c
commit
4509a729cb
4 changed files with 149 additions and 1 deletions
|
|
@ -23,6 +23,7 @@ import { RootIssueSubTreesWidgetService } from './project-dashboard/widgets/root
|
|||
import { DynamicLoader } from './configs/dynamic-loader';
|
||||
import { RedminePublicUrlConverter } from './converters/redmine-public-url.converter';
|
||||
import { IssueUrlEnhancer } from './issue-enhancers/issue-url-enhancer';
|
||||
import { ListIssuesByUsersWidgetService } from './project-dashboard/widgets/list-issues-by-users.widget.service';
|
||||
|
||||
@Module({})
|
||||
export class EventEmitterModule implements OnModuleInit {
|
||||
|
|
@ -50,6 +51,7 @@ export class EventEmitterModule implements OnModuleInit {
|
|||
DynamicLoader,
|
||||
RedminePublicUrlConverter,
|
||||
IssueUrlEnhancer,
|
||||
ListIssuesByUsersWidgetService,
|
||||
],
|
||||
exports: [
|
||||
EventEmitterService,
|
||||
|
|
@ -69,6 +71,7 @@ export class EventEmitterModule implements OnModuleInit {
|
|||
DynamicLoader,
|
||||
RedminePublicUrlConverter,
|
||||
IssueUrlEnhancer,
|
||||
ListIssuesByUsersWidgetService,
|
||||
],
|
||||
controllers: [MainController, UsersController, IssuesController],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
import { IssuesService } 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 { 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 ListIssuesByUsersWidgetNs {
|
||||
export namespace Models {
|
||||
export type Params = {
|
||||
fromRootIssueId?: number;
|
||||
fromQuery?: nano.MangoQuery;
|
||||
userKey: string;
|
||||
statuses: string[];
|
||||
};
|
||||
|
||||
export type FindResult = {
|
||||
result?: any;
|
||||
error?: FindErrors;
|
||||
};
|
||||
|
||||
export enum FindErrors {
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Params = ListIssuesByUsersWidgetNs.Models.Params;
|
||||
type ExtendedIssue = RedmineTypes.Issue & Record<string, any>;
|
||||
type FindResult = ListIssuesByUsersWidgetNs.Models.FindResult;
|
||||
|
||||
@Injectable()
|
||||
export class ListIssuesByUsersWidgetService
|
||||
implements WidgetInterface<Params, any, any>
|
||||
{
|
||||
private logger = new Logger(ListIssuesByUsersWidgetService.name);
|
||||
|
||||
constructor(private issuesService: IssuesService) {
|
||||
return;
|
||||
}
|
||||
|
||||
isMyConfig(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async render(widgetParams: Params): Promise<any> {
|
||||
// if (widgetParams)
|
||||
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(`Wrong widgetParams value`);
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
const grouped = store.groupByStatusWithExtra((issue) => {
|
||||
const res = this.getUserValueByKey(issue, widgetParams.userKey);
|
||||
return res.result || 'Unknown';
|
||||
}, widgetParams.statuses);
|
||||
const res = [] as any[];
|
||||
for (const user in grouped) {
|
||||
if (Object.prototype.hasOwnProperty.call(grouped, user)) {
|
||||
const data = grouped[user];
|
||||
res.push({
|
||||
data: data,
|
||||
metainfo: this.createMetaInfo(user),
|
||||
});
|
||||
}
|
||||
}
|
||||
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.bind(this));
|
||||
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 async issuesLoader(
|
||||
ids: number[],
|
||||
): Promise<Record<number, RedmineTypes.Issue | null>> {
|
||||
const issues = await this.issuesService.getIssues(ids);
|
||||
const res = {} as Record<number, RedmineTypes.Issue | null>;
|
||||
for (let i = 0; i < issues.length; i++) {
|
||||
const issue = issues[i];
|
||||
res[issue.id] = issue;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private getUserValueByKey(issue: ExtendedIssue, key: string): FindResult {
|
||||
const keys = key.split('.');
|
||||
let res: any = issue;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i];
|
||||
if (!res.hasOwnProperty(k)) {
|
||||
return { error: ListIssuesByUsersWidgetNs.Models.FindErrors.NOT_FOUND };
|
||||
}
|
||||
res = res[k];
|
||||
}
|
||||
return { result: res };
|
||||
}
|
||||
|
||||
private createMetaInfo(user: string): Record<string, any> {
|
||||
return {
|
||||
title: user,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader';
|
||||
import { ListIssuesByUsersWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users.widget.service';
|
||||
import { RootIssueSubTreesWidgetService } from '@app/event-emitter/project-dashboard/widgets/root-issue-subtrees.widget.service';
|
||||
import { Controller, Get, Param, Render } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
|
@ -12,6 +13,7 @@ export class SimpleKanbanBoardController {
|
|||
private rootIssueSubTreesWidgetService: RootIssueSubTreesWidgetService,
|
||||
private dynamicLoader: DynamicLoader,
|
||||
private configService: ConfigService,
|
||||
private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService,
|
||||
) {
|
||||
this.path = this.configService.get<string>('simpleKanbanBoard.path');
|
||||
}
|
||||
|
|
@ -31,4 +33,20 @@ export class SimpleKanbanBoardController {
|
|||
async get(@Param('name') name: string): Promise<any> {
|
||||
return await this.getRawData(name);
|
||||
}
|
||||
|
||||
@Get('/by-users/:name/raw')
|
||||
async getByUsersRawData(@Param('name') name: string): Promise<any> {
|
||||
const cfg = this.dynamicLoader.load(name, {
|
||||
path: this.path,
|
||||
ext: 'jsonc',
|
||||
parser: parse,
|
||||
});
|
||||
return await this.listIssuesByUsersWidgetService.render(cfg);
|
||||
}
|
||||
|
||||
@Get('/by-users/:name')
|
||||
@Render('simple-kanban-board')
|
||||
async getByUsers(@Param('name') name: string): Promise<any> {
|
||||
return await this.getByUsersRawData(name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<div class="kanban-header">{{this.status}}</div>
|
||||
{{#each this.issues}}
|
||||
<div class="kanban-card">
|
||||
<div class="kanban-card-title">{{this.tracker.name}} #{{this.id}} - {{this.subject}}</div>
|
||||
<div class="kanban-card-title"><a href="{{{this.url.url}}}">{{this.tracker.name}} #{{this.id}} - {{this.subject}}</a></div>
|
||||
<div>Исп.: {{this.current_user.name}}</div>
|
||||
<div>Прогресс: {{this.done_ration}}</div>
|
||||
<div>Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue