Compare commits
No commits in common. "8479b6d68ddf7e0fbd47b390926ee627f12e77ec" and "04a0736c9a5ae000e21c6f4ef804586a8cd7eccc" have entirely different histories.
8479b6d68d
...
04a0736c9a
8 changed files with 15 additions and 243 deletions
|
|
@ -24,7 +24,7 @@
|
|||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "BROWSER=none react-scripts start --open=false",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
|
|
|
|||
|
|
@ -1,146 +0,0 @@
|
|||
import React from 'react';
|
||||
import * as DashboardStoreNs from '../dashboard-store';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { DebugInfo } from '../../misc-components/debug-info';
|
||||
import { Instance, onSnapshot, types } from 'mobx-state-tree';
|
||||
|
||||
export const DailyEccmV2Data = types.model({
|
||||
id: types.string,
|
||||
dashboardId: types.string,
|
||||
widgetId: types.string,
|
||||
datetime: types.number,
|
||||
datetimeFormatted: types.string,
|
||||
reportIssues: types.array(types.frozen()),
|
||||
issuesMetrics: types.frozen(),
|
||||
latest: types.boolean,
|
||||
});
|
||||
|
||||
export const DailyEccmV2Store = types
|
||||
.model({
|
||||
data: DailyEccmV2Data,
|
||||
})
|
||||
.actions((self) => ({
|
||||
setData(data: any) {
|
||||
self.data = data;
|
||||
},
|
||||
}));
|
||||
|
||||
export type IDailyEccmV2Store = Instance<typeof DailyEccmV2Store>;
|
||||
|
||||
export const DailyEccmV2 = observer(
|
||||
(props: { store: IDailyEccmV2Store }): JSX.Element => {
|
||||
const dashboardId = props.store?.data?.dashboardId;
|
||||
const widgetId = props.store?.data?.widgetId;
|
||||
const keyPrefix = `dashboard-${dashboardId}-widget-${widgetId}`;
|
||||
|
||||
const issuesByStatusCount: Record<string, any> =
|
||||
props.store?.data?.issuesMetrics?.issuesByStatusCount ?? {};
|
||||
const issuesByVersionsCount: Record<string, any> =
|
||||
props.store?.data?.issuesMetrics?.issuesByVersionsCount ?? {};
|
||||
const issuesByUsername: Record<string, any> =
|
||||
props.store?.data?.issuesMetrics?.issuesByUsername ?? {};
|
||||
|
||||
const byStatusLi: JSX.Element[] = [];
|
||||
|
||||
Object.keys(issuesByStatusCount).forEach((status) => {
|
||||
let byStatusHint: JSX.Element;
|
||||
const byVersion = issuesByStatusCount[status]?.byVersion;
|
||||
const keyPrefixForCurrentStatusBlock = `${keyPrefix}-issuesByStatusCount-${status}`;
|
||||
if (byVersion && Object.keys(byVersion).length > 0) {
|
||||
const byVersionSimple = Object.keys(byVersion).reduce(
|
||||
(acc: Record<string, number>, version) => {
|
||||
acc[version] = byVersion[version].count;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
byStatusHint = (
|
||||
<span
|
||||
key={`${keyPrefixForCurrentStatusBlock}-byVersionHint`}
|
||||
style={{ marginLeft: '10px', color: 'lightgray' }}
|
||||
>
|
||||
(Версии: {JSON.stringify(byVersionSimple)})
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
byStatusHint = <></>;
|
||||
}
|
||||
byStatusLi.push(
|
||||
<li key={`${keyPrefixForCurrentStatusBlock}`}>
|
||||
{status}: {issuesByStatusCount[status].count} {byStatusHint}
|
||||
</li>,
|
||||
);
|
||||
});
|
||||
|
||||
const byVersionsLi: JSX.Element[] = [];
|
||||
|
||||
Object.keys(issuesByVersionsCount).forEach((version) => {
|
||||
const byStatus = issuesByVersionsCount[version].byStatus;
|
||||
const keyPrefixForCurrentVersionBlock = `${keyPrefix}-issuesByVersionsCount-${version}`;
|
||||
|
||||
let byStatusHint: JSX.Element = <></>;
|
||||
if (byStatus && Object.keys(byStatus).length > 0) {
|
||||
const byStatusSimple = Object.keys(byStatus).reduce((acc, status) => {
|
||||
acc[status] = byStatus[status].count;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
byStatusHint = (
|
||||
<span
|
||||
style={{ marginLeft: '10px', color: 'lightgray' }}
|
||||
key={`${keyPrefixForCurrentVersionBlock}-byStatusHint`}
|
||||
>
|
||||
(Статусы: {JSON.stringify(byStatusSimple)})
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
byVersionsLi.push(
|
||||
<li key={keyPrefixForCurrentVersionBlock}>
|
||||
{version}: {issuesByVersionsCount[version].count} {byStatusHint}
|
||||
</li>,
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Daily ECCM V2</h1>
|
||||
<p>Дата выгрузки: {props.store?.data?.datetimeFormatted}</p>
|
||||
<p>Метрики:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Простые счётчики:
|
||||
<ul>
|
||||
<li>
|
||||
По статусам:
|
||||
<ul>{byStatusLi}</ul>
|
||||
</li>
|
||||
<li>
|
||||
По версиям:
|
||||
<ul>{byVersionsLi}</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<DebugInfo value={JSON.stringify(props.store?.data, null, 2)} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export type Props = {
|
||||
store: DashboardStoreNs.IWidget;
|
||||
};
|
||||
|
||||
export const DailyEccmV2Widget = observer((props: Props): JSX.Element => {
|
||||
const dailyEccmV2Store = DailyEccmV2Store.create();
|
||||
onSnapshot(props.store, (storeState) => {
|
||||
if (storeState.data) {
|
||||
dailyEccmV2Store.setData(storeState.data);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<DailyEccmV2 store={dailyEccmV2Store} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -6,7 +6,6 @@ import * as KanbanWidgetNs from './kanban';
|
|||
import { DebugInfo } from '../../misc-components/debug-info';
|
||||
import * as IssuesListNs from './issues-list';
|
||||
import * as CalendarNextEventsNs from './calendar-next-events';
|
||||
import * as DailyEccmV2 from './daily-eccm-v2';
|
||||
|
||||
export type Props = {
|
||||
store: Instance<typeof DashboardStoreNs.Widget>;
|
||||
|
|
@ -27,10 +26,6 @@ export const WidgetFactory = observer((props: Props): JSX.Element => {
|
|||
return <CalendarNextEventsNs.CalendarNextEvents store={props.store} />;
|
||||
}
|
||||
|
||||
if (type === 'daily_eccm_v2') {
|
||||
return <DailyEccmV2.DailyEccmV2 store={props.store} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Unknown widget</div>
|
||||
|
|
|
|||
|
|
@ -119,19 +119,11 @@ export class RedmineDataLoader {
|
|||
skip_empty_lines: true,
|
||||
};
|
||||
}
|
||||
let resp;
|
||||
try {
|
||||
resp = await fetch(urlQuery, {
|
||||
const resp = await fetch(urlQuery, {
|
||||
headers: {
|
||||
'X-Redmine-API-Key': this.redmineToken,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Failed to fetch data from Redmine by url ${urlQuery}: ${err}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
const rawData = await resp.text();
|
||||
let res;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ import { TelegramBotController } from './telegram-bot/telegram-bot.controller';
|
|||
import { DailyEccmV2ReportTaskRunnerService } from './reports/daily-eccm-v2-report-task-runner.service';
|
||||
import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
|
||||
import { DailyEccmReportsV2Datasource } from './couchdb-datasources/daily-eccm-reports-v2.datasource';
|
||||
import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards/widget-data-loader/daily-eccm-v2.widget-data-loader.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -122,7 +121,6 @@ import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards
|
|||
DashboardsService,
|
||||
DailyEccmReportsV2Datasource,
|
||||
DailyEccmV2ReportTaskRunnerService,
|
||||
DailyEccmReportsV2DataLoaderService,
|
||||
],
|
||||
})
|
||||
export class AppModule implements OnModuleInit {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||
import { WidgetsCollectionService } from '@app/event-emitter/dashboards/widgets-collection.service';
|
||||
import { IssuesByTagsWidgetDataLoaderService } from './widget-data-loader/issues-by-tags.widget-data-loader.service';
|
||||
import { createInteractiveWidget } from '@app/event-emitter/dashboards/interactive-widget-factory';
|
||||
import { DailyEccmReportsV2DataLoaderService } from 'src/eccm-statistic/dashboards/widget-data-loader/daily-eccm-v2.widget-data-loader.service';
|
||||
import { WIDGET_TYPE as DAILY_ECCM_V2_WIDGET_TYPE } from 'src/reports/daily-eccm-v2-report-task-runner.service';
|
||||
|
||||
@Injectable()
|
||||
export class DashboardInitService {
|
||||
constructor(
|
||||
private issuesByTagsWidgetDataLoaderService: IssuesByTagsWidgetDataLoaderService,
|
||||
private dailyEccmReportsV2DataLoaderService: DailyEccmReportsV2DataLoaderService,
|
||||
private widgetsCollectionService: WidgetsCollectionService,
|
||||
) {}
|
||||
|
||||
|
|
@ -23,10 +20,6 @@ export class DashboardInitService {
|
|||
this.issuesByTagsWidgetDataLoaderService,
|
||||
'issues_list_by_tags',
|
||||
),
|
||||
createInteractiveWidget(
|
||||
this.dailyEccmReportsV2DataLoaderService,
|
||||
DAILY_ECCM_V2_WIDGET_TYPE,
|
||||
),
|
||||
];
|
||||
collection.forEach((w) => this.widgetsCollectionService.appendWidget(w));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import { WidgetDataLoaderInterface } from '@app/event-emitter/dashboards/widget-data-loader-interface';
|
||||
import {
|
||||
AppError,
|
||||
createAppError,
|
||||
fail,
|
||||
Result,
|
||||
success,
|
||||
} from '@app/event-emitter/utils/result';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DailyEccmReportsV2Datasource } from 'src/couchdb-datasources/daily-eccm-reports-v2.datasource';
|
||||
|
||||
@Injectable()
|
||||
export class DailyEccmReportsV2DataLoaderService
|
||||
implements WidgetDataLoaderInterface<any, any, any>
|
||||
{
|
||||
isMyConfig(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async load(
|
||||
dataLoaderParams: any,
|
||||
dashboardParams: any,
|
||||
dashboardId: string,
|
||||
widgetId: string,
|
||||
): Promise<Result<any, AppError>> {
|
||||
const ds = await DailyEccmReportsV2Datasource.getDatasource();
|
||||
const response = await ds.find({
|
||||
selector: {
|
||||
dashboardId: dashboardId,
|
||||
widgetId: widgetId,
|
||||
latest: true,
|
||||
},
|
||||
limit: 1,
|
||||
});
|
||||
const data = response.docs[0];
|
||||
if (!data) {
|
||||
return fail(createAppError('No data found'));
|
||||
}
|
||||
return success(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -229,26 +229,8 @@ export class DailyEccmV2ReportTaskRunnerService {
|
|||
JSON.stringify(issuesByVersionsCount),
|
||||
);
|
||||
|
||||
// ШАГ 3. Подсчёт количества задач по работникам
|
||||
this.logger.debug('Step 3. Calculating issues by user names');
|
||||
const issuesByUsername = {};
|
||||
issues.forEach((issue: any) => {
|
||||
const currentUser = issue.current_user?.name;
|
||||
if (currentUser) {
|
||||
if (!issuesByUsername[currentUser]) {
|
||||
issuesByUsername[currentUser] = { count: 0, issueIds: [] };
|
||||
}
|
||||
issuesByUsername[currentUser].count += 1;
|
||||
issuesByUsername[currentUser].issueIds.push(issue.id);
|
||||
}
|
||||
});
|
||||
this.logger.debug(
|
||||
'Step 3. Calculating issues by user names - done',
|
||||
JSON.stringify(issuesByUsername),
|
||||
);
|
||||
|
||||
// ШАГ 4. Подсчет количества внутренних изменений
|
||||
this.logger.debug('Step 4. Calculating internal changes');
|
||||
// ШАГ 3. Подсчет количества внутренних изменений
|
||||
this.logger.debug('Step 3. Calculating internal changes');
|
||||
const changesInterval =
|
||||
(widget?.dataLoaderParams['changesInterval'] as number) ??
|
||||
24 * 60 * 60 * 1000;
|
||||
|
|
@ -318,12 +300,12 @@ export class DailyEccmV2ReportTaskRunnerService {
|
|||
});
|
||||
}
|
||||
this.logger.debug(
|
||||
'Step 4. Calculating internal changes - done',
|
||||
'Step 3. Calculating internal changes - done',
|
||||
JSON.stringify(changesCount),
|
||||
);
|
||||
|
||||
// ШАГ 5: Количество задач с оценками трудозатрат
|
||||
this.logger.debug('Step 5. Counting issues with estimates and spent hours');
|
||||
// ШАГ 4: Количество задач с оценками трудозатрат
|
||||
this.logger.debug('Step 4. Counting issues with estimates and spent hours');
|
||||
const issuesWithEstimatesAndSpenthoursCount = {
|
||||
withEstimates: { count: 0, issueIds: [] },
|
||||
withoutEstimates: { count: 0, issueIds: [] },
|
||||
|
|
@ -420,12 +402,12 @@ export class DailyEccmV2ReportTaskRunnerService {
|
|||
}
|
||||
});
|
||||
this.logger.debug(
|
||||
'Step 5. Counting issues with estimates and spent hours - done',
|
||||
'Step 4. Counting issues with estimates and spent hours - done',
|
||||
JSON.stringify(issuesWithEstimatesAndSpenthoursCount),
|
||||
);
|
||||
|
||||
// ШАГ 6: Счётчики сравнения с предыдущим отчётом
|
||||
this.logger.debug('Step 6: Calculating differences with previous report');
|
||||
// ШАГ 5: Счётчики сравнения с предыдущим отчётом
|
||||
this.logger.debug('Step 5: Calculating differences with previous report');
|
||||
const differencesCount = {
|
||||
newIssues: { count: 0, issueIds: [] },
|
||||
lostIssues: { count: 0, issueIds: [] },
|
||||
|
|
@ -479,14 +461,13 @@ export class DailyEccmV2ReportTaskRunnerService {
|
|||
}
|
||||
});
|
||||
this.logger.debug(
|
||||
'Step 6: Calculating differences with previous report - done',
|
||||
'Step 5: Calculating differences with previous report - done',
|
||||
JSON.stringify(differencesCount),
|
||||
);
|
||||
|
||||
const metrics = {
|
||||
issuesByStatusCount: issuesByStatusCount,
|
||||
issuesByVersionsCount: issuesByVersionsCount,
|
||||
issuesByUsername: issuesByUsername,
|
||||
changesCount: changesCount,
|
||||
issuesWithEstimatesAndSpenthoursCount:
|
||||
issuesWithEstimatesAndSpenthoursCount,
|
||||
|
|
|
|||
Loading…
Reference in a new issue