This commit is contained in:
bakhirev 2024-10-21 16:12:21 +03:00
parent 7b9e41d928
commit 2c4528c941
25 changed files with 155 additions and 84 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,6 +14,7 @@
&_item { &_item {
display: inline-block; display: inline-block;
width: 10px; width: 10px;
min-width: 1px;
vertical-align: bottom; vertical-align: bottom;
cursor: pointer; cursor: pointer;

View file

@ -6,13 +6,12 @@ function getWidth(value: number, max: number) {
return Math.round(value * (100 / max)); return Math.round(value * (100 / max));
} }
function getFormattedOther(other: any[], options: any): ISubLine { function getFormattedOther(other: any[], normalWidth: number, options: any): ISubLine {
let width = 0;
let value = 0; let value = 0;
const width = 100 - normalWidth;
const titles: string[] = []; const titles: string[] = [];
other.forEach((field: any) => { other.forEach((field: any) => {
width += field.width;
value += field.value; value += field.value;
if (field.title) titles.push(field.title); if (field.title) titles.push(field.title);
}); });
@ -36,6 +35,7 @@ export default function getSubLines(
const normal: ISubLine[] = []; const normal: ISubLine[] = [];
const other: ISubLine[] = []; const other: ISubLine[] = [];
let normalWidth = 0;
list.forEach(([title, value]: any) => { list.forEach(([title, value]: any) => {
const width = getWidth(value || 0, currentMax); const width = getWidth(value || 0, currentMax);
const field: ISubLine = { title, value, width }; const field: ISubLine = { title, value, width };
@ -43,6 +43,7 @@ export default function getSubLines(
allItems.push(field); allItems.push(field);
if (width >= options.limit) { if (width >= options.limit) {
normal.push(field); normal.push(field);
normalWidth += width;
} else { } else {
other.push(field); other.push(field);
} }
@ -50,6 +51,6 @@ export default function getSubLines(
if (other.length === 0) return normal; if (other.length === 0) return normal;
if (other.length === 1) return allItems; if (other.length === 1) return allItems;
return [...normal, getFormattedOther(other, options)] return [...normal, getFormattedOther(other, normalWidth, options)]
.filter((item: any) => item.width > 1); .filter((item: any) => item.width > 1);
} }

View file

@ -68,7 +68,7 @@
&_item { &_item {
display: block; display: block;
width: 100%; width: 100%;
margin: 0 0 24px 0; margin: 0;
} }
&_white { &_white {
display: block; display: block;
@ -76,7 +76,7 @@
} }
.main_wrapper_item + .main_wrapper_item { .main_wrapper_item + .main_wrapper_item {
display: block; display: block;
margin: 0 0 24px 0; margin: 0;
} }
} }

View file

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { IOptions, ISubLine } from 'ts/components/LineChart/interfaces'; import { IOptions, ISubLine } from 'ts/components/LineChart/interfaces';
import { getSegmentPath } from './helpers'; import { getSegmentPath } from './helpers';
@ -17,16 +18,23 @@ function PieSVG({
parts, parts,
center, center,
}: IPieSVGProps): React.ReactElement | null { }: IPieSVGProps): React.ReactElement | null {
const { t } = useTranslation();
const centerRadius = 49 * ((center || 72) / 100); const centerRadius = 49 * ((center || 72) / 100);
const suffix = options.suffix ? t(options.suffix) : '';
let prev = 0; let prev = 0;
const paths = parts.map((item: ISubLine) => { const paths = parts.map((item: ISubLine) => {
const fill = options.color.get(item.title).first; const fill = options.color.get(item.title).first;
const angle = 360 * item.width / 100; const angle = 360 * item.width / 100;
const next = Math.min(prev + angle, 360); const formattedAngle = angle === 360 ? 359.9 : angle;
const next = Math.min(prev + formattedAngle, 360);
const d = getSegmentPath(50, 50, centerRadius, 50, prev + ROTATE, next + ROTATE); const d = getSegmentPath(50, 50, centerRadius, 50, prev + ROTATE, next + ROTATE);
prev += angle; prev += angle;
const formattedValue = item.value && suffix
? ` (${item.value || ''} ${suffix})`
: '';
return ( return (
<path <path
key={item.title} key={item.title}
@ -35,7 +43,7 @@ function PieSVG({
className={style.pie_svg_sector} className={style.pie_svg_sector}
> >
<title> <title>
{`${item.title} ${item.description || ''}`} {`${t(item.title)}${formattedValue}`}
</title> </title>
</path> </path>
); );

View file

@ -195,7 +195,8 @@ export default class DataGripByAuthor {
const daysWorkedLosses = workDays + (lazyDays > 0 ? lazyDays : 0); const daysWorkedLosses = workDays + (lazyDays > 0 ? lazyDays : 0);
const percentWork = workDays * 100 / daysWorkedLosses; const percentWork = workDays * 100 / daysWorkedLosses;
const isStaff = daysWorkedLosses < 20 || (percentWork < 15); const isBot = (/[^a-z]bot[^a-z]/gim).test(dot.author);
const isStaff = daysWorkedLosses < 20 || (percentWork < 15) || isBot;
const authorInfo = { const authorInfo = {
...dot, ...dot,

View file

@ -21,7 +21,7 @@ export default class DataGripByPR {
if (!commit.prId) return; if (!commit.prId) return;
const statistic = this.pr.get(commit.prId); const statistic = this.pr.get(commit.prId);
if (statistic) { if (statistic) {
console.log('PR error'); console.log('Parsing error. PR already exist.');
} else { } else {
this.#addCommitByPR(commit); this.#addCommitByPR(commit);
} }

View file

@ -3,7 +3,7 @@ import IHashMap from 'ts/interfaces/HashMap';
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope'; import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
import getInfoFromNameAndEmail from './getCompany'; import getInfoFromNameAndEmail from './getCompany';
import { getGithubPrInfo } from './getMergeInfo'; import { getGithubPrInfo, getGitlabPrInfo } from './getMergeInfo';
import getCountryByTimeZone from './getCountryByTimeZone'; import getCountryByTimeZone from './getCountryByTimeZone';
const MASTER_BRANCH = { const MASTER_BRANCH = {
@ -148,13 +148,12 @@ export default function getCommitInfo(
} else if (isGitlabPR) { } else if (isGitlabPR) {
commitType = COMMIT_TYPE.PR_GITLAB; commitType = COMMIT_TYPE.PR_GITLAB;
[, branch, toBranch ] = message [branch, toBranch] = getGitlabPrInfo(message);
.replace(/'/gim, '')
.replace(/(Merge\sbranch\s)|(\sinto\s)/gim, ',')
.split(',');
if (toBranch && MASTER_BRANCH[toBranch]) { if (toBranch && MASTER_BRANCH[toBranch]) {
task = getTask(branch) || `#${getTaskNumber(branch)}`; task = getTask(branch);
prId = task; taskNumber = getTaskNumber(branch);
prId = `#${taskNumber}-${Math.random()}`;
if (!task && taskNumber) task = `#${taskNumber}`;
} }
} }
taskNumber = getTaskNumber(task); taskNumber = getTaskNumber(task);

View file

@ -1,9 +1,10 @@
// "Merge pull request #3 in repository from TASK-123-add-profile to master" /* "Merge pull request #3 in repository from TASK-123-add-profile to master"
// "Merge pull request #3 from facebook/compiler into master" * "Merge pull request #3 from facebook/compiler into master"
// "Merge pull request #3 from facebook/compiler" * "Merge pull request #3 from facebook/compiler"
*/
export function getGithubPrInfo(text: string) { export function getGithubPrInfo(text: string) {
const json = (text || '') const json = (text || '')
.replace(/"/gim, '') .replace(/["']+/gim, '')
.replace('#', '#": "') .replace('#', '#": "')
.replace(' in ', '", "in": "') .replace(' in ', '", "in": "')
.replace(' from ', '", "from": "') .replace(' from ', '", "from": "')
@ -12,3 +13,12 @@ export function getGithubPrInfo(text: string) {
const data = JSON.parse(`{"${json}"}`); const data = JSON.parse(`{"${json}"}`);
return [data['Merge pull request #'], data.in, data.from, data.to]; return [data['Merge pull request #'], data.in, data.from, data.to];
} }
/* "Merge branch 'J123456' into 'develop'" */
export function getGitlabPrInfo(text: string) {
const prefix = text.substring(14, text.length - 1);
const index = prefix.indexOf("'");
const branch = prefix.substring(0, index);
const toBranch = prefix.substring(index + 8);
return [branch, toBranch];
}

View file

@ -16,6 +16,7 @@ interface IChangesProps {
} }
function Changes({ statistic }: IChangesProps) { function Changes({ statistic }: IChangesProps) {
const files = dataGripStore.fileGrip.files.list;
const maxData = statistic.changesByTimestampCounter.maxData; const maxData = statistic.changesByTimestampCounter.maxData;
const [selected, setSelected] = useState<any>(maxData); const [selected, setSelected] = useState<any>(maxData);
@ -26,7 +27,8 @@ function Changes({ statistic }: IChangesProps) {
value: dot.addedAndChanges, value: dot.addedAndChanges,
meta: dot, meta: dot,
})); }));
if (!dots?.length) return (<NothingFound />);
if (!dots?.length || !files?.length) return (<NothingFound />);
const [fullDay, shortDay] = getDateByTimestamp(maxData.timestamp); const [fullDay, shortDay] = getDateByTimestamp(maxData.timestamp);
const recommendations = [ const recommendations = [

View file

@ -32,6 +32,7 @@ function Countries({ response, updateSort, rowsForExcel, mode }: CompaniesProps)
updateSort={updateSort} updateSort={updateSort}
type={mode === 'print' ? 'cards' : undefined} type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined} columnCount={mode === 'print' ? 3 : undefined}
fullScreenMode="countries"
> >
<Column <Column
isFixed isFixed

View file

@ -31,6 +31,7 @@ function Travel({ response, updateSort, rowsForExcel, mode }: TravelProps) {
updateSort={updateSort} updateSort={updateSort}
type={mode === 'print' ? 'cards' : undefined} type={mode === 'print' ? 'cards' : undefined}
columnCount={mode === 'print' ? 3 : undefined} columnCount={mode === 'print' ? 3 : undefined}
fullScreenMode="travel"
> >
<Column <Column
isFixed isFixed

View file

@ -15,6 +15,7 @@ import Countries from './components/Countries';
import CountryCharts from './components/Charts'; import CountryCharts from './components/Charts';
import TimeZoneMap from 'ts/components/TimeZoneMap'; import TimeZoneMap from 'ts/components/TimeZoneMap';
import PageWrapper from 'ts/components/Page/Box'; import PageWrapper from 'ts/components/Page/Box';
import fullScreen from 'ts/store/FullScreen';
import Travel from './components/Travel'; import Travel from './components/Travel';
@ -26,32 +27,45 @@ const Country = observer(({
const travel = authors.filter((dot: any) => dot?.country?.length) const travel = authors.filter((dot: any) => dot?.country?.length)
.sort((a: any, b: any) => b?.country?.length - a?.country?.length); .sort((a: any, b: any) => b?.country?.length - a?.country?.length);
const canShowByCountries = (!fullScreen.isOpen || fullScreen.mode === 'countries');
const canShowByTravel = (!fullScreen.isOpen || fullScreen.mode === 'travel') && travel.length;
if (!countryRows?.length) { if (!countryRows?.length) {
return mode !== 'print' ? (<NothingFound/>) : null; return mode !== 'print' ? (<NothingFound/>) : null;
} }
return ( return (
<> <>
<PageWrapper> {!fullScreen.isOpen && (
<Title title="page.team.country.byTimezone"/> <>
<TimeZoneMap authors={authors}/> <PageWrapper>
</PageWrapper> <Title title="page.team.country.byTimezone"/>
<CountryCharts/> <TimeZoneMap authors={authors}/>
<Title title="page.team.country.table.title"/> </PageWrapper>
<DataLoader <CountryCharts/>
to="response" </>
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({ )}
content: countryRows, pagination, sort, mode,
})} {canShowByCountries ? (
watch={`${mode}${dataGripStore.hash}`} <>
> <Title title="page.team.country.table.title"/>
<Countries <DataLoader
mode={mode} to="response"
rowsForExcel={countryRows} loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
/> content: countryRows, pagination, sort, mode,
<Pagination/> })}
</DataLoader> watch={`${mode}${dataGripStore.hash}`}
{travel.length ? ( >
<Countries
mode={mode}
rowsForExcel={countryRows}
/>
<Pagination/>
</DataLoader>
</>
) : null}
{canShowByTravel ? (
<> <>
<Title title="page.team.country.travel.title"/> <Title title="page.team.country.travel.title"/>
<DataLoader <DataLoader

View file

@ -57,7 +57,6 @@ function View({ response, updateSort, rowsForExcel, mode }: CompaniesProps) {
.reverse() .reverse()
.map((taskId: any) => dataGripStore.dataGrip.tasks.statisticByName.get(taskId)) .map((taskId: any) => dataGripStore.dataGrip.tasks.statisticByName.get(taskId))
.filter(v => v); .filter(v => v);
console.log(content);
return ( return (
<Tasks // @ts-ignore <Tasks // @ts-ignore
response={{ content }} response={{ content }}

View file

@ -16,11 +16,45 @@ import PageWrapper from 'ts/components/Page/wrapper';
import Filters from './Filters'; import Filters from './Filters';
import View from './View'; import View from './View';
interface IFilters {
user: number;
company: number;
}
function getContentByFilters(content: any, filters: IFilters) {
if (filters.user) {
const author = dataGripStore.dataGrip.author.statistic?.[filters.user]?.author;
return author
? content.filter((task: any) => {
return task.author === author || task.authors?.[author];
})
: content;
}
if (filters.company) {
const employments = dataGripStore.dataGrip.company.statistic?.[filters.company].employments;
const employmentInCompany = new Map(employments.map((key: string) => [key, true]));
return employments?.length
? content.filter((task: any) => employmentInCompany.has(task.author))
: content;
}
return content;
}
const Tasks = observer(({ const Tasks = observer(({
mode, mode,
}: ICommonPageProps): React.ReactElement | null => { }: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.tasks.statistic; const rows = dataGripStore.dataGrip.tasks.statistic;
const [filters, setFilters] = useState<any>({ user: 0, company: 0 }); const [filters, setFilters] = useState<any>({ user: 0, company: 0 });
const content = getContentByFilters(rows, filters);
const hash = [
mode,
dataGripStore.hash,
filters.user,
filters.company,
content.length,
].join('.');
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null; if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
@ -36,9 +70,9 @@ const Tasks = observer(({
<DataLoader <DataLoader
to="response" to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({ loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode, content, pagination, sort, mode,
})} })}
watch={`${mode}${dataGripStore.hash}`} watch={hash}
> >
<View <View
mode={mode} mode={mode}

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -1,28 +1,28 @@
export default ` export default `
§ page.settings.document.title: Display settings § page.settings.document.title: Anzeigeeinstellungen
§ page.settings.document.name: Page title § page.settings.document.name: Seitentitel
§ page.settings.document.language: Interface language § page.settings.document.language: Sprache der benutzeroberfläche
§ page.settings.document.depersonalize: Hide personal data § page.settings.document.depersonalize: Persönliche daten verstecken
§ page.settings.links.title: Link prefixes § page.settings.links.title: Link-Präfixe
§ page.settings.links.task: For task numbers § page.settings.links.task: Für Aufgabennummern
§ page.settings.links.pr: For PR § page.settings.links.pr: Für PR
§ page.settings.user.title: Individual settings § page.settings.user.title: Individuelle einstellungen
§ page.settings.user.notFound: No individual settings. Data for all employees are calculated based on common parameters. § page.settings.user.notFound: Keine individuellen Einstellungen. Daten für alle Mitarbeiter werden anhand allgemeiner Parameter berechnet.
§ page.settings.user.subTitle: Addendum to employment contract $1 § page.settings.user.subTitle: Zusatz zum arbeitsvertrag . $1
§ page.settings.user.from: Start date § page.settings.user.from: Startdatum
§ page.settings.mailmap: .mailmap example § page.settings.mailmap: Beispiel .mailmap
§ page.settings.common.title: General salary data § page.settings.common.title: Allgemeine Gehaltsdaten
§ page.settings.common.type.title: Project work type § page.settings.common.type.title: Art der projektarbeit
§ page.settings.common.type.full: Full-time employment § page.settings.common.type.full: Vollzeitbeschäftigung
§ page.settings.common.type.part: Project work § page.settings.common.type.part: Projektarbeit
§ page.settings.common.salary: Monthly salary in USD (US dollar, $) § page.settings.common.salary: Monatliches gehalt in USD (US-Dollar, $)
§ page.settings.common.currency: Currency for view § page.settings.common.currency: Währung zur ansicht
§ page.settings.common.workDaysInYear: Number of working days in a year § page.settings.common.workDaysInYear: Anzahl der arbeitstage im Jahr
§ page.settings.common.vacationDaysInYear: Number of vacation days in a year § page.settings.common.vacationDaysInYear: Anzahl der urlaubstage im Jahr
§ page.settings.common.workDaysInWeek: Workdays § page.settings.common.workDaysInWeek: Arbeitstage
§ page.settings.form.save: Save § page.settings.form.save: Speichern
§ page.settings.form.cancel: Cancel § page.settings.form.cancel: Abbrechen
§ page.settings.form.remove: Remove § page.settings.form.remove: Entfernen
§ page.settings.form.addEmployee: Add an employee § page.settings.form.addEmployee: Mitarbeiter hinzufügen
§ page.settings.form.addContract: Add an employment contract § page.settings.form.addContract: Arbeitsvertrag hinzufügen
`; `;

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit

View file

@ -126,7 +126,7 @@ export default `
§ page.team.company.employments.title: По количеству сотрудников § page.team.company.employments.title: По количеству сотрудников
§ page.team.company.employments.item: сотрудников § page.team.company.employments.item: сотрудников
§ page.team.company.daysChart.title: По длительности контракта § page.team.company.daysChart.title: По длительности контракта
§ page.team.company.daysChart.item: дней § page.team.company.daysChart.item: компаний
§ page.team.company.active.yes: активна § page.team.company.active.yes: активна
§ page.team.company.active.no: контракт истёк § page.team.company.active.no: контракт истёк
§ page.team.country.byTimezone: По времени последнего коммита § page.team.country.byTimezone: По времени последнего коммита

View file

@ -121,7 +121,7 @@ export default `
§ page.team.company.employments.title: By number of employees § page.team.company.employments.title: By number of employees
§ page.team.company.employments.item: employments § page.team.company.employments.item: employments
§ page.team.company.daysChart.title: By duration of the contract § page.team.company.daysChart.title: By duration of the contract
§ page.team.company.daysChart.item: days § page.team.company.daysChart.item: companies
§ page.team.company.active.yes: active § page.team.company.active.yes: active
§ page.team.company.active.no: contract has expired § page.team.company.active.no: contract has expired
§ page.team.country.byTimezone: By the time of the last commit § page.team.country.byTimezone: By the time of the last commit