Добавлена динамическая загрузка досок kanban
This commit is contained in:
parent
c3ad1df1b6
commit
99b31164cf
13 changed files with 201 additions and 42 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -41,3 +41,5 @@ configs/redmine-status-changes-config.jsonc
|
|||
configs/eccm-versions-config.jsonc
|
||||
configs/eccm-config.jsonc
|
||||
configs/current-user-rules.jsonc
|
||||
configs/simple-kanban-board-config.jsonc
|
||||
configs/kanban-boards/
|
||||
|
|
|
|||
3
configs/simple-kanban-board-config.jsonc.dist
Normal file
3
configs/simple-kanban-board-config.jsonc.dist
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"path": ""
|
||||
}
|
||||
27
libs/event-emitter/src/configs/dynamic-loader.ts
Normal file
27
libs/event-emitter/src/configs/dynamic-loader.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { CacheTTL, Injectable, Logger } from '@nestjs/common';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicLoader {
|
||||
private logger = new Logger(DynamicLoader.name);
|
||||
|
||||
@CacheTTL(60)
|
||||
load(
|
||||
file: string,
|
||||
opts: { path: string; ext: string; parser: (raw: any) => any },
|
||||
): any {
|
||||
const fullFileName = join(opts.path, `${file}.${opts.ext}`);
|
||||
if (!existsSync(fullFileName)) return null;
|
||||
let rawData: any;
|
||||
let data: any;
|
||||
try {
|
||||
rawData = readFileSync(fullFileName, { encoding: 'utf-8' });
|
||||
data = opts.parser(rawData);
|
||||
} catch (ex) {
|
||||
this.logger.error(`Error at config read - ${ex}`);
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import { TimestampEnhancer } from './issue-enhancers/timestamps-enhancer';
|
|||
import { EnhancerService } from './issue-enhancers/enhancer.service';
|
||||
import { ProjectDashboardService } from './project-dashboard/project-dashboard.service';
|
||||
import { RootIssueSubTreesWidgetService } from './project-dashboard/widgets/root-issue-subtrees.widget.service';
|
||||
import { DynamicLoader } from './configs/dynamic-loader';
|
||||
|
||||
@Module({})
|
||||
export class EventEmitterModule implements OnModuleInit {
|
||||
|
|
@ -44,6 +45,7 @@ export class EventEmitterModule implements OnModuleInit {
|
|||
EnhancerService,
|
||||
ProjectDashboardService,
|
||||
RootIssueSubTreesWidgetService,
|
||||
DynamicLoader,
|
||||
],
|
||||
exports: [
|
||||
EventEmitterService,
|
||||
|
|
@ -60,6 +62,7 @@ export class EventEmitterModule implements OnModuleInit {
|
|||
EnhancerService,
|
||||
ProjectDashboardService,
|
||||
RootIssueSubTreesWidgetService,
|
||||
DynamicLoader,
|
||||
],
|
||||
controllers: [MainController, UsersController, IssuesController],
|
||||
};
|
||||
|
|
|
|||
39
package-lock.json
generated
39
package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"hbs": "^4.2.0",
|
||||
"imap-simple": "^5.1.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"luxon": "^3.1.0",
|
||||
"nano": "^10.0.0",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
|
|
@ -198,6 +199,12 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
|
|
@ -1707,6 +1714,12 @@
|
|||
"yarn": ">= 1.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics/node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@nestjs/schematics/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
|
|
@ -7091,10 +7104,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
||||
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
|
|
@ -10448,6 +10460,12 @@
|
|||
"rxjs": "6.6.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
|
|
@ -11625,6 +11643,12 @@
|
|||
"rxjs": "6.6.7"
|
||||
}
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
|
|
@ -15740,10 +15764,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
||||
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"hbs": "^4.2.0",
|
||||
"imap-simple": "^5.1.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"luxon": "^3.1.0",
|
||||
"nano": "^10.0.0",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import { DailyEccmReportsUserCommentsDatasource } from './couchdb-datasources/da
|
|||
import { DailyEccmUserCommentsService } from './reports/daily-eccm-user-comments.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 { Eccm110DashboardController } from './dashboards/eccm110-dashboard.controller';
|
||||
import { SimpleKanbanBoardController } from './dashboards/simple-kanban-board.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -54,7 +54,7 @@ import { Eccm110DashboardController } from './dashboards/eccm110-dashboard.contr
|
|||
MainController,
|
||||
CurrentIssuesEccmReportController,
|
||||
DailyEccmReportController,
|
||||
Eccm110DashboardController,
|
||||
SimpleKanbanBoardController,
|
||||
],
|
||||
providers: [
|
||||
AppService,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ import RedmineStatusesConfigLoader from './statuses.config';
|
|||
import RedmineStatusChangesConfigLoader from './status-changes.config';
|
||||
import RedmineEccmConfig from './eccm.config';
|
||||
import RedmineCurrentUserRulesConfig from './current-user-rules.config';
|
||||
import SimpleKanbanBoardConfig from './simple-kanban-board.config';
|
||||
|
||||
const redmineIssueEventEmitterConfig = RedmineIssueEventEmitterConfigLoader();
|
||||
const redmineStatusesConfig = RedmineStatusesConfigLoader();
|
||||
const redmineStatusChanges = RedmineStatusChangesConfigLoader();
|
||||
const redmineEccm = RedmineEccmConfig();
|
||||
const redmineCurrentUserRules = RedmineCurrentUserRulesConfig();
|
||||
const simpleKanbanBoard = SimpleKanbanBoardConfig();
|
||||
|
||||
let appConfig: AppConfig;
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ export default (): AppConfig => {
|
|||
redmineStatusChanges: redmineStatusChanges,
|
||||
redmineEccm: redmineEccm,
|
||||
redmineCurrentUserRules: redmineCurrentUserRules,
|
||||
simpleKanbanBoard: simpleKanbanBoard,
|
||||
};
|
||||
|
||||
return appConfig;
|
||||
|
|
|
|||
27
src/configs/simple-kanban-board.config.ts
Normal file
27
src/configs/simple-kanban-board.config.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { parse } from 'jsonc-parser';
|
||||
|
||||
export type SimpleKanbanBoardConfig = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
let config: SimpleKanbanBoardConfig;
|
||||
|
||||
export default (): SimpleKanbanBoardConfig => {
|
||||
if (config) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const userDefinedConfigPath =
|
||||
process.env['ELTEX_REDMINE_HELPER_KANBAN_SIMPLE_BOARD_CONFIG_PATH'];
|
||||
const defaultConfigPath = join('configs', 'simple-kanban-board-config.jsonc');
|
||||
|
||||
const configPath = userDefinedConfigPath || defaultConfigPath;
|
||||
|
||||
const rawData = readFileSync(configPath, { encoding: 'utf-8' });
|
||||
|
||||
config = parse(rawData);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
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',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
34
src/dashboards/simple-kanban-board.controller.ts
Normal file
34
src/dashboards/simple-kanban-board.controller.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader';
|
||||
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';
|
||||
import { parse } from 'jsonc-parser';
|
||||
|
||||
@Controller('simple-kanban-board')
|
||||
export class SimpleKanbanBoardController {
|
||||
private path: string;
|
||||
|
||||
constructor(
|
||||
private rootIssueSubTreesWidgetService: RootIssueSubTreesWidgetService,
|
||||
private dynamicLoader: DynamicLoader,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.path = this.configService.get<string>('simpleKanbanBoard.path');
|
||||
}
|
||||
|
||||
@Get('/tree/:name/raw')
|
||||
async getRawData(@Param('name') name: string): Promise<any> {
|
||||
const cfg = this.dynamicLoader.load(name, {
|
||||
path: this.path,
|
||||
ext: 'jsonc',
|
||||
parser: parse,
|
||||
});
|
||||
return await this.rootIssueSubTreesWidgetService.render(cfg);
|
||||
}
|
||||
|
||||
@Get('/tree/:name')
|
||||
@Render('simple-kanban-board')
|
||||
async get(@Param('name') name: string): Promise<any> {
|
||||
return await this.getRawData(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { MainConfigModel } from '@app/event-emitter/models/main-config-model';
|
||||
import { SimpleKanbanBoardConfig } from 'src/configs/simple-kanban-board.config';
|
||||
import { EccmConfig } from './eccm-config.model';
|
||||
import { StatusChangesConfig } from './status-changes-config.model';
|
||||
import { StatusesConfig } from './statuses-config.model';
|
||||
|
|
@ -9,6 +10,7 @@ export type AppConfig = {
|
|||
redmineStatusChanges: StatusChangesConfig.Config;
|
||||
redmineEccm: EccmConfig.Config;
|
||||
redmineCurrentUserRules: Record<string, string>;
|
||||
simpleKanbanBoard: SimpleKanbanBoardConfig;
|
||||
couchDb: {
|
||||
dbs: {
|
||||
changes: string;
|
||||
|
|
|
|||
66
views/simple-kanban-board.hbs
Normal file
66
views/simple-kanban-board.hbs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>ECCM 1.10 Kanban board</title>
|
||||
<style>
|
||||
.kanban-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
.kanban-column {
|
||||
width: 200px;
|
||||
background-color: rgb(225, 225, 225);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.kanban-header {
|
||||
text-align: center;
|
||||
width: 200px;
|
||||
}
|
||||
.kanban-card {
|
||||
background-color: rgb(196, 196, 196);
|
||||
border-width: 1px;
|
||||
border-color: rgba(255, 255, 255, 0.2) rgba(96, 96, 96, 0.2) rgba(96, 96, 96, 0.2) rgba(255, 255, 255, 0.2);
|
||||
/*border-color: black;*/
|
||||
border-style: solid;
|
||||
margin: 2px;
|
||||
padding: 3px;
|
||||
width: 190px;
|
||||
/*display: flex;*/
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.5);
|
||||
border-radius: 3px;
|
||||
z-index: 100;
|
||||
}
|
||||
.kanban-card div {
|
||||
font-size: small;
|
||||
}
|
||||
.kanban-card .kanban-card-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{#each this}}
|
||||
<h1>{{this.metainfo.title}}</h1>
|
||||
<div class="kanban-container">
|
||||
{{#each this.data}}
|
||||
<div class="kanban-column">
|
||||
<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>Исп.: {{this.current_user.name}}</div>
|
||||
<div>Прогресс: {{this.done_ration}}</div>
|
||||
<div>Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue