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
build/static
src/ts
components
BarChart/styles
LineChart/helpers
Page
PieSVG
helpers
pages
Common/components
Team/components
translations

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