diff --git a/frontend/src/dashboard/widgets/daily-eccm-v2.tsx b/frontend/src/dashboard/widgets/daily-eccm-v2.tsx index b44978d..dfeec16 100644 --- a/frontend/src/dashboard/widgets/daily-eccm-v2.tsx +++ b/frontend/src/dashboard/widgets/daily-eccm-v2.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react-lite'; import { DebugInfo } from '../../misc-components/debug-info'; import { Instance, onSnapshot, types } from 'mobx-state-tree'; import { v4 as uuidv4 } from 'uuid'; +import { text2id } from '../../utils/text-to-id'; export const DailyEccmV2Data = types.model({ id: types.string, @@ -29,7 +30,7 @@ export const DailyEccmV2Store = types export type IDailyEccmV2Store = Instance; const SimpleCounterViewComponent = (props: { - key?: string; + id: string; label: string; count: number; issueIds?: number[]; @@ -38,41 +39,178 @@ const SimpleCounterViewComponent = (props: { }): JSX.Element => { let detailsHintComponent = <>; if (props.details) { - const parentKey = props.key - ? props.key + const parentKey = props.id + ? props.id : `simple-counter-component-${uuidv4()}`; const detailsKey = `${parentKey}-details`; detailsHintComponent = ( ); } return ( - + {props.label}: {props.count} {detailsHintComponent} ); }; const DetailsHintViewComponent = (props: { - key?: string; + id: string; mainLabel: string; details: Record; }): JSX.Element => { return ( - + ({props.mainLabel}: {JSON.stringify(props.details)}) ); }; +const ChangesCounterTotalViewComponent = (props: { + id: string; + data: Record; +}): JSX.Element => { + if (!props.id) return <>; + const titles: Record = { + byIssue: 'По задачам', + byStatus: 'По статусам', + byVersion: 'По версиям', + byUserName: 'По работникам', + }; + const groups = Object.keys(titles); + const details: JSX.Element[] = []; + groups.forEach((groupName) => { + const groupKey = `${props.id}-details-${groupName}`; + const groupTitle = titles[groupName]; + const groupData = props.data[groupName]; + const cmp = ( + + ); + details.push(cmp); + }); + return ( +
    +
  • Итого изменений: {props.data?.totalChanges ?? 0}
  • +
  • Итого комментариев: {props.data?.totalComments ?? 0}
  • + {details} +
+ ); +}; + +const ChangesCounterListViewComponent = (props: { + id: string; + title: string; + data?: Record; +}): JSX.Element => { + if (!props.data || !props.id) return <>; + const dataKeys = Object.keys(props.data ?? {}); + if (!dataKeys || dataKeys.length <= 0) return <>; + + const res: JSX.Element[] = []; + + dataKeys.forEach((itemKey) => { + const itemData = props.data && props.data[itemKey]; + if (!itemData) return; + const key = `${props.id}-${text2id(props.title)}-${text2id(itemKey)}`; + res.push( +
  • + +
  • , + ); + }); + + return ( + <> + {props.title}:
      {res}
    + + ); +}; + +const ChangesCounterItemViewComponent = (props: { + id: string; + type: 'issue' | 'other'; + label: string; + changes: number; + comments: number; +}): JSX.Element => { + return ( + + + {props.type == 'issue' ? '#' : ''} + {props.label}:  + + все изменения - {props.changes},  + комментарии - {props.comments} + + ); +}; + +const VerySimpleCounterViewComponent = (props: { + id: string; + title: string; + value: number | string; + details?: any; +}): JSX.Element => { + return ( + + {props.title}: +  {props.value} + +  {props.details ? JSON.stringify(props.details) : ''} + + + ); +}; + +const VerySimpleCounterListViewComponent = (props: { + id: string; + data: { title: string; value: number | string; details?: any }[]; +}): JSX.Element => { + const items: JSX.Element[] = []; + props.data.forEach((item) => { + const itemId = `${props.id}-${text2id(item.title)}`; + items.push( + , + ); + }); + return ( +
      + {items.map((item) => ( +
    • {item}
    • + ))} +
    + ); +}; + export const DailyEccmV2 = observer( (props: { store: IDailyEccmV2Store }): JSX.Element => { const dashboardId = props.store?.data?.dashboardId; const widgetId = props.store?.data?.widgetId; + if (!dashboardId || !widgetId) return <>; + const keyPrefix = `dashboard-${dashboardId}-widget-${widgetId}`; const issuesByStatusCount: Record = @@ -81,6 +219,233 @@ export const DailyEccmV2 = observer( props.store?.data?.issuesMetrics?.issuesByVersionsCount ?? {}; const issuesByUsername: Record = props.store?.data?.issuesMetrics?.issuesByUsername ?? {}; + const changesCount: Record = + props.store?.data?.issuesMetrics?.changesCount ?? {}; + + const otherData: { + title: string; + value: number | string; + details?: any; + }[] = []; + + // ШАГ 1. Количество задач по статусам + const issuesByStatusCount2 = + props.store?.data?.issuesMetrics?.issuesByStatusCount ?? {}; + const issuesByStatusCount2Prefix = `Счётчики с количеством задач по статусам`; + Object.keys(issuesByStatusCount2).forEach((statusName) => { + const statusData = issuesByStatusCount2[statusName]; + if (statusData) { + otherData.push({ + title: `${issuesByStatusCount2Prefix} / ${statusName} / Количество задач`, + value: statusData.count, + details: { + issueIds: statusData.issueIds, + }, + }); + const byVersions = statusData.byVersion; + const byVersionsPrefix = `${issuesByStatusCount2Prefix} / ${statusName} / По версиям`; + if (byVersions) { + Object.keys(byVersions).forEach((versionName) => { + otherData.push({ + title: `${byVersionsPrefix} / ${versionName} / Количество задач`, + value: byVersions[versionName].count, + details: { + issueIds: byVersions[versionName].issueIds, + }, + }); + }); + } + } + }); + + // ШАГ 2. Количество задач по версиям + const issuesByVersionCount2 = + props.store?.data?.issuesMetrics?.issuesByVersionsCount ?? {}; + const issuesByVersionCount2Prefix = 'Счётчики по версиям'; + Object.keys(issuesByVersionCount2).forEach((versionName) => { + const versionData = issuesByVersionCount2[versionName]; + if (versionData) { + otherData.push({ + title: `${issuesByVersionCount2Prefix} / ${versionName} / Количество задач`, + value: versionData.count, + details: { + issueIds: versionData.issueIds, + }, + }); + const byStatuses = versionData.byStatus; + const byStatusesPrefix = `${issuesByVersionCount2Prefix} / ${versionName} / По статусам`; + if (byStatuses) { + Object.keys(byStatuses).forEach((statusName) => { + otherData.push({ + title: `${byStatusesPrefix} / ${statusName} / Количество задач`, + value: byStatuses[statusName].count, + details: { + issueIds: byStatuses[statusName].issueIds, + }, + }); + }); + } + } + }); + + // ШАГ 3. Количество задач по работникам + const issuesByUsername2 = + props.store?.data?.issuesMetrics?.issuesByUsername ?? {}; + const issuesByUsername2Prefix = 'Счётчики по работникам'; + Object.keys(issuesByUsername2).forEach((username) => { + const userData = issuesByUsername2[username]; + if (userData) { + otherData.push({ + title: `${issuesByUsername2Prefix} / ${username} / Количество задач`, + value: userData.count, + details: { + issueIds: userData.issueIds, + }, + }); + } + }); + + // ШАГ 4. Количество внутренних изменений + const changesCount2 = props.store?.data?.issuesMetrics?.changesCount ?? {}; + const changesCount2Prefix = 'Счётчики изменений'; + otherData.push({ + title: `${changesCount2Prefix} / Всего изменений`, + value: changesCount2.totalChanges || 0, + }); + otherData.push({ + title: `${changesCount2Prefix} / Всего комментариев`, + value: changesCount2.totalComments || 0, + }); + const sections: Record = { + byIssue: 'По задачам', + byStatus: 'По статусам', + byVersion: 'По версиям', + byUserName: 'По работникам', + }; + Object.keys(sections).forEach((sectionName) => { + const sectionData = changesCount2[sectionName]; + if (!sectionData) return; + const sectionPrefix = `${changesCount2Prefix} / ${sections[sectionName]}`; + Object.keys(sectionData).forEach((itemKey) => { + const itemData = sectionData[itemKey]; + otherData.push({ + title: `${sectionPrefix} / ${itemKey} / Изменения`, + value: itemData.changes || 0, + }); + otherData.push({ + title: `${sectionPrefix} / ${itemKey} / Комментарии`, + value: itemData.comments || 0, + }); + }); + }); + + // ШАГ 5. Количество задач с оценками трудозатрат + const issuesWithEstimatesAndSpenthoursCount = + props.store?.data?.issuesMetrics?.issuesWithEstimatesAndSpenthoursCount ?? + {}; + + const estimatesPrefix = + 'Счётчики с оценкой трудозатрат и потраченных часов'; + + if (issuesWithEstimatesAndSpenthoursCount.withEstimates) { + otherData.push({ + title: `${estimatesPrefix} / С оценкой трудозатрат`, + value: issuesWithEstimatesAndSpenthoursCount.withEstimates.count, + details: { + issueIds: + issuesWithEstimatesAndSpenthoursCount.withEstimates.issueIds, + }, + }); + } + if (issuesWithEstimatesAndSpenthoursCount.withoutEstimates) { + otherData.push({ + title: `${estimatesPrefix} / Без оценки трудозатрат`, + value: issuesWithEstimatesAndSpenthoursCount.withoutEstimates.count, + details: { + issueIds: + issuesWithEstimatesAndSpenthoursCount.withoutEstimates.issueIds, + }, + }); + } + if (issuesWithEstimatesAndSpenthoursCount.withSpentHoursOverEstimates) { + otherData.push({ + title: `${estimatesPrefix} / С потраченными часами больше, чем оценка трудозатрат`, + value: + issuesWithEstimatesAndSpenthoursCount.withSpentHoursOverEstimates + .count, + details: { + issueIds: + issuesWithEstimatesAndSpenthoursCount.withSpentHoursOverEstimates + .issueIds, + }, + }); + } + if (issuesWithEstimatesAndSpenthoursCount.byVersion) { + const estimatesPrefixVersions = `${estimatesPrefix} / По версиям`; + Object.keys(issuesWithEstimatesAndSpenthoursCount.byVersion).forEach( + (versionName) => { + const estimatesPrefixVersion = `${estimatesPrefixVersions} / ${versionName}`; + const versionData = + issuesWithEstimatesAndSpenthoursCount.byVersion[versionName]; + if (versionData.withEstimates) { + otherData.push({ + title: `${estimatesPrefixVersion} / С оценкой трудозатрат`, + value: versionData.withEstimates.count, + details: { issueIds: versionData.withEstimates.issueIds }, + }); + } + if (versionData.withoutEstimates) { + otherData.push({ + title: `${estimatesPrefixVersion} / Без оценки трудозатрат`, + value: versionData.withoutEstimates.count, + details: { issueIds: versionData.withoutEstimates.issueIds }, + }); + } + if (versionData.withSpentHoursOverEstimates) { + otherData.push({ + title: `${estimatesPrefixVersion} / С потраченными часами больше, чем оценка трудозатрат`, + value: versionData.withSpentHoursOverEstimates.count, + details: { + issueIds: versionData.withSpentHoursOverEstimates.issueIds, + }, + }); + } + }, + ); + } + if (issuesWithEstimatesAndSpenthoursCount.byStatus) { + const estimatesPrefixStatuses = `${estimatesPrefix} / По статусам`; + Object.keys(issuesWithEstimatesAndSpenthoursCount.byStatus).forEach( + (statusName) => { + const estimatesPrefixStatus = `${estimatesPrefixStatuses} / ${statusName}`; + const statusData = + issuesWithEstimatesAndSpenthoursCount.byStatus[statusName]; + if (statusData.withEstimates) { + otherData.push({ + title: `${estimatesPrefixStatus} / С оценкой трудозатрат`, + value: statusData.withEstimates.count, + details: { issueIds: statusData.withEstimates.issueIds }, + }); + } + if (statusData.withoutEstimates) { + otherData.push({ + title: `${estimatesPrefixStatus} / Без оценки трудозатрат`, + value: statusData.withoutEstimates.count, + details: { issueIds: statusData.withoutEstimates.issueIds }, + }); + } + if (statusData.withSpentHoursOverEstimates) { + otherData.push({ + title: `${estimatesPrefixStatus} / С потраченными часами больше, чем оценка трудозатрат`, + value: statusData.withSpentHoursOverEstimates.count, + details: { + issueIds: statusData.withSpentHoursOverEstimates.issueIds, + }, + }); + } + }, + ); + } const byStatusLi: JSX.Element[] = []; @@ -90,7 +455,8 @@ export const DailyEccmV2 = observer( byStatusLi.push(
  • +
  • + По изменениям в задачах за период: + +
  • +
  • + Другие счётчики: + +
  • diff --git a/frontend/src/utils/text-to-id.ts b/frontend/src/utils/text-to-id.ts new file mode 100644 index 0000000..1b4e7f6 --- /dev/null +++ b/frontend/src/utils/text-to-id.ts @@ -0,0 +1,55 @@ +export function transformUtf8ToTransliteAscii(inputText: string): string { + // Define a mapping of Russian characters to their translite ASCII equivalents + const transliterationMap: { [key: string]: string } = { + а: 'a', + б: 'b', + в: 'v', + г: 'g', + д: 'd', + е: 'e', + ё: 'e', + ж: 'zh', + з: 'z', + и: 'i', + й: 'y', + к: 'k', + л: 'l', + м: 'm', + н: 'n', + о: 'o', + п: 'p', + р: 'r', + с: 's', + т: 't', + у: 'u', + ф: 'f', + х: 'kh', + ц: 'ts', + ч: 'ch', + ш: 'sh', + щ: 'sch', + ъ: '', + ы: 'y', + ь: '', + э: 'e', + ю: 'yu', + я: 'ya', + // Add more mappings as needed... + ' ': '-', + '/': 'then', + }; + + // Transform the input text to translite ASCII without spaces + let transformedText = ''; + for (const char of inputText.toLowerCase()) { + if (transliterationMap[char]) { + transformedText += transliterationMap[char]; + } else { + transformedText += char; + } + } + + return transformedText; +} + +export const text2id = transformUtf8ToTransliteAscii; diff --git a/src/reports/daily-eccm-v2-report-task-runner.service.ts b/src/reports/daily-eccm-v2-report-task-runner.service.ts index 3e575aa..eaab0ae 100644 --- a/src/reports/daily-eccm-v2-report-task-runner.service.ts +++ b/src/reports/daily-eccm-v2-report-task-runner.service.ts @@ -314,7 +314,9 @@ export class DailyEccmV2ReportTaskRunnerService { comments: 0, }, ); - changesCount.byIssue[issue.id] = changes; + if (changes && (changes.changes > 0 || changes.comments > 0)) { + changesCount.byIssue[issue.id] = changes; + } }); } this.logger.debug(