This commit is contained in:
bakhirev 2024-10-16 17:50:23 +03:00
parent 81cb8a1b40
commit 320e4b0485
60 changed files with 79224 additions and 79657 deletions

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,12 @@ function CommitInfo({ commits }: { commits: ICommit[] }): React.ReactElement {
function TaskInfo({ tasks }: { tasks: ITask }): React.ReactElement { function TaskInfo({ tasks }: { tasks: ITask }): React.ReactElement {
const items = Object.entries(tasks) const items = Object.entries(tasks)
.map(([task, commits]: [string, any]) => { .map(([task, commits]: [string, any]) => {
const prId = dataGrip.pr.prByTask.get(task); const taskInfo = dataGrip.tasks.statisticByName.get(task);
const milliseconds = commits[0].milliseconds;
const prId = taskInfo?.prIds?.find((id: string) => {
const pr = dataGrip.pr.pr.get(id);
return pr.dateMerge >= milliseconds;
});
return ( return (
<> <>
<div className={style.day_info_link}> <div className={style.day_info_link}>

View file

@ -21,7 +21,7 @@
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
background-color: var(--color-white); background-color: var(--color-white);
animation: notification_item 3s linear 0.5s forwards; animation: notification_item 0.5s linear 3s forwards;
&_icon { &_icon {
position: absolute; position: absolute;
@ -56,7 +56,7 @@
padding: 0; padding: 0;
text-align: left; text-align: left;
color: var(--color-white); color: var(--color-white);
animation: notification_title 3s linear 0.5s forwards; animation: notification_title 0.5s linear 3s forwards;
} }
&_title { &_title {
@ -72,12 +72,10 @@
@keyframes notification_item { @keyframes notification_item {
80% { 80% {
overflow: hidden; overflow: hidden;
height: auto;
padding: var(--space-xxl) var(--space-xxl) var(--space-xxl) 56px; padding: var(--space-xxl) var(--space-xxl) var(--space-xxl) 56px;
opacity: 1; opacity: 1;
} }
to { to {
height: 0;
padding: 0 var(--space-xxl) 0 64px; padding: 0 var(--space-xxl) 0 64px;
opacity: 0; opacity: 0;
} }

View file

@ -19,16 +19,17 @@
display: inline-block; display: inline-block;
width: 50%; width: 50%;
height: 100%; height: 100%;
vertical-align: middle;
} }
&_icon { &_icon {
width: 160px; width: 160px;
vertical-align: top;
} }
&_legend { &_legend {
min-width: 160px; min-width: 160px;
padding-left: var(--space-xxl); padding-left: var(--space-xxl);
vertical-align: middle;
} }
&_line { &_line {
@ -55,6 +56,7 @@
line-height: 1.3; line-height: 1.3;
white-space: normal; white-space: normal;
text-decoration: none; text-decoration: none;
vertical-align: top;
color: var(--color-black); color: var(--color-black);
} }

View file

@ -31,10 +31,11 @@ function DetailsCell({
const localClassName = getClassName(style.table_cell, column, ['body', row], className); const localClassName = getClassName(style.table_cell, column, ['body', row], className);
const hasIcon = ((column.properties && row[column.properties]) const value = row?.[column?.properties || ''];
|| !column.properties const notNull = (Array.isArray(value) || value instanceof Set) // @ts-ignore
|| !column.properties?.length) ? (value?.length || value?.size)
&& column.formatter; : !!value;
const hasIcon = notNull || !column?.properties;
const onClick = () => { const onClick = () => {
if (!hasIcon || !updateRowsConfig) return; if (!hasIcon || !updateRowsConfig) return;

View file

@ -33,5 +33,5 @@ export default function getAdaptiveColumnWidth(
adaptiveColumnsWidth = adaptiveTableWidth / adaptiveColumnsCount; adaptiveColumnsWidth = adaptiveTableWidth / adaptiveColumnsCount;
}); });
return Math.max(adaptiveColumnsWidth, 40); return Math.max(adaptiveColumnsWidth || 40, 40);
} }

View file

@ -18,13 +18,14 @@ export default function getDefaultProps(children: React.ReactNode) {
// @ts-ignore // @ts-ignore
const defaultWidth = child?.props?.width || { const defaultWidth = child?.props?.width || {
[ColumnTypesEnum.STRING]: 200,
[ColumnTypesEnum.NUMBER]: 110,
[ColumnTypesEnum.SHORT_NUMBER]: 70, [ColumnTypesEnum.SHORT_NUMBER]: 70,
}[template || ''] || 0; }[template || ''] || 0;
// @ts-ignore // @ts-ignore
const minWidth = child?.props?.minWidth || 40; const minWidth = child?.props?.minWidth || {
[ColumnTypesEnum.STRING]: 200,
[ColumnTypesEnum.NUMBER]: 110,
}[template || ''] || 40;
// @ts-ignore // @ts-ignore
const isSortable = child?.props?.isSortable // @ts-ignore const isSortable = child?.props?.isSortable // @ts-ignore
@ -42,4 +43,4 @@ export default function getDefaultProps(children: React.ReactNode) {
userWidth: undefined, userWidth: undefined,
}; };
}); });
} }

View file

@ -24,7 +24,7 @@ function getFormattedDate(commits: ICommit[]) {
function getTags(commits: ICommit[]) { function getTags(commits: ICommit[]) {
const uniqueTypes = new Set(commits.map((commit: ICommit) => commit.type)); const uniqueTypes = new Set(commits.map((commit: ICommit) => commit.type));
const tags = Array.from(uniqueTypes) const tags = Array.from(uniqueTypes)
.filter((title: string) => title && title !== '—') .filter((title: string) => title)
.map((title: string) => ( .map((title: string) => (
<p <p
key={title} key={title}
@ -43,7 +43,13 @@ interface ITaskProps {
function Task({ title, commits }: ITaskProps) { function Task({ title, commits }: ITaskProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const prId = dataGrip.pr.prByTask.get(title); const task = dataGrip.tasks.statisticByName.get(title);
const milliseconds = commits[0].milliseconds;
const prId = task?.prIds?.find((id: string) => {
const pr = dataGrip.pr.pr.get(id);
return pr.dateMerge >= milliseconds;
});
return ( return (
<div <div
key={title} key={title}

View file

@ -27,5 +27,5 @@
} }
.ui_kit_tags_item + .ui_kit_tags_item { .ui_kit_tags_item + .ui_kit_tags_item {
margin-right: var(--space-xs); margin-left: var(--space-xs);
} }

View file

@ -2,8 +2,9 @@ import ICommit from 'ts/interfaces/Commit';
import IHashMap, { HashMap } from 'ts/interfaces/HashMap'; import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
import { ONE_DAY } from 'ts/helpers/formatter'; import { ONE_DAY } from 'ts/helpers/formatter';
import getCountryByTimeZone from 'ts/helpers/Parser/getCountryByTimeZone';
import getCountryBySymbol from 'ts/helpers/Parser/getCountryBySymbol';
import { createHashMap, createIncrement, increment } from 'ts/helpers/Math'; import { createHashMap, createIncrement, increment } from 'ts/helpers/Math';
import getCompany from 'ts/helpers/Parser/getCompany';
import userSettings from 'ts/store/UserSettings'; import userSettings from 'ts/store/UserSettings';
@ -38,6 +39,7 @@ export default class DataGripByAuthor {
#updateCommitByAuthor(statistic: any, commit: ICommit) { #updateCommitByAuthor(statistic: any, commit: ICommit) {
statistic.commits += 1; statistic.commits += 1;
statistic.lastCommit = commit; statistic.lastCommit = commit;
statistic.device = statistic.device || commit.device;
statistic.days[commit.timestamp] = true; statistic.days[commit.timestamp] = true;
statistic.tasks[commit.task] = commit.added + commit.changes + commit.removed statistic.tasks[commit.task] = commit.added + commit.changes + commit.removed
+ (statistic.tasks[commit.task] ? statistic.tasks[commit.task] : 0); + (statistic.tasks[commit.task] ? statistic.tasks[commit.task] : 0);
@ -61,6 +63,10 @@ export default class DataGripByAuthor {
statistic.lastCompany = commit.company; statistic.lastCompany = commit.company;
statistic.company.push({ title: commit.company, from: commit.timestamp }); statistic.company.push({ title: commit.company, from: commit.timestamp });
} }
if (commit.country && statistic.lastCountry !== commit.country) {
statistic.lastCountry = commit.country;
statistic.country.add(commit.country);
}
} }
#addCommitByAuthor(commit: ICommit) { #addCommitByAuthor(commit: ICommit) {
@ -70,6 +76,11 @@ export default class DataGripByAuthor {
const commitsByHour = new Array(24).fill(0); const commitsByHour = new Array(24).fill(0);
commitsByHour[commit.hours] += 1; commitsByHour[commit.hours] += 1;
const country = commit.country
|| getCountryBySymbol(commit.author)
|| getCountryBySymbol(commit.message)
|| getCountryByTimeZone(commit.timezone, commit.author);
this.commits.set(commit.author, { this.commits.set(commit.author, {
author: commit.author, author: commit.author,
commits: 1, commits: 1,
@ -81,9 +92,12 @@ export default class DataGripByAuthor {
scopes: createIncrement(commit.scope), scopes: createIncrement(commit.scope),
hours: [commit.hours], hours: [commit.hours],
company: commit.company company: commit.company
? [{ title: commit.company, from: commit.timestamp }] ? [{ title: commit.company, from: commit.milliseconds }]
: [], : [],
lastCompany: commit.company, lastCompany: commit.company,
country: new Set([country]),
lastCountry: country,
device: commit.device,
commitsByDayAndHour, commitsByDayAndHour,
commitsByHour, commitsByHour,
messageLength: [commit.text.length || 0], messageLength: [commit.text.length || 0],
@ -195,7 +209,6 @@ export default class DataGripByAuthor {
daysForTask: isStaff ? 0 : workDays / tasks.length, daysForTask: isStaff ? 0 : workDays / tasks.length,
taskInDay: isStaff ? 0 : tasks.length / workDays, taskInDay: isStaff ? 0 : tasks.length / workDays,
changesForTask: DataGripByAuthor.getMiddleValue(tasksSize), changesForTask: DataGripByAuthor.getMiddleValue(tasksSize),
lastCompany: getCompany(dot.author, dot.lastCommit.email),
days: workDays, days: workDays,
money: isStaff ? 0 : moneyWorked, money: isStaff ? 0 : moneyWorked,

View file

@ -1,87 +1,76 @@
import ICommit from 'ts/interfaces/Commit';
import IHashMap, { HashMap } from 'ts/interfaces/HashMap'; import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
import { createIncrement, increment } from 'ts/helpers/Math'; import { ONE_DAY } from 'ts/helpers/formatter';
export default class DataGripByCompany { export default class DataGripByCompany {
commits: HashMap<any> = new Map(); companies: HashMap<any> = new Map();
statistic: any = []; statistic: any = [];
statisticByName: IHashMap<any> = {}; statisticByName: IHashMap<any> = {};
clear() { clear() {
this.commits.clear(); this.companies.clear();
this.statistic = []; this.statistic = [];
this.statisticByName = {}; this.statisticByName = {};
} }
addCommit(commit: ICommit) { #addAuthor(statByAuthor: any) {
if (!commit.company) return; const company = statByAuthor.company[statByAuthor.company.length - 1]?.title;
const statistic = this.commits.get(commit.company); if (!company) return;
const statistic = this.companies.get(company);
if (statistic) { if (statistic) {
this.#updateCommitByCompany(statistic, commit); this.#updateCommitByCompany(statistic, statByAuthor);
} else { } else {
this.#addCommitByCompany(commit); this.#addCommitByCompany(company, statByAuthor);
} }
} }
#addCommitByCompany(commit: ICommit) { #addCommitByCompany(company: string, statByAuthor: any) {
this.commits.set(commit.company, { this.companies.set(company, {
company: commit.company, company,
commits: 1, isActive: !statByAuthor.isDismissed && !statByAuthor.isStaff,
firstCommit: commit, commits: statByAuthor.commits,
lastCommit: commit, from: statByAuthor.firstCommit.milliseconds,
days: createIncrement(commit.timestamp), to: statByAuthor.lastCommit.milliseconds,
employments: createIncrement(commit.author), daysWorked: statByAuthor.daysWorked,
tasks: createIncrement(commit.task), employments: [statByAuthor.author],
types: createIncrement(commit.type), tasks: [statByAuthor.tasks],
scopes: createIncrement(commit.scope), // types: statByAuthor.types,
// scopes: createIncrement(commit.scope),
}); });
} }
#updateCommitByCompany(statistic: any, commit: ICommit) { #updateCommitByCompany(statistic: any, statByAuthor: any) {
statistic.commits += 1; if (!statistic.isActive) {
statistic.lastCommit = commit; statistic.isActive = !statByAuthor.isDismissed && !statByAuthor.isStaff;
statistic.days[commit.timestamp] = true; }
statistic.employments[commit.author] = true; statistic.commits += statByAuthor.commits;
statistic.from = statistic.from > statByAuthor.firstCommit.milliseconds
increment(statistic.tasks, commit.task); ? statByAuthor.firstCommit.milliseconds
increment(statistic.types, commit.type); : statistic.from;
increment(statistic.scopes, commit.scope); statistic.to = statistic.to < statByAuthor.lastCommit.milliseconds
? statByAuthor.lastCommit.milliseconds
: statistic.to;
statistic.daysWorked += statByAuthor.daysWorked;
statistic.employments.push(statByAuthor.author);
statistic.tasks.push(statByAuthor.tasks);
} }
updateTotalInfo(dataGripByAuthor: any) { updateTotalInfo(dataGripByAuthor: any) {
console.dir(dataGripByAuthor); dataGripByAuthor.statistic.forEach((data: any) => {
this.statistic = Array.from(this.commits.values()) this.#addAuthor(data);
.sort((dotA: any, dotB: any) => dotB.commits - dotA.commits) });
.map((statistic: any) => {
const tasks = Object.keys(statistic.tasks);
const days = Object.keys(statistic.days);
const employments = Object.keys(statistic.employments);
let isActive = false; this.statistic = Array.from(this.companies.values())
employments.forEach((name) => { .map((company: any) => {
const author = dataGripByAuthor.statisticByName[name]; company.tasks = Array.from(new Set(company.tasks.flat(1))).length;
if (!author) return; company.totalDays = Math.max(((company.to - company.from) / ONE_DAY), 1);
if (author.lastCompany === statistic.company) isActive = true;
});
const companyInfo = { this.statisticByName[company.company] = company;
...statistic,
employments,
tasks,
totalTasks: tasks.length,
totalDays: days.length,
totalEmployments: employments.length,
isActive,
};
delete companyInfo.days;
this.statisticByName[statistic.company] = companyInfo; return company;
return companyInfo;
}); });
this.commits.clear(); this.companies.clear();
} }
} }

View file

@ -1,105 +1,83 @@
import { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit'; import { ISystemCommit } from 'ts/interfaces/Commit';
import IHashMap, { HashMap } from 'ts/interfaces/HashMap'; import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
import { createIncrement, increment, WeightedAverage } from 'ts/helpers/Math'; import { WeightedAverage } from 'ts/helpers/Math';
import { ONE_DAY } from 'ts/helpers/formatter';
const IS_PR = {
[COMMIT_TYPE.PR_BITBUCKET]: true,
[COMMIT_TYPE.PR_GITHUB]: true,
[COMMIT_TYPE.PR_GITLAB]: true,
};
export default class DataGripByPR { export default class DataGripByPR {
pr: HashMap<any> = new Map(); pr: HashMap<any> = new Map();
prByTask: HashMap<any> = new Map();
lastCommitByTaskNumber: HashMap<any> = new Map();
statistic: any[] = []; statistic: any[] = [];
statisticByName: IHashMap<any> = []; statisticByName: IHashMap<any> = {};
clear() { clear() {
this.pr.clear(); this.pr.clear();
this.prByTask.clear();
this.lastCommitByTaskNumber.clear();
this.statistic = []; this.statistic = [];
this.statisticByName = {};
} }
addCommit(commit: ISystemCommit) { addCommit(commit: ISystemCommit) {
if (!commit.commitType) { // commitType PR
const commitByTaskNumber = this.lastCommitByTaskNumber.get(commit.task); if (!commit.prId) return;
if (commitByTaskNumber) { const statistic = this.pr.get(commit.prId);
this.#updateCommitByTaskNumber(commitByTaskNumber, commit); if (statistic) {
} else { console.log('PR error');
this.#addCommitByTaskNumber(commit); } else {
}
} else if (!this.pr.has(commit.prId) && IS_PR[commit.commitType || '']) {
this.#addCommitByPR(commit); this.#addCommitByPR(commit);
} }
} }
#addCommitByTaskNumber(commit: ISystemCommit) {
this.lastCommitByTaskNumber.set(commit.task, {
commits : 1,
beginTaskTime: commit.milliseconds,
endTaskTime: commit.milliseconds,
commitsByAuthors: createIncrement(commit.author),
firstCommit: commit,
});
}
#updateCommitByTaskNumber(statistic: any, commit: ISystemCommit) {
statistic.endTaskTime = commit.milliseconds;
statistic.commits += 1;
increment(statistic.commitsByAuthors, commit.author);
}
#addCommitByPR(commit: ISystemCommit) { #addCommitByPR(commit: ISystemCommit) {
const lastCommit = this.lastCommitByTaskNumber.get(commit.task); this.pr.set(commit.prId, {
if (lastCommit) { prId : commit.prId,
// коммиты после влития PR сгорают, чтобы не засчитать технические PR мержи веток author: commit.author,
this.lastCommitByTaskNumber.delete(commit.task); task: commit.task,
const delay = commit.milliseconds - lastCommit.endTaskTime; type: commit.type,
const work = lastCommit.endTaskTime - lastCommit.beginTaskTime; branch: commit.branch,
this.pr.set(commit.prId, { message: commit.message,
...commit, dateCreate: commit.milliseconds, // last commit date before PR
...lastCommit, dateMerge: commit.milliseconds,
delay, daysReview: 1,
delayDays: delay / (24 * 60 * 60 * 1000), daysInWork: 1,
workDays: work === 0 ? 1 : (work / (24 * 60 * 60 * 1000)), });
});
this.prByTask.set(commit.task, commit.prId);
} else {
this.pr.set(commit.prId, { ...commit });
}
} }
updateTotalInfo(dataGripByAuthor: any) { updateTotalInfo(dataGripByTasks: any, dataGripByAuthor: any) {
const employment = dataGripByAuthor.employment; const byAuthor = new Map();
const authors = [...employment.active, ...employment.dismissed];
const refAuthorPR: any = Object.fromEntries(authors.map((name: string) => ([name, []])));
this.statistic = Object.values(this.pr) this.pr.forEach((pr: any) => {
.filter((item: any) => item.delay && item.task) if (!pr.task) return;
.sort((a: any, b: any) => b.delay - a.delay); const task = dataGripByTasks.statisticByName.get(pr.task);
if (!task) return;
this.statistic = []; task.prIds.push(pr.prId);
this.statisticByName = {};
Object.values(this.pr).forEach((item: any) => { pr.daysInWork = task.daysInWork;
if (!item.delay || !item.task) return; let lastCommitDateBeforePR = task?.to || task?.from;
if (lastCommitDateBeforePR > pr.dateMerge) {
this.statistic.push(item); if (!task.timestamps) {
if (refAuthorPR[item.firstCommit.author]) { console.log('x');
refAuthorPR[item.firstCommit.author].push(item); return;
}
const more = task.timestamps.find((milliseconds: number) => milliseconds > pr.dateMerge);
const index = task.timestamps.indexOf(more);
lastCommitDateBeforePR = task.timestamps[index - 1];
task.timestamps = task.timestamps.slice(index);
pr.daysInWork = index;
} }
// TODO он не мог быть пустым. Надо расследовать TASK-110 в тестовой выборке.
pr.dateCreate = lastCommitDateBeforePR || pr.dateCreate;
pr.daysReview = ((pr.dateMerge - pr.dateCreate) || ONE_DAY) / ONE_DAY;
const list = byAuthor.get(pr.author);
if (list) list.push(pr);
else byAuthor.set(pr.author, [pr]);
}); });
this.statistic.sort((a: any, b: any) => b.delay - a.delay); this.statistic = Array.from(this.pr.values())
this.updateTotalByAuthor(authors, refAuthorPR); .sort((a: any, b: any) => b.daysReview - a.daysReview);
this.lastCommitByTaskNumber.clear(); this.updateTotalByAuthor(byAuthor, dataGripByAuthor);
} }
static getPRByGroups(list: any, propertyName: string) { static getPRByGroups(list: any, propertyName: string) {
@ -121,11 +99,14 @@ export default class DataGripByPR {
[TITLES.MORE]: 0, [TITLES.MORE]: 0,
}; };
let max = 0;
const weightedAverage = new WeightedAverage(); const weightedAverage = new WeightedAverage();
list.forEach((pr: any) => { list.forEach((pr: any) => {
const value = pr[propertyName]; const value = pr[propertyName];
if (value > max) max = value;
weightedAverage.update(value); weightedAverage.update(value);
if (value <= 1) details[TITLES.DAY]++; if (value <= 1) details[TITLES.DAY]++;
@ -138,36 +119,33 @@ export default class DataGripByPR {
const order = Object.keys(details); const order = Object.keys(details);
return { details, order, weightedAverage: weightedAverage.get() }; return { details, order, weightedAverage: weightedAverage.get(), max };
} }
updateTotalByAuthor(authors: any, refAuthorPR: IHashMap<any>) { updateTotalByAuthor(refAuthorPR: HashMap<any>, dataGripByAuthor: any) {
this.statisticByName = {}; this.statisticByName = {};
authors.map((name: string) => { refAuthorPR.forEach((prs: any) => {
const author = prs[0].author;
const stat = dataGripByAuthor.statisticByName[author];
if (!stat || stat?.isStaff) return;
let maxDelayDays = 0; const daysReview = DataGripByPR.getPRByGroups(prs, 'daysReview');
refAuthorPR[name].forEach((pr: any) => { const daysReviewWeightedAverage = parseInt(daysReview.weightedAverage.toFixed(1), 10);
if (pr.delayDays > maxDelayDays) maxDelayDays = pr.delayDays;
});
// TODO: сложын и не интересные показатели. Гистаграмму? const daysInWork = DataGripByPR.getPRByGroups(prs, 'daysInWork');
const delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays'); const daysInWorkWeightedAverage = parseInt(daysInWork.weightedAverage.toFixed(1), 10);
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
const workDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'workDays'); this.statisticByName[author] = {
const workDaysWeightedAverage = parseInt(workDays.weightedAverage.toFixed(1), 10); author,
maxDelayDays: daysReview.max,
numberMergedPr: prs.length,
this.statisticByName[name] = { workDays: daysInWork.details,
author: name, delayDays: daysReview.details,
maxDelayDays, weightedAverage: daysInWorkWeightedAverage + daysReviewWeightedAverage,
numberMergedPr: refAuthorPR[name].length,
workDays: workDays.details,
delayDays: delayDays.details,
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
weightedAverageDetails: { weightedAverageDetails: {
workDays: workDaysWeightedAverage, workDays: daysInWorkWeightedAverage,
delayDays: delayDaysWeightedAverage, delayDays: daysReviewWeightedAverage,
}, },
}; };
}); });

View file

@ -28,34 +28,54 @@ export default class DataGripByRelease {
addCommit(commit: ISystemCommit) { addCommit(commit: ISystemCommit) {
if (commit.commitType === COMMIT_TYPE.AUTO_MERGE) { if (commit.commitType === COMMIT_TYPE.AUTO_MERGE) {
if (this.release[commit.branch]) { if (this.release[commit.branch]) {
this.#updateRelease(commit); this.#updateDateInRelease(commit.branch, commit);
} else { } else {
this.#addRelease(commit); this.#addReleaseForBB(commit);
} }
} else if (commit.commitType === COMMIT_TYPE.PR_GITHUB || commit.commitType === COMMIT_TYPE.PR_BITBUCKET) { } else if (commit.commitType === COMMIT_TYPE.PR_GITHUB) {
this.lastPrList.push(commit); if (!this.release[commit.toBranch]) this.#addRelease(commit.toBranch, commit);
if (!this.release[commit.toBranch]) return;
this.#updateDateInRelease(commit.toBranch, commit);
this.#updatePRInRelease(commit.toBranch, commit);
} else if (commit.commitType === COMMIT_TYPE.PR_BITBUCKET) {
this.lastPrList.push(commit.prId);
} }
} }
#updateRelease(commit: ISystemCommit) { #updateDateInRelease(branch: string, commit: ISystemCommit) {
const statistic = this.release[commit.branch]; const statistic = this.release[branch];
statistic.lastCommit = commit; statistic.lastCommit = commit;
statistic.to = commit.timestamp; statistic.to = commit.timestamp;
statistic.delayInDays = getRangeInDay(statistic.firstCommit, commit) || statistic.delayInDays; statistic.delayInDays = getRangeInDay(statistic.firstCommit, commit) || statistic.delayInDays;
} }
#addRelease(commit: ISystemCommit) { #updatePRInRelease(branch: string, commit: ISystemCommit) {
const statistic = this.release[branch];
statistic.prIds.push(commit);
statistic.prLength = statistic.prIds.length;
}
#addReleaseForBB(commit: ISystemCommit) {
if (!commit.branch) return; if (!commit.branch) return;
const index = commit.branch.lastIndexOf('release'); const status = this.#addRelease(commit.branch, commit);
if (index === -1) return; if (!status) return;
const title = commit.branch this.release[commit.branch].prIds = this.lastPrList;
this.release[commit.branch].prLength = this.lastPrList.length;
this.lastPrList = [];
}
#addRelease(branch: string, commit: ISystemCommit) {
const index = branch.lastIndexOf('release');
if (index === -1) return false;
const title = branch
.substring(index + 7) .substring(index + 7)
.replace(/([^\w.]*)/, '') .replace(/([^\w.]*)|(["']*)/gim, '')
.trim(); .trim();
this.release[commit.branch] = { this.release[branch] = {
title, title,
firstCommit: commit, firstCommit: commit,
lastCommit: commit, lastCommit: commit,
@ -63,14 +83,14 @@ export default class DataGripByRelease {
to: null, to: null,
delayInDays: 0, delayInDays: 0,
waitingInDays: 0, waitingInDays: 0,
pr: this.lastPrList, prIds: [],
prLength: this.lastPrList.length, prLength: 0,
}; };
this.lastPrList = []; return true;
} }
updateTotalInfo() { updateTotalInfo(dataGripByTasks: any, dataGripByPR: any) {
let prev: any = null; let prev: any = null;
this.lastPrList = []; this.lastPrList = [];
@ -80,6 +100,14 @@ export default class DataGripByRelease {
.map((a: any) => { .map((a: any) => {
const item = a[1]; const item = a[1];
item.prIds.forEach((prId: string) => {
const pr = dataGripByPR.pr.get(prId);
if (!pr) return;
const task = dataGripByTasks.statisticByName.get(pr.task);
if (!task) return;
task.releaseIds.add(a[0]);
});
item.to = item.from !== item.to && item.to item.to = item.from !== item.to && item.to
? item.lastCommit.date ? item.lastCommit.date
: null; : null;

View file

@ -1,46 +1,40 @@
import ICommit from 'ts/interfaces/Commit'; import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap'; import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
import { ONE_DAY } from 'ts/helpers/formatter'; import { increment } from 'ts/helpers/Math';
export default class DataGripByTasks { export default class DataGripByTasks {
commits: IHashMap<ICommit[]> = {}; commits: HashMap<ICommit[]> = new Map();
statistic: any = []; statistic: any = [];
statisticByName: HashMap<any> = new Map();
// achievements // achievements
longTaskByAuthor: IHashMap<number> = {}; longTaskByAuthor: IHashMap<number> = {};
clear() { clear() {
this.commits = {}; this.commits.clear();
this.statistic = []; this.statistic = [];
this.statisticByName.clear();
this.longTaskByAuthor = {}; this.longTaskByAuthor = {};
} }
addCommit(commit: ICommit) { addCommit(commit: ICommit) {
if (this.commits.hasOwnProperty(commit.task)) { if (this.commits.has(commit.task)) {
this.#updateCommitByTask(commit); this.commits.get(commit.task)?.push(commit);
} else { } else {
this.#addCommitByTask(commit); this.commits.set(commit.task, [commit]);
} }
} }
#updateCommitByTask(commit: ICommit) {
this.commits[commit.task].push(commit);
}
#addCommitByTask(commit: ICommit) {
this.commits[commit.task] = [commit];
}
// TODO: тут двойной пробег получился. А должен был частями собрать инфу // TODO: тут двойной пробег получился. А должен был частями собрать инфу
updateTotalInfo(PRs: any) { updateTotalInfo() {
this.statistic = Object.entries(this.commits) this.statistic = Array.from(this.commits, ([k, v]) => [k, v]) // @ts-ignore
.map(([task, commits]: [string, ICommit[]]) => { .map(([task, commits]: [string, ICommit[]]) => {
const firstCommit = commits[0]; const firstCommit = commits[0];
const lastCommit = commits[commits.length - 1]; const lastCommit = commits[commits.length - 1];
const from = firstCommit.milliseconds; const from = firstCommit.milliseconds;
const pr = PRs.prByTask.get(task) ? PRs.pr.get(PRs.prByTask.get(task)) : null;
const shortInfo = { const shortInfo = {
task, task,
@ -48,30 +42,31 @@ export default class DataGripByTasks {
from, from,
commits: 1, commits: 1,
daysInWork: 1, daysInWork: 1,
prDate: pr?.milliseconds, prIds: [],
prDelayDays: pr?.delayDays, releaseIds: new Set(),
prAuthor: firstCommit.author === pr?.author ? null : pr?.author,
comments: firstCommit.text, comments: firstCommit.text,
types: firstCommit.type && firstCommit.type !== '—' ? [firstCommit.type] : [], types: firstCommit.type ? { [firstCommit.type]: 1 } : {},
scope: firstCommit.scope && firstCommit.scope !== '—' ? [firstCommit.scope] : [], scope: firstCommit.scope ? { [firstCommit.scope]: 1 } : {},
}; };
if (commits.length === 1) return shortInfo; if (commits.length === 1) return shortInfo;
const authors = new Set();
const messages = new Set(); const messages = new Set();
const types = new Set(); const timestamps = new Set();
const scope = new Set(); const authors = {};
const types = {};
const scope = {};
commits.forEach((commit: ICommit) => { commits.forEach((commit: ICommit) => {
authors.add(commit.author);
messages.add(commit.text); messages.add(commit.text);
if (commit.type !== '—') types.add(commit.type); timestamps.add(commit.milliseconds);
if (commit.scope !== '—') scope.add(commit.scope); increment(authors, commit.author);
increment(types, commit.type);
increment(scope, commit.scope);
}); });
const comments = Array.from(messages).join(', '); const comments = Array.from(messages).join(', ');
const to = lastCommit.milliseconds; const to = lastCommit.milliseconds;
const daysInWork = Math.ceil((to - from) / ONE_DAY) + 1; const daysInWork = timestamps.size;
const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author]; const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author];
if (!longTaskByAuthor || longTaskByAuthor < daysInWork) { if (!longTaskByAuthor || longTaskByAuthor < daysInWork) {
@ -82,16 +77,21 @@ export default class DataGripByTasks {
...shortInfo, ...shortInfo,
to: to !== from ? to : undefined, to: to !== from ? to : undefined,
commits: commits.length, commits: commits.length,
timestamps: Array.from(timestamps),
daysInWork, daysInWork,
authors: Array.from(authors),
comments, comments,
types: Array.from(types), authors,
scope: Array.from(scope), types,
scope,
}; };
}) })
.filter((dot) => dot.task) .filter((dot) => dot.task)
.sort((dotA, dotB) => dotB.from - dotA.from); .sort((dotA, dotB) => dotB.from - dotA.from);
this.commits = {}; this.statistic.forEach((item: any) => {
this.statisticByName.set(item.task, item);
});
this.commits.clear();
} }
} }

View file

@ -15,6 +15,7 @@ import DataGripByTasks from './components/tasks';
import DataGripByRelease from './components/release'; import DataGripByRelease from './components/release';
import DataGripByScoring from './components/scoring'; import DataGripByScoring from './components/scoring';
import DataGripByCompany from './components/company'; import DataGripByCompany from './components/company';
import DataGripByCountry from './components/country';
class DataGrip { class DataGrip {
firstLastCommit: any = new MinMaxCounter(); firstLastCommit: any = new MinMaxCounter();
@ -23,6 +24,8 @@ class DataGrip {
company: any = new DataGripByCompany(); company: any = new DataGripByCompany();
country: any = new DataGripByCountry();
team: any = new DataGripByTeam(); team: any = new DataGripByTeam();
scope: any = new DataGripByScope(); scope: any = new DataGripByScope();
@ -49,6 +52,7 @@ class DataGrip {
this.firstLastCommit.clear(); this.firstLastCommit.clear();
this.author.clear(); this.author.clear();
this.company.clear(); this.company.clear();
this.country.clear();
this.team.clear(); this.team.clear();
this.scope.clear(); this.scope.clear();
this.type.clear(); this.type.clear();
@ -75,7 +79,6 @@ class DataGrip {
this.get.addCommit(commit); this.get.addCommit(commit);
this.week.addCommit(commit); this.week.addCommit(commit);
this.tasks.addCommit(commit); this.tasks.addCommit(commit);
this.company.addCommit(commit);
} }
} }
@ -87,11 +90,12 @@ class DataGrip {
this.timestamp.updateTotalInfo(this.author); this.timestamp.updateTotalInfo(this.author);
this.week.updateTotalInfo(this.author); this.week.updateTotalInfo(this.author);
this.recommendations.updateTotalInfo(this); this.recommendations.updateTotalInfo(this);
this.pr.updateTotalInfo(this.author); this.tasks.updateTotalInfo();
this.tasks.updateTotalInfo(this.pr); this.pr.updateTotalInfo(this.tasks, this.author);
this.release.updateTotalInfo(); this.release.updateTotalInfo(this.tasks, this.pr);
this.scoring.updateTotalInfo(this.author, this.timestamp); this.scoring.updateTotalInfo(this.author, this.timestamp);
this.company.updateTotalInfo(this.author); this.company.updateTotalInfo(this.author);
this.country.updateTotalInfo(this.author);
} }
} }

View file

@ -109,3 +109,24 @@ export const FAKE_EMAILS = FAKE_AUTHORS
export const FAKE_TASK_PREFIXES = 'axeurtyqwpsdfghjklzcvbnm' export const FAKE_TASK_PREFIXES = 'axeurtyqwpsdfghjklzcvbnm'
.split('') .split('')
.map((symbol) => (new Array(5)).fill(symbol.toUpperCase()).join('')); .map((symbol) => (new Array(5)).fill(symbol.toUpperCase()).join(''));
export const FAKE_COMPANIES = [
'Apple',
'Microsoft',
'Samsung Electronics',
'Alphabet',
'AT&T', 'Amazon',
'Verizon Communications',
'China Mobile',
'Walt Disney',
'Facebook',
'Alibaba',
'Intel',
'Softbank',
'IBM',
'Tencent Holdings',
'Cisco Systems',
'Oracle',
'Deutsche Telekom',
'Taiwan Semiconductor',
];

View file

@ -4,6 +4,7 @@ import {
FAKE_AUTHORS, FAKE_AUTHORS,
FAKE_EMAILS, FAKE_EMAILS,
FAKE_TASK_PREFIXES, FAKE_TASK_PREFIXES,
FAKE_COMPANIES,
} from './constants'; } from './constants';
import FakeName from './FakeName'; import FakeName from './FakeName';
@ -14,21 +15,26 @@ export default class Depersonalized {
fakeTaskPrefix: any = null; fakeTaskPrefix: any = null;
fakeCompany: any = null;
constructor() { constructor() {
this.fakeName = new FakeName('User', FAKE_AUTHORS); this.fakeName = new FakeName('User', FAKE_AUTHORS);
this.fakeEmail = new FakeName('user', FAKE_EMAILS); this.fakeEmail = new FakeName('user', FAKE_EMAILS);
this.fakeTaskPrefix = new FakeName('JIRA', FAKE_TASK_PREFIXES); this.fakeTaskPrefix = new FakeName('JIRA', FAKE_TASK_PREFIXES);
this.fakeCompany = new FakeName('Company', FAKE_COMPANIES);
} }
getCommit(commit: ICommit | ISystemCommit): ICommit | ISystemCommit { getCommit(commit: ICommit | ISystemCommit): ICommit | ISystemCommit {
const author = this.fakeName.get(commit.author); const author = this.fakeName.get(commit.author);
const email = this.fakeEmail.get(commit.author); const email = this.fakeEmail.get(commit.author);
const company = this.fakeCompany.get(commit.company);
if (!commit.task) { if (!commit.task) {
return { return {
...commit, ...commit,
author, author,
email, email,
company,
}; };
} }
@ -53,6 +59,7 @@ export default class Depersonalized {
message, message,
author, author,
email, email,
company,
branch, branch,
toBranch, toBranch,
}; };

View file

@ -35,6 +35,7 @@ export default class FileBuilderCommon {
// @ts-ignore // @ts-ignore
const parts = file.path.split('/'); const parts = file.path.split('/');
parts.pop(); parts.pop();
file.pathString = file.path;
file.path = parts; file.path = parts;
} }
} }

View file

@ -5,6 +5,7 @@ import { increment } from 'ts/helpers/Math';
import FileBuilderCommon from './Common'; import FileBuilderCommon from './Common';
import FileBuilderLineStat from './LineStat'; import FileBuilderLineStat from './LineStat';
import FileBuilderTasks from './Tasks';
export default class FileGripByPaths { export default class FileGripByPaths {
list: IDirtyFile[] = []; list: IDirtyFile[] = [];
@ -39,17 +40,20 @@ export default class FileGripByPaths {
#getNewDirtyFile(fileChange: IFileChange, commit: ICommit): any { #getNewDirtyFile(fileChange: IFileChange, commit: ICommit): any {
const commonProps = FileBuilderCommon.getProps(fileChange, commit); const commonProps = FileBuilderCommon.getProps(fileChange, commit);
const statProps = FileBuilderLineStat.getProps(fileChange, commit); const statProps = FileBuilderLineStat.getProps(fileChange, commit);
const tasksProps = FileBuilderTasks.getProps(commit);
return { return {
id: fileChange.id, id: fileChange.id,
...commonProps, ...commonProps,
...statProps, ...statProps,
...tasksProps,
}; };
} }
#updateDirtyFile(file: any, fileChange: IFileChange, commit: ICommit) { #updateDirtyFile(file: any, fileChange: IFileChange, commit: ICommit) {
FileBuilderCommon.updateProps(file, fileChange, commit); FileBuilderCommon.updateProps(file, fileChange, commit);
FileBuilderLineStat.updateProps(file, fileChange, commit); FileBuilderLineStat.updateProps(file, fileChange, commit);
FileBuilderTasks.updateProps(file, commit);
} }
#renameFile(file: any, newId: string) { #renameFile(file: any, newId: string) {
@ -74,6 +78,7 @@ export default class FileGripByPaths {
FileBuilderCommon.updateTotal(file); FileBuilderCommon.updateTotal(file);
FileBuilderLineStat.updateTotal(file); FileBuilderLineStat.updateTotal(file);
FileBuilderTasks.updateTotal(file);
if (file.type) { if (file.type) {
let refExtensionType = this.refExtensionType.get(file.extension); let refExtensionType = this.refExtensionType.get(file.extension);

View file

@ -4,12 +4,25 @@ import IHashMap from 'ts/interfaces/HashMap';
import { getValuesInPercent } from '../helpers'; import { getValuesInPercent } from '../helpers';
function getFolder(name?: string, path?: string[], file?: IDirtyFile): IFolder { function getFolder(name?: string, path?: string[], file?: IDirtyFile): IFolder {
const tasks = file?.tasks
? new Set(file.tasks)
: new Set();
const timestamp = file?.timestamp
? new Set(file.timestamp) as Set<string>
: new Set();
return { return {
id: Math.random(), id: Math.random(),
name: name || '', // @ts-ignore name: name || '', // @ts-ignore
path: path || [], path: path || [],
pathString: `${(path || []).join('/')}/${name || ''}`, pathString: `${(path || []).join('/')}/${name || ''}`,
content: {}, content: new Map(),
tasks: tasks as Set<string>,
timestamp: timestamp as Set<string>,
totalTasks: tasks.size,
totalDays: timestamp.size,
lines: file?.lines || 0, lines: file?.lines || 0,
@ -48,6 +61,12 @@ function updateFolder(folder: any, file: IDirtyFile) {
folder.removedLines += file.removedLines || 0; folder.removedLines += file.removedLines || 0;
folder.changedLines += file.changedLines || 0; folder.changedLines += file.changedLines || 0;
// TODO: bad performance
folder.tasks = new Set([...folder.tasks, ...file.tasks]);
folder.timestamp = new Set([...folder.timestamp, ...file.timestamp]);
folder.totalTasks = folder.tasks.size;
folder.totalDays = folder.timestamp.size;
updateFolderBy(folder, file, 'addedLinesByAuthor'); updateFolderBy(folder, file, 'addedLinesByAuthor');
updateFolderBy(folder, file, 'removedLinesByAuthor'); updateFolderBy(folder, file, 'removedLinesByAuthor');
updateFolderBy(folder, file, 'changedLinesByAuthor'); updateFolderBy(folder, file, 'changedLinesByAuthor');
@ -69,17 +88,19 @@ export default class FileGripByFolder {
addFile(file: IDirtyFile) { addFile(file: IDirtyFile) {
let prev: any = this.tree.content; let prev: any = this.tree.content;
file.path.forEach((folderName: any, index: number) => { file.path.forEach((folderName: any, index: number) => {
let folder = prev[folderName]; const folder = prev.get(folderName);
if (!folder || !folder.content) { if (!folder?.content) {
const path = file.path.slice(0, index); const path = file.path.slice(0, index);
prev[folderName] = getFolder(folderName, path, file); const newFolder = getFolder(folderName, path, file);
this.folders.push(prev[folderName]); prev.set(folderName, newFolder);
this.folders.push(newFolder);
prev = newFolder.content;
} else { } else {
updateFolder(folder, file); updateFolder(folder, file);
prev = folder.content;
} }
prev = prev[folderName].content;
}); });
prev[file.name] = file; prev.set(file.name, file);
} }
updateTotalInfo() { updateTotalInfo() {

View file

@ -6,6 +6,7 @@ import FileGripByExtension from './components/extension';
import FileGripByType from './components/type'; import FileGripByType from './components/type';
import FileGripByFolder from './components/folder'; import FileGripByFolder from './components/folder';
import FileGripByAuthor from './components/author'; import FileGripByAuthor from './components/author';
import FileGripByRefactor from './components/refactor';
class FileGrip { class FileGrip {
files: any = new FileBuilder(); files: any = new FileBuilder();
@ -20,6 +21,8 @@ class FileGrip {
author: any = new FileGripByAuthor(); author: any = new FileGripByAuthor();
refactor: any = new FileGripByRefactor();
clear() { clear() {
this.files.clear(); this.files.clear();
this.extension.clear(); this.extension.clear();
@ -27,6 +30,7 @@ class FileGrip {
this.tree.clear(); this.tree.clear();
this.removedTree.clear(); this.removedTree.clear();
this.author.clear(); this.author.clear();
this.refactor.clear();
} }
addCommit(commit: ICommit | ISystemCommit) { addCommit(commit: ICommit | ISystemCommit) {
@ -54,6 +58,7 @@ class FileGrip {
this.author.updateTotalInfo(); this.author.updateTotalInfo();
this.tree.updateTotalInfo(); this.tree.updateTotalInfo();
this.removedTree.updateTotalInfo(); this.removedTree.updateTotalInfo();
this.refactor.updateTotalInfo(this.files.list);
} }
} }

View file

@ -2,7 +2,8 @@ import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap'; import IHashMap from 'ts/interfaces/HashMap';
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope'; import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
import getCompany from './getCompany'; import getInfoFromNameAndEmail from './getCompany';
import { getGithubPrInfo } from './getMergeInfo';
const MASTER_BRANCH = { const MASTER_BRANCH = {
master: true, master: true,
@ -35,6 +36,7 @@ export default function getCommitInfo(
prevDate = date; prevDate = date;
const day = date.getDay() - 1; const day = date.getDay() - 1;
const timestamp = sourceDate.substring(0, 10); // split('T')[0]; const timestamp = sourceDate.substring(0, 10); // split('T')[0];
const timezone = sourceDate.substring(19, 25);
let milliseconds = refTimestampTime.get(timestamp); let milliseconds = refTimestampTime.get(timestamp);
if (!milliseconds) { if (!milliseconds) {
milliseconds = (new Date(timestamp)).getTime(); milliseconds = (new Date(timestamp)).getTime();
@ -47,12 +49,11 @@ export default function getCommitInfo(
const companyKey = `${author}>in>${email}`; const companyKey = `${author}>in>${email}`;
if (!refEmailAuthor[companyKey]) { if (!refEmailAuthor[companyKey]) {
const companyForKey = getCompany(author, email);
// @ts-ignore // @ts-ignore
refEmailAuthor[companyKey] = { company: companyForKey }; refEmailAuthor[companyKey] = getInfoFromNameAndEmail(author, email);
} }
// @ts-ignore // @ts-ignore
const company = refEmailAuthor[companyKey].company; const { company, country, device } = refEmailAuthor[companyKey];
const authorID = author.replace(/\s|\t/gm, ''); const authorID = author.replace(/\s|\t/gm, '');
if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) { if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) {
@ -86,6 +87,7 @@ export default function getCommitInfo(
month: date.getMonth(), month: date.getMonth(),
year: date.getUTCFullYear(), year: date.getUTCFullYear(),
week: 0, week: 0,
timezone,
timestamp, timestamp,
milliseconds, milliseconds,
@ -93,10 +95,12 @@ export default function getCommitInfo(
email, email,
message, message,
company, company,
country,
device,
text: '', text: '',
type: '', type: '',
scope: '', scope: '',
fileChanges: [], fileChanges: [],
}; };
@ -114,24 +118,29 @@ export default function getCommitInfo(
if (isSystemCommit) { if (isSystemCommit) {
let commitType = COMMIT_TYPE.MERGE; let commitType = COMMIT_TYPE.MERGE;
let prId, repository, branch, toBranch, task, taskNumber; let prId, repository, branch, toBranch, task, taskNumber, type, scope;
if (isGithubPR) { if (isGithubPR) {
// "Merge pull request #3 in repository from TASK-123-add-profile to master"
// "Merge pull request #3 from facebook/compiler"
commitType = COMMIT_TYPE.PR_GITHUB; commitType = COMMIT_TYPE.PR_GITHUB;
[, prId, repository, branch, toBranch ] = message [prId, repository, branch, toBranch] = getGithubPrInfo(message);
.replace(/(Merge\spull\srequest\s#)|(\sfrom\s)|(\sin\s)|(\sto\s)/gim, ',')
.split(',');
task = getTask(branch); task = getTask(branch);
} else if (isBitbucketPR) {
} else if (isBitbucketPR) { // "Pull request #3: TASK-123 fix: Add profile"
commitType = COMMIT_TYPE.PR_BITBUCKET; commitType = COMMIT_TYPE.PR_BITBUCKET;
const messageParts = message.substring(14).split(':'); const messageParts = message.substring(14).split(':');
prId = messageParts.shift(); prId = messageParts.shift();
task = getTask(messageParts.join(':')); const description = messageParts.join(':');
} else if (isAutoMerge) { task = getTask(description);
[type, scope] = getTypeAndScope(description, task);
} else if (isAutoMerge) { // "Automatic merge from release/release-2.8.0 -> master"
commitType = COMMIT_TYPE.AUTO_MERGE; commitType = COMMIT_TYPE.AUTO_MERGE;
[, branch, toBranch ] = message [, branch, toBranch ] = message
.replace(/(Automatic\smerge\sfrom\s)|(\s->\s)/gim, ',') .replace(/(Automatic\smerge\sfrom\s)|(\s->\s)/gim, ',')
.replace(/(Merge\sremote-tracking\sbranch\s')|('\sinto\s)/gim, ',') .replace(/(Merge\sremote-tracking\sbranch\s')|('\sinto\s)/gim, ',')
.split(','); .split(',');
} else if (isGitlabPR) { } else if (isGitlabPR) {
commitType = COMMIT_TYPE.PR_GITLAB; commitType = COMMIT_TYPE.PR_GITLAB;
[, branch, toBranch ] = message [, branch, toBranch ] = message
@ -147,6 +156,8 @@ export default function getCommitInfo(
return { return {
...commonInfo, ...commonInfo,
type,
scope,
prId: prId || '', prId: prId || '',
task: task || '', task: task || '',
taskNumber: taskNumber || '', taskNumber: taskNumber || '',
@ -155,25 +166,25 @@ export default function getCommitInfo(
toBranch: toBranch || '', toBranch: toBranch || '',
commitType, commitType,
}; };
} else {
const textIndex = (message || '').indexOf(':');
const text = textIndex > 1
? message.substring(textIndex + 2).trim()
: message;
const task = getTask(message);
const taskNumber = getTaskNumber(task);
const [type, scope] = getTypeAndScope(message, task);
return {
...commonInfo,
task,
taskNumber,
text,
type: type || '',
scope: scope || '',
changes: 0,
added: 0,
removed: 0,
};
} }
const textIndex = (message || '').indexOf(':');
const text = textIndex > 1
? message.substring(textIndex + 2).trim()
: message;
const task = getTask(message);
const taskNumber = getTaskNumber(task);
const [type, scope] = getTypeAndScope(message, task);
return {
...commonInfo,
task,
taskNumber,
text,
type: type || '—',
scope: scope || '—',
changes: 0,
added: 0,
removed: 0,
};
} }

View file

@ -1,6 +1,10 @@
import getCountryByDomain from './getCountryByDomain';
import getDevice from './getDevice';
const PUBLIC_SERVICES = [ const PUBLIC_SERVICES = [
'icloud', 'icloud',
'google', 'google',
'inbox',
'yahoo', 'yahoo',
'aol', 'aol',
'zoho', 'zoho',
@ -25,6 +29,8 @@ const isPublicService = Object.fromEntries(
Object.values(PUBLIC_SERVICES).map(key => [key.toUpperCase(), true]), Object.values(PUBLIC_SERVICES).map(key => [key.toUpperCase(), true]),
); );
const isIP = /([0-9]{1,3}(-|_|.)[0-9]{1,3}(-|_|.)[0-9]{1,3}(-|_|.)[0-9]{1,3})/;
function getCompanyByName(author?: string): string { function getCompanyByName(author?: string): string {
const tags = (author || '') const tags = (author || '')
.toUpperCase() .toUpperCase()
@ -39,11 +45,12 @@ function getCompanyByName(author?: string): string {
: ''; : '';
} }
function getCompanyByEmail(email?: string) { function getCompanyAndDomainByEmail(email?: string) {
const domain = (email || '').split('@').pop() || ''; const fullDomain = (email || '').split('@').pop() || '';
const parts = domain.split('.'); const parts = fullDomain.split('.');
parts.pop(); const domain = parts.pop();
return (parts.pop() || '').toUpperCase(); const company = (parts.pop() || '').toUpperCase();
return [company, domain];
} }
function getClearText(text: string) { function getClearText(text: string) {
@ -63,12 +70,19 @@ function isUserName(author?: string, company?: string): boolean {
return !!clearAuthor.match(clearCompany); return !!clearAuthor.match(clearCompany);
} }
function getCompany(author?: string, email?: string) { export default function getInfoFromNameAndEmail(author?: string, email?: string) {
const company = getCompanyByName(author) || getCompanyByEmail(email) || ''; const companyByAuthor = getCompanyByName(author);
const isMailService = company.indexOf('MAIL') !== -1; const [companyByEmail, domain] = getCompanyAndDomainByEmail(email);
return isPublicService[company] || isMailService || isUserName(author, company) const country = getCountryByDomain(domain);
? '' const device = getDevice(companyByEmail);
: company;
}
export default getCompany; const companyName = companyByAuthor || companyByEmail || '';
const isMailService = companyName.indexOf('MAIL') !== -1;
const isInCorrect = isPublicService[companyName]
|| isMailService
|| isUserName(author, companyName)
|| isIP.test(companyName);
const company = (!isInCorrect && !device) ? companyName : '';
return { company, country, device };
}

View file

@ -21,14 +21,17 @@ export interface ILog {
minutes: number; // 59, minutes: number; // 59,
month: number; // 1, month: number; // 1,
year: number; // 2021, year: number; // 2021,
timestamp: string; // 2021-02-09", timezone: string; // "+03:00",
timestamp: string; // "2021-02-09",
milliseconds: number; // 1612828800000, milliseconds: number; // 1612828800000,
week: number; // 42, week: number; // 42,
// user // user
author: string; // "Dart Vader", author: string; // "Dart Vader",
email: string; // "d.vader@emap.com", email: string; // "d.vader@emap.ru",
company: string; // "emap", company: string; // "emap",
country: string; // "ru",
device: string; // "Macbook",
// task // task
message: string; // "JIRA-0000 fix(profile): add new avatar", message: string; // "JIRA-0000 fix(profile): add new avatar",

View file

@ -1,7 +1,12 @@
import ICommit, { ISystemCommit } from './Commit'; import ICommit, { ISystemCommit } from './Commit';
import IHashMap from './HashMap'; import IHashMap, { HashMap } from './HashMap';
interface IFileStat { interface IFileStat {
tasks: Set<string>; // ['JIRA-123', 'JIRA-444']
timestamp: Set<string>; // ['2021-02-09', '2021-03-09', '2021-04-09']
totalTasks: number; // 2
totalDays: number; // 3
lines: number; // 38, line in file for this moment lines: number; // 38, line in file for this moment
addedLines: number; addedLines: number;
@ -35,5 +40,5 @@ export interface IFolder extends IFileStat {
name?: string; name?: string;
path: string[]; // ['src'] path: string[]; // ['src']
pathString: string; // 'src\\ts' pathString: string; // 'src\\ts'
content: IHashMap<IDirtyFile>, content: HashMap<IDirtyFile>,
} }

View file

@ -37,10 +37,16 @@ export const TEAM = [
icon: './assets/menu/team_type.svg', icon: './assets/menu/team_type.svg',
}, },
{ {
id: 'pr', id: 'company',
link: '/team/pr', link: '/team/company',
title: 'sidebar.team.pr', title: 'sidebar.team.company',
icon: './assets/menu/pull_request.svg', icon: './assets/menu/company.svg',
},
{
id: 'country',
link: '/team/country',
title: 'sidebar.team.country',
icon: './assets/menu/country.svg',
}, },
{}, {},
{ {
@ -86,6 +92,19 @@ export const TEAM = [
title: 'sidebar.team.extension', title: 'sidebar.team.extension',
icon: './assets/menu/team_files_ext.svg', icon: './assets/menu/team_files_ext.svg',
}, },
{
id: 'refactor',
link: '/team/refactor',
title: 'sidebar.team.refactor',
icon: './assets/menu/refactor.svg',
},
{},
{
id: 'release',
link: '/team/release',
title: 'sidebar.team.release',
icon: './assets/menu/team_release.svg',
},
{ {
id: 'tasks', id: 'tasks',
link: '/team/tasks', link: '/team/tasks',
@ -93,10 +112,10 @@ export const TEAM = [
icon: './assets/menu/team_tasks.svg', icon: './assets/menu/team_tasks.svg',
}, },
{ {
id: 'release', id: 'pr',
link: '/team/release', link: '/team/pr',
title: 'sidebar.team.release', title: 'sidebar.team.pr',
icon: './assets/menu/team_release.svg', icon: './assets/menu/pull_request.svg',
}, },
{}, {},
{ {

View file

@ -10,7 +10,7 @@ import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter'; import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound'; import NothingFound from 'ts/components/NothingFound';
import { TasksView } from 'ts/pages/Team/components/Tasks'; import TasksView from 'ts/pages/Team/components/Tasks/View';
import IPersonCommonProps from '../interfaces/CommonProps'; import IPersonCommonProps from '../interfaces/CommonProps';

View file

@ -1,262 +0,0 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useTranslation } from 'react-i18next';
import ISort from 'ts/interfaces/Sort';
import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap';
import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination';
import { getDate, getMoney, getShortNumber } from 'ts/helpers/formatter';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import PageWrapper from 'ts/components/Page/wrapper';
import PageColumn from 'ts/components/Page/column';
import UiKitTags from 'ts/components/UiKit/components/Tags';
import DataLoader from 'ts/components/DataLoader';
import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title';
import DataView from 'ts/components/DataView';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import Recommendations from 'ts/components/Recommendations';
import { getMax, getMaxByLength } from 'ts/pages/Common/helpers/getMax';
import Description from 'ts/components/Description';
interface AuthorViewProps {
response?: IPagination<any>;
updateSort?: Function;
rowsForExcel?: any[];
mode?: string;
}
export function AuthorView({ response, updateSort, rowsForExcel, mode }: AuthorViewProps) {
const { t } = useTranslation();
if (!response) return null;
const [works, dismissed, staff] = [
t('page.team.author.type.work'),
t('page.team.author.type.dismissed'),
t('page.team.author.type.staff'),
];
const textWork = t('page.team.author.worked');
const textLosses = t('page.team.author.losses');
const daysWorked = getOptions({ order: [textWork, textLosses], suffix: 'page.team.author.days' });
const taskChart = getOptions({ max: getMaxByLength(response, 'tasks'), suffix: 'page.team.author.tasksSmall' });
const commitsChart = getOptions({ max: getMax(response, 'commits') });
const typeChart = getOptions({ order: dataGripStore.dataGrip.type.list });
return (
<DataView
rowsForExcel={rowsForExcel}
rows={response.content}
sort={response.sort}
updateSort={updateSort}
mode={mode}
type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined}
>
<Column
isFixed
template={ColumnTypesEnum.STRING}
formatter={(row: any, index: number) => (index + 1)}
width={40}
/>
<Column
isFixed
template={ColumnTypesEnum.STRING}
properties="author"
title="page.team.pr.author"
width={200}
/>
<Column
title="page.team.author.status"
formatter={(row: any) => {
if (row.isStaff) return staff;
if (row.isDismissed) return dismissed;
return works;
}}
template={(value: string) => <UiKitTags value={value} />}
width={100}
/>
<Column
isSortable="company"
title="page.team.author.company"
properties="lastCompany"
template={(value: string) => <UiKitTags value={value} />}
width={150}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="firstCommit"
title="page.team.author.firstCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="lastCommit"
title="page.team.author.lastCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
title="page.team.author.daysAll"
properties="daysAll"
formatter={(value: number) => value || 1}
width={90}
/>
<Column
isSortable="daysWorked"
title="page.team.author.workedLosses"
minWidth={300}
template={(details: any) => (
<LineChart
options={daysWorked}
details={details}
/>
)}
formatter={(row: any) => {
return { [textWork]: row.daysWorked, [textLosses]: row.daysLosses };
}}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="tasks"
formatter={(tasks: string[]) => (tasks?.length || 0)}
/>
<Column
isSortable
properties="tasks"
title="page.team.author.tasks"
minWidth={200}
template={(value: number) => (
<LineChart
options={taskChart}
value={value}
/>
)}
formatter={(tasks: any) => (tasks?.length || 0)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
title="page.team.author.daysForTask"
properties="daysForTask"
formatter={getShortNumber}
width={120}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
title="page.team.author.scopes"
properties="scopes"
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="commits"
/>
<Column
isSortable
title="page.team.author.commits"
properties="commits"
minWidth={100}
template={(value: number) => (
<LineChart
options={commitsChart}
value={value}
/>
)}
/>
<Column
title="page.team.author.types"
properties="types"
width={400}
template={(details: IHashMap<number>) => (
<LineChart
options={typeChart}
details={details}
/>
)}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyAll"
properties="moneyAll"
formatter={getMoney}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyWorked"
properties="moneyWorked"
formatter={getMoney}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyLosses"
properties="moneyLosses"
formatter={getMoney}
/>
</DataView>
);
}
AuthorView.defaultProps = {
response: undefined,
};
const Author = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const { t } = useTranslation();
const rows = dataGripStore.dataGrip.author.statistic;
if (!rows?.length) {
return mode !== 'print' ? (<NothingFound />) : null;
}
const recommendations = dataGripStore.dataGrip.recommendations.team?.byAuthor;
return (
<>
{mode !== 'fullscreen' && (
<Recommendations
mode={mode}
recommendations={recommendations}
/>
)}
<Title title="page.team.author.title"/>
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.hash}`}
>
<AuthorView
mode={mode}
rowsForExcel={rows}
/>
<Pagination />
</DataLoader>
<PageWrapper>
<PageColumn>
<Description
text={t('page.team.author.description1')}
/>
</PageColumn>
<PageColumn>
<Description
text={t('page.team.author.description2')}
/>
</PageColumn>
</PageWrapper>
</>
);
});
export default Author;

View file

@ -1,143 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import ICommit from 'ts/interfaces/Commit';
import { IPagination } from 'ts/interfaces/Pagination';
import { getDate } from 'ts/helpers/formatter';
import dataGripStore from 'ts/store/DataGrip';
import UiKitTags from 'ts/components/UiKit/components/Tags';
import DataView from 'ts/components/DataView';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import Employments from './Employments';
interface CompaniesProps {
response?: IPagination<any>;
updateSort?: Function;
rowsForExcel?: any[];
mode?: string;
}
function Companies({ response, updateSort, rowsForExcel, mode }: CompaniesProps) {
const { t } = useTranslation();
if (!response) return null;
const [works, dismissed] = [
t('page.team.author.type.work'),
t('page.team.author.type.dismissed'),
];
const taskChart = getOptions({ max: getMax(response, 'totalTasks'), suffix: 'page.team.author.tasksSmall' });
const daysChart = getOptions({ max: getMax(response, 'totalDays'), suffix: 'page.team.author.days' });
return (
<DataView
rowsForExcel={rowsForExcel}
rows={response.content}
sort={response.sort}
updateSort={updateSort}
type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined}
>
<Column
isFixed
template={ColumnTypesEnum.DETAILS}
width={40}
formatter={(row: any) => {
const content = row.employments.map((name: string) => (
dataGripStore?.dataGrip?.author?.statisticByName?.[name]
)).filter((v: any) => v);
return (
<Employments // @ts-ignore
response={{ content }}
mode="details"
/>
);
}}
/>
<Column
isFixed
template={ColumnTypesEnum.STRING}
properties="company"
title="page.team.pr.author"
width={200}
/>
<Column
title="page.team.author.status"
formatter={(row: any) => (row.isActive ? works : dismissed)}
template={(value: string) => <UiKitTags value={value} />}
width={100}
/>
{/*<Column*/}
{/* template={ColumnTypesEnum.SHORT_NUMBER}*/}
{/* title="page.team.company.people"*/}
{/* properties="totalEmployments"*/}
{/* width={90}*/}
{/*/>*/}
<Column
template={ColumnTypesEnum.STRING}
properties="firstCommit"
title="page.team.author.firstCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="lastCommit"
title="page.team.author.lastCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="totalDays"
width={90}
/>
<Column
isSortable="totalDays"
title="page.team.author.daysAll"
properties="totalDays"
width={150}
template={(value: number) => (
<LineChart
options={daysChart}
value={value}
/>
)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="totalTasks"
width={90}
/>
<Column
isSortable="totalTasks"
title="page.team.author.tasks"
properties="totalTasks"
width={150}
template={(value: number) => (
<LineChart
options={taskChart}
value={value}
/>
)}
/>
<Column
properties="emptyCell"
minWidth={40}
/>
</DataView>
);
}
Companies.defaultProps = {
response: undefined,
};
export default Companies;

View file

@ -1,146 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import ICommit from 'ts/interfaces/Commit';
import { IPagination } from 'ts/interfaces/Pagination';
import { getDate, getMoney } from 'ts/helpers/formatter';
import dataGripStore from 'ts/store/DataGrip';
import UiKitTags from 'ts/components/UiKit/components/Tags';
import DataView from 'ts/components/DataView';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
interface EmploymentsProps {
response?: IPagination<any>;
updateSort?: Function;
rowsForExcel?: any[];
mode?: string;
}
export function Employments({ response, updateSort, rowsForExcel, mode }: EmploymentsProps) {
const { t } = useTranslation();
if (!response) return null;
const [works, dismissed, staff] = [
t('page.team.author.type.work'),
t('page.team.author.type.dismissed'),
t('page.team.author.type.staff'),
];
const textWork = t('page.team.author.worked');
const textLosses = t('page.team.author.losses');
const daysWorked = getOptions({ order: [textWork, textLosses], suffix: 'page.team.author.days' });
const typeChart = getOptions({
suffix: 'page.team.author.tasksSmall',
order: dataGripStore.dataGrip.type.list,
});
return (
<DataView
rowsForExcel={rowsForExcel}
rows={response.content}
sort={response.sort}
updateSort={updateSort}
mode={mode}
type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined}
>
<Column
isFixed
template={ColumnTypesEnum.STRING}
formatter={(row: any, index: number) => (index + 1)}
width={40}
/>
<Column
isFixed
template={ColumnTypesEnum.STRING}
properties="author"
title="page.team.pr.author"
width={158}
/>
<Column
formatter={(row: any) => {
if (row.isStaff) return staff;
if (row.isDismissed) return dismissed;
return works;
}}
template={(value: string) => <UiKitTags value={value} />}
width={100}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="firstCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="lastCommit"
width={130}
formatter={(commit: ICommit) => getDate(commit.timestamp)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="daysAll"
formatter={(value: number) => value || 1}
width={90}
/>
<Column
isSortable="daysWorked"
width={150}
template={(details: any) => (
<LineChart
options={daysWorked}
details={details}
/>
)}
formatter={(row: any) => {
return { [textWork]: row.daysWorked, [textLosses]: row.daysLosses };
}}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="tasks"
formatter={(tasks: string[]) => (tasks?.length || 0)}
width={90}
/>
<Column
isSortable
width={150}
template={(row: any) => (
<LineChart
options={typeChart}
details={row.types}
/>
)}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyAll"
properties="moneyAll"
formatter={getMoney}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyWorked"
properties="moneyWorked"
formatter={getMoney}
/>
<Column
template={ColumnTypesEnum.NUMBER}
title="page.team.author.moneyLosses"
properties="moneyLosses"
formatter={getMoney}
/>
</DataView>
);
}
Employments.defaultProps = {
response: undefined,
};
export default Employments;

View file

@ -11,30 +11,32 @@ import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter'; import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound'; import NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title'; import Title from 'ts/components/Title';
import Companies from './Companies'; import Companies from './components/Companies';
import CompanyCharts from './components/Charts';
const Company = observer(({ const Company = observer(({
mode, mode,
}: ICommonPageProps): React.ReactElement | null => { }: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.company.statistic; const companyRows = dataGripStore.dataGrip.company.statistic;
if (!rows?.length) { if (!companyRows?.length) {
return mode !== 'print' ? (<NothingFound />) : null; return mode !== 'print' ? (<NothingFound />) : null;
} }
return ( return (
<> <>
<Title title="page.team.author.title"/> <CompanyCharts />
<Title title="page.team.company.title"/>
<DataLoader <DataLoader
to="response" to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({ loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode, content: companyRows, pagination, sort, mode,
})} })}
watch={`${mode}${dataGripStore.hash}`} watch={`${mode}${dataGripStore.hash}`}
> >
<Companies <Companies
mode={mode} mode={mode}
rowsForExcel={rows} rowsForExcel={companyRows}
/> />
<Pagination /> <Pagination />
</DataLoader> </DataLoader>

View file

@ -7,24 +7,20 @@ import treeStore from '../../store/Tree';
const FileBreadcrumbs = observer((): React.ReactElement => { const FileBreadcrumbs = observer((): React.ReactElement => {
const directories = treeStore.selectedPath const directories = treeStore.selectedPath
.map((dirName: string, index: number) => ( .map((dirName: string, index: number) => (
<> <span key={dirName}>
<span <span className={style.file_breadcrumbs_text}>
key={`${dirName}.`} {'/'}
className={style.file_breadcrumbs_text} </span>
> <span
{'/'} className={`${style.file_breadcrumbs_text} ${style.file_breadcrumbs_link}`}
onClick={() => {
const newPath = treeStore.selectedPath.slice(0, index + 1);
treeStore.updateFilter('selectedPath', newPath);
}}
>
{dirName}
</span>
</span> </span>
<span
key={dirName}
className={`${style.file_breadcrumbs_text} ${style.file_breadcrumbs_link}`}
onClick={() => {
const newPath = treeStore.selectedPath.slice(0, index + 1);
treeStore.updateFilter('selectedPath', newPath);
}}
>
{dirName}
</span>
</>
)); ));
return ( return (

View file

@ -14,6 +14,7 @@ import { getMax } from 'ts/pages/Common/helpers/getMax';
import { getDate } from 'ts/helpers/formatter'; import { getDate } from 'ts/helpers/formatter';
import treeStore from '../../store/Tree'; import treeStore from '../../store/Tree';
import Tasks from './Tasks';
interface IViewProps { interface IViewProps {
response?: IPagination<any>; response?: IPagination<any>;
@ -23,6 +24,8 @@ function View({ response }: IViewProps) {
if (!response) return null; if (!response) return null;
const fileSizeChart = getOptions({ max: getMax(response, 'lines'), suffix: 'page.team.tree.line' }); const fileSizeChart = getOptions({ max: getMax(response, 'lines'), suffix: 'page.team.tree.line' });
const totalTasksChart = getOptions({ max: getMax(response, 'totalTasks'), suffix: 'page.team.tree.tasks' });
const totalDaysChart = getOptions({ max: getMax(response, 'totalDays'), suffix: 'page.team.tree.days' });
const addedLinesChart = getOptions({ order: dataGripStore.dataGrip.author.list, suffix: 'page.team.tree.line' }); const addedLinesChart = getOptions({ order: dataGripStore.dataGrip.author.list, suffix: 'page.team.tree.line' });
const addedRemovedChangedChart = getOptions({ order: [ const addedRemovedChangedChart = getOptions({ order: [
'page.team.tree.linesAdded', 'page.team.tree.linesAdded',
@ -44,6 +47,24 @@ function View({ response }: IViewProps) {
return (treeStore.authorId && !author) || (commits < limit); return (treeStore.authorId && !author) || (commits < limit);
}} }}
> >
<Column
isFixed
template={ColumnTypesEnum.DETAILS}
width={40}
properties="tasks"
formatter={(row: any) => {
const content = Array.from(row?.tasks)
.reverse()
.map((taskId: any) => dataGripStore.dataGrip.tasks.statisticByName.get(taskId))
.filter(v => v);
return (
<Tasks // @ts-ignore
response={{ content }}
mode="details"
/>
);
}}
/>
<Column <Column
isFixed isFixed
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}
@ -56,13 +77,14 @@ function View({ response }: IViewProps) {
/> />
<Column <Column
isSortable isSortable
width={50} width={60}
properties="lines" properties="lines"
template={ColumnTypesEnum.SHORT_NUMBER} template={ColumnTypesEnum.SHORT_NUMBER}
/> />
<Column <Column
isSortable isSortable
properties="lines" properties="lines"
title="page.team.tree.totalLines"
minWidth={100} minWidth={100}
template={(value: any) => ( template={(value: any) => (
<LineChart <LineChart
@ -71,6 +93,42 @@ function View({ response }: IViewProps) {
/> />
)} )}
/> />
<Column
isSortable
width={50}
properties="totalTasks"
template={ColumnTypesEnum.SHORT_NUMBER}
/>
<Column
isSortable
properties="totalTasks"
title="page.team.tree.totalTasks"
minWidth={100}
template={(value: any) => (
<LineChart
options={totalTasksChart}
value={value}
/>
)}
/>
<Column
isSortable
width={50}
properties="totalDays"
template={ColumnTypesEnum.SHORT_NUMBER}
/>
<Column
isSortable
properties="totalDays"
title="page.team.tree.totalDays"
minWidth={100}
template={(value: any) => (
<LineChart
options={totalDaysChart}
value={value}
/>
)}
/>
<Column <Column
isSortable isSortable
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}

View file

@ -28,9 +28,9 @@ function AllPR({
}: IPRViewProps) { }: IPRViewProps) {
if (!response) return null; if (!response) return null;
const workChart = getOptions({ max: getMax(response, 'workDays') }); const tasks = dataGripStore.dataGrip.tasks.statisticByName;
const delayChart = getOptions({ max: getMax(response, 'delayDays') }); const workChart = getOptions({ max: getMax(response, 'daysInWork') });
const commitsChart = getOptions({ order: dataGripStore.dataGrip.author.list }); const delayChart = getOptions({ max: getMax(response, 'daysReview') });
return ( return (
<DataView <DataView
@ -38,6 +38,7 @@ function AllPR({
rows={response.content} rows={response.content}
sort={response.sort} sort={response.sort}
updateSort={updateSort} updateSort={updateSort}
mode={mode}
type={mode === 'print' ? 'cards' : undefined} type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 2 : undefined} columnCount={mode === 'print' ? 2 : undefined}
fullScreenMode="all" fullScreenMode="all"
@ -47,7 +48,7 @@ function AllPR({
isSortable isSortable
title="page.team.pr.task" title="page.team.pr.task"
properties="task" properties="task"
width={120} width={140}
/> />
) : ( ) : (
<Column <Column
@ -66,31 +67,29 @@ function AllPR({
/> />
)} )}
<Column <Column
isSortable
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}
title="page.team.pr.firstCommitTime" title="page.team.pr.firstCommitTime"
properties="beginTaskTime" formatter={(row: any) => getDate(tasks.get(row.task)?.from || row.beginTaskTime)}
formatter={getDate}
width={130} width={130}
/> />
<Column <Column
isSortable isSortable
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}
title="page.team.pr.lastCommitTime" title="page.team.pr.lastCommitTime"
properties="endTaskTime" properties="dateCreate"
formatter={getDate} formatter={getDate}
width={130} width={130}
/> />
<Column <Column
template={ColumnTypesEnum.SHORT_NUMBER} template={ColumnTypesEnum.SHORT_NUMBER}
properties="workDays" properties="daysInWork"
width={40} width={40}
/> />
<Column <Column
isSortable isSortable
title="page.team.pr.all.workDays" title="page.team.pr.all.workDays"
properties="workDays" properties="daysInWork"
minWidth={100} minWidth={170}
template={(value: any) => ( template={(value: any) => (
<LineChart <LineChart
options={workChart} options={workChart}
@ -100,31 +99,14 @@ function AllPR({
/> />
<Column <Column
template={ColumnTypesEnum.SHORT_NUMBER} template={ColumnTypesEnum.SHORT_NUMBER}
properties="commits" properties="daysReview"
width={40}
/>
<Column
isSortable
title="page.team.pr.commits"
properties="commitsByAuthors"
minWidth={100}
template={(details: any) => (
<LineChart
options={commitsChart}
details={details}
/>
)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="delayDays"
width={40} width={40}
/> />
<Column <Column
isSortable isSortable
title="page.team.pr.all.delayDays" title="page.team.pr.all.delayDays"
properties="delayDays" properties="daysReview"
minWidth={200} minWidth={170}
template={(value: any) => ( template={(value: any) => (
<LineChart <LineChart
options={delayChart} options={delayChart}
@ -136,7 +118,7 @@ function AllPR({
isSortable isSortable
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}
title="page.team.pr.date" title="page.team.pr.date"
properties="milliseconds" properties="dateMerge"
formatter={getDate} formatter={getDate}
width={130} width={130}
/> />

View file

@ -15,10 +15,10 @@ function Total() {
const allPR = dataGripStore.dataGrip.pr.statistic; const allPR = dataGripStore.dataGrip.pr.statistic;
const workChart = DataGripByPR.getPRByGroups(allPR, 'workDays'); const workChart = DataGripByPR.getPRByGroups(allPR, 'daysInWork');
const workChartOptions = getOptions({ order: workChart.order, limit: 3, suffix: 'page.team.pr.tasks' }); const workChartOptions = getOptions({ order: workChart.order, limit: 3, suffix: 'page.team.pr.tasks' });
const delayChart = DataGripByPR.getPRByGroups(allPR, 'delayDays'); const delayChart = DataGripByPR.getPRByGroups(allPR, 'daysReview');
const delayChartOptions = getOptions({ order: delayChart.order, limit: 3, suffix: 'PR' }); const delayChartOptions = getOptions({ order: delayChart.order, limit: 3, suffix: 'PR' });
const workDaysWeightedAverage = Math.round(workChart.weightedAverage); const workDaysWeightedAverage = Math.round(workChart.weightedAverage);

View file

@ -16,28 +16,46 @@ import fullScreen from 'ts/store/FullScreen';
import Total from './Total'; import Total from './Total';
import Authors from './Authors'; import Authors from './Authors';
import Anonymous from './Anonymous';
import All from './All'; import All from './All';
function getGroupsByTasks(list: any[]) {
const withTask: any[] = [];
const withoutTask: any[] = [];
list.forEach((pr: any) => {
if (pr.task) withTask.push(pr);
else withoutTask.push(pr);
});
return [withTask, withoutTask.reverse()];
}
const PR = observer(({ const PR = observer(({
mode, mode,
}: ICommonPageProps): React.ReactElement | null => { }: ICommonPageProps): React.ReactElement | null => {
const allPR = dataGripStore.dataGrip.pr.statistic; const allPR = dataGripStore.dataGrip.pr.statistic;
const rows = allPR.filter((item: any) => item.delayDays > 3); const [withTask, withoutTask] = getGroupsByTasks(allPR);
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null; const longReview = withTask.filter((item: any) => item.daysReview > 4);
const canShowByReview = (!fullScreen.isOpen || fullScreen.mode === 'all') && longReview.length > 1;
const canShowByAnonymous = (!fullScreen.isOpen || fullScreen.mode === 'anonymous') && withoutTask.length;
if (!canShowByReview && !canShowByAnonymous) {
return mode !== 'print' ? (<NothingFound />) : null;
}
const PRbyName = dataGripStore.dataGrip.pr.statisticByName; const PRbyName = dataGripStore.dataGrip.pr.statisticByName;
const authorsStat = Object.values(PRbyName); const authorsStat = Object.values(PRbyName);
return ( return (
<> <>
{!fullScreen.isOpen && ( {!fullScreen.isOpen && canShowByReview && (
<> <>
<Title title="page.team.pr.oneTaskDays"/> <Title title="page.team.pr.oneTaskDays"/>
<Total/> <Total/>
</> </>
)} )}
{!fullScreen.isOpen || fullScreen.mode === 'author' ? ( {canShowByReview ? (
<> <>
<Title title="page.team.pr.statByAuthors"/> <Title title="page.team.pr.statByAuthors"/>
<DataLoader <DataLoader
@ -52,18 +70,12 @@ const PR = observer(({
/> />
<Pagination/> <Pagination/>
</DataLoader> </DataLoader>
</> <PageBreak/>
) : null}
<PageBreak/>
{!fullScreen.isOpen || fullScreen.mode === 'all' ? (
<>
<Title title="page.team.pr.longDelay"/> <Title title="page.team.pr.longDelay"/>
<DataLoader <DataLoader
to="response" to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({ loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, content: longReview,
pagination: mode === 'print' pagination: mode === 'print'
? { size: 20 } ? { size: 20 }
: pagination, : pagination,
@ -72,7 +84,31 @@ const PR = observer(({
> >
<All <All
mode={mode} mode={mode}
rowsForExcel={rows} rowsForExcel={longReview}
/>
{mode !== 'print' && <Pagination/>}
</DataLoader>
</>
) : null}
<PageBreak/>
{canShowByAnonymous ? (
<>
<Title title="page.team.pr.anonymous"/>
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: withoutTask,
pagination: mode === 'print'
? { size: 20 }
: pagination,
sort,
})}
>
<Anonymous
mode={mode}
rowsForExcel={withoutTask}
/> />
{mode !== 'print' && <Pagination/>} {mode !== 'print' && <Pagination/>}
</DataLoader> </DataLoader>

View file

@ -1,164 +0,0 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import DataLoader from 'ts/components/DataLoader';
import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title';
import DataView from 'ts/components/DataView';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import LineChart from 'ts/components/LineChart';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import { getDate } from 'ts/helpers/formatter';
import AllPR from './PR/All';
interface IReleaseViewProps {
response?: IPagination<any>;
updateSort?: Function;
rowsForExcel?: any[];
mode?: string;
}
function ReleaseView({ response, updateSort, rowsForExcel, mode }: IReleaseViewProps) {
if (!response) return null;
const delay = getMax(response, 'delayInDays');
const waiting = getMax(response, 'waitingInDays');
const max = Math.max(delay, waiting);
const delayChart = getOptions({ max, suffix: 'page.team.release.chart' });
return (
<DataView
rowsForExcel={rowsForExcel}
rows={response.content}
sort={response.sort}
updateSort={updateSort}
type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined}
>
<Column
isFixed
template={ColumnTypesEnum.DETAILS}
width={40}
formatter={(row: any) => {
const content = row.pr.map((commit: any) => (
dataGripStore?.dataGrip?.pr?.pr?.get(commit.prId)
)).filter((item: any) => item?.firstCommit);
return (
<AllPR // @ts-ignore
response={{ content }}
/>
);
}}
/>
<Column
isFixed
template={ColumnTypesEnum.STRING}
title="page.team.release.title"
properties="title"
width={200}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.release.from"
width={150}
properties="from"
formatter={getDate}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.release.to"
width={150}
properties="to"
formatter={getDate}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
title="page.team.release.prLength"
properties="prLength"
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="delayInDays"
/>
<Column
isSortable
title="page.team.release.delay"
properties="delayInDays"
width={170}
minWidth={170}
template={(value: number) => (
<LineChart
options={delayChart}
value={value}
/>
)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="waitingInDays"
/>
<Column
isSortable
title="page.team.release.waiting"
properties="waitingInDays"
width={170}
minWidth={170}
template={(value: number) => (
<LineChart
options={delayChart}
value={value}
/>
)}
/>
</DataView>
);
}
ReleaseView.defaultProps = {
response: undefined,
};
const Release = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.release.statistic;
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
return (
<>
{mode === 'print' ? (
<Title title="sidebar.team.extension"/>
) : (
<>
<br/>
<br/>
<br/>
</>
)}
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.hash}`}
>
<ReleaseView
mode={mode}
rowsForExcel={rows}
/>
<Pagination />
</DataLoader>
</>
);
});
export default Release;

View file

@ -1,176 +0,0 @@
import React, { useState } from 'react';
import { observer } from 'mobx-react-lite';
import ISort from 'ts/interfaces/Sort';
import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import DataLoader from 'ts/components/DataLoader';
import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import DataView from 'ts/components/DataView';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import UiKitTags from 'ts/components/UiKit/components/Tags';
import { PRLink, TaskLink } from 'ts/components/ExternalLink';
import Title from 'ts/components/Title';
import PageWrapper from 'ts/components/Page/wrapper';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import { getDate } from 'ts/helpers/formatter';
import TasksFilters from './TasksFilters';
interface ITasksViewProps {
response?: IPagination<any>;
updateSort?: Function;
rowsForExcel?: any[];
mode?: string;
}
export function TasksView({ response, updateSort, rowsForExcel, mode }: ITasksViewProps) {
if (!response) return null;
const commitsChart = getOptions({ max: getMax(response, 'commits'), suffix: 'page.team.type.tasksSmall' });
return (
<DataView
rowsForExcel={rowsForExcel}
rows={response.content}
sort={response.sort}
updateSort={updateSort}
type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined}
>
<Column
isFixed
isSortable
template={(value: string) => (
<TaskLink task={value} />
)}
title="page.team.tasks.task"
properties="task"
width={120}
/>
<Column
properties="types"
template={(value: string) => (
<UiKitTags value={value} />
)}
width={100}
/>
<Column
properties="scope"
template={(value: string) => (
<UiKitTags value={value} />
)}
width={100}
/>
<Column
isSortable
template={(value: string, row: any) => (
<PRLink prId={row?.prId}/>
)}
properties="task"
width={40}
/>
<Column
template={ColumnTypesEnum.STRING}
properties="comments"
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.tasks.author"
properties="author"
width={170}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.tasks.from"
properties="from"
width={150}
formatter={getDate}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.tasks.to"
properties="to"
width={150}
formatter={getDate}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.tasks.pr"
properties="to"
width={150}
formatter={getDate}
/>
<Column
template={ColumnTypesEnum.STRING}
title="page.team.tasks.prAuthor"
properties="prAuthor"
width={170}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="commits"
/>
<Column
isSortable
title="page.team.tasks.commits"
properties="commits"
minWidth={170}
template={(value: number) => (
<LineChart
options={commitsChart}
value={value}
/>
)}
/>
</DataView>
);
}
TasksView.defaultProps = {
response: undefined,
};
const Tasks = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.tasks.statistic;
const [filters, setFilters] = useState<any>({ user: 0, company: 0 });
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
return (
<>
<Title title="common.filters" />
<PageWrapper>
<TasksFilters
filters={filters}
onChange={setFilters}
/>
</PageWrapper>
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.hash}`}
>
<TasksView
mode={mode}
rowsForExcel={rows}
/>
<Pagination />
</DataLoader>
</>
);
});
export default Tasks;

View file

@ -1,60 +0,0 @@
import React, { useMemo } from 'react';
import { observer } from 'mobx-react-lite';
import { useTranslation } from 'react-i18next';
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
import dataGripStore from 'ts/store/DataGrip';
import style from '../styles/filters.module.scss';
function getFormattedUsers(rows: any[], t: Function) {
const options = rows.map((title: string, id: number) => ({ id: id + 1, title }));
options.unshift({ id: 0, title: t('page.team.tree.filters.all') });
return options;
}
interface ITempoFiltersProps {
filters: {
company?: number;
user?: number;
};
onChange: Function;
}
const TasksFilters = observer(({
filters,
onChange,
}: ITempoFiltersProps): React.ReactElement => {
const { t } = useTranslation();
const users = dataGripStore.dataGrip.author.list;
const userOptions = useMemo(() => getFormattedUsers(users, t), [users]);
const companies = dataGripStore.dataGrip.company.statistic.map((v: any) => v.company);
const companyOptions = useMemo(() => getFormattedUsers(companies, t), [companies]);
return (
<div className={style.table_filters}>
<SelectWithButtons
title="page.team.tree.filters.author"
value={filters.user}
className={style.table_filters_item}
options={userOptions}
onChange={(user: number) => {
onChange({ ...filters, user, company: 0 });
}}
/>
<SelectWithButtons
title="page.team.tree.filters.author"
value={filters.company}
className={style.table_filters_item}
options={companyOptions}
onChange={(company: number) => {
onChange({ ...filters, user: 0, company });
}}
/>
</div>
);
});
export default TasksFilters;

View file

@ -32,6 +32,9 @@ interface ITypeViewProps {
} }
function TypeView({ response, updateSort, rowsForExcel, mode }: ITypeViewProps) { function TypeView({ response, updateSort, rowsForExcel, mode }: ITypeViewProps) {
const { t } = useTranslation();
const unknown = t('page.team.type.unknown');
if (!response) return null; if (!response) return null;
const taskChart = getOptions({ max: getMax(response, 'tasks'), suffix: 'page.team.type.tasksSmall' }); const taskChart = getOptions({ max: getMax(response, 'tasks'), suffix: 'page.team.type.tasksSmall' });
@ -52,6 +55,7 @@ function TypeView({ response, updateSort, rowsForExcel, mode }: ITypeViewProps)
template={ColumnTypesEnum.STRING} template={ColumnTypesEnum.STRING}
title="page.team.type.type" title="page.team.type.type"
properties="type" properties="type"
formatter={(type: string) => type || unknown}
width={150} width={150}
/> />
<Column <Column

View file

@ -1,14 +1,15 @@
import { IFolder } from 'ts/interfaces/FileInfo'; import { IFolder } from 'ts/interfaces/FileInfo';
function getSubTree(tree: IFolder, path: string[]) { function getSubTree(tree: IFolder, path: string[]) {
return (path || []).reduce((subTree: any, folderName: string) => { return (path || [])
subTree = subTree.content[folderName] || { content: [] }; .reduce((subTree: any, folderName: string) => {
return subTree; subTree = subTree.content.get(folderName) || { content: new Map() };
}, tree || { content: [] }); return subTree;
}, tree || { content: new Map() });
} }
function getSortedContent(subTree: any) { function getSortedContent(subTree: IFolder) {
return Object.values(subTree.content) return Array.from(subTree.content.values())
.sort((a: any, b: any) => { .sort((a: any, b: any) => {
if (a.content && !b.content) return -1; if (a.content && !b.content) return -1;
if (!a.content && b.content) return 1; if (!a.content && b.content) return 1;

View file

@ -9,6 +9,7 @@ import fullScreen from 'ts/store/FullScreen';
import Author from './components/Author'; import Author from './components/Author';
import Commits from './components/Commits'; import Commits from './components/Commits';
import Company from './components/Company'; import Company from './components/Company';
import Country from './components/Country';
import Changes from './components/Changes'; import Changes from './components/Changes';
import Hours from './components/Hours'; import Hours from './components/Hours';
import PopularWords from './components/PopularWords'; import PopularWords from './components/PopularWords';
@ -25,6 +26,7 @@ import Building from './components/Building';
import Pr from './components/PR'; import Pr from './components/PR';
import Print from './components/Print'; import Print from './components/Print';
import Release from './components/Release'; import Release from './components/Release';
import Refactor from './components/Refactor';
interface ViewProps { interface ViewProps {
page?: string; page?: string;
@ -39,6 +41,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
if (page === 'scope') return <Scope mode={mode}/>; if (page === 'scope') return <Scope mode={mode}/>;
if (page === 'author') return <Author mode={mode}/>; if (page === 'author') return <Author mode={mode}/>;
if (page === 'company') return <Company mode={mode}/>; if (page === 'company') return <Company mode={mode}/>;
if (page === 'country') return <Country mode={mode}/>;
if (page === 'type') return <Type mode={mode}/>; if (page === 'type') return <Type mode={mode}/>;
if (page === 'pr') return <Pr mode={mode}/>; if (page === 'pr') return <Pr mode={mode}/>;
if (page === 'day') return <Tempo/>; if (page === 'day') return <Tempo/>;
@ -55,6 +58,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
if (page === 'building') return <Building/>; if (page === 'building') return <Building/>;
if (page === 'print') return <Print/>; if (page === 'print') return <Print/>;
if (page === 'tasks') return <Tasks/>; if (page === 'tasks') return <Tasks/>;
if (page === 'refactor') return <Refactor/>;
return <Total/>; return <Total/>;
}); });

View file

@ -90,6 +90,7 @@ class DataGripStore {
this.#updateRender(); this.#updateRender();
console.dir(this.dataGrip); console.dir(this.dataGrip);
console.dir(this.fileGrip);
if (!applicationHasCustom.title) { if (!applicationHasCustom.title) {
document.title = getTitle(this.dataGrip, this.fileGrip, this.commits); document.title = getTitle(this.dataGrip, this.fileGrip, this.commits);
} }

View file

@ -30,6 +30,9 @@ export default `
§ sidebar.team.changes: Alle Änderungen § sidebar.team.changes: Alle Änderungen
§ sidebar.team.words: Beliebte Wörter § sidebar.team.words: Beliebte Wörter
§ sidebar.team.building: Quiz § sidebar.team.building: Quiz
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: Die Einstellungen § sidebar.team.settings: Die Einstellungen
§ sidebar.person.total: Allgemeine Informationen § sidebar.person.total: Allgemeine Informationen
§ sidebar.person.money: Arbeitskosten § sidebar.person.money: Arbeitskosten

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report § page.print.title: Git repository report
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: The data for the report was obtained from the commit history. § page.print.description: The data for the report was obtained from the commit history.
§ page.team.author.title: Employee statistics § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work. § page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees). § page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Task type statistics § page.team.type.title: Task type statistics
§ page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes. § page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes.
§ page.team.type.type: Type of work § page.team.type.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks § page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks § page.team.type.tasksSmall: tasks
§ page.team.type.days: Days § page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Number of commits § page.team.tree.filters.commits: Number of commits
§ page.team.tree.filters.help: The minimum number of commits an employee has made in a file § page.team.tree.filters.help: The minimum number of commits an employee has made in a file
§ page.team.tree.filters.all: All employees § page.team.tree.filters.all: All employees
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Who added § page.team.tree.add: Who added
§ page.team.tree.change: Who changed § page.team.tree.change: Who changed
§ page.team.tree.remove: Who removed § page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added § page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: removed § page.team.tree.linesRemoved: removed
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Weekly statistics § page.team.week.title: Weekly statistics
@ -137,7 +174,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date § page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by § page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee § page.team.pr.author: Employee
@ -155,10 +191,6 @@ export default `
§ page.team.tasks.from: First commit § page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit § page.team.tasks.to: Last commit
§ page.team.tasks.daysInWork: Days in work § page.team.tasks.daysInWork: Days in work
§ page.team.tasks.commits: Commits number
§ page.team.tasks.pr: Merge date
§ page.team.tasks.prAuthor: Merged by user
§ page.team.tasks.prDelayDays: Delay before merge in days
§ page.team.tasks.comments: Comments § page.team.tasks.comments: Comments
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -167,6 +199,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -28,6 +28,9 @@ export default `
§ sidebar.team.changes: All changes § sidebar.team.changes: All changes
§ sidebar.team.words: Popular words § sidebar.team.words: Popular words
§ sidebar.team.building: Quiz § sidebar.team.building: Quiz
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: Settings § sidebar.team.settings: Settings
§ sidebar.person.total: Common info § sidebar.person.total: Common info
§ sidebar.person.money: Work cost § sidebar.person.money: Work cost

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report § page.print.title: Git repository report
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: The data for the report was obtained from the commit history. § page.print.description: The data for the report was obtained from the commit history.
§ page.team.author.title: Employee statistics § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work. § page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees). § page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Task type statistics § page.team.type.title: Task type statistics
§ page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes. § page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes.
§ page.team.type.type: Type of work § page.team.type.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks § page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks § page.team.type.tasksSmall: tasks
§ page.team.type.days: Days § page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Number of commits § page.team.tree.filters.commits: Number of commits
§ page.team.tree.filters.help: The minimum number of commits an employee has made in a file § page.team.tree.filters.help: The minimum number of commits an employee has made in a file
§ page.team.tree.filters.all: All employees § page.team.tree.filters.all: All employees
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Who added § page.team.tree.add: Who added
§ page.team.tree.change: Who changed § page.team.tree.change: Who changed
§ page.team.tree.remove: Who removed § page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added § page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: removed § page.team.tree.linesRemoved: removed
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Weekly statistics § page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date § page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by § page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee § page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit § page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit § page.team.tasks.to: Last commit
§ page.team.tasks.daysInWork: Days in work § page.team.tasks.daysInWork: Days in work
§ page.team.tasks.commits: Commits number
§ page.team.tasks.pr: Merge date
§ page.team.tasks.prAuthor: Merged by user
§ page.team.tasks.prDelayDays: Delay before merge in days
§ page.team.tasks.comments: Comments § page.team.tasks.comments: Comments
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -30,6 +30,9 @@ export default `
§ sidebar.team.changes: Todos los cambios § sidebar.team.changes: Todos los cambios
§ sidebar.team.words: Palabras populares § sidebar.team.words: Palabras populares
§ sidebar.team.building: Concurso § sidebar.team.building: Concurso
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: Ajustes § sidebar.team.settings: Ajustes
§ sidebar.person.total: Información general § sidebar.person.total: Información general
§ sidebar.person.money: Costo del trabajo § sidebar.person.money: Costo del trabajo

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Informe del repositorio git § page.print.title: Informe del repositorio git
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: Los datos para el informe se obtuvieron del historial de commits. § page.print.description: Los datos para el informe se obtuvieron del historial de commits.
§ page.team.author.title: Estadísticas de los empleados § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: Parte de las estadísticas (la velocidad del trabajo, el dinero gastado, etc.) para los empleados con el tipo de "Asistente" no cuenta, ya que no es un rol permanente en el proyecto. Su trabajo es insignificante y puede ser ignorado. § page.team.author.description1: Parte de las estadísticas (la velocidad del trabajo, el dinero gastado, etc.) para los empleados con el tipo de "Asistente" no cuenta, ya que no es un rol permanente en el proyecto. Su trabajo es insignificante y puede ser ignorado.
§ page.team.author.description2: La clasificación predeterminada es la clasificación por número de tareas y grupos(empleados actuales, despedidos, ayudantes). § page.team.author.description2: La clasificación predeterminada es la clasificación por número de tareas y grupos(empleados actuales, despedidos, ayudantes).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Estadísticas por tipo de tarea § page.team.type.title: Estadísticas por tipo de tarea
§ page.team.type.description: *Contribución personal* se considera por el número de Commits, no por el volumen de líneas o archivos modificados. Por lo tanto, también debe ver la sección "Análisis de archivos" para evaluar el alcance de los cambios § page.team.type.description: *Contribución personal* se considera por el número de Commits, no por el volumen de líneas o archivos modificados. Por lo tanto, también debe ver la sección "Análisis de archivos" para evaluar el alcance de los cambios
§ page.team.type.type: Tipo de trabajo § page.team.type.type: Tipo de trabajo
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tareas § page.team.type.tasks: Tareas
§ page.team.type.tasksSmall: Tareas § page.team.type.tasksSmall: Tareas
§ page.team.type.days: Día § page.team.type.days: Día
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Número de commits § page.team.tree.filters.commits: Número de commits
§ page.team.tree.filters.help: El número mínimo de commits que hizo un empleado en el archivo § page.team.tree.filters.help: El número mínimo de commits que hizo un empleado en el archivo
§ page.team.tree.filters.all: Todos los empleados § page.team.tree.filters.all: Todos los empleados
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Quien ha añadido § page.team.tree.add: Quien ha añadido
§ page.team.tree.change: Quien cambió § page.team.tree.change: Quien cambió
§ page.team.tree.remove: Quién borró § page.team.tree.remove: Quién borró
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: agregaron § page.team.tree.linesAdded: agregaron
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: cambiaron § page.team.tree.linesRemoved: cambiaron
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Estadísticas semanales § page.team.week.title: Estadísticas semanales
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: commits
§ page.team.pr.date: Date of injection § page.team.pr.date: Date of injection
§ page.team.pr.mergeAuthor: I poured it in § page.team.pr.mergeAuthor: I poured it in
§ page.team.pr.author: Employee § page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: The first commits § page.team.tasks.from: The first commits
§ page.team.tasks.to: Last commits § page.team.tasks.to: Last commits
§ page.team.tasks.daysInWork: Days in the work § page.team.tasks.daysInWork: Days in the work
§ page.team.tasks.commits: Number of commits
§ page.team.tasks.pr: Date of injection
§ page.team.tasks.prAuthor: I poured it in
§ page.team.tasks.prDelayDays: Days of waiting for the infusion
§ page.team.tasks.comments: Comments § page.team.tasks.comments: Comments
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -27,7 +27,10 @@ export default `
§ sidebar.team.commits: all commits § sidebar.team.commits: all commits
§ sidebar.team.changes: Tous les changements § sidebar.team.changes: Tous les changements
§ sidebar.team.words: Mots populaires § sidebar.team.words: Mots populaires
§ sidebar.team.building: quiz § sidebar.team.building: Quiz
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: Réglages § sidebar.team.settings: Réglages
§ sidebar.person.total: Informations générales § sidebar.person.total: Informations générales
§ sidebar.person.money: Coût des travaux § sidebar.person.money: Coût des travaux

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Rapport sur dépôt git § page.print.title: Rapport sur dépôt git
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: Les données du rapport ont é extraites de l'historique des commits. § page.print.description: Les données du rapport ont é extraites de l'historique des commits.
§ page.team.author.title: Statistiques du personnel § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: Partie des statistiques (vitesse de travail, argent dépensé, etc.) pour les collaborateurs de type Assistant, ce nest pas une rôle permanente dans le projet. Leur travail est insignifiant et peut être ignoré. § page.team.author.description1: Partie des statistiques (vitesse de travail, argent dépensé, etc.) pour les collaborateurs de type Assistant, ce nest pas une rôle permanente dans le projet. Leur travail est insignifiant et peut être ignoré.
§ page.team.author.description2: Le tri par défaut est le tri par nombre de tâches et de groupes (employés actuels, licenciés et aidants). § page.team.author.description2: Le tri par défaut est le tri par nombre de tâches et de groupes (employés actuels, licenciés et aidants).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Statistiques par type de tâche § page.team.type.title: Statistiques par type de tâche
§ page.team.type.description: *Contribution personnelle* compte tenu du nombre de commits plutôt que de la taille des lignes ou fichiers modifiés. Vous devez donc également consulter la section Analyse des fichiers afin dévaluer lampleur des modifications. § page.team.type.description: *Contribution personnelle* compte tenu du nombre de commits plutôt que de la taille des lignes ou fichiers modifiés. Vous devez donc également consulter la section Analyse des fichiers afin dévaluer lampleur des modifications.
§ page.team.type.type: Type de travail § page.team.type.type: Type de travail
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Задач § page.team.type.tasks: Задач
§ page.team.type.tasksSmall: Tâche § page.team.type.tasksSmall: Tâche
§ page.team.type.days: Jours § page.team.type.days: Jours
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Nombre de commits § page.team.tree.filters.commits: Nombre de commits
§ page.team.tree.filters.help: Minimum commits que l'employé a fait dans le fichier § page.team.tree.filters.help: Minimum commits que l'employé a fait dans le fichier
§ page.team.tree.filters.all: Tous les employés § page.team.tree.filters.all: Tous les employés
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Qui a Ajouté § page.team.tree.add: Qui a Ajouté
§ page.team.tree.change: Qui a changé § page.team.tree.change: Qui a changé
§ page.team.tree.remove: Qui a supprimé § page.team.tree.remove: Qui a supprimé
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: ajoutâtes § page.team.tree.linesAdded: ajoutâtes
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: modifiâtes § page.team.tree.linesRemoved: modifiâtes
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Statistiques par semaine § page.team.week.title: Statistiques par semaine
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Date de diffusion § page.team.pr.date: Date de diffusion
§ page.team.pr.mergeAuthor: Versai § page.team.pr.mergeAuthor: Versai
§ page.team.pr.author: Employé § page.team.pr.author: Employé
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: Premier commit § page.team.tasks.from: Premier commit
§ page.team.tasks.to: Dernier commit § page.team.tasks.to: Dernier commit
§ page.team.tasks.daysInWork: Jours de travail § page.team.tasks.daysInWork: Jours de travail
§ page.team.tasks.commits: Nombre de commits
§ page.team.tasks.pr: Date de diffusion
§ page.team.tasks.prAuthor: Versai
§ page.team.tasks.prDelayDays: Jours d'attente
§ page.team.tasks.comments: Commentaires § page.team.tasks.comments: Commentaires
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -29,6 +29,9 @@ export default `
§ sidebar.team.changes: すべての変更 § sidebar.team.changes: すべての変更
§ sidebar.team.words: 人気のある言葉 § sidebar.team.words: 人気のある言葉
§ sidebar.team.building: クイズ § sidebar.team.building: クイズ
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: 設定 § sidebar.team.settings: 設定
§ sidebar.person.total: 一般的な情報 § sidebar.person.total: 一般的な情報
§ sidebar.person.money: 仕事のコスト § sidebar.person.money: 仕事のコスト

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report § page.print.title: Git repository report
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: The data for the report was obtained from the commit history. § page.print.description: The data for the report was obtained from the commit history.
§ page.team.author.title: Employee statistics § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work. § page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees). § page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Task type statistics § page.team.type.title: Task type statistics
§ page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes. § page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes.
§ page.team.type.type: Type of work § page.team.type.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks § page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks § page.team.type.tasksSmall: tasks
§ page.team.type.days: Days § page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Number of commits § page.team.tree.filters.commits: Number of commits
§ page.team.tree.filters.help: The minimum number of commits an employee has made in a file § page.team.tree.filters.help: The minimum number of commits an employee has made in a file
§ page.team.tree.filters.all: All employees § page.team.tree.filters.all: All employees
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Who added § page.team.tree.add: Who added
§ page.team.tree.change: Who changed § page.team.tree.change: Who changed
§ page.team.tree.remove: Who removed § page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added § page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: removed § page.team.tree.linesRemoved: removed
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Weekly statistics § page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date § page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by § page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee § page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit § page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit § page.team.tasks.to: Last commit
§ page.team.tasks.daysInWork: Days in work § page.team.tasks.daysInWork: Days in work
§ page.team.tasks.commits: Commits number
§ page.team.tasks.pr: Merge date
§ page.team.tasks.prAuthor: Merged by user
§ page.team.tasks.prDelayDays: Delay before merge in days
§ page.team.tasks.comments: Comments § page.team.tasks.comments: Comments
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -29,6 +29,9 @@ export default `
§ sidebar.team.changes: Todas as alterações § sidebar.team.changes: Todas as alterações
§ sidebar.team.words: Palavras populares § sidebar.team.words: Palavras populares
§ sidebar.team.building: Concurso § sidebar.team.building: Concurso
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: Sintonização § sidebar.team.settings: Sintonização
§ sidebar.person.total: Informação geral § sidebar.person.total: Informação geral
§ sidebar.person.money: Custo do trabalho § sidebar.person.money: Custo do trabalho

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report § page.print.title: Git repository report
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: The data for the report was obtained from the commit history. § page.print.description: The data for the report was obtained from the commit history.
§ page.team.author.title: Employee statistics § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work. § page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees). § page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Task type statistics § page.team.type.title: Task type statistics
§ page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes. § page.team.type.description: *Personal contribution* is counted by the number of commits, not the volume of changed lines or files. Therefore, the "File Analysis" section should also be consulted to assess the scale of changes.
§ page.team.type.type: Type of work § page.team.type.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks § page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks § page.team.type.tasksSmall: tasks
§ page.team.type.days: Days § page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Number of commits § page.team.tree.filters.commits: Number of commits
§ page.team.tree.filters.help: The minimum number of commits an employee has made in a file § page.team.tree.filters.help: The minimum number of commits an employee has made in a file
§ page.team.tree.filters.all: All employees § page.team.tree.filters.all: All employees
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Who added § page.team.tree.add: Who added
§ page.team.tree.change: Who changed § page.team.tree.change: Who changed
§ page.team.tree.remove: Who removed § page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added § page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: removed § page.team.tree.linesRemoved: removed
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: Weekly statistics § page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date § page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by § page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee § page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit § page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit § page.team.tasks.to: Last commit
§ page.team.tasks.daysInWork: Days in work § page.team.tasks.daysInWork: Days in work
§ page.team.tasks.commits: Commits number
§ page.team.tasks.pr: Merge date
§ page.team.tasks.prAuthor: Merged by user
§ page.team.tasks.prDelayDays: Delay before merge in days
§ page.team.tasks.comments: Comments § page.team.tasks.comments: Comments
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date

View file

@ -28,6 +28,9 @@ export default `
§ sidebar.team.changes: Все изменения § sidebar.team.changes: Все изменения
§ sidebar.team.words: Популярные слова § sidebar.team.words: Популярные слова
§ sidebar.team.building: Викторина § sidebar.team.building: Викторина
§ sidebar.team.refactor: Рефакторинг
§ sidebar.team.company: Компании
§ sidebar.team.country: Местоположение
§ sidebar.team.settings: Настройки § sidebar.team.settings: Настройки
§ sidebar.person.total: Общая информация § sidebar.person.total: Общая информация
§ sidebar.person.money: Стоимость работы § sidebar.person.money: Стоимость работы

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Отчёт по git-репозиторию § page.print.title: Отчёт по git-репозиторию
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: Данные для отчёта были получены из истории коммитов. § page.print.description: Данные для отчёта были получены из истории коммитов.
§ page.team.author.title: Статистика по сотрудникам § page.team.author.statusChart.title: Текущий статус
§ page.team.author.daysChart.title: Время работы
§ page.team.author.daysChart.item: дней
§ page.team.author.days.half: пол года
§ page.team.author.days.one: год
§ page.team.author.days.15: полтора
§ page.team.author.days.two: два года
§ page.team.author.days.more: больше
§ page.team.author.title: Детализация
§ page.team.author.description1: *Часть статистики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помощник» не считается*, т.к. это эпизодическая роль в проекте. Предполагаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы. § page.team.author.description1: *Часть статистики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помощник» не считается*, т.к. это эпизодическая роль в проекте. Предполагаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы.
§ page.team.author.description2: *Сортировка по умолчанию* это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники). § page.team.author.description2: *Сортировка по умолчанию* это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
§ page.team.author.status: Статус § page.team.author.status: Статус
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Статистика по типам задач § page.team.type.title: Статистика по типам задач
§ page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений. § page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений.
§ page.team.type.type: Тип работы § page.team.type.type: Тип работы
§ page.team.type.unknown: неизвестный
§ page.team.type.tasks: Задач § page.team.type.tasks: Задач
§ page.team.type.tasksSmall: задач § page.team.type.tasksSmall: задач
§ page.team.type.days: Дней § page.team.type.days: Дней
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Количество коммитов § page.team.tree.filters.commits: Количество коммитов
§ page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле § page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле
§ page.team.tree.filters.all: Все сотрудники § page.team.tree.filters.all: Все сотрудники
§ page.team.tree.totalLines: Строк
§ page.team.tree.totalTasks: Задач
§ page.team.tree.totalDays: Дней
§ page.team.tree.tasks: задач
§ page.team.tree.days: дней
§ page.team.tree.add: Кто добавлял § page.team.tree.add: Кто добавлял
§ page.team.tree.change: Кто менял § page.team.tree.change: Кто менял
§ page.team.tree.remove: Кто удалял § page.team.tree.remove: Кто удалял
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: добавили § page.team.tree.linesAdded: добавили
§ page.team.tree.linesChanged: изменили § page.team.tree.linesChanged: изменили
§ page.team.tree.linesRemoved: удалили § page.team.tree.linesRemoved: удалили
§ page.team.company.title: Детализация
§ page.team.company.employments.title: По количеству сотрудников
§ page.team.company.employments.item: сотрудников
§ page.team.company.daysChart.title: По длительности контракта
§ page.team.company.daysChart.item: дней
§ page.team.company.active.yes: активна
§ page.team.company.active.no: контракт истёк
§ page.team.country.byTimezone: По времени последнего коммита
§ page.team.country.pieByDomain.title: По почте, времени и языку
§ page.team.country.pieByTimezone.title: По времени
§ page.team.country.chart.item: сотрудников
§ page.team.country.table.title: Список сотрудников
§ page.team.country.table.country: Местоположение
§ page.team.country.table.employments: Сотрудники
§ page.team.refactor.title: Кандидаты на рефакторинг
§ page.team.refactor.lines: строк
§ page.team.refactor.tasks: задач
§ page.team.refactor.days: дней
§ page.team.refactor.path: Путь
§ page.team.refactor.firstCommit: Первый коммит
§ page.team.refactor.totalLines: Строк
§ page.team.refactor.totalTasks: Задач
§ page.team.refactor.totalDays: Дней в разработке
§ page.team.day.commits: Коммиты § page.team.day.commits: Коммиты
§ page.team.day.activity: Активность § page.team.day.activity: Активность
§ page.team.week.title: Статистика по неделям § page.team.week.title: Статистика по неделям
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: две недели § page.team.pr.chart.14day: две недели
§ page.team.pr.chart.30day: месяц § page.team.pr.chart.30day: месяц
§ page.team.pr.chart.more: более § page.team.pr.chart.more: более
§ page.team.pr.commits: Коммиты
§ page.team.pr.date: Дата влития § page.team.pr.date: Дата влития
§ page.team.pr.mergeAuthor: Влил § page.team.pr.mergeAuthor: Влил
§ page.team.pr.author: Сотрудник § page.team.pr.author: Сотрудник
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: Первый коммит § page.team.tasks.from: Первый коммит
§ page.team.tasks.to: Последний коммит § page.team.tasks.to: Последний коммит
§ page.team.tasks.daysInWork: Дней в работе § page.team.tasks.daysInWork: Дней в работе
§ page.team.tasks.commits: Количество коммитов
§ page.team.tasks.pr: Дата влития
§ page.team.tasks.prAuthor: Влил
§ page.team.tasks.prDelayDays: Дней ожидания влития
§ page.team.tasks.comments: Комментарии § page.team.tasks.comments: Комментарии
§ page.team.extension.extension: Расширения файлов § page.team.extension.extension: Расширения файлов
§ page.team.extension.type: Подтип файлов § page.team.extension.type: Подтип файлов
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Количество § page.team.extension.current.count: Количество
§ page.team.extension.removed.count: Количество удалённых § page.team.extension.removed.count: Количество удалённых
§ page.team.extension.files: файлов § page.team.extension.files: файлов
§ page.team.release.download: Скачать
§ page.team.release.title: Релиз § page.team.release.title: Релиз
§ page.team.release.from: Дата создания § page.team.release.from: Дата создания
§ page.team.release.to: Дата завершения § page.team.release.to: Дата завершения

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Отчёт по git-репозиторию § page.print.title: Отчёт по git-репозиторию
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: Данные для отчёта были получены из истории коммитов. § page.print.description: Данные для отчёта были получены из истории коммитов.
§ page.team.author.title: Статистика по сотрудникам § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: Часть статистики (скорость работы, затраченные деньги и т.п.) по сотрудникам с типом «Помощник» не считается, т.к. это не постоянная роль в проекте. Их работа незначительно и её можно не учитывать. § page.team.author.description1: Часть статистики (скорость работы, затраченные деньги и т.п.) по сотрудникам с типом «Помощник» не считается, т.к. это не постоянная роль в проекте. Их работа незначительно и её можно не учитывать.
§ page.team.author.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники). § page.team.author.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
§ page.team.author.status: Status § page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Статистика по типам задач § page.team.type.title: Статистика по типам задач
§ page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений. § page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений.
§ page.team.type.type: Тип работы § page.team.type.type: Тип работы
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Задач § page.team.type.tasks: Задач
§ page.team.type.tasksSmall: задач § page.team.type.tasksSmall: задач
§ page.team.type.days: Дней § page.team.type.days: Дней
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Количество коммитов § page.team.tree.filters.commits: Количество коммитов
§ page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле § page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле
§ page.team.tree.filters.all: Все сотрудники § page.team.tree.filters.all: Все сотрудники
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: Кто добавлял § page.team.tree.add: Кто добавлял
§ page.team.tree.change: Кто менял § page.team.tree.change: Кто менял
§ page.team.tree.remove: Кто удалял § page.team.tree.remove: Кто удалял
@ -132,7 +146,6 @@ export default `
§ page.team.pr.delayDays: Дней ожидания влития § page.team.pr.delayDays: Дней ожидания влития
§ page.team.pr.all.workDays: Время работы над задачей § page.team.pr.all.workDays: Время работы над задачей
§ page.team.pr.all.delayDays: Время ревью PR § page.team.pr.all.delayDays: Время ревью PR
§ page.team.pr.commits: Коммиты
§ page.team.pr.date: Дата влития § page.team.pr.date: Дата влития
§ page.team.pr.mergeAuthor: Влил § page.team.pr.mergeAuthor: Влил
§ page.team.pr.author: Сотрудник § page.team.pr.author: Сотрудник
@ -157,10 +170,6 @@ export default `
§ page.team.tasks.from: Первый коммит § page.team.tasks.from: Первый коммит
§ page.team.tasks.to: Последний коммит § page.team.tasks.to: Последний коммит
§ page.team.tasks.daysInWork: Дней в работе § page.team.tasks.daysInWork: Дней в работе
§ page.team.tasks.commits: Количество коммитов
§ page.team.tasks.pr: Дата влития
§ page.team.tasks.prAuthor: Влил
§ page.team.tasks.prDelayDays: Дней ожидания влития
§ page.team.tasks.comments: Комментарии § page.team.tasks.comments: Комментарии
§ page.person.print.photo.title: Фотография § page.person.print.photo.title: Фотография
§ page.person.print.photo.description: место для фотографии § page.person.print.photo.description: место для фотографии

View file

@ -29,6 +29,9 @@ export default `
§ sidebar.team.changes: 所有更改 § sidebar.team.changes: 所有更改
§ sidebar.team.words: 流行语 § sidebar.team.words: 流行语
§ sidebar.team.building: 测验 § sidebar.team.building: 测验
§ sidebar.team.refactor: Refactoring
§ sidebar.team.company: Companies
§ sidebar.team.country: Locations
§ sidebar.team.settings: 设置 § sidebar.team.settings: 设置
§ sidebar.person.total: 般资料 § sidebar.person.total: 般资料
§ sidebar.person.money: 工作的成本 § sidebar.person.money: 工作的成本

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git仓库报告 § page.print.title: Git仓库报告
§ page.print.sub_title: «$1» § page.print.sub_title: «$1»
§ page.print.description: 报告的数据是从历史记录中获得的 Commits. § page.print.description: 报告的数据是从历史记录中获得的 Commits.
§ page.team.author.title: 雇员统计数字 § page.team.author.statusChart.title: Status
§ page.team.author.daysChart.title: Days of work
§ page.team.author.daysChart.item: days
§ page.team.author.days.half: half year
§ page.team.author.days.one: year
§ page.team.author.days.15: year and a half
§ page.team.author.days.two: two years
§ page.team.author.days.more: more
§ page.team.author.title: Details
§ page.team.author.description1: 部分统计数字 (.) "助理" . § page.team.author.description1: 部分统计数字 (.) "助理" .
§ page.team.author.description2: 默认排序是按任务和组数排序 (). § page.team.author.description2: 默认排序是按任务和组数排序 ().
§ page.team.author.types: 工作类别 § page.team.author.types: 工作类别
@ -57,6 +65,7 @@ export default `
§ page.team.type.title: 按任务类型划分的统计信息 § page.team.type.title: 按任务类型划分的统计信息
§ page.team.type.description: ** Commits, "文件分析" § page.team.type.description: ** Commits, "文件分析"
§ page.team.type.type: 工作类别 § page.team.type.type: 工作类别
§ page.team.type.unknown: unknown
§ page.team.type.tasks: 任务 § page.team.type.tasks: 任务
§ page.team.type.tasksSmall: 任务 § page.team.type.tasksSmall: 任务
§ page.team.type.days: 天数 § page.team.type.days: 天数
@ -96,6 +105,11 @@ export default `
§ page.team.tree.filters.commits: 数量 Commits § page.team.tree.filters.commits: 数量 Commits
§ page.team.tree.filters.help: 最低数量 Commits, § page.team.tree.filters.help: 最低数量 Commits,
§ page.team.tree.filters.all: 所有员工 § page.team.tree.filters.all: 所有员工
§ page.team.tree.totalLines: Lines
§ page.team.tree.totalTasks: Tasks
§ page.team.tree.totalDays: Days
§ page.team.tree.tasks: tasks
§ page.team.tree.days: days
§ page.team.tree.add: 谁加的 § page.team.tree.add: 谁加的
§ page.team.tree.change: 谁改变了它 § page.team.tree.change: 谁改变了它
§ page.team.tree.remove: 谁删除了它 § page.team.tree.remove: 谁删除了它
@ -103,6 +117,29 @@ export default `
§ page.team.tree.linesAdded: 补充道 § page.team.tree.linesAdded: 补充道
§ page.team.tree.linesChanged: changed § page.team.tree.linesChanged: changed
§ page.team.tree.linesRemoved: 改变了 § page.team.tree.linesRemoved: 改变了
§ page.team.company.title: Details
§ page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days
§ page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit
§ page.team.country.pieByDomain.title: By email, timezone and language
§ page.team.country.pieByTimezone.title: By timezone
§ page.team.country.chart.item: employments
§ page.team.country.table.title: List of employees
§ page.team.country.table.country: Country
§ page.team.country.table.employments: Employments
§ page.team.refactor.title: Candidates for refactoring
§ page.team.refactor.lines: lines
§ page.team.refactor.tasks: tasks
§ page.team.refactor.days: days
§ page.team.refactor.path: Path
§ page.team.refactor.firstCommit: First commit
§ page.team.refactor.totalLines: Lines
§ page.team.refactor.totalTasks: Tasks
§ page.team.refactor.totalDays: Days in development
§ page.team.day.commits: Commits § page.team.day.commits: Commits
§ page.team.day.activity: Activity § page.team.day.activity: Activity
§ page.team.week.title: 按周划分的统计数字 § page.team.week.title: 按周划分的统计数字
@ -134,7 +171,6 @@ export default `
§ page.team.pr.chart.14day: two weeks § page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month § page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more § page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: 注射日期 § page.team.pr.date: 注射日期
§ page.team.pr.mergeAuthor: 填写 § page.team.pr.mergeAuthor: 填写
§ page.team.pr.author: 雇员 § page.team.pr.author: 雇员
@ -152,10 +188,6 @@ export default `
§ page.team.tasks.from: 第一个 Commits § page.team.tasks.from: 第一个 Commits
§ page.team.tasks.to: 最后一次 Commits § page.team.tasks.to: 最后一次 Commits
§ page.team.tasks.daysInWork: 工作中的日子 § page.team.tasks.daysInWork: 工作中的日子
§ page.team.tasks.commits: 数量 Commits
§ page.team.tasks.pr: 注射日期
§ page.team.tasks.prAuthor:
§ page.team.tasks.prDelayDays: 等待输液的日子
§ page.team.tasks.comments: 评论 § page.team.tasks.comments: 评论
§ page.team.extension.extension: File extensions § page.team.extension.extension: File extensions
§ page.team.extension.type: File sub types § page.team.extension.type: File sub types
@ -164,6 +196,7 @@ export default `
§ page.team.extension.current.count: Number § page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed § page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files § page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release § page.team.release.title: Release
§ page.team.release.from: Created date § page.team.release.from: Created date
§ page.team.release.to: Delivery date § page.team.release.to: Delivery date