Compare commits

..

No commits in common. "8479b6d68ddf7e0fbd47b390926ee627f12e77ec" and "04a0736c9a5ae000e21c6f4ef804586a8cd7eccc" have entirely different histories.

8 changed files with 15 additions and 243 deletions

View file

@ -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"

View file

@ -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} />
</>
);
});

View file

@ -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>

View file

@ -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 {

View file

@ -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 {

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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,