Анализ списков задач, анализ дерева задач, тестовый контроллер
This commit is contained in:
parent
7cde09c895
commit
ce413b8cad
7 changed files with 487 additions and 0 deletions
|
|
@ -18,6 +18,7 @@ import { IssuesService } from './issues/issues.service';
|
||||||
import { IssuesController } from './issues/issues.controller';
|
import { IssuesController } from './issues/issues.controller';
|
||||||
import { TimestampEnhancer } from './issue-enhancers/timestamps-enhancer';
|
import { TimestampEnhancer } from './issue-enhancers/timestamps-enhancer';
|
||||||
import { EnhancerService } from './issue-enhancers/enhancer.service';
|
import { EnhancerService } from './issue-enhancers/enhancer.service';
|
||||||
|
import { RootIssueSubTreesWidgetService } from './project-dashboard/widgets/root-issue-subtrees.widget.service';
|
||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
export class EventEmitterModule implements OnModuleInit {
|
export class EventEmitterModule implements OnModuleInit {
|
||||||
|
|
@ -40,6 +41,7 @@ export class EventEmitterModule implements OnModuleInit {
|
||||||
IssuesService,
|
IssuesService,
|
||||||
TimestampEnhancer,
|
TimestampEnhancer,
|
||||||
EnhancerService,
|
EnhancerService,
|
||||||
|
RootIssueSubTreesWidgetService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
EventEmitterService,
|
EventEmitterService,
|
||||||
|
|
@ -54,6 +56,7 @@ export class EventEmitterModule implements OnModuleInit {
|
||||||
IssuesService,
|
IssuesService,
|
||||||
TimestampEnhancer,
|
TimestampEnhancer,
|
||||||
EnhancerService,
|
EnhancerService,
|
||||||
|
RootIssueSubTreesWidgetService,
|
||||||
],
|
],
|
||||||
controllers: [MainController, UsersController, IssuesController],
|
controllers: [MainController, UsersController, IssuesController],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface WidgetInterface<W, D, R> {
|
||||||
|
isMyConfig(widgetParams: W): boolean;
|
||||||
|
render(widgetParams: W, dashboardParams: D): Promise<R>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* 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 {
|
||||||
|
TreeIssuesStore,
|
||||||
|
TreeIssuesStoreNs,
|
||||||
|
} from '@app/event-emitter/utils/tree-issues-store';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { WidgetInterface } from '../widget-interface';
|
||||||
|
|
||||||
|
export namespace RootIssueSubTreesWidgetNs {
|
||||||
|
export namespace Models {
|
||||||
|
export type Params = {
|
||||||
|
rootIssueId: number;
|
||||||
|
parentsAsGroups?: boolean;
|
||||||
|
groups?: GroupCfg;
|
||||||
|
statuses: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupCfg = {
|
||||||
|
fromIssues: Group[];
|
||||||
|
fromIssuesIncluded: boolean;
|
||||||
|
showOthers: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Group = {
|
||||||
|
name: string;
|
||||||
|
issueId: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params = RootIssueSubTreesWidgetNs.Models.Params;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RootIssueSubTreesWidgetService
|
||||||
|
implements WidgetInterface<Params, any, any>
|
||||||
|
{
|
||||||
|
constructor(private issuesService: IssuesService) {}
|
||||||
|
|
||||||
|
isMyConfig(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(widgetParams: Params): Promise<any> {
|
||||||
|
const treeStore = new TreeIssuesStore();
|
||||||
|
const rootIssue = await this.issuesService.getIssue(
|
||||||
|
widgetParams.rootIssueId,
|
||||||
|
);
|
||||||
|
treeStore.setRootIssue(rootIssue);
|
||||||
|
await treeStore.fillData(this.issuesLoader.bind(this));
|
||||||
|
let stories: TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
||||||
|
if (widgetParams.parentsAsGroups) {
|
||||||
|
stories = treeStore.getFlatStoriesByParents();
|
||||||
|
} else if (widgetParams.groups) {
|
||||||
|
const fromIssues = widgetParams.groups.fromIssues.map((g) => g.issueId);
|
||||||
|
stories = treeStore.getFlatStories(
|
||||||
|
fromIssues,
|
||||||
|
widgetParams.groups.fromIssuesIncluded,
|
||||||
|
widgetParams.groups.showOthers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return stories.map((s) => {
|
||||||
|
return {
|
||||||
|
data: s.store.groupByStatus(widgetParams.statuses),
|
||||||
|
metainfo: s.metainfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
181
libs/event-emitter/src/utils/flat-issues-store.ts
Normal file
181
libs/event-emitter/src/utils/flat-issues-store.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
import { RedmineTypes } from '../models/redmine-types';
|
||||||
|
|
||||||
|
export namespace FlatIssuesStoreNs {
|
||||||
|
export type IssuesLoader = (
|
||||||
|
ids: number[],
|
||||||
|
) => Promise<Record<number, RedmineTypes.Issue | null>>;
|
||||||
|
|
||||||
|
export namespace Models {
|
||||||
|
export type ByStatus = {
|
||||||
|
status: string;
|
||||||
|
count: number;
|
||||||
|
issues: RedmineTypes.Issue[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ByStatuses = ByStatus[];
|
||||||
|
|
||||||
|
export enum FindErrors {
|
||||||
|
NOT_FOUND = 'NOT_FOUND',
|
||||||
|
NOT_LOADED = 'NOT_LOADED',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FindResult = {
|
||||||
|
data?: RedmineTypes.Issue;
|
||||||
|
error?: FindErrors;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FlatIssuesStore {
|
||||||
|
private issues: Record<number, RedmineTypes.Issue | null> = {};
|
||||||
|
|
||||||
|
push(issue: number | string | RedmineTypes.Issue): void {
|
||||||
|
let id: any;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof issue === 'number' ||
|
||||||
|
(typeof issue === 'string' && Number.isFinite(Number(issue)))
|
||||||
|
) {
|
||||||
|
id = Number(issue);
|
||||||
|
data = null;
|
||||||
|
} else if (typeof issue === 'object' && typeof issue['id'] === 'number') {
|
||||||
|
id = issue['id'];
|
||||||
|
data = issue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof id === 'number' &&
|
||||||
|
!Object.prototype.hasOwnProperty.call(this.issues, id)
|
||||||
|
) {
|
||||||
|
this.issues[id] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillData(loader: FlatIssuesStoreNs.IssuesLoader): Promise<void> {
|
||||||
|
const ids = [] as number[];
|
||||||
|
for (const id in this.issues) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.issues, id)) {
|
||||||
|
const issue = this.issues[id];
|
||||||
|
if (!issue) {
|
||||||
|
ids.push(Number(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = await loader(ids);
|
||||||
|
for (const id in data) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, id)) {
|
||||||
|
const issue = data[id];
|
||||||
|
if (issue) {
|
||||||
|
this.issues[id] = issue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIds(): number[] {
|
||||||
|
return Object.keys(this.issues).map((i) => Number(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
getIssues(): RedmineTypes.Issue[] {
|
||||||
|
return Object.values(this.issues).filter((i) => !!i);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasIssue(id: number): boolean {
|
||||||
|
return Object.prototype.hasOwnProperty.call(this.issues, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedIssue(id: number): boolean {
|
||||||
|
return this.hasIssue(id) && !!this.issues[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
getIssue(id: number): FlatIssuesStoreNs.Models.FindResult {
|
||||||
|
if (!this.hasIssue(id)) {
|
||||||
|
return { error: FlatIssuesStoreNs.Models.FindErrors.NOT_FOUND };
|
||||||
|
} else if (!this.issues[id]) {
|
||||||
|
return { error: FlatIssuesStoreNs.Models.FindErrors.NOT_LOADED };
|
||||||
|
} else {
|
||||||
|
return { data: this.issues[id] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFullLoaded(): boolean {
|
||||||
|
return Object.values(this.issues).indexOf(null) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupBy(
|
||||||
|
iteratee: (issue: RedmineTypes.Issue) => string | number,
|
||||||
|
): Record<string | number, RedmineTypes.Issue[]> {
|
||||||
|
const res = {} as Record<string | number, RedmineTypes.Issue[]>;
|
||||||
|
const items = this.getIssues();
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const issue = items[i];
|
||||||
|
const key = iteratee(issue);
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(res, key)) {
|
||||||
|
res[key] = [];
|
||||||
|
}
|
||||||
|
res[key].push(issue);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupByToStories(
|
||||||
|
iteratee: (issue: RedmineTypes.Issue) => string | number,
|
||||||
|
): Record<string | number, FlatIssuesStore> {
|
||||||
|
const res = {} as Record<string | number, FlatIssuesStore>;
|
||||||
|
const rawData = this.groupBy(iteratee);
|
||||||
|
for (const key in rawData) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(rawData, key)) {
|
||||||
|
const issues = rawData[key];
|
||||||
|
res[key] = new FlatIssuesStore();
|
||||||
|
for (let i = 0; i < issues.length; i++) {
|
||||||
|
const issue = issues[i];
|
||||||
|
res[key].push(issue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupByStatus(statuses: string[]): FlatIssuesStoreNs.Models.ByStatuses {
|
||||||
|
const res = [] as FlatIssuesStoreNs.Models.ByStatuses;
|
||||||
|
for (let i = 0; i < statuses.length; i++) {
|
||||||
|
const status = statuses[i];
|
||||||
|
res.push({ status: status, count: 0, issues: [] });
|
||||||
|
}
|
||||||
|
const groupedIssues = this.groupBy((issue) => issue.status.name);
|
||||||
|
for (const status in groupedIssues) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(groupedIssues, status)) {
|
||||||
|
const issues = groupedIssues[status];
|
||||||
|
const foundItem = res.find((i) => i.status === status);
|
||||||
|
if (!foundItem) continue;
|
||||||
|
foundItem.issues.push(...issues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
const item = res[i];
|
||||||
|
item.count = item.issues.length;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupByStatusWithExtra(
|
||||||
|
iteratee: (issue: RedmineTypes.Issue) => string | number,
|
||||||
|
statuses: string[],
|
||||||
|
): Record<string | number, FlatIssuesStoreNs.Models.ByStatuses> {
|
||||||
|
const res = {} as Record<
|
||||||
|
string | number,
|
||||||
|
FlatIssuesStoreNs.Models.ByStatuses
|
||||||
|
>;
|
||||||
|
const groupedIssues = this.groupByToStories(iteratee);
|
||||||
|
for (const key in groupedIssues) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(groupedIssues, key)) {
|
||||||
|
const store = groupedIssues[key];
|
||||||
|
res[key] = store.groupByStatus(statuses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
183
libs/event-emitter/src/utils/tree-issues-store.ts
Normal file
183
libs/event-emitter/src/utils/tree-issues-store.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
import { RedmineTypes } from '../models/redmine-types';
|
||||||
|
import { FlatIssuesStore, FlatIssuesStoreNs } from './flat-issues-store';
|
||||||
|
|
||||||
|
export namespace TreeIssuesStoreNs {
|
||||||
|
export namespace Models {
|
||||||
|
export namespace GetFlatStories {
|
||||||
|
export type Item = {
|
||||||
|
metainfo: Record<string, any>;
|
||||||
|
store: FlatIssuesStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result = Item[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TreeIssuesStore {
|
||||||
|
private rootIssue: RedmineTypes.Issue;
|
||||||
|
private flatStore: FlatIssuesStore;
|
||||||
|
|
||||||
|
setRootIssue(issue: RedmineTypes.Issue): void {
|
||||||
|
this.rootIssue = issue;
|
||||||
|
this.prepareFlatIssuesStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillData(loader: FlatIssuesStoreNs.IssuesLoader): Promise<void> {
|
||||||
|
await this.flatStore.fillData(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFlatStore(): FlatIssuesStore {
|
||||||
|
return this.flatStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareFlatIssuesStore(): void {
|
||||||
|
this.flatStore = new FlatIssuesStore();
|
||||||
|
this.flatStore.push(this.rootIssue);
|
||||||
|
if (this.rootIssue.children && this.rootIssue.children.length > 0) {
|
||||||
|
this.fillChildrenFlatIssuesStore(this.rootIssue.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fillChildrenFlatIssuesStore(
|
||||||
|
childrenIssues: RedmineTypes.Children,
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < childrenIssues.length; i++) {
|
||||||
|
const issue = childrenIssues[i];
|
||||||
|
this.flatStore.push(issue.id);
|
||||||
|
if (issue.children && issue.children.length > 0) {
|
||||||
|
this.fillChildrenFlatIssuesStore(issue.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFullLoaded(): boolean {
|
||||||
|
return this.flatStore.isFullLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFlatStories(
|
||||||
|
fromIssues: number[],
|
||||||
|
fromIssuesIncluded: boolean,
|
||||||
|
showOthers: boolean,
|
||||||
|
): TreeIssuesStoreNs.Models.GetFlatStories.Result {
|
||||||
|
const res = [] as TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
||||||
|
|
||||||
|
for (let i = 0; i < fromIssues.length; i++) {
|
||||||
|
const fromIssue = this.flatStore.getIssue(fromIssues[i]);
|
||||||
|
if (!fromIssue.data) continue;
|
||||||
|
const store = new FlatIssuesStore();
|
||||||
|
this.putIssuesToStore(
|
||||||
|
store,
|
||||||
|
fromIssue.data,
|
||||||
|
fromIssues,
|
||||||
|
fromIssuesIncluded,
|
||||||
|
);
|
||||||
|
res.push({
|
||||||
|
store: store,
|
||||||
|
metainfo: this.createMetaInfo(fromIssue.data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showOthers) {
|
||||||
|
const othersStore = new FlatIssuesStore();
|
||||||
|
res.push({
|
||||||
|
store: othersStore,
|
||||||
|
metainfo: this.createMetaInfo(this.rootIssue),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private putIssuesToStore(
|
||||||
|
store: FlatIssuesStore,
|
||||||
|
rootIssue: RedmineTypes.Issue,
|
||||||
|
fromIssues: number[],
|
||||||
|
fromIssuesIncluded: boolean,
|
||||||
|
): void {
|
||||||
|
if (fromIssuesIncluded) {
|
||||||
|
store.push(rootIssue);
|
||||||
|
}
|
||||||
|
if (!rootIssue.children || rootIssue.children.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < rootIssue.children.length; i++) {
|
||||||
|
const childIssue = rootIssue.children[i];
|
||||||
|
const issueData = this.flatStore.getIssue(childIssue.id);
|
||||||
|
if (!issueData.data) continue;
|
||||||
|
if (fromIssues.indexOf(issueData.data.id) < 0) {
|
||||||
|
this.putIssuesToStore(
|
||||||
|
store,
|
||||||
|
issueData.data,
|
||||||
|
fromIssues,
|
||||||
|
fromIssuesIncluded,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (fromIssuesIncluded == false) {
|
||||||
|
store.push(issueData.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createMetaInfo(issue: RedmineTypes.Issue): Record<string, any> {
|
||||||
|
return {
|
||||||
|
title: `${issue.tracker.name} #${issue.id} - ${issue.subject}`,
|
||||||
|
rootIssue: {
|
||||||
|
id: issue.id,
|
||||||
|
tracker: issue.tracker,
|
||||||
|
subject: issue.subject,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getParents(issue: RedmineTypes.Issue): RedmineTypes.Issue[] {
|
||||||
|
const res = [] as RedmineTypes.Issue[];
|
||||||
|
let parentId: number;
|
||||||
|
let parentIssueData: FlatIssuesStoreNs.Models.FindResult;
|
||||||
|
let parentIssue: RedmineTypes.Issue;
|
||||||
|
parentId = issue.parent?.id;
|
||||||
|
while (parentId) {
|
||||||
|
parentIssueData = this.flatStore.getIssue(parentId);
|
||||||
|
parentIssue = parentIssueData.data;
|
||||||
|
if (!parentIssue) break;
|
||||||
|
res.push(parentIssue);
|
||||||
|
parentId = parentIssue.parent?.id;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFlatStoriesByParents(): TreeIssuesStoreNs.Models.GetFlatStories.Result {
|
||||||
|
const stories = [] as TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
||||||
|
return this.fillFlatStoriesByParents(stories, this.rootIssue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fillFlatStoriesByParents(
|
||||||
|
stories: TreeIssuesStoreNs.Models.GetFlatStories.Result,
|
||||||
|
rootIssue: RedmineTypes.Issue,
|
||||||
|
): TreeIssuesStoreNs.Models.GetFlatStories.Result {
|
||||||
|
if (!stories) {
|
||||||
|
stories = [] as TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
||||||
|
}
|
||||||
|
if (!rootIssue) {
|
||||||
|
rootIssue = this.rootIssue;
|
||||||
|
}
|
||||||
|
if (!rootIssue.children || rootIssue.children.length <= 0) {
|
||||||
|
return stories;
|
||||||
|
}
|
||||||
|
const store = new FlatIssuesStore();
|
||||||
|
for (let i = 0; i < rootIssue.children.length; i++) {
|
||||||
|
const child = rootIssue.children[i];
|
||||||
|
const childIssueData = this.flatStore.getIssue(child.id);
|
||||||
|
if (!childIssueData.data) continue;
|
||||||
|
store.push(childIssueData.data);
|
||||||
|
this.fillFlatStoriesByParents(stories, childIssueData.data);
|
||||||
|
}
|
||||||
|
stories.push({
|
||||||
|
store: store,
|
||||||
|
metainfo: this.createMetaInfo(rootIssue),
|
||||||
|
});
|
||||||
|
return stories;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ import { DailyEccmReportsUserCommentsDatasource } from './couchdb-datasources/da
|
||||||
import { DailyEccmUserCommentsService } from './reports/daily-eccm-user-comments.service';
|
import { DailyEccmUserCommentsService } from './reports/daily-eccm-user-comments.service';
|
||||||
import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service';
|
import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service';
|
||||||
import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service';
|
import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service';
|
||||||
|
import { Eccm110DashboardController } from './dashboards/eccm110-dashboard.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -53,6 +54,7 @@ import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-d
|
||||||
MainController,
|
MainController,
|
||||||
CurrentIssuesEccmReportController,
|
CurrentIssuesEccmReportController,
|
||||||
DailyEccmReportController,
|
DailyEccmReportController,
|
||||||
|
Eccm110DashboardController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AppService,
|
AppService,
|
||||||
|
|
|
||||||
32
src/dashboards/eccm110-dashboard.controller.ts
Normal file
32
src/dashboards/eccm110-dashboard.controller.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { RootIssueSubTreesWidgetService } from '@app/event-emitter/project-dashboard/widgets/root-issue-subtrees.widget.service';
|
||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('eccm-1.10-dashboard')
|
||||||
|
export class Eccm110DashboardController {
|
||||||
|
constructor(
|
||||||
|
private rootIssueSubTreesWidgetService: RootIssueSubTreesWidgetService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// TODO: code for Eccm110DashboardController
|
||||||
|
|
||||||
|
@Get('/raw')
|
||||||
|
async getRawData(): Promise<any> {
|
||||||
|
return await this.rootIssueSubTreesWidgetService.render({
|
||||||
|
rootIssueId: 2,
|
||||||
|
parentsAsGroups: true,
|
||||||
|
statuses: [
|
||||||
|
'New',
|
||||||
|
'Closed',
|
||||||
|
'In Progress',
|
||||||
|
'Re-opened',
|
||||||
|
'Code Review',
|
||||||
|
'Resolved',
|
||||||
|
'Testing',
|
||||||
|
'Wait Release',
|
||||||
|
'Pending',
|
||||||
|
'Feedback',
|
||||||
|
'Rejected',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue