JIRA-1234 feat(lang): test test test

This commit is contained in:
bakhirev 2023-10-13 15:17:21 +03:00
parent 3d4cb821ac
commit 9f77159b31
18 changed files with 77 additions and 257 deletions

View file

@ -1,5 +1,6 @@
import React, { ReactNode } from 'react';
import localization from 'ts/helpers/Localization';
import style from '../styles/index.module.scss';
export interface IUiKitWrapperProps {
@ -25,22 +26,22 @@ function UiKitWrapper({
return (
<div
className={`${style.wrapper} ${className || ''}`}
title={help}
title={localization.get(help)}
>
{title && (
<h6 className={style.title}>
{title}
{localization.get(title)}
</h6>
)}
{description && (
<p className={style.description}>
{description}
{localization.get(description)}
</p>
)}
{children}
{help && (
<p className={style.help}>
{example}
{localization.get(example)}
</p>
)}
{error && (

View file

@ -28,6 +28,16 @@ localization.parse('ru', `
§ sidebar.person.changes: Все изменения
§ sidebar.person.words: Популярные слова
§ sidebar.person.settings: Настройки
§ page.welcome.step1: Выполните команду в корне вашего проекта
§ page.welcome.step2: Перетащите файл log.txt на эту страницу
§ page.welcome.description1: Git создаст файл log.txt. Он содержит данные для построения отчёта. Или git shortlog -s -n -e если отчёт вам не нужен. Создайте файл
§ page.welcome.description2: в корне проекта, чтобы обьединить статистику по сотрудникам.
§ page.welcome.description: Git создаст файл log.txt. Он содержит данные для построения отчёта. Или git shortlog -s -n -e если отчёт вам не нужен. Создайте файл [.mailmap|https://git-scm.com/docs/gitmailmap] в корне проекта, чтобы обьединить статистику по сотрудникам.
§ page.welcome.warning1: Сервис *НЕ ХРАНИТ* и *НЕ ПЕРЕДАЁТ* ваши данные. Все расчёты выполняются локально в вашем браузере прямо на вашей машине.
§ page.welcome.warning2: Сервис *НЕ СОБИРАЕТ СТАТИСТИКУ* по проектам. Вы можете отключить интернет, проверить трафик и даже собрать локальный билд из [исходников|https://github.com/bakhirev/assayo].
§ page.team.author.title: Статистика по сотрудникам
§ page.team.author.description1: *Часть статитики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помошник» не считается*, т.к. это эпизодическая роль в проекте. Предпологаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы.
§ page.team.author.description2: *Сортировка по умолчанию* это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
§ page.team.author.types: Тип работ
§ page.team.author.commits: Коммитов
§ page.team.author.commitsSmall: коммитов
@ -41,6 +51,9 @@ localization.parse('ru', `
§ page.team.author.moneyAll: Получил
§ page.team.author.moneyWorked: Отработал
§ page.team.author.moneyLosses: Переплата
§ page.team.hours.title: Распределение коммитов в течении каждого дня недели
§ page.team.month.title: Календарь работы по проекту
§ page.team.scope.title: Статистика по фичам
§ page.team.scope.scope: Фича
§ page.team.scope.days: Раб. дней
§ page.team.scope.authorsDays: Человеко-дней
@ -50,13 +63,18 @@ localization.parse('ru', `
§ page.team.scope.types: Тип работ
§ page.team.scope.authors: Персональный вклад
§ page.team.scope.cost: Стоимость
§ page.team.type.title: Статистика по типам задач
§ page.team.type.description: *Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений.
§ page.team.type.type: Тип работы
§ page.team.type.tasks: Задач
§ page.team.type.tasksSmall: задач
§ page.team.type.days: Дней
§ page.team.type.daysSmall: дней
§ page.team.type.authorsDays: Человеко-дней
§ page.team.type.commits: Коммитов
§ page.team.type.commitsSmall: коммитов
§ page.team.type.authors: Персональный вклад
§ page.team.total.titleA: Объём работ
§ page.team.total.titleB: Стоимость
§ page.team.total.daysWorked.title: человеко-дней
§ page.team.total.daysWorked.description: Учтены только дни, в которые делались коммиты
§ page.team.total.commits.title: коммитов
@ -77,12 +95,16 @@ localization.parse('ru', `
§ page.team.total.workSpeed.description: Средняя скорость работы команды при текущем составе сотрудников
§ page.team.total.moneySpeed.title: в месяц
§ page.team.total.moneySpeed.description: Прогнозируемая сумма выплаты на зп при текущем составе сотрудников без учета налогов и сопутствующих затрат
§ page.team.total.titleA: Объём работ
§ page.team.total.titleB: Стоимость
§ page.team.tree.filters1: Пользователь
§ page.team.tree.filters2: и более
§ page.team.tree.filters3: коммитов в файле или папке
§ page.team.tree.percent: Процент перезаписи
§ page.team.total.description1: *Человеко-дни* это работа одного сотрудника в течение одного рабочего дня. Например, за один календарный день, команда из трех сотрудников выдает объем работы в три человеко-дня.
§ page.team.total.description2: *Днями прогулов* считаются только рабочие дни, когда коммиты могли бы быть сделаны. Выходные, государственные праздники и отпуска в расчёте не участвуют.
§ page.team.total.description3: Карточка *работает и уволилось* показывает фактический состав сотрудников, которые постоянно участвуют в работе. Кроме этого, есть «помощники» это сотрудники, как правило другой специализации, которые могут иногда делать коммиты в проект.
§ page.team.total.description4: *Переплатой* считаются только рабочие дни, когда коммиты могли бы быть сделаны. Выходные, государственные праздники и отпуска в расчёте не участвуют. Именно поэтому переплата + фактическая стоимость != общей. В общей стоимости заложена оплата выходных, государственных праздников и отпусков.
§ page.team.total.description5: *Работой на выходных* считается по коэфициенту х2 от оплаты обычного дня. Выше отображена именно переплата (х1), т.к. сам факт переработки в данном контексте не интересен. Мы не смотрим скорость сжигания бюджета. Мы смотрим переплату при увеличении скорости работы.
§ page.team.tree.title: Дерево проекта с учётом выбранных фильтров
§ page.team.tree.filters.author: Сотрудник
§ page.team.tree.filters.commits: Количество коммитов
§ page.team.tree.filters.help: Минимальное количество коммитов, которое сделал сотрудник в файле
§ page.team.tree.filters.all: Все сотрудники
§ page.team.tree.add: Кто добавлял
§ page.team.tree.change: Кто менял
§ page.team.tree.remove: Кто удалял

View file

@ -8,7 +8,6 @@ import SplashScreen from 'ts/components/SplashScreen';
import Confirm from 'ts/components/ModalWindow/Confirm';
import PageWrapper from '../../PageWrapper';
// import Main from '../../Main/index';
import Team from '../../Team/index';
import Person from '../../Person/index';
import Welcome from '../../Welcome/index';

View file

@ -1,48 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import UiKitButton from 'ts/components/UiKit/components/Button';
import localization from 'ts/helpers/Localization';
import style from '../styles/card.module.scss';
interface ICardProps {
icon: string;
title: string;
description: string;
link: string;
}
function Card({
icon,
title,
description,
link,
}: ICardProps): React.ReactElement | null {
const navigate = useNavigate();
return (
<figure className={style.card}>
<h4 className={style.card_title}>
{localization.get(title)}
</h4>
<img
className={style.card_icon}
src={icon}
/>
<figcaption className={style.card_description}>
{localization.get(description)}
</figcaption>
<UiKitButton
className={style.card_button}
onClick={() => {
navigate(link);
}}
>
Перейти в отчёт
</UiKitButton>
</figure>
);
}
export default Card;

View file

@ -1,31 +0,0 @@
import React from 'react';
import Card from './components/Card';
import style from './styles/index.module.scss';
function Main() {
return (
<section className={style.main}>
<h2 className={style.main_title}>
Выберите раздел аналити
</h2>
<div className={style.main_cards}>
<Card
icon="./assets/cards/money_lazy.png"
title="Команда"
description="Собраны метрики работы команды в целом, сумарные финансовые показатели, рекомендации для менеджера проекта."
link="/team/total"
/>
<Card
icon="./assets/cards/money_lazy.png"
title="Сотрудник"
description="Данные по каждому сотруднику отдельно. Личные достижения, характеристики, показатели работоспособности."
link="/person/total/0"
/>
</div>
</section>
);
}
export default Main;

View file

@ -1,86 +0,0 @@
@import '../../../../styles/variables';
.card {
display: inline-block;
width: 300px;
min-height: 270px;
margin: 0 24px 24px 0;
padding: 16px;
vertical-align: top;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid var(--color-border);
background-color: #FFFFFF;
&_icon {
display: block;
width: auto;
height: 90px;
margin: 16px auto;
box-sizing: border-box;
vertical-align: top;
}
&_title,
&_description {
display: block;
margin: 0 auto;
padding: 0;
line-height: 1.3;
text-align: center;
text-decoration: none;
color: var(--color-black);
}
&_title {
font-size: 28px;
font-weight: bold;
color: var(--color-11);
}
&_description {
font-size: var(--font-xs);
font-weight: 100;
line-height: 16px;
color: var(--color-grey);
}
&_button {
padding: 0 16px;
margin-top: 16px;
}
}
@media (max-width: 900px) {
.card {
min-height: 220px;
padding: 16px 0;
&_title {
margin: 0;
}
&_description {
display: none;
}
}
}
@media (max-width: 650px) {
.card {
min-height: auto;
padding: 32px 0;
&_value {
font-size: 22px;
}
&_title {
font-size: var(--font-s);
}
&_icon {
display: none;
}
}
}

View file

@ -1,40 +0,0 @@
@import '../../../../styles/variables';
.main {
display: grid;
grid-template-areas: 'header' 'main';
grid-template-columns: 1fr;
grid-template-rows: 66px 1fr;
min-height: 100vh;
background-color: #F5F7F9;
&_title {
grid-area: header;
font-size: var(--font-l);
line-height: var(--font-l);
font-weight: 100;
display: block;
padding: 24px;
margin: 0;
box-sizing: border-box;
text-align: center;
color: #84858D;
background-color: #252735;
}
&_cards {
grid-area: main;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: calc(100vh - 60px);
text-align: center;
}
}
@media (max-width: 800px) {
}

View file

@ -162,7 +162,7 @@ const Author = observer(({
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по сотрудникам"/>
<Title title="page.team.author.title"/>
<PageWrapper template="table">
<DataLoader
to="response"
@ -177,12 +177,12 @@ const Author = observer(({
<PageWrapper>
<PageColumn>
<Description
text="*Часть статитики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помошник» не считается*, т.к. это эпизодическая роль в проекте. Предпологаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы."
text={localization.get('page.team.author.description1')}
/>
</PageColumn>
<PageColumn>
<Description
text="*Сортировка по умолчанию* — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники)."
text={localization.get('page.team.author.description2')}
/>
</PageColumn>
</PageWrapper>

View file

@ -16,7 +16,7 @@ const Hours = observer((): React.ReactElement => {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Title title="Распределение коммитов в течении каждого дня недели"/>
<Title title="page.team.hours.title"/>
<PageWrapper template="table">
<HoursChart statistic={statistic} />
</PageWrapper>

View file

@ -1,7 +1,6 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import localization from 'ts/helpers/Localization';
import dataGripStore from 'ts/store/DataGrip';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
@ -24,7 +23,7 @@ const Month = observer(({
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations}/>
)}
<Title title={localization.get('Календарь работы по проекту')}/>
<Title title="page.team.month.title"/>
<PageWrapper template="table">
<YearChart
maxCommits={max}

View file

@ -116,7 +116,7 @@ const Scope = observer(({
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по фичам"/>
<Title title="page.team.scope.title"/>
<PageWrapper template="table">
<DataLoader
to="response"

View file

@ -4,7 +4,6 @@ import { observer } from 'mobx-react-lite';
import { IPagination } from 'ts/interfaces/Pagination';
import dataGripStore from 'ts/store/DataGrip';
import { getShortDateRange } from 'ts/helpers/formatter';
import localization from 'ts/helpers/Localization';
import UiKitButton from 'ts/components/UiKit/components/Button';
import UiKitSelect from 'ts/components/UiKit/components/Select';
@ -60,7 +59,7 @@ const Tempo = observer((): React.ReactElement => {
if (!partOfData?.length) return (<NothingFound />);
return (
<>
<Title title={localization.get('common.filters')} />
<Title title="common.filters" />
<PageWrapper>
<div className={style.tempo_page_filters}>
<UiKitButton

View file

@ -26,7 +26,7 @@ const Total = observer((): React.ReactElement => {
return (
<PageWrapper>
<PageColumn>
<Title title={localization.get('page.team.total.titleA')}/>
<Title title="page.team.total.titleA"/>
<div>
<CardWithIcon
value={statistic.daysWorked}
@ -61,17 +61,17 @@ const Total = observer((): React.ReactElement => {
/>
</div>
<Description
text="*Человеко-дни* — это работа одного сотрудника в течение одного рабочего дня. Например, за один календарный день, команда из трех сотрудников выдает объем работы в три человеко-дня."
text={localization.get('page.team.total.description1')}
/>
<Description
text="*Днями прогулов* считаются только рабочие дни, когда коммиты могли бы быть сделаны. Выходные, государственные праздники и отпуска в расчёте не участвуют."
text={localization.get('page.team.total.description2')}
/>
<Description
text="Карточка *работает и уволилось* показывает фактический состав сотрудников, которые постоянно участвуют в работе. Кроме этого, есть «помощники» — это сотрудники, как правило другой специализации, которые могут иногда делать коммиты в проект."
text={localization.get('page.team.total.description3')}
/>
</PageColumn>
<PageColumn>
<Title title={localization.get('page.team.total.titleB')}/>
<Title title="page.team.total.titleB"/>
<div>
<CardWithIcon
value={getShortMoney(statistic.moneyAll)}
@ -106,10 +106,10 @@ const Total = observer((): React.ReactElement => {
/>
</div>
<Description
text="*Переплатой* считаются только рабочие дни, когда коммиты могли бы быть сделаны. Выходные, государственные праздники и отпуска в расчёте не участвуют. Именно поэтому переплата + фактическая стоимость != общей. В общей стоимости заложена оплата выходных, государственных праздников и отпусков."
text={localization.get('page.team.total.description4')}
/>
<Description
text="*Работой на выходных* считается по коэфициенту х2 от оплаты обычного дня. Выше отображена именно переплата (х1), т.к. сам факт переработки в данном контексте не интересен. Мы не смотрим скорость сжигания бюджета. Мы смотрим переплату при увеличении скорости работы."
text={localization.get('page.team.total.description5')}
/>
</PageColumn>
</PageWrapper>

View file

@ -37,7 +37,10 @@ function TreeView({ response }: ITreeViewProps) {
};
const fileChart = getOptions({ order: dataGripStore.dataGrip.author.list, suffix: 'строк' });
const rewriteChart = getOptions({ order: ['добавили', 'изменили'], suffix: 'строк' });
const rewriteChart = getOptions({ order: [
'page.team.tree.lineAdd',
'page.team.tree.lineRemove',
], suffix: 'page.team.tree.line' });
return (
<Table
@ -69,14 +72,14 @@ function TreeView({ response }: ITreeViewProps) {
value={file ? 100 : 0}
options={rewriteChart}
details={{
'добавили': file?.lines || 0,
'изменили': (file?.total?.changes || 0) + (file?.total?.removed || 0),
'page.team.tree.lineAdd': file?.lines || 0,
'page.team.tree.lineRemove': (file?.total?.changes || 0) + (file?.total?.removed || 0),
}}
/>
)}
/>
<Column
title="Кто добавлял"
title="page.team.tree.add"
properties="file"
minWidth={200}
template={(file: any) => (
@ -88,7 +91,7 @@ function TreeView({ response }: ITreeViewProps) {
)}
/>
<Column
title="Кто менял"
title="page.team.tree.change"
properties="file"
minWidth={200}
template={(file: any) => (
@ -100,7 +103,7 @@ function TreeView({ response }: ITreeViewProps) {
)}
/>
<Column
title="Кто удалял"
title="page.team.tree.remove"
properties="file"
minWidth={200}
template={(file: any) => (
@ -128,7 +131,7 @@ const Tree = observer((): React.ReactElement => {
<>
<Title title={localization.get('common.filters')} />
<TreeFilters/>
<Title title="Дерево проекта с учётом выбранных фильтров"/>
<Title title="page.team.tree.title"/>
<PageWrapper template="table">
<DataLoader
to="response"

View file

@ -4,6 +4,7 @@ import { observer } from 'mobx-react-lite';
import dataGripStore from 'ts/store/DataGrip';
import UiKitSelect from 'ts/components/UiKit/components/SelectWithButtons';
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
import localization from 'ts/helpers/Localization';
import treeStore from '../store/Tree';
import style from '../styles/filters.module.scss';
@ -11,12 +12,12 @@ import style from '../styles/filters.module.scss';
const TreeFilters = observer((): React.ReactElement => {
const authors = dataGripStore.dataGrip.author.list;
const options = authors.map((title: string, id: number) => ({ id: id + 1, title }));
options.unshift({ id: 0, title: 'Все сотрудники' });
options.unshift({ id: 0, title: localization.get('page.team.tree.filters.all') });
return (
<>
<UiKitSelect
title="Сотрудник"
title="page.team.tree.filters.author"
value={treeStore.authorId}
options={options}
className={style.filter}
@ -25,8 +26,8 @@ const TreeFilters = observer((): React.ReactElement => {
}}
/>
<UiKitInputNumber
title="Количество коммитов"
help="Минимальное количество коммитов, которое сделал сотрудник в файле"
title="page.team.tree.filters.commits"
help="page.team.tree.filters.help"
value={treeStore.minCommits}
className={style.filter}
onChange={(minCommits: number) => {

View file

@ -22,6 +22,7 @@ import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Description from 'ts/components/Description';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import localization from 'ts/helpers/Localization';
interface ITypeViewProps {
response?: IPagination<any>;
@ -31,8 +32,8 @@ interface ITypeViewProps {
function TypeView({ response, updateSort }: ITypeViewProps) {
if (!response) return null;
const taskChart = getOptions({ max: getMax(response, 'tasks'), suffix: 'задач' });
const daysByAuthorsChart = getOptions({ max: getMax(response, 'daysByAuthorsTotal'), suffix: 'дней' });
const taskChart = getOptions({ max: getMax(response, 'tasks'), suffix: 'page.team.type.tasksSmall' });
const daysByAuthorsChart = getOptions({ max: getMax(response, 'daysByAuthorsTotal'), suffix: 'page.team.type.daysSmall' });
const authorChart = getOptions({ order: dataGripStore.dataGrip.author.list });
return (
@ -121,7 +122,7 @@ const Type = observer(({
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по типам задач"/>
<Title title="page.team.type.title"/>
<PageWrapper template="table">
<DataLoader
to="response"
@ -135,7 +136,7 @@ const Type = observer(({
</PageWrapper>
<PageWrapper>
<Description
text="*Персональный вклад* считается по количеству коммитов, а не объему измененных строк или файлов. Поэтому следует так же смотреть раздел «Анализ файлов», чтобы оценить масштаб изменений."
text={localization.get('page.team.type.description')}
/>
</PageWrapper>
</>

View file

@ -2,6 +2,8 @@ import React from 'react';
import { Link } from 'react-router-dom';
import Console from 'ts/components/Console';
import localization from 'ts/helpers/Localization';
import style from './styles/index.module.scss';
function WarningInfo() {
@ -33,34 +35,32 @@ function WarningInfo() {
}
function Welcome() {
const canShowWarning = true;
const command = 'git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n';
return (
<>
{true && (<WarningInfo />)}
{canShowWarning && (<WarningInfo />)}
<section className={style.welcome}>
<div className={style.welcome_row}>
<h2 className={style.welcome_first_title}>
Выполните команду в корне вашего проекта
{localization.get('page.welcome.step1')}
</h2>
<Console
className={style.welcome_console}
textForCopy={command}
/>
<p className={style.welcome_description}>
Git создаст файл log.txt.
Он содержит данные для построения отчёта.
Или git shortlog -s -n -e если отчёт вам не нужен.
Создайте файл
{localization.get('page.welcome.description1')}
<Link
className={`${style.welcome_link}`}
target="_blank"
to="https://git-scm.com/docs/gitmailmap">
.mailmap
</Link>
{' в корне проекта, чтобы обьединить статистику по сотрудникам.'}
{localization.get('page.welcome.description2')}
</p>
<h2 className={style.welcome_last_title}>
Перетащите файл log.txt на эту страницу
{localization.get('page.welcome.step2')}
</h2>
</div>
</section>

View file

@ -84,7 +84,7 @@
&_link {
display: inline;
margin: 16px 0 0 4px;
margin: 16px 4px 0 4px;
text-decoration: underline;
}
}