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 {
const items = Object.entries(tasks)
.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 (
<>
<div className={style.day_info_link}>

View file

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

View file

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

View file

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

View file

@ -33,5 +33,5 @@ export default function getAdaptiveColumnWidth(
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
const defaultWidth = child?.props?.width || {
[ColumnTypesEnum.STRING]: 200,
[ColumnTypesEnum.NUMBER]: 110,
[ColumnTypesEnum.SHORT_NUMBER]: 70,
}[template || ''] || 0;
// @ts-ignore
const minWidth = child?.props?.minWidth || 40;
const minWidth = child?.props?.minWidth || {
[ColumnTypesEnum.STRING]: 200,
[ColumnTypesEnum.NUMBER]: 110,
}[template || ''] || 40;
// @ts-ignore
const isSortable = child?.props?.isSortable // @ts-ignore

View file

@ -24,7 +24,7 @@ function getFormattedDate(commits: ICommit[]) {
function getTags(commits: ICommit[]) {
const uniqueTypes = new Set(commits.map((commit: ICommit) => commit.type));
const tags = Array.from(uniqueTypes)
.filter((title: string) => title && title !== '—')
.filter((title: string) => title)
.map((title: string) => (
<p
key={title}
@ -43,7 +43,13 @@ interface ITaskProps {
function Task({ title, commits }: ITaskProps) {
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 (
<div
key={title}

View file

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

View file

@ -1,87 +1,76 @@
import ICommit from 'ts/interfaces/Commit';
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 {
commits: HashMap<any> = new Map();
companies: HashMap<any> = new Map();
statistic: any = [];
statisticByName: IHashMap<any> = {};
clear() {
this.commits.clear();
this.companies.clear();
this.statistic = [];
this.statisticByName = {};
}
addCommit(commit: ICommit) {
if (!commit.company) return;
const statistic = this.commits.get(commit.company);
#addAuthor(statByAuthor: any) {
const company = statByAuthor.company[statByAuthor.company.length - 1]?.title;
if (!company) return;
const statistic = this.companies.get(company);
if (statistic) {
this.#updateCommitByCompany(statistic, commit);
this.#updateCommitByCompany(statistic, statByAuthor);
} else {
this.#addCommitByCompany(commit);
this.#addCommitByCompany(company, statByAuthor);
}
}
#addCommitByCompany(commit: ICommit) {
this.commits.set(commit.company, {
company: commit.company,
commits: 1,
firstCommit: commit,
lastCommit: commit,
days: createIncrement(commit.timestamp),
employments: createIncrement(commit.author),
tasks: createIncrement(commit.task),
types: createIncrement(commit.type),
scopes: createIncrement(commit.scope),
#addCommitByCompany(company: string, statByAuthor: any) {
this.companies.set(company, {
company,
isActive: !statByAuthor.isDismissed && !statByAuthor.isStaff,
commits: statByAuthor.commits,
from: statByAuthor.firstCommit.milliseconds,
to: statByAuthor.lastCommit.milliseconds,
daysWorked: statByAuthor.daysWorked,
employments: [statByAuthor.author],
tasks: [statByAuthor.tasks],
// types: statByAuthor.types,
// scopes: createIncrement(commit.scope),
});
}
#updateCommitByCompany(statistic: any, commit: ICommit) {
statistic.commits += 1;
statistic.lastCommit = commit;
statistic.days[commit.timestamp] = true;
statistic.employments[commit.author] = true;
increment(statistic.tasks, commit.task);
increment(statistic.types, commit.type);
increment(statistic.scopes, commit.scope);
#updateCommitByCompany(statistic: any, statByAuthor: any) {
if (!statistic.isActive) {
statistic.isActive = !statByAuthor.isDismissed && !statByAuthor.isStaff;
}
statistic.commits += statByAuthor.commits;
statistic.from = statistic.from > statByAuthor.firstCommit.milliseconds
? statByAuthor.firstCommit.milliseconds
: statistic.from;
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) {
console.dir(dataGripByAuthor);
this.statistic = Array.from(this.commits.values())
.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);
dataGripByAuthor.statistic.forEach((data: any) => {
this.#addAuthor(data);
});
let isActive = false;
employments.forEach((name) => {
const author = dataGripByAuthor.statisticByName[name];
if (!author) return;
if (author.lastCompany === statistic.company) isActive = true;
});
this.statistic = Array.from(this.companies.values())
.map((company: any) => {
company.tasks = Array.from(new Set(company.tasks.flat(1))).length;
company.totalDays = Math.max(((company.to - company.from) / ONE_DAY), 1);
const companyInfo = {
...statistic,
employments,
tasks,
totalTasks: tasks.length,
totalDays: days.length,
totalEmployments: employments.length,
isActive,
};
delete companyInfo.days;
this.statisticByName[company.company] = company;
this.statisticByName[statistic.company] = companyInfo;
return companyInfo;
return company;
});
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 { createIncrement, increment, WeightedAverage } from 'ts/helpers/Math';
const IS_PR = {
[COMMIT_TYPE.PR_BITBUCKET]: true,
[COMMIT_TYPE.PR_GITHUB]: true,
[COMMIT_TYPE.PR_GITLAB]: true,
};
import { WeightedAverage } from 'ts/helpers/Math';
import { ONE_DAY } from 'ts/helpers/formatter';
export default class DataGripByPR {
pr: HashMap<any> = new Map();
prByTask: HashMap<any> = new Map();
lastCommitByTaskNumber: HashMap<any> = new Map();
statistic: any[] = [];
statisticByName: IHashMap<any> = [];
statisticByName: IHashMap<any> = {};
clear() {
this.pr.clear();
this.prByTask.clear();
this.lastCommitByTaskNumber.clear();
this.statistic = [];
this.statisticByName = {};
}
addCommit(commit: ISystemCommit) {
if (!commit.commitType) {
const commitByTaskNumber = this.lastCommitByTaskNumber.get(commit.task);
if (commitByTaskNumber) {
this.#updateCommitByTaskNumber(commitByTaskNumber, commit);
} else {
this.#addCommitByTaskNumber(commit);
}
} else if (!this.pr.has(commit.prId) && IS_PR[commit.commitType || '']) {
// commitType PR
if (!commit.prId) return;
const statistic = this.pr.get(commit.prId);
if (statistic) {
console.log('PR error');
} else {
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) {
const lastCommit = this.lastCommitByTaskNumber.get(commit.task);
if (lastCommit) {
// коммиты после влития PR сгорают, чтобы не засчитать технические PR мержи веток
this.lastCommitByTaskNumber.delete(commit.task);
const delay = commit.milliseconds - lastCommit.endTaskTime;
const work = lastCommit.endTaskTime - lastCommit.beginTaskTime;
this.pr.set(commit.prId, {
...commit,
...lastCommit,
delay,
delayDays: delay / (24 * 60 * 60 * 1000),
workDays: work === 0 ? 1 : (work / (24 * 60 * 60 * 1000)),
});
this.prByTask.set(commit.task, commit.prId);
} else {
this.pr.set(commit.prId, { ...commit });
}
this.pr.set(commit.prId, {
prId : commit.prId,
author: commit.author,
task: commit.task,
type: commit.type,
branch: commit.branch,
message: commit.message,
dateCreate: commit.milliseconds, // last commit date before PR
dateMerge: commit.milliseconds,
daysReview: 1,
daysInWork: 1,
});
}
updateTotalInfo(dataGripByAuthor: any) {
const employment = dataGripByAuthor.employment;
const authors = [...employment.active, ...employment.dismissed];
const refAuthorPR: any = Object.fromEntries(authors.map((name: string) => ([name, []])));
updateTotalInfo(dataGripByTasks: any, dataGripByAuthor: any) {
const byAuthor = new Map();
this.statistic = Object.values(this.pr)
.filter((item: any) => item.delay && item.task)
.sort((a: any, b: any) => b.delay - a.delay);
this.pr.forEach((pr: any) => {
if (!pr.task) return;
const task = dataGripByTasks.statisticByName.get(pr.task);
if (!task) return;
this.statistic = [];
this.statisticByName = {};
task.prIds.push(pr.prId);
Object.values(this.pr).forEach((item: any) => {
if (!item.delay || !item.task) return;
this.statistic.push(item);
if (refAuthorPR[item.firstCommit.author]) {
refAuthorPR[item.firstCommit.author].push(item);
pr.daysInWork = task.daysInWork;
let lastCommitDateBeforePR = task?.to || task?.from;
if (lastCommitDateBeforePR > pr.dateMerge) {
if (!task.timestamps) {
console.log('x');
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.updateTotalByAuthor(authors, refAuthorPR);
this.statistic = Array.from(this.pr.values())
.sort((a: any, b: any) => b.daysReview - a.daysReview);
this.lastCommitByTaskNumber.clear();
this.updateTotalByAuthor(byAuthor, dataGripByAuthor);
}
static getPRByGroups(list: any, propertyName: string) {
@ -121,11 +99,14 @@ export default class DataGripByPR {
[TITLES.MORE]: 0,
};
let max = 0;
const weightedAverage = new WeightedAverage();
list.forEach((pr: any) => {
const value = pr[propertyName];
if (value > max) max = value;
weightedAverage.update(value);
if (value <= 1) details[TITLES.DAY]++;
@ -138,36 +119,33 @@ export default class DataGripByPR {
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 = {};
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;
refAuthorPR[name].forEach((pr: any) => {
if (pr.delayDays > maxDelayDays) maxDelayDays = pr.delayDays;
});
const daysReview = DataGripByPR.getPRByGroups(prs, 'daysReview');
const daysReviewWeightedAverage = parseInt(daysReview.weightedAverage.toFixed(1), 10);
// TODO: сложын и не интересные показатели. Гистаграмму?
const delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays');
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
const daysInWork = DataGripByPR.getPRByGroups(prs, 'daysInWork');
const daysInWorkWeightedAverage = parseInt(daysInWork.weightedAverage.toFixed(1), 10);
const workDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'workDays');
const workDaysWeightedAverage = parseInt(workDays.weightedAverage.toFixed(1), 10);
this.statisticByName[author] = {
author,
maxDelayDays: daysReview.max,
numberMergedPr: prs.length,
this.statisticByName[name] = {
author: name,
maxDelayDays,
numberMergedPr: refAuthorPR[name].length,
workDays: workDays.details,
delayDays: delayDays.details,
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
workDays: daysInWork.details,
delayDays: daysReview.details,
weightedAverage: daysInWorkWeightedAverage + daysReviewWeightedAverage,
weightedAverageDetails: {
workDays: workDaysWeightedAverage,
delayDays: delayDaysWeightedAverage,
workDays: daysInWorkWeightedAverage,
delayDays: daysReviewWeightedAverage,
},
};
});

View file

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

View file

@ -1,46 +1,40 @@
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 {
commits: IHashMap<ICommit[]> = {};
commits: HashMap<ICommit[]> = new Map();
statistic: any = [];
statisticByName: HashMap<any> = new Map();
// achievements
longTaskByAuthor: IHashMap<number> = {};
clear() {
this.commits = {};
this.commits.clear();
this.statistic = [];
this.statisticByName.clear();
this.longTaskByAuthor = {};
}
addCommit(commit: ICommit) {
if (this.commits.hasOwnProperty(commit.task)) {
this.#updateCommitByTask(commit);
if (this.commits.has(commit.task)) {
this.commits.get(commit.task)?.push(commit);
} 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: тут двойной пробег получился. А должен был частями собрать инфу
updateTotalInfo(PRs: any) {
this.statistic = Object.entries(this.commits)
updateTotalInfo() {
this.statistic = Array.from(this.commits, ([k, v]) => [k, v]) // @ts-ignore
.map(([task, commits]: [string, ICommit[]]) => {
const firstCommit = commits[0];
const lastCommit = commits[commits.length - 1];
const from = firstCommit.milliseconds;
const pr = PRs.prByTask.get(task) ? PRs.pr.get(PRs.prByTask.get(task)) : null;
const shortInfo = {
task,
@ -48,30 +42,31 @@ export default class DataGripByTasks {
from,
commits: 1,
daysInWork: 1,
prDate: pr?.milliseconds,
prDelayDays: pr?.delayDays,
prAuthor: firstCommit.author === pr?.author ? null : pr?.author,
prIds: [],
releaseIds: new Set(),
comments: firstCommit.text,
types: firstCommit.type && firstCommit.type !== '—' ? [firstCommit.type] : [],
scope: firstCommit.scope && firstCommit.scope !== '—' ? [firstCommit.scope] : [],
types: firstCommit.type ? { [firstCommit.type]: 1 } : {},
scope: firstCommit.scope ? { [firstCommit.scope]: 1 } : {},
};
if (commits.length === 1) return shortInfo;
const authors = new Set();
const messages = new Set();
const types = new Set();
const scope = new Set();
const timestamps = new Set();
const authors = {};
const types = {};
const scope = {};
commits.forEach((commit: ICommit) => {
authors.add(commit.author);
messages.add(commit.text);
if (commit.type !== '—') types.add(commit.type);
if (commit.scope !== '—') scope.add(commit.scope);
timestamps.add(commit.milliseconds);
increment(authors, commit.author);
increment(types, commit.type);
increment(scope, commit.scope);
});
const comments = Array.from(messages).join(', ');
const to = lastCommit.milliseconds;
const daysInWork = Math.ceil((to - from) / ONE_DAY) + 1;
const daysInWork = timestamps.size;
const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author];
if (!longTaskByAuthor || longTaskByAuthor < daysInWork) {
@ -82,16 +77,21 @@ export default class DataGripByTasks {
...shortInfo,
to: to !== from ? to : undefined,
commits: commits.length,
timestamps: Array.from(timestamps),
daysInWork,
authors: Array.from(authors),
comments,
types: Array.from(types),
scope: Array.from(scope),
authors,
types,
scope,
};
})
.filter((dot) => dot.task)
.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 DataGripByScoring from './components/scoring';
import DataGripByCompany from './components/company';
import DataGripByCountry from './components/country';
class DataGrip {
firstLastCommit: any = new MinMaxCounter();
@ -23,6 +24,8 @@ class DataGrip {
company: any = new DataGripByCompany();
country: any = new DataGripByCountry();
team: any = new DataGripByTeam();
scope: any = new DataGripByScope();
@ -49,6 +52,7 @@ class DataGrip {
this.firstLastCommit.clear();
this.author.clear();
this.company.clear();
this.country.clear();
this.team.clear();
this.scope.clear();
this.type.clear();
@ -75,7 +79,6 @@ class DataGrip {
this.get.addCommit(commit);
this.week.addCommit(commit);
this.tasks.addCommit(commit);
this.company.addCommit(commit);
}
}
@ -87,11 +90,12 @@ class DataGrip {
this.timestamp.updateTotalInfo(this.author);
this.week.updateTotalInfo(this.author);
this.recommendations.updateTotalInfo(this);
this.pr.updateTotalInfo(this.author);
this.tasks.updateTotalInfo(this.pr);
this.release.updateTotalInfo();
this.tasks.updateTotalInfo();
this.pr.updateTotalInfo(this.tasks, this.author);
this.release.updateTotalInfo(this.tasks, this.pr);
this.scoring.updateTotalInfo(this.author, this.timestamp);
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'
.split('')
.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_EMAILS,
FAKE_TASK_PREFIXES,
FAKE_COMPANIES,
} from './constants';
import FakeName from './FakeName';
@ -14,21 +15,26 @@ export default class Depersonalized {
fakeTaskPrefix: any = null;
fakeCompany: any = null;
constructor() {
this.fakeName = new FakeName('User', FAKE_AUTHORS);
this.fakeEmail = new FakeName('user', FAKE_EMAILS);
this.fakeTaskPrefix = new FakeName('JIRA', FAKE_TASK_PREFIXES);
this.fakeCompany = new FakeName('Company', FAKE_COMPANIES);
}
getCommit(commit: ICommit | ISystemCommit): ICommit | ISystemCommit {
const author = this.fakeName.get(commit.author);
const email = this.fakeEmail.get(commit.author);
const company = this.fakeCompany.get(commit.company);
if (!commit.task) {
return {
...commit,
author,
email,
company,
};
}
@ -53,6 +59,7 @@ export default class Depersonalized {
message,
author,
email,
company,
branch,
toBranch,
};

View file

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

View file

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

View file

@ -4,12 +4,25 @@ import IHashMap from 'ts/interfaces/HashMap';
import { getValuesInPercent } from '../helpers';
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 {
id: Math.random(),
name: name || '', // @ts-ignore
path: path || [],
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,
@ -48,6 +61,12 @@ function updateFolder(folder: any, file: IDirtyFile) {
folder.removedLines += file.removedLines || 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, 'removedLinesByAuthor');
updateFolderBy(folder, file, 'changedLinesByAuthor');
@ -69,17 +88,19 @@ export default class FileGripByFolder {
addFile(file: IDirtyFile) {
let prev: any = this.tree.content;
file.path.forEach((folderName: any, index: number) => {
let folder = prev[folderName];
if (!folder || !folder.content) {
const folder = prev.get(folderName);
if (!folder?.content) {
const path = file.path.slice(0, index);
prev[folderName] = getFolder(folderName, path, file);
this.folders.push(prev[folderName]);
const newFolder = getFolder(folderName, path, file);
prev.set(folderName, newFolder);
this.folders.push(newFolder);
prev = newFolder.content;
} else {
updateFolder(folder, file);
prev = folder.content;
}
prev = prev[folderName].content;
});
prev[file.name] = file;
prev.set(file.name, file);
}
updateTotalInfo() {

View file

@ -6,6 +6,7 @@ import FileGripByExtension from './components/extension';
import FileGripByType from './components/type';
import FileGripByFolder from './components/folder';
import FileGripByAuthor from './components/author';
import FileGripByRefactor from './components/refactor';
class FileGrip {
files: any = new FileBuilder();
@ -20,6 +21,8 @@ class FileGrip {
author: any = new FileGripByAuthor();
refactor: any = new FileGripByRefactor();
clear() {
this.files.clear();
this.extension.clear();
@ -27,6 +30,7 @@ class FileGrip {
this.tree.clear();
this.removedTree.clear();
this.author.clear();
this.refactor.clear();
}
addCommit(commit: ICommit | ISystemCommit) {
@ -54,6 +58,7 @@ class FileGrip {
this.author.updateTotalInfo();
this.tree.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 { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
import getCompany from './getCompany';
import getInfoFromNameAndEmail from './getCompany';
import { getGithubPrInfo } from './getMergeInfo';
const MASTER_BRANCH = {
master: true,
@ -35,6 +36,7 @@ export default function getCommitInfo(
prevDate = date;
const day = date.getDay() - 1;
const timestamp = sourceDate.substring(0, 10); // split('T')[0];
const timezone = sourceDate.substring(19, 25);
let milliseconds = refTimestampTime.get(timestamp);
if (!milliseconds) {
milliseconds = (new Date(timestamp)).getTime();
@ -47,12 +49,11 @@ export default function getCommitInfo(
const companyKey = `${author}>in>${email}`;
if (!refEmailAuthor[companyKey]) {
const companyForKey = getCompany(author, email);
// @ts-ignore
refEmailAuthor[companyKey] = { company: companyForKey };
refEmailAuthor[companyKey] = getInfoFromNameAndEmail(author, email);
}
// @ts-ignore
const company = refEmailAuthor[companyKey].company;
const { company, country, device } = refEmailAuthor[companyKey];
const authorID = author.replace(/\s|\t/gm, '');
if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) {
@ -86,6 +87,7 @@ export default function getCommitInfo(
month: date.getMonth(),
year: date.getUTCFullYear(),
week: 0,
timezone,
timestamp,
milliseconds,
@ -93,10 +95,12 @@ export default function getCommitInfo(
email,
message,
company,
country,
device,
text: '',
type: '',
scope: '',
type: '',
scope: '',
fileChanges: [],
};
@ -114,24 +118,29 @@ export default function getCommitInfo(
if (isSystemCommit) {
let commitType = COMMIT_TYPE.MERGE;
let prId, repository, branch, toBranch, task, taskNumber;
let prId, repository, branch, toBranch, task, taskNumber, type, scope;
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;
[, prId, repository, branch, toBranch ] = message
.replace(/(Merge\spull\srequest\s#)|(\sfrom\s)|(\sin\s)|(\sto\s)/gim, ',')
.split(',');
[prId, repository, branch, toBranch] = getGithubPrInfo(message);
task = getTask(branch);
} else if (isBitbucketPR) {
} else if (isBitbucketPR) { // "Pull request #3: TASK-123 fix: Add profile"
commitType = COMMIT_TYPE.PR_BITBUCKET;
const messageParts = message.substring(14).split(':');
prId = messageParts.shift();
task = getTask(messageParts.join(':'));
} else if (isAutoMerge) {
const description = messageParts.join(':');
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;
[, branch, toBranch ] = message
.replace(/(Automatic\smerge\sfrom\s)|(\s->\s)/gim, ',')
.replace(/(Merge\sremote-tracking\sbranch\s')|('\sinto\s)/gim, ',')
.split(',');
} else if (isGitlabPR) {
commitType = COMMIT_TYPE.PR_GITLAB;
[, branch, toBranch ] = message
@ -147,6 +156,8 @@ export default function getCommitInfo(
return {
...commonInfo,
type,
scope,
prId: prId || '',
task: task || '',
taskNumber: taskNumber || '',
@ -155,25 +166,25 @@ export default function getCommitInfo(
toBranch: toBranch || '',
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 = [
'icloud',
'google',
'inbox',
'yahoo',
'aol',
'zoho',
@ -25,6 +29,8 @@ const isPublicService = Object.fromEntries(
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 {
const tags = (author || '')
.toUpperCase()
@ -39,11 +45,12 @@ function getCompanyByName(author?: string): string {
: '';
}
function getCompanyByEmail(email?: string) {
const domain = (email || '').split('@').pop() || '';
const parts = domain.split('.');
parts.pop();
return (parts.pop() || '').toUpperCase();
function getCompanyAndDomainByEmail(email?: string) {
const fullDomain = (email || '').split('@').pop() || '';
const parts = fullDomain.split('.');
const domain = parts.pop();
const company = (parts.pop() || '').toUpperCase();
return [company, domain];
}
function getClearText(text: string) {
@ -63,12 +70,19 @@ function isUserName(author?: string, company?: string): boolean {
return !!clearAuthor.match(clearCompany);
}
function getCompany(author?: string, email?: string) {
const company = getCompanyByName(author) || getCompanyByEmail(email) || '';
const isMailService = company.indexOf('MAIL') !== -1;
return isPublicService[company] || isMailService || isUserName(author, company)
? ''
: company;
}
export default function getInfoFromNameAndEmail(author?: string, email?: string) {
const companyByAuthor = getCompanyByName(author);
const [companyByEmail, domain] = getCompanyAndDomainByEmail(email);
const country = getCountryByDomain(domain);
const device = getDevice(companyByEmail);
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,
month: number; // 1,
year: number; // 2021,
timestamp: string; // 2021-02-09",
timezone: string; // "+03:00",
timestamp: string; // "2021-02-09",
milliseconds: number; // 1612828800000,
week: number; // 42,
// user
author: string; // "Dart Vader",
email: string; // "d.vader@emap.com",
email: string; // "d.vader@emap.ru",
company: string; // "emap",
country: string; // "ru",
device: string; // "Macbook",
// task
message: string; // "JIRA-0000 fix(profile): add new avatar",

View file

@ -1,7 +1,12 @@
import ICommit, { ISystemCommit } from './Commit';
import IHashMap from './HashMap';
import IHashMap, { HashMap } from './HashMap';
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
addedLines: number;
@ -35,5 +40,5 @@ export interface IFolder extends IFileStat {
name?: string;
path: string[]; // ['src']
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',
},
{
id: 'pr',
link: '/team/pr',
title: 'sidebar.team.pr',
icon: './assets/menu/pull_request.svg',
id: 'company',
link: '/team/company',
title: 'sidebar.team.company',
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',
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',
link: '/team/tasks',
@ -93,10 +112,10 @@ export const TEAM = [
icon: './assets/menu/team_tasks.svg',
},
{
id: 'release',
link: '/team/release',
title: 'sidebar.team.release',
icon: './assets/menu/team_release.svg',
id: 'pr',
link: '/team/pr',
title: 'sidebar.team.pr',
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 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';

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 NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title';
import Companies from './Companies';
import Companies from './components/Companies';
import CompanyCharts from './components/Charts';
const Company = observer(({
mode,
}: 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 (
<>
<Title title="page.team.author.title"/>
<CompanyCharts />
<Title title="page.team.company.title"/>
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
content: companyRows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.hash}`}
>
<Companies
mode={mode}
rowsForExcel={rows}
rowsForExcel={companyRows}
/>
<Pagination />
</DataLoader>

View file

@ -7,24 +7,20 @@ import treeStore from '../../store/Tree';
const FileBreadcrumbs = observer((): React.ReactElement => {
const directories = treeStore.selectedPath
.map((dirName: string, index: number) => (
<>
<span
key={`${dirName}.`}
className={style.file_breadcrumbs_text}
>
{'/'}
<span key={dirName}>
<span 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
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 (

View file

@ -14,6 +14,7 @@ import { getMax } from 'ts/pages/Common/helpers/getMax';
import { getDate } from 'ts/helpers/formatter';
import treeStore from '../../store/Tree';
import Tasks from './Tasks';
interface IViewProps {
response?: IPagination<any>;
@ -23,6 +24,8 @@ function View({ response }: IViewProps) {
if (!response) return null;
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 addedRemovedChangedChart = getOptions({ order: [
'page.team.tree.linesAdded',
@ -44,6 +47,24 @@ function View({ response }: IViewProps) {
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
isFixed
template={ColumnTypesEnum.STRING}
@ -56,13 +77,14 @@ function View({ response }: IViewProps) {
/>
<Column
isSortable
width={50}
width={60}
properties="lines"
template={ColumnTypesEnum.SHORT_NUMBER}
/>
<Column
isSortable
properties="lines"
title="page.team.tree.totalLines"
minWidth={100}
template={(value: any) => (
<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
isSortable
template={ColumnTypesEnum.STRING}

View file

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

View file

@ -15,10 +15,10 @@ function Total() {
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 delayChart = DataGripByPR.getPRByGroups(allPR, 'delayDays');
const delayChart = DataGripByPR.getPRByGroups(allPR, 'daysReview');
const delayChartOptions = getOptions({ order: delayChart.order, limit: 3, suffix: 'PR' });
const workDaysWeightedAverage = Math.round(workChart.weightedAverage);

View file

@ -16,28 +16,46 @@ import fullScreen from 'ts/store/FullScreen';
import Total from './Total';
import Authors from './Authors';
import Anonymous from './Anonymous';
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(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const allPR = dataGripStore.dataGrip.pr.statistic;
const rows = allPR.filter((item: any) => item.delayDays > 3);
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
const [withTask, withoutTask] = getGroupsByTasks(allPR);
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 authorsStat = Object.values(PRbyName);
return (
<>
{!fullScreen.isOpen && (
{!fullScreen.isOpen && canShowByReview && (
<>
<Title title="page.team.pr.oneTaskDays"/>
<Total/>
</>
)}
{!fullScreen.isOpen || fullScreen.mode === 'author' ? (
{canShowByReview ? (
<>
<Title title="page.team.pr.statByAuthors"/>
<DataLoader
@ -52,18 +70,12 @@ const PR = observer(({
/>
<Pagination/>
</DataLoader>
</>
) : null}
<PageBreak/>
{!fullScreen.isOpen || fullScreen.mode === 'all' ? (
<>
<PageBreak/>
<Title title="page.team.pr.longDelay"/>
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows,
content: longReview,
pagination: mode === 'print'
? { size: 20 }
: pagination,
@ -72,7 +84,31 @@ const PR = observer(({
>
<All
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/>}
</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) {
const { t } = useTranslation();
const unknown = t('page.team.type.unknown');
if (!response) return null;
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}
title="page.team.type.type"
properties="type"
formatter={(type: string) => type || unknown}
width={150}
/>
<Column

View file

@ -1,14 +1,15 @@
import { IFolder } from 'ts/interfaces/FileInfo';
function getSubTree(tree: IFolder, path: string[]) {
return (path || []).reduce((subTree: any, folderName: string) => {
subTree = subTree.content[folderName] || { content: [] };
return subTree;
}, tree || { content: [] });
return (path || [])
.reduce((subTree: any, folderName: string) => {
subTree = subTree.content.get(folderName) || { content: new Map() };
return subTree;
}, tree || { content: new Map() });
}
function getSortedContent(subTree: any) {
return Object.values(subTree.content)
function getSortedContent(subTree: IFolder) {
return Array.from(subTree.content.values())
.sort((a: any, b: any) => {
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 Commits from './components/Commits';
import Company from './components/Company';
import Country from './components/Country';
import Changes from './components/Changes';
import Hours from './components/Hours';
import PopularWords from './components/PopularWords';
@ -25,6 +26,7 @@ import Building from './components/Building';
import Pr from './components/PR';
import Print from './components/Print';
import Release from './components/Release';
import Refactor from './components/Refactor';
interface ViewProps {
page?: string;
@ -39,6 +41,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
if (page === 'scope') return <Scope mode={mode}/>;
if (page === 'author') return <Author 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 === 'pr') return <Pr mode={mode}/>;
if (page === 'day') return <Tempo/>;
@ -55,6 +58,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
if (page === 'building') return <Building/>;
if (page === 'print') return <Print/>;
if (page === 'tasks') return <Tasks/>;
if (page === 'refactor') return <Refactor/>;
return <Total/>;
});

View file

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

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report
§ page.print.sub_title: «$1»
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ 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.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks
§ page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ 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.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.change: Who changed
§ page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Weekly statistics
@ -137,7 +174,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee
@ -155,10 +191,6 @@ export default `
§ page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -167,6 +199,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report
§ page.print.sub_title: «$1»
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ 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.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks
§ page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ 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.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.change: Who changed
§ page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Informe del repositorio git
§ page.print.sub_title: «$1»
§ 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.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
@ -62,6 +70,7 @@ export default `
§ 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.type: Tipo de trabajo
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tareas
§ page.team.type.tasksSmall: Tareas
§ 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.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.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.change: Quien cambió
§ page.team.tree.remove: Quién borró
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: agregaron
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Estadísticas semanales
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: commits
§ page.team.pr.date: Date of injection
§ page.team.pr.mergeAuthor: I poured it in
§ page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: The first commits
§ page.team.tasks.to: Last commits
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

@ -27,7 +27,10 @@ export default `
§ sidebar.team.commits: all commits
§ sidebar.team.changes: Tous les changements
§ 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.person.total: Informations générales
§ 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.sub_title: «$1»
§ 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.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
@ -62,6 +70,7 @@ export default `
§ 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.type: Type de travail
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Задач
§ page.team.type.tasksSmall: Tâche
§ page.team.type.days: Jours
@ -101,6 +110,11 @@ export default `
§ 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.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.change: Qui a changé
§ page.team.tree.remove: Qui a supprimé
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: ajoutâtes
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Statistiques par semaine
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Date de diffusion
§ page.team.pr.mergeAuthor: Versai
§ page.team.pr.author: Employé
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: Premier commit
§ page.team.tasks.to: Dernier commit
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report
§ page.print.sub_title: «$1»
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ 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.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks
§ page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ 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.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.change: Who changed
§ page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git repository report
§ page.print.sub_title: «$1»
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
§ page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ 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.type: Type of work
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Tasks
§ page.team.type.tasksSmall: tasks
§ page.team.type.days: Days
@ -101,6 +110,11 @@ export default `
§ 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.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.change: Who changed
§ page.team.tree.remove: Who removed
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: added
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: Weekly statistics
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: Merge Date
§ page.team.pr.mergeAuthor: Merged by
§ page.team.pr.author: Employee
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: First commit
§ page.team.tasks.to: Last commit
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Отчёт по git-репозиторию
§ page.print.sub_title: «$1»
§ 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.description2: *Сортировка по умолчанию* это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
§ page.team.author.status: Статус
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Статистика по типам задач
§ page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений.
§ page.team.type.type: Тип работы
§ page.team.type.unknown: неизвестный
§ page.team.type.tasks: Задач
§ page.team.type.tasksSmall: задач
§ page.team.type.days: Дней
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Количество коммитов
§ page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле
§ 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.change: Кто менял
§ page.team.tree.remove: Кто удалял
@ -108,6 +122,29 @@ export default `
§ page.team.tree.linesAdded: добавили
§ page.team.tree.linesChanged: изменили
§ 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.activity: Активность
§ page.team.week.title: Статистика по неделям
@ -139,7 +176,6 @@ export default `
§ page.team.pr.chart.14day: две недели
§ page.team.pr.chart.30day: месяц
§ page.team.pr.chart.more: более
§ page.team.pr.commits: Коммиты
§ page.team.pr.date: Дата влития
§ page.team.pr.mergeAuthor: Влил
§ page.team.pr.author: Сотрудник
@ -157,10 +193,6 @@ export default `
§ page.team.tasks.from: Первый коммит
§ page.team.tasks.to: Последний коммит
§ 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.extension.extension: Расширения файлов
§ page.team.extension.type: Подтип файлов
@ -169,6 +201,7 @@ export default `
§ page.team.extension.current.count: Количество
§ page.team.extension.removed.count: Количество удалённых
§ page.team.extension.files: файлов
§ page.team.release.download: Скачать
§ page.team.release.title: Релиз
§ page.team.release.from: Дата создания
§ page.team.release.to: Дата завершения

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Отчёт по git-репозиторию
§ page.print.sub_title: «$1»
§ 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.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
§ page.team.author.status: Status
@ -62,6 +70,7 @@ export default `
§ page.team.type.title: Статистика по типам задач
§ page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений.
§ page.team.type.type: Тип работы
§ page.team.type.unknown: unknown
§ page.team.type.tasks: Задач
§ page.team.type.tasksSmall: задач
§ page.team.type.days: Дней
@ -101,6 +110,11 @@ export default `
§ page.team.tree.filters.commits: Количество коммитов
§ page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле
§ 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.change: Кто менял
§ page.team.tree.remove: Кто удалял
@ -132,7 +146,6 @@ export default `
§ page.team.pr.delayDays: Дней ожидания влития
§ page.team.pr.all.workDays: Время работы над задачей
§ page.team.pr.all.delayDays: Время ревью PR
§ page.team.pr.commits: Коммиты
§ page.team.pr.date: Дата влития
§ page.team.pr.mergeAuthor: Влил
§ page.team.pr.author: Сотрудник
@ -157,10 +170,6 @@ export default `
§ page.team.tasks.from: Первый коммит
§ page.team.tasks.to: Последний коммит
§ 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.person.print.photo.title: Фотография
§ page.person.print.photo.description: место для фотографии

View file

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

View file

@ -20,7 +20,15 @@ export default `
§ page.print.title: Git仓库报告
§ page.print.sub_title: «$1»
§ 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.description2: 默认排序是按任务和组数排序 ().
§ page.team.author.types: 工作类别
@ -57,6 +65,7 @@ export default `
§ page.team.type.title: 按任务类型划分的统计信息
§ page.team.type.description: ** Commits, "文件分析"
§ page.team.type.type: 工作类别
§ page.team.type.unknown: unknown
§ page.team.type.tasks: 任务
§ page.team.type.tasksSmall: 任务
§ page.team.type.days: 天数
@ -96,6 +105,11 @@ export default `
§ page.team.tree.filters.commits: 数量 Commits
§ page.team.tree.filters.help: 最低数量 Commits,
§ 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.change: 谁改变了它
§ page.team.tree.remove: 谁删除了它
@ -103,6 +117,29 @@ export default `
§ page.team.tree.linesAdded: 补充道
§ page.team.tree.linesChanged: changed
§ 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.activity: Activity
§ page.team.week.title: 按周划分的统计数字
@ -134,7 +171,6 @@ export default `
§ page.team.pr.chart.14day: two weeks
§ page.team.pr.chart.30day: month
§ page.team.pr.chart.more: more
§ page.team.pr.commits: Commits
§ page.team.pr.date: 注射日期
§ page.team.pr.mergeAuthor: 填写
§ page.team.pr.author: 雇员
@ -152,10 +188,6 @@ export default `
§ page.team.tasks.from: 第一个 Commits
§ page.team.tasks.to: 最后一次 Commits
§ 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.extension.extension: File extensions
§ page.team.extension.type: File sub types
@ -164,6 +196,7 @@ export default `
§ page.team.extension.current.count: Number
§ page.team.extension.removed.count: Number of removed
§ page.team.extension.files: files
§ page.team.release.download: Download
§ page.team.release.title: Release
§ page.team.release.from: Created date
§ page.team.release.to: Delivery date