mirror of
https://github.com/bakhirev/assayo.git
synced 2024-11-16 08:11:40 +00:00
test $
-.&? ${}
This commit is contained in:
parent
6f6ac5a749
commit
81cb8a1b40
|
@ -1 +1 @@
|
||||||
<!doctype html><html><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="theme-color" content="white"/><meta name="defaultLanguage" content="ru"><meta name="availableLanguages" content="en, es, fr, ja, pt, de, zh, ru"><link rel="canonical" href="https://assayo.online/demo/"><script type="text/javascript">var report=[];function r(r){report.push(r)}var f=String.raw.bind(String)</script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Git Statistics</title><script src='./log.txt'></script><script src='./log-1.txt'></script><script src='./log-2.txt'></script><script src='./log-3.txt'></script><script src='./log-4.txt'></script><script src='./log-5.txt'></script><script src='./log-6.txt'></script><script src='../log.txt'></script><script src='../log-1.txt'></script><script src='../log-2.txt'></script><script src='../log-3.txt'></script><script src='../log-4.txt'></script><script src='../log-5.txt'></script><script src='../log-6.txt'></script><script src='../../log.txt'></script><script src='../../log-1.txt'></script><script src='../../log-2.txt'></script><script src='../../log-3.txt'></script><script src='../../log-4.txt'></script><script src='../../log-5.txt'></script><script src='../../log-6.txt'></script><script src='/log.txt'></script><script src='/log-1.txt'></script><script src='/log-2.txt'></script><script src='/log-3.txt'></script><script src='/log-4.txt'></script><script src='/log-5.txt'></script><script src='/log-6.txt'></script><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="https://assayo.online/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="https://assayo.online/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="https://assayo.online/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.online"><meta name="twitter:site" content="assayo.online"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="https://assayo.online/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/index.js"></script><link href="./static/index.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
<!doctype html><html><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="theme-color" content="white"/><meta name="defaultLanguage" content="ru"><meta name="availableLanguages" content="en, es, fr, ja, pt, de, zh, ru"><link rel="canonical" href="https://assayo.online/demo/"><script type="text/javascript">var report=[];function r(r){report.push(r)}function R(r){report=report.concat(r.split("\n"))}var f=String.raw.bind(String)</script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><title>Git Statistics</title><script src='./log.txt'></script><script src='./log-1.txt'></script><script src='./log-2.txt'></script><script src='./log-3.txt'></script><script src='./log-4.txt'></script><script src='./log-5.txt'></script><script src='./log-6.txt'></script><script src='../log.txt'></script><script src='../log-1.txt'></script><script src='../log-2.txt'></script><script src='../log-3.txt'></script><script src='../log-4.txt'></script><script src='../log-5.txt'></script><script src='../log-6.txt'></script><script src='../../log.txt'></script><script src='../../log-1.txt'></script><script src='../../log-2.txt'></script><script src='../../log-3.txt'></script><script src='../../log-4.txt'></script><script src='../../log-5.txt'></script><script src='../../log-6.txt'></script><script src='/log.txt'></script><script src='/log-1.txt'></script><script src='/log-2.txt'></script><script src='/log-3.txt'></script><script src='/log-4.txt'></script><script src='/log-5.txt'></script><script src='/log-6.txt'></script><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="https://assayo.online/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="https://assayo.online/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="https://assayo.online/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.online"><meta name="twitter:site" content="assayo.online"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="https://assayo.online/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/index.js"></script><link href="./static/index.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -145,7 +145,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:\"%ad>%aN>%aE>%s\" | sed -e 's/\\\\/\\\\\\\\/g' | sed -e 's/`/\"/g' | sed -e 's/^/r(f\\`/g' | sed 's/$/\\`\\);/g' | sed 's/\\$/_/g' > log.txt"
|
"git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:\"%ad>%aN>%aE>%s\" | sed -e 's/\\\\/\\\\\\\\/g' | sed -e 's/`/\"/g' | sed -e 's/\\$/S/g' | sed -e '1s/^/R(f\\`/' | sed -e '$s/$/\\`\\);/' > log.txt",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,10 +18,13 @@
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var report = [];
|
var report = [];
|
||||||
|
var f = String.raw.bind(String);
|
||||||
function r(t) {
|
function r(t) {
|
||||||
report.push(t);
|
report.push(t);
|
||||||
}
|
}
|
||||||
var f = String.raw.bind(String);
|
function R(t) {
|
||||||
|
report = report.concat(t.split("\n"));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
|
15634
public/test.txt
15634
public/test.txt
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@ import PageWrapper from '../Page/wrapper';
|
||||||
interface IDataViewProps {
|
interface IDataViewProps {
|
||||||
rowsForExcel?: any[];
|
rowsForExcel?: any[];
|
||||||
rows: any[];
|
rows: any[];
|
||||||
|
mode?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
sort?: ISort[];
|
sort?: ISort[];
|
||||||
columnCount?: number,
|
columnCount?: number,
|
||||||
|
@ -32,6 +33,7 @@ function DataView({
|
||||||
rows = [],
|
rows = [],
|
||||||
sort = [],
|
sort = [],
|
||||||
type,
|
type,
|
||||||
|
mode,
|
||||||
columnCount,
|
columnCount,
|
||||||
className,
|
className,
|
||||||
fullScreenMode = '',
|
fullScreenMode = '',
|
||||||
|
@ -58,6 +60,7 @@ function DataView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{mode !== 'details' && (
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<div className={style.data_view_buttons}>
|
<div className={style.data_view_buttons}>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
|
@ -95,8 +98,9 @@ function DataView({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{localType === 'table' && (
|
{localType === 'table' && mode !== 'details' && (
|
||||||
<PageWrapper template="table">
|
<PageWrapper template="table">
|
||||||
<Table
|
<Table
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
@ -109,6 +113,17 @@ function DataView({
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{localType === 'table' && mode === 'details' && (
|
||||||
|
<Table
|
||||||
|
rows={rows}
|
||||||
|
sort={sort}
|
||||||
|
disabledRow={disabledRow}
|
||||||
|
updateSort={updateSort}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
|
||||||
{localType === 'cards' && (
|
{localType === 'cards' && (
|
||||||
<Cards
|
<Cards
|
||||||
items={rows}
|
items={rows}
|
||||||
|
|
|
@ -39,7 +39,7 @@ function CommitInfo({ commits }: { commits: ICommit[] }): React.ReactElement {
|
||||||
function TaskInfo({ tasks }: { tasks: ITask }): React.ReactElement {
|
function TaskInfo({ tasks }: { tasks: ITask }): React.ReactElement {
|
||||||
const items = Object.entries(tasks)
|
const items = Object.entries(tasks)
|
||||||
.map(([task, commits]: [string, any]) => {
|
.map(([task, commits]: [string, any]) => {
|
||||||
const prId = dataGrip.pr.prByTask[task];
|
const prId = dataGrip.pr.prByTask.get(task);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={style.day_info_link}>
|
<div className={style.day_info_link}>
|
||||||
|
|
|
@ -8,28 +8,31 @@
|
||||||
// onChange('meta', { byTaskId });
|
// onChange('meta', { byTaskId });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
import splashScreenStore from 'ts/components/SplashScreen/store';
|
||||||
|
|
||||||
function getGlobalValue() { // @ts-ignore
|
function getGlobalValue() { // @ts-ignore
|
||||||
return window.report;
|
return window.report;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGlobalValue(value?: any) { // @ts-ignore
|
function setGlobalValue(value?: any) { // @ts-ignore
|
||||||
window.report = value || [];
|
window.report = value || [];
|
||||||
|
splashScreenStore.setDelay((value || [])?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStringsForParser(text: string) {
|
export function getStringsForParser(text: string) {
|
||||||
let temp = getGlobalValue();
|
|
||||||
setGlobalValue([]);
|
setGlobalValue([]);
|
||||||
|
|
||||||
const firstText = text.slice(0, 3);
|
const firstText = text.slice(0, 3);
|
||||||
if (firstText === 'rep' || firstText === 'r(f') {
|
const isNeedClear = {
|
||||||
try {
|
'rep': true,
|
||||||
eval(text);
|
'r(f': true,
|
||||||
} catch (e) {
|
'R(f': true,
|
||||||
setGlobalValue(temp);
|
}[firstText];
|
||||||
return;
|
|
||||||
|
if (isNeedClear) {
|
||||||
|
text = text.replace(/(R\(f`)|(r\(f`)|(report\.push\(`)|(`\);)/gim, '');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setGlobalValue(text.split('\n'));
|
setGlobalValue(text.split('\n'));
|
||||||
}
|
|
||||||
|
|
||||||
return getGlobalValue();
|
return getGlobalValue();
|
||||||
}
|
}
|
||||||
|
@ -56,7 +59,6 @@ export function getOnDrop(setLoading: Function, onChange: Function) {
|
||||||
.map((file: any) => file.kind === 'file' ? file?.getAsFile() : null)
|
.map((file: any) => file.kind === 'file' ? file?.getAsFile() : null)
|
||||||
.filter(file => file);
|
.filter(file => file);
|
||||||
|
|
||||||
console.log(files);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ function GetItem({ commit, mode }: IGetItemProps) {
|
||||||
const className = size > 5
|
const className = size > 5
|
||||||
? style.get_list_big_number
|
? style.get_list_big_number
|
||||||
: '';
|
: '';
|
||||||
const prId = dataGrip.pr.prByTask[commit.task];
|
const prId = dataGrip.pr.prByTask.get(commit.task);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.get_list}>
|
<div className={style.get_list}>
|
||||||
|
|
|
@ -12,16 +12,27 @@ const SplashScreen = observer((): React.ReactElement | null => {
|
||||||
if (!splashScreenStore.isOpen) return;
|
if (!splashScreenStore.isOpen) return;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
splashScreenStore.hide();
|
splashScreenStore.hide();
|
||||||
}, 5400);
|
}, splashScreenStore.delay);
|
||||||
}, [splashScreenStore.isOpen]);
|
}, [splashScreenStore.isOpen]);
|
||||||
|
|
||||||
if (!splashScreenStore.isOpen) return null;
|
if (!splashScreenStore.isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.splash_screen}>
|
<div
|
||||||
<div className={style.splash_screen_container}>
|
className={style.splash_screen}
|
||||||
|
style={{ animationDelay: splashScreenStore.getDelay(100) }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={style.splash_screen_container}
|
||||||
|
style={{ animationDelay: splashScreenStore.getDelay(-1400) }}
|
||||||
|
>
|
||||||
<Logo center />
|
<Logo center />
|
||||||
<div className={progress.progress_bar}></div>
|
<div className={progress.progress_bar}>
|
||||||
|
<div
|
||||||
|
className={progress.progress_bar_line}
|
||||||
|
style={{ animationDuration: splashScreenStore.getDelay(-1100) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
background-color: #404148;
|
background-color: #404148;
|
||||||
|
|
||||||
&:after {
|
&_line {
|
||||||
content: '';
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
display: block;
|
display: block;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -33,11 +32,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress_bar,
|
.progress_bar,
|
||||||
.progress_bar:after {
|
.progress_bar_line {
|
||||||
transition: background-color 0.7s, width 0.5s;
|
transition: background-color 0.7s, width 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress_bar:after {
|
.progress_bar_line {
|
||||||
animation: progress_bar 4.3s linear forwards;
|
animation: progress_bar 4.3s linear forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,41 @@
|
||||||
import { observable, action, makeObservable } from 'mobx';
|
import { observable, action, makeObservable } from 'mobx';
|
||||||
import globalScroll from 'ts/helpers/globalScroll';
|
import globalScroll from 'ts/helpers/globalScroll';
|
||||||
|
|
||||||
|
const DEFAULT_DELAY = 3400;
|
||||||
|
|
||||||
class SplashScreenStore {
|
class SplashScreenStore {
|
||||||
isOpen: boolean = false;
|
isOpen: boolean = false;
|
||||||
|
|
||||||
|
delay: number = DEFAULT_DELAY;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
isOpen: observable,
|
isOpen: observable,
|
||||||
|
delay: observable,
|
||||||
show: action,
|
show: action,
|
||||||
hide: action,
|
hide: action,
|
||||||
|
setDelay: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
globalScroll.off(5400);
|
globalScroll.off(this.delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDelay(logSize: number) {
|
||||||
|
const delay = (logSize / 190) + 400;
|
||||||
|
this.delay = Math.max(DEFAULT_DELAY, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDelay(diff?: number) {
|
||||||
|
const delay = this.delay + (diff || 0);
|
||||||
|
return (delay / 1000).toFixed(1) + 's';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const splashScreen = new SplashScreenStore();
|
const splashScreen = new SplashScreenStore();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { IColumn, IRowsConfig } from '../../interfaces/Column';
|
import { IColumn, IRowsConfig } from '../../interfaces/Column';
|
||||||
|
import getClassName from '../../helpers/getClassName';
|
||||||
import style from '../../styles/index.module.scss';
|
import style from '../../styles/index.module.scss';
|
||||||
|
|
||||||
interface IDefaultCellProps {
|
interface IDefaultCellProps {
|
||||||
|
@ -24,14 +25,12 @@ function DetailsCell({
|
||||||
|
|
||||||
const left = column?.isFixed ? marginLeft : 0;
|
const left = column?.isFixed ? marginLeft : 0;
|
||||||
|
|
||||||
const columnClassName = typeof column.className === 'function'
|
|
||||||
? column.className('body', row)
|
|
||||||
: column.className;
|
|
||||||
|
|
||||||
const iconClassName = config?.details
|
const iconClassName = config?.details
|
||||||
? style.table_cell_icon_open
|
? style.table_cell_icon_open
|
||||||
: style.table_cell_icon_close;
|
: style.table_cell_icon_close;
|
||||||
|
|
||||||
|
const localClassName = getClassName(style.table_cell, column, ['body', row], className);
|
||||||
|
|
||||||
const hasIcon = ((column.properties && row[column.properties])
|
const hasIcon = ((column.properties && row[column.properties])
|
||||||
|| !column.properties
|
|| !column.properties
|
||||||
|| !column.properties?.length)
|
|| !column.properties?.length)
|
||||||
|
@ -47,13 +46,13 @@ function DetailsCell({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={column.title} // @ts-ignore
|
key={column.title}
|
||||||
className={`${style.table_cell} ${className || ''} ${columnClassName || ''}`}
|
className={localClassName}
|
||||||
style={{
|
style={{
|
||||||
|
left,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
left,
|
}}
|
||||||
}} // @ts-ignore
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{hasIcon && (
|
{hasIcon && (
|
||||||
|
|
|
@ -41,11 +41,13 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
display: block;
|
display: block;
|
||||||
padding-top: 24px;
|
padding: 0 var(--space-m) var(--space-xxl) 42px;
|
||||||
padding-left: 12px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-bottom: 1px solid #EEEEEE;
|
|
||||||
break-inside: auto;
|
break-inside: auto;
|
||||||
|
|
||||||
|
border-bottom: 1px solid #EEEEEE;
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
&_cell,
|
&_cell,
|
||||||
|
@ -62,6 +64,8 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
background-color: var(--color-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&_fixed {
|
&_fixed {
|
||||||
|
@ -97,11 +101,11 @@
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
transform: rotate(0);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&_open {
|
&_open {
|
||||||
transform: rotate(-180deg);
|
transform: rotate(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ interface ITaskProps {
|
||||||
|
|
||||||
function Task({ title, commits }: ITaskProps) {
|
function Task({ title, commits }: ITaskProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const prId = dataGrip.pr.prByTask[title];
|
const prId = dataGrip.pr.prByTask.get(title);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={title}
|
key={title}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import ICommit from 'ts/interfaces/Commit';
|
import ICommit from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||||
|
|
||||||
import { ONE_DAY } from 'ts/helpers/formatter';
|
import { ONE_DAY } from 'ts/helpers/formatter';
|
||||||
import { increment } from 'ts/helpers/Math';
|
import { createHashMap, createIncrement, increment } from 'ts/helpers/Math';
|
||||||
import getCompany from '../helpers/getCompany';
|
import getCompany from 'ts/helpers/Parser/getCompany';
|
||||||
|
|
||||||
import userSettings from 'ts/store/UserSettings';
|
import userSettings from 'ts/store/UserSettings';
|
||||||
|
|
||||||
export default class DataGripByAuthor {
|
export default class DataGripByAuthor {
|
||||||
list: string[] = [];
|
list: string[] = [];
|
||||||
|
|
||||||
commits: IHashMap<any> = {};
|
commits: HashMap<any> = new Map();
|
||||||
|
|
||||||
statistic: any = [];
|
statistic: any = [];
|
||||||
|
|
||||||
|
@ -20,22 +20,22 @@ export default class DataGripByAuthor {
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
this.commits = {};
|
this.commits.clear();
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
this.statisticByName = {};
|
this.statisticByName = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommit(commit: ICommit) {
|
addCommit(commit: ICommit) {
|
||||||
if (this.commits.hasOwnProperty(commit.author)) {
|
const statistic = this.commits.get(commit.author);
|
||||||
this.#updateCommitByAuthor(commit);
|
if (statistic) {
|
||||||
|
this.#updateCommitByAuthor(statistic, commit);
|
||||||
} else {
|
} else {
|
||||||
this.#addCommitByAuthor(commit);
|
this.#addCommitByAuthor(commit);
|
||||||
}
|
}
|
||||||
this.#setMoneyByMonth(commit);
|
this.#setMoneyByMonth(commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateCommitByAuthor(commit: ICommit) {
|
#updateCommitByAuthor(statistic: any, commit: ICommit) {
|
||||||
const statistic = this.commits[commit.author];
|
|
||||||
statistic.commits += 1;
|
statistic.commits += 1;
|
||||||
statistic.lastCommit = commit;
|
statistic.lastCommit = commit;
|
||||||
statistic.days[commit.timestamp] = true;
|
statistic.days[commit.timestamp] = true;
|
||||||
|
@ -56,6 +56,11 @@ export default class DataGripByAuthor {
|
||||||
}
|
}
|
||||||
statistic.commitsByHour[commit.hours] += 1;
|
statistic.commitsByHour[commit.hours] += 1;
|
||||||
statistic.wordStatistics = DataGripByAuthor.#updateWordStatistics(commit, statistic.wordStatistics);
|
statistic.wordStatistics = DataGripByAuthor.#updateWordStatistics(commit, statistic.wordStatistics);
|
||||||
|
|
||||||
|
if (commit.company && statistic.lastCompany !== commit.company) {
|
||||||
|
statistic.lastCompany = commit.company;
|
||||||
|
statistic.company.push({ title: commit.company, from: commit.timestamp });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#addCommitByAuthor(commit: ICommit) {
|
#addCommitByAuthor(commit: ICommit) {
|
||||||
|
@ -65,16 +70,20 @@ export default class DataGripByAuthor {
|
||||||
const commitsByHour = new Array(24).fill(0);
|
const commitsByHour = new Array(24).fill(0);
|
||||||
commitsByHour[commit.hours] += 1;
|
commitsByHour[commit.hours] += 1;
|
||||||
|
|
||||||
this.commits[commit.author] = {
|
this.commits.set(commit.author, {
|
||||||
author: commit.author,
|
author: commit.author,
|
||||||
commits: 1,
|
commits: 1,
|
||||||
firstCommit: commit,
|
firstCommit: commit,
|
||||||
lastCommit: commit,
|
lastCommit: commit,
|
||||||
days: { [commit.timestamp]: true },
|
days: createHashMap(commit.timestamp),
|
||||||
tasks: { [commit.task]: commit.added + commit.changes + commit.removed },
|
tasks: { [commit.task]: commit.added + commit.changes + commit.removed },
|
||||||
types: { [commit.type]: 1 },
|
types: createIncrement(commit.type),
|
||||||
scopes: { [commit.scope]: 1 },
|
scopes: createIncrement(commit.scope),
|
||||||
hours: [commit.hours],
|
hours: [commit.hours],
|
||||||
|
company: commit.company
|
||||||
|
? [{ title: commit.company, from: commit.timestamp }]
|
||||||
|
: [],
|
||||||
|
lastCompany: commit.company,
|
||||||
commitsByDayAndHour,
|
commitsByDayAndHour,
|
||||||
commitsByHour,
|
commitsByHour,
|
||||||
messageLength: [commit.text.length || 0],
|
messageLength: [commit.text.length || 0],
|
||||||
|
@ -82,12 +91,12 @@ export default class DataGripByAuthor {
|
||||||
maxMessageLength: commit.text.length || 0,
|
maxMessageLength: commit.text.length || 0,
|
||||||
wordStatistics: DataGripByAuthor.#updateWordStatistics(commit),
|
wordStatistics: DataGripByAuthor.#updateWordStatistics(commit),
|
||||||
moneyByMonth: {},
|
moneyByMonth: {},
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#setMoneyByMonth(commit: ICommit) {
|
#setMoneyByMonth(commit: ICommit) {
|
||||||
const key = `${commit.year}-${commit.month}`;
|
const key = `${commit.year}-${commit.month}`;
|
||||||
if (this.commits[commit.author].moneyByMonth[key]) {
|
if (this.commits.get(commit.author).moneyByMonth[key]) {
|
||||||
this.#updateMoneyByMonth(commit, key);
|
this.#updateMoneyByMonth(commit, key);
|
||||||
} else {
|
} else {
|
||||||
this.#addMoneyByMonth(commit, key);
|
this.#addMoneyByMonth(commit, key);
|
||||||
|
@ -95,7 +104,7 @@ export default class DataGripByAuthor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateMoneyByMonth(commit: ICommit, key: string) {
|
#updateMoneyByMonth(commit: ICommit, key: string) {
|
||||||
const statistic = this.commits[commit.author].moneyByMonth[key];
|
const statistic = this.commits.get(commit.author).moneyByMonth[key];
|
||||||
if (statistic.alreadyAdded[commit.milliseconds]) return;
|
if (statistic.alreadyAdded[commit.milliseconds]) return;
|
||||||
statistic.alreadyAdded[commit.milliseconds] = true;
|
statistic.alreadyAdded[commit.milliseconds] = true;
|
||||||
|
|
||||||
|
@ -110,7 +119,7 @@ export default class DataGripByAuthor {
|
||||||
#addMoneyByMonth(commit: ICommit, key: string) {
|
#addMoneyByMonth(commit: ICommit, key: string) {
|
||||||
const contract = userSettings.getEmploymentContract(commit.author, commit.milliseconds);
|
const contract = userSettings.getEmploymentContract(commit.author, commit.milliseconds);
|
||||||
const isWorkDay = contract.workDaysInWeek[commit.day];
|
const isWorkDay = contract.workDaysInWeek[commit.day];
|
||||||
this.commits[commit.author].moneyByMonth[key] = {
|
this.commits.get(commit.author).moneyByMonth[key] = {
|
||||||
workDay: isWorkDay ? 1 : 0,
|
workDay: isWorkDay ? 1 : 0,
|
||||||
weekDay: isWorkDay ? 0 : 1,
|
weekDay: isWorkDay ? 0 : 1,
|
||||||
alreadyAdded: {
|
alreadyAdded: {
|
||||||
|
@ -147,7 +156,7 @@ export default class DataGripByAuthor {
|
||||||
active: [],
|
active: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.statistic = Object.values(this.commits)
|
this.statistic = Array.from(this.commits.values())
|
||||||
.sort((dotA: any, dotB: any) => dotB.commits - dotA.commits)
|
.sort((dotA: any, dotB: any) => dotB.commits - dotA.commits)
|
||||||
.map((dot: any) => {
|
.map((dot: any) => {
|
||||||
const from = dot.firstCommit.milliseconds;
|
const from = dot.firstCommit.milliseconds;
|
||||||
|
@ -186,7 +195,7 @@ export default class DataGripByAuthor {
|
||||||
daysForTask: isStaff ? 0 : workDays / tasks.length,
|
daysForTask: isStaff ? 0 : workDays / tasks.length,
|
||||||
taskInDay: isStaff ? 0 : tasks.length / workDays,
|
taskInDay: isStaff ? 0 : tasks.length / workDays,
|
||||||
changesForTask: DataGripByAuthor.getMiddleValue(tasksSize),
|
changesForTask: DataGripByAuthor.getMiddleValue(tasksSize),
|
||||||
company: getCompany(dot.author, dot.lastCommit.email),
|
lastCompany: getCompany(dot.author, dot.lastCommit.email),
|
||||||
|
|
||||||
days: workDays,
|
days: workDays,
|
||||||
money: isStaff ? 0 : moneyWorked,
|
money: isStaff ? 0 : moneyWorked,
|
||||||
|
|
87
src/ts/helpers/DataGrip/components/company.ts
Normal file
87
src/ts/helpers/DataGrip/components/company.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import ICommit from 'ts/interfaces/Commit';
|
||||||
|
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||||
|
import { createIncrement, increment } from 'ts/helpers/Math';
|
||||||
|
|
||||||
|
export default class DataGripByCompany {
|
||||||
|
commits: HashMap<any> = new Map();
|
||||||
|
|
||||||
|
statistic: any = [];
|
||||||
|
|
||||||
|
statisticByName: IHashMap<any> = {};
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.commits.clear();
|
||||||
|
this.statistic = [];
|
||||||
|
this.statisticByName = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommit(commit: ICommit) {
|
||||||
|
if (!commit.company) return;
|
||||||
|
const statistic = this.commits.get(commit.company);
|
||||||
|
if (statistic) {
|
||||||
|
this.#updateCommitByCompany(statistic, commit);
|
||||||
|
} else {
|
||||||
|
this.#addCommitByCompany(commit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let isActive = false;
|
||||||
|
employments.forEach((name) => {
|
||||||
|
const author = dataGripByAuthor.statisticByName[name];
|
||||||
|
if (!author) return;
|
||||||
|
if (author.lastCompany === statistic.company) isActive = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const companyInfo = {
|
||||||
|
...statistic,
|
||||||
|
employments,
|
||||||
|
tasks,
|
||||||
|
totalTasks: tasks.length,
|
||||||
|
totalDays: days.length,
|
||||||
|
totalEmployments: employments.length,
|
||||||
|
isActive,
|
||||||
|
};
|
||||||
|
delete companyInfo.days;
|
||||||
|
|
||||||
|
this.statisticByName[statistic.company] = companyInfo;
|
||||||
|
|
||||||
|
return companyInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.commits.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
import { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import { increment, WeightedAverage } from 'ts/helpers/Math';
|
import { createIncrement, increment, WeightedAverage } from 'ts/helpers/Math';
|
||||||
|
|
||||||
const IS_PR = {
|
const IS_PR = {
|
||||||
[COMMIT_TYPE.PR_BITBUCKET]: true,
|
[COMMIT_TYPE.PR_BITBUCKET]: true,
|
||||||
|
@ -9,71 +9,69 @@ const IS_PR = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class DataGripByPR {
|
export default class DataGripByPR {
|
||||||
pr: IHashMap<any> = {};
|
pr: HashMap<any> = new Map();
|
||||||
|
|
||||||
prByTask: IHashMap<string> = {};
|
prByTask: HashMap<any> = new Map();
|
||||||
|
|
||||||
lastCommitByTaskNumber: IHashMap<any> = {};
|
lastCommitByTaskNumber: HashMap<any> = new Map();
|
||||||
|
|
||||||
statistic: any[] = [];
|
statistic: any[] = [];
|
||||||
|
|
||||||
statisticByName: IHashMap<any> = [];
|
statisticByName: IHashMap<any> = [];
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.pr = {};
|
this.pr.clear();
|
||||||
this.prByTask = {};
|
this.prByTask.clear();
|
||||||
this.lastCommitByTaskNumber = {};
|
this.lastCommitByTaskNumber.clear();
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommit(commit: ISystemCommit) {
|
addCommit(commit: ISystemCommit) {
|
||||||
if (!commit.commitType) {
|
if (!commit.commitType) {
|
||||||
if (!this.lastCommitByTaskNumber[commit.task]) {
|
const commitByTaskNumber = this.lastCommitByTaskNumber.get(commit.task);
|
||||||
this.#addCommitByTaskNumber(commit);
|
if (commitByTaskNumber) {
|
||||||
|
this.#updateCommitByTaskNumber(commitByTaskNumber, commit);
|
||||||
} else {
|
} else {
|
||||||
this.#updateCommitByTaskNumber(commit);
|
this.#addCommitByTaskNumber(commit);
|
||||||
}
|
}
|
||||||
} else if (!this.pr[commit.prId] && IS_PR[commit.commitType || '']) {
|
} else if (!this.pr.has(commit.prId) && IS_PR[commit.commitType || '']) {
|
||||||
this.#addCommitByPR(commit);
|
this.#addCommitByPR(commit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#addCommitByTaskNumber(commit: ISystemCommit) {
|
#addCommitByTaskNumber(commit: ISystemCommit) {
|
||||||
this.lastCommitByTaskNumber[commit.task] = {
|
this.lastCommitByTaskNumber.set(commit.task, {
|
||||||
commits : 1,
|
commits : 1,
|
||||||
beginTaskTime: commit.milliseconds,
|
beginTaskTime: commit.milliseconds,
|
||||||
endTaskTime: commit.milliseconds,
|
endTaskTime: commit.milliseconds,
|
||||||
commitsByAuthors: {
|
commitsByAuthors: createIncrement(commit.author),
|
||||||
[commit.author]: 1,
|
|
||||||
},
|
|
||||||
firstCommit: commit,
|
firstCommit: commit,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateCommitByTaskNumber(commit: ISystemCommit) {
|
#updateCommitByTaskNumber(statistic: any, commit: ISystemCommit) {
|
||||||
const statistic = this.lastCommitByTaskNumber[commit.task];
|
|
||||||
statistic.endTaskTime = commit.milliseconds;
|
statistic.endTaskTime = commit.milliseconds;
|
||||||
statistic.commits += 1;
|
statistic.commits += 1;
|
||||||
increment(statistic.commitsByAuthors, commit.author);
|
increment(statistic.commitsByAuthors, commit.author);
|
||||||
}
|
}
|
||||||
|
|
||||||
#addCommitByPR(commit: ISystemCommit) {
|
#addCommitByPR(commit: ISystemCommit) {
|
||||||
const lastCommit = this.lastCommitByTaskNumber[commit.task];
|
const lastCommit = this.lastCommitByTaskNumber.get(commit.task);
|
||||||
if (lastCommit) {
|
if (lastCommit) {
|
||||||
// коммиты после влития PR сгорают, чтобы не засчитать технические PR мержи веток
|
// коммиты после влития PR сгорают, чтобы не засчитать технические PR мержи веток
|
||||||
delete this.lastCommitByTaskNumber[commit.task];
|
this.lastCommitByTaskNumber.delete(commit.task);
|
||||||
const delay = commit.milliseconds - lastCommit.endTaskTime;
|
const delay = commit.milliseconds - lastCommit.endTaskTime;
|
||||||
const work = lastCommit.endTaskTime - lastCommit.beginTaskTime;
|
const work = lastCommit.endTaskTime - lastCommit.beginTaskTime;
|
||||||
this.pr[commit.prId] = {
|
this.pr.set(commit.prId, {
|
||||||
...commit,
|
...commit,
|
||||||
...lastCommit,
|
...lastCommit,
|
||||||
delay,
|
delay,
|
||||||
delayDays: delay / (24 * 60 * 60 * 1000),
|
delayDays: delay / (24 * 60 * 60 * 1000),
|
||||||
workDays: work === 0 ? 1 : (work / (24 * 60 * 60 * 1000)),
|
workDays: work === 0 ? 1 : (work / (24 * 60 * 60 * 1000)),
|
||||||
};
|
});
|
||||||
this.prByTask[commit.task] = commit.prId;
|
this.prByTask.set(commit.task, commit.prId);
|
||||||
} else {
|
} else {
|
||||||
this.pr[commit.prId] = { ...commit };
|
this.pr.set(commit.prId, { ...commit });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +99,7 @@ export default class DataGripByPR {
|
||||||
this.statistic.sort((a: any, b: any) => b.delay - a.delay);
|
this.statistic.sort((a: any, b: any) => b.delay - a.delay);
|
||||||
this.updateTotalByAuthor(authors, refAuthorPR);
|
this.updateTotalByAuthor(authors, refAuthorPR);
|
||||||
|
|
||||||
this.lastCommitByTaskNumber = {};
|
this.lastCommitByTaskNumber.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPRByGroups(list: any, propertyName: string) {
|
static getPRByGroups(list: any, propertyName: string) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import ICommit from 'ts/interfaces/Commit';
|
import ICommit from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
import userSettings from 'ts/store/UserSettings';
|
import userSettings from 'ts/store/UserSettings';
|
||||||
import { increment } from 'ts/helpers/Math';
|
import { createHashMap, createIncrement, increment } from 'ts/helpers/Math';
|
||||||
|
|
||||||
interface IStatByAuthor {
|
interface IStatByAuthor {
|
||||||
commits: number; // number of commits by author in this scope
|
commits: number; // number of commits by author in this scope
|
||||||
|
@ -60,10 +60,10 @@ export default class DataGripByScope {
|
||||||
this.commits[commit.scope] = {
|
this.commits[commit.scope] = {
|
||||||
scope: commit.scope,
|
scope: commit.scope,
|
||||||
commits: 1,
|
commits: 1,
|
||||||
days: { [commit.timestamp]: true },
|
days: createHashMap(commit.timestamp),
|
||||||
tasks: { [commit.task]: true },
|
tasks: createHashMap(commit.task),
|
||||||
types: { [commit.type]: 1 },
|
types: createIncrement(commit.type),
|
||||||
authors: { [commit.author]: this.#getDefaultAuthorForScope(commit) },
|
authors: createIncrement(commit.author, this.#getDefaultAuthorForScope(commit)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class DataGripByTasks {
|
||||||
const firstCommit = commits[0];
|
const firstCommit = commits[0];
|
||||||
const lastCommit = commits[commits.length - 1];
|
const lastCommit = commits[commits.length - 1];
|
||||||
const from = firstCommit.milliseconds;
|
const from = firstCommit.milliseconds;
|
||||||
const pr = PRs.prByTask[task] ? PRs.pr[PRs.prByTask[task]] : null;
|
const pr = PRs.prByTask.get(task) ? PRs.pr.get(PRs.prByTask.get(task)) : null;
|
||||||
|
|
||||||
const shortInfo = {
|
const shortInfo = {
|
||||||
task,
|
task,
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import ICommit from 'ts/interfaces/Commit';
|
import ICommit from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import userSettings from 'ts/store/UserSettings';
|
import userSettings from 'ts/store/UserSettings';
|
||||||
import { increment } from 'ts/helpers/Math';
|
import { increment } from 'ts/helpers/Math';
|
||||||
|
|
||||||
import MinMaxCounter from './counter';
|
import MinMaxCounter from './counter';
|
||||||
|
|
||||||
export default class DataGripByTimestamp {
|
export default class DataGripByTimestamp {
|
||||||
commits: IHashMap<any> = {};
|
commits: HashMap<any> = new Map();
|
||||||
|
|
||||||
commitsByAuthor: IHashMap<any> = {};
|
commitsByAuthor: HashMap<any> = new Map();
|
||||||
|
|
||||||
statistic: any = [];
|
statistic: any = [];
|
||||||
|
|
||||||
|
@ -19,29 +19,35 @@ export default class DataGripByTimestamp {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.commits = {};
|
this.commits.clear();
|
||||||
this.commitsByAuthor = {};
|
this.commitsByAuthor.clear();
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
this.statisticByAuthor = {};
|
this.statisticByAuthor = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommit(commit: ICommit) {
|
addCommit(commit: ICommit) {
|
||||||
if (this.commits[commit.milliseconds]) {
|
const commitByMilliseconds = this.commits.get(commit.milliseconds);
|
||||||
this.#updateCommitByTimestamp(commit, this.commits[commit.milliseconds]);
|
if (commitByMilliseconds) {
|
||||||
|
this.#updateCommitByTimestamp(commitByMilliseconds, commit);
|
||||||
} else {
|
} else {
|
||||||
this.commits[commit.milliseconds] = this.#getDefaultCommitByTimestamp(commit);
|
this.commits.set(commit.milliseconds, this.#getDefaultCommitByTimestamp(commit));
|
||||||
}
|
}
|
||||||
if (!this.commitsByAuthor[commit.author]) {
|
|
||||||
this.commitsByAuthor[commit.author] = {};
|
let commitsByAuthor = this.commitsByAuthor.get(commit.author);
|
||||||
|
if (!commitsByAuthor) {
|
||||||
|
commitsByAuthor = new Map();
|
||||||
|
this.commitsByAuthor.set(commit.author, commitsByAuthor);
|
||||||
}
|
}
|
||||||
if (this.commitsByAuthor[commit.author][commit.milliseconds]) {
|
|
||||||
this.#updateCommitByTimestamp(commit, this.commitsByAuthor[commit.author][commit.milliseconds]);
|
const commitByAuthorMilliseconds = commitsByAuthor.get(commit.milliseconds);
|
||||||
|
if (commitByAuthorMilliseconds) {
|
||||||
|
this.#updateCommitByTimestamp(commitByAuthorMilliseconds, commit);
|
||||||
} else {
|
} else {
|
||||||
this.commitsByAuthor[commit.author][commit.milliseconds] = this.#getDefaultCommitByTimestamp(commit);
|
commitsByAuthor.set(commit.milliseconds, this.#getDefaultCommitByTimestamp(commit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateCommitByTimestamp(commit: ICommit, statistic: any) {
|
#updateCommitByTimestamp(statistic: any, commit: ICommit) {
|
||||||
statistic.commits += 1;
|
statistic.commits += 1;
|
||||||
statistic.addedAndChanges += commit.added + commit.changes;
|
statistic.addedAndChanges += commit.added + commit.changes;
|
||||||
increment(statistic.tasks, commit.task);
|
increment(statistic.tasks, commit.task);
|
||||||
|
@ -76,16 +82,16 @@ export default class DataGripByTimestamp {
|
||||||
updateTotalInfo(dataGripByAuthor: any) {
|
updateTotalInfo(dataGripByAuthor: any) {
|
||||||
this.statistic = this.#getTotalInfo(this.commits);
|
this.statistic = this.#getTotalInfo(this.commits);
|
||||||
this.statistic.weekendPayment = 0;
|
this.statistic.weekendPayment = 0;
|
||||||
for (let author in this.commitsByAuthor) {
|
for (let author of this.commitsByAuthor.keys()) {
|
||||||
const statistic = this.#getTotalInfo(this.commitsByAuthor[author]);
|
const statistic = this.#getTotalInfo(this.commitsByAuthor.get(author));
|
||||||
statistic.weekendPayment = this.#getWeekendPaymentByAuthor(statistic, dataGripByAuthor.statisticByName[author]);
|
statistic.weekendPayment = this.#getWeekendPaymentByAuthor(statistic, dataGripByAuthor.statisticByName[author || '']);
|
||||||
this.statisticByAuthor[author] = statistic; // TODO: странный результат, неверный расчёт?
|
this.statisticByAuthor[author || ''] = statistic; // TODO: странный результат, неверный расчёт?
|
||||||
this.statistic.weekendPayment += statistic.weekendPayment;
|
this.statistic.weekendPayment += statistic.weekendPayment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#getTotalInfo(uniqCommitsByTimestamp: any) {
|
#getTotalInfo(uniqCommitsByTimestamp: HashMap<any>) {
|
||||||
const allCommitsByTimestamp = Object.values(uniqCommitsByTimestamp);
|
const allCommitsByTimestamp = Array.from(uniqCommitsByTimestamp.values());
|
||||||
|
|
||||||
const commitsCounter = new MinMaxCounter();
|
const commitsCounter = new MinMaxCounter();
|
||||||
const changesCounter = new MinMaxCounter();
|
const changesCounter = new MinMaxCounter();
|
||||||
|
@ -111,12 +117,6 @@ export default class DataGripByTimestamp {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#getMiddleValue(list: any, property: string) {
|
|
||||||
const sortList = list.sort((a: any, b: any) => b[property] - a[property]);
|
|
||||||
const gap = Math.floor(sortList.length * 0.05);
|
|
||||||
return sortList.slice(gap, sortList.length - gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
#getWeekendPaymentByAuthor(statistic: any, dataGripByAuthor: any) {
|
#getWeekendPaymentByAuthor(statistic: any, dataGripByAuthor: any) {
|
||||||
if (dataGripByAuthor.isStaff) return 0;
|
if (dataGripByAuthor.isStaff) return 0;
|
||||||
const salaryInMonth = userSettings.getCurrentSalaryInMonth(dataGripByAuthor.author);
|
const salaryInMonth = userSettings.getCurrentSalaryInMonth(dataGripByAuthor.author);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ICommit from 'ts/interfaces/Commit';
|
import ICommit from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
import { increment } from 'ts/helpers/Math';
|
import { createIncrement, increment } from 'ts/helpers/Math';
|
||||||
import { POPULAR_TYPES } from 'ts/helpers/Parser/getTypeAndScope';
|
import { POPULAR_TYPES } from 'ts/helpers/Parser/getTypeAndScope';
|
||||||
|
|
||||||
export default class DataGripByType {
|
export default class DataGripByType {
|
||||||
|
@ -39,10 +39,12 @@ export default class DataGripByType {
|
||||||
this.commits[commit.type] = {
|
this.commits[commit.type] = {
|
||||||
type: commit.type,
|
type: commit.type,
|
||||||
commits: 1,
|
commits: 1,
|
||||||
days: { [commit.timestamp]: true },
|
days: createIncrement(commit.timestamp, true),
|
||||||
tasks: { [commit.task]: true },
|
tasks: createIncrement(commit.task, true),
|
||||||
commitsByAuthors: { [commit.author]: 1 },
|
commitsByAuthors: createIncrement(commit.author, true),
|
||||||
daysByAuthors: { [commit.author]: { [commit.timestamp]: true } },
|
daysByAuthors: {
|
||||||
|
[commit.author]: createIncrement(commit.timestamp, true),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,15 @@ import DataGripByPR from './components/pr';
|
||||||
import DataGripByTasks from './components/tasks';
|
import DataGripByTasks from './components/tasks';
|
||||||
import DataGripByRelease from './components/release';
|
import DataGripByRelease from './components/release';
|
||||||
import DataGripByScoring from './components/scoring';
|
import DataGripByScoring from './components/scoring';
|
||||||
|
import DataGripByCompany from './components/company';
|
||||||
|
|
||||||
class DataGrip {
|
class DataGrip {
|
||||||
firstLastCommit: any = new MinMaxCounter();
|
firstLastCommit: any = new MinMaxCounter();
|
||||||
|
|
||||||
author: any = new DataGripByAuthor();
|
author: any = new DataGripByAuthor();
|
||||||
|
|
||||||
|
company: any = new DataGripByCompany();
|
||||||
|
|
||||||
team: any = new DataGripByTeam();
|
team: any = new DataGripByTeam();
|
||||||
|
|
||||||
scope: any = new DataGripByScope();
|
scope: any = new DataGripByScope();
|
||||||
|
@ -45,6 +48,7 @@ class DataGrip {
|
||||||
clear() {
|
clear() {
|
||||||
this.firstLastCommit.clear();
|
this.firstLastCommit.clear();
|
||||||
this.author.clear();
|
this.author.clear();
|
||||||
|
this.company.clear();
|
||||||
this.team.clear();
|
this.team.clear();
|
||||||
this.scope.clear();
|
this.scope.clear();
|
||||||
this.type.clear();
|
this.type.clear();
|
||||||
|
@ -71,6 +75,7 @@ class DataGrip {
|
||||||
this.get.addCommit(commit);
|
this.get.addCommit(commit);
|
||||||
this.week.addCommit(commit);
|
this.week.addCommit(commit);
|
||||||
this.tasks.addCommit(commit);
|
this.tasks.addCommit(commit);
|
||||||
|
this.company.addCommit(commit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +91,7 @@ class DataGrip {
|
||||||
this.tasks.updateTotalInfo(this.pr);
|
this.tasks.updateTotalInfo(this.pr);
|
||||||
this.release.updateTotalInfo();
|
this.release.updateTotalInfo();
|
||||||
this.scoring.updateTotalInfo(this.author, this.timestamp);
|
this.scoring.updateTotalInfo(this.author, this.timestamp);
|
||||||
|
this.company.updateTotalInfo(this.author);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import ICommit, { IFileChange } from 'ts/interfaces/Commit';
|
import ICommit, { IFileChange } from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||||
|
import { increment } from 'ts/helpers/Math';
|
||||||
|
|
||||||
import FileBuilderCommon from './Common';
|
import FileBuilderCommon from './Common';
|
||||||
import FileBuilderLineStat from './LineStat';
|
import FileBuilderLineStat from './LineStat';
|
||||||
|
@ -8,25 +9,26 @@ import FileBuilderLineStat from './LineStat';
|
||||||
export default class FileGripByPaths {
|
export default class FileGripByPaths {
|
||||||
list: IDirtyFile[] = [];
|
list: IDirtyFile[] = [];
|
||||||
|
|
||||||
refFileIds: IHashMap<IDirtyFile> = {};
|
refFileIds: HashMap<IDirtyFile> = new Map();
|
||||||
|
|
||||||
refRemovedFileIds: IHashMap<IDirtyFile> = {};
|
refRemovedFileIds: HashMap<IDirtyFile> = new Map();
|
||||||
|
|
||||||
refExtensionType: IHashMap<IHashMap<number>> = {}; // TODO: remove me?
|
refExtensionType: HashMap<IHashMap<number>> = new Map(); // TODO: remove me?
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
this.refFileIds = {};
|
this.refFileIds.clear();
|
||||||
this.refRemovedFileIds = {};
|
this.refRemovedFileIds.clear();
|
||||||
|
this.refExtensionType.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommit(fileChange: IFileChange, commit: ICommit) {
|
addCommit(fileChange: IFileChange, commit: ICommit) {
|
||||||
let file = this.refFileIds[fileChange.id] || this.refFileIds[fileChange.newId || ''];
|
let file = this.refFileIds.get(fileChange.id) || this.refFileIds.get(fileChange.newId);
|
||||||
if (file) {
|
if (file) {
|
||||||
this.#updateDirtyFile(file, fileChange, commit);
|
this.#updateDirtyFile(file, fileChange, commit);
|
||||||
} else {
|
} else {
|
||||||
file = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
|
file = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
|
||||||
this.refFileIds[fileChange.id] = file;
|
this.refFileIds.set(fileChange.id, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileChange.newId) {
|
if (fileChange.newId) {
|
||||||
|
@ -51,20 +53,22 @@ export default class FileGripByPaths {
|
||||||
}
|
}
|
||||||
|
|
||||||
#renameFile(file: any, newId: string) {
|
#renameFile(file: any, newId: string) {
|
||||||
this.refFileIds[newId] = this.refFileIds[file.id];
|
const oldFile = this.refFileIds.get(file.id) as IDirtyFile;
|
||||||
delete this.refFileIds[file.id];
|
this.refFileIds.set(newId, oldFile);
|
||||||
|
this.refFileIds.delete(file.id);
|
||||||
file.id = newId;
|
file.id = newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
#removeFile(file: any) {
|
#removeFile(file: any) {
|
||||||
file.action = 'D';
|
file.action = 'D';
|
||||||
this.refRemovedFileIds[file.id] = this.refFileIds[file.id];
|
const oldFile = this.refFileIds.get(file.id) as IDirtyFile;
|
||||||
this.refRemovedFileIds[file.id].action = 'D';
|
oldFile.action = 'D';
|
||||||
delete this.refFileIds[file.id];
|
this.refRemovedFileIds.set(file.id, oldFile);
|
||||||
|
this.refFileIds.delete(file.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTotalInfo(callback?: Function) {
|
updateTotalInfo(callback?: Function) {
|
||||||
this.list = Object.values(this.refFileIds);
|
this.list = Array.from(this.refFileIds.values());
|
||||||
this.list.forEach((temp: any) => {
|
this.list.forEach((temp: any) => {
|
||||||
const file = temp;
|
const file = temp;
|
||||||
|
|
||||||
|
@ -72,10 +76,12 @@ export default class FileGripByPaths {
|
||||||
FileBuilderLineStat.updateTotal(file);
|
FileBuilderLineStat.updateTotal(file);
|
||||||
|
|
||||||
if (file.type) {
|
if (file.type) {
|
||||||
if (!this.refExtensionType[file.extension]) this.refExtensionType[file.extension] = {};
|
let refExtensionType = this.refExtensionType.get(file.extension);
|
||||||
this.refExtensionType[file.extension][file.type] = this.refExtensionType[file.extension][file.type]
|
if (!refExtensionType) {
|
||||||
? (this.refExtensionType[file.extension][file.type] + 1)
|
refExtensionType = {};
|
||||||
: 1;
|
this.refExtensionType.set(file.extension, refExtensionType);
|
||||||
|
}
|
||||||
|
increment(refExtensionType, file.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.lines === 0
|
if (file.lines === 0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||||
|
|
||||||
interface IStatByAuthor {
|
interface IStatByAuthor {
|
||||||
|
@ -8,12 +8,12 @@ interface IStatByAuthor {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FileGripByAuthor {
|
export default class FileGripByAuthor {
|
||||||
statisticByName: IHashMap<IStatByAuthor> = {};
|
statisticByName: HashMap<IStatByAuthor> = new Map();
|
||||||
|
|
||||||
totalAddedFiles: number = 0;
|
totalAddedFiles: number = 0;
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.statisticByName = {};
|
this.statisticByName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFile(file: IDirtyFile) {
|
addFile(file: IDirtyFile) {
|
||||||
|
@ -28,17 +28,17 @@ export default class FileGripByAuthor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#addCommitByAuthor(author: string) {
|
#addCommitByAuthor(author: string) {
|
||||||
if (this.statisticByName[author]) return;
|
if (this.statisticByName.has(author)) return;
|
||||||
this.statisticByName[author] = {
|
this.statisticByName.set(author, {
|
||||||
addedFiles: 0,
|
addedFiles: 0,
|
||||||
removedFiles: 0,
|
removedFiles: 0,
|
||||||
addedWithoutRemoveFiles: 0,
|
addedWithoutRemoveFiles: 0,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateCommitByAuthor(file: IDirtyFile, firstAuthor: string, lastAuthor: string) {
|
#updateCommitByAuthor(file: IDirtyFile, firstAuthor: string, lastAuthor: string) {
|
||||||
const createStatistic = this.statisticByName[firstAuthor];
|
const createStatistic = this.statisticByName.get(firstAuthor) as IStatByAuthor;
|
||||||
const removeStatistic = this.statisticByName[lastAuthor];
|
const removeStatistic = this.statisticByName.get(lastAuthor) as IStatByAuthor;
|
||||||
|
|
||||||
createStatistic.addedWithoutRemoveFiles += 1;
|
createStatistic.addedWithoutRemoveFiles += 1;
|
||||||
if (file.action === 'D') {
|
if (file.action === 'D') {
|
||||||
|
@ -49,7 +49,7 @@ export default class FileGripByAuthor {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTotalInfo() {
|
updateTotalInfo() {
|
||||||
this.totalAddedFiles = Object.values(this.statisticByName)
|
this.totalAddedFiles = Array.from(this.statisticByName.values())
|
||||||
.reduce((sum: number, stat: any) => sum + stat.addedFiles, 0);
|
.reduce((sum: number, stat: any) => sum + stat.addedFiles, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||||
|
|
||||||
interface IStatByExtension {
|
interface IStatByExtension {
|
||||||
|
@ -22,7 +22,7 @@ const IGNORE_LIST = [
|
||||||
export default class FileGripByExtension {
|
export default class FileGripByExtension {
|
||||||
statistic: IStatByExtension[] = [];
|
statistic: IStatByExtension[] = [];
|
||||||
|
|
||||||
statisticByName: IHashMap<IStatByExtension> = {};
|
statisticByName: HashMap<IStatByExtension> = new Map();
|
||||||
|
|
||||||
property: string = '';
|
property: string = '';
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export default class FileGripByExtension {
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
this.statisticByName = {};
|
this.statisticByName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFile(file: IDirtyFile) {
|
addFile(file: IDirtyFile) {
|
||||||
|
@ -40,17 +40,18 @@ export default class FileGripByExtension {
|
||||||
|
|
||||||
if (!key || IGNORE_LIST.includes(file.name)) return;
|
if (!key || IGNORE_LIST.includes(file.name)) return;
|
||||||
|
|
||||||
if (!this.statisticByName[key]) {
|
let extension = this.statisticByName.get(key);
|
||||||
this.statisticByName[key] = this.#getNewExtension(file);
|
if (!extension) {
|
||||||
|
extension = this.#getNewExtension(file);
|
||||||
|
this.statisticByName.set(key, extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensions = this.statisticByName[key];
|
|
||||||
if (file.action === 'D') {
|
if (file.action === 'D') {
|
||||||
extensions.removedFiles.push(file);
|
extension.removedFiles.push(file);
|
||||||
extensions.removedCount += 1;
|
extension.removedCount += 1;
|
||||||
} else {
|
} else {
|
||||||
extensions.files.push(file);
|
extension.files.push(file);
|
||||||
extensions.count += 1;
|
extension.count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +68,7 @@ export default class FileGripByExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTotalInfo() {
|
updateTotalInfo() {
|
||||||
this.statistic = Object.entries(this.statisticByName)
|
this.statistic = Array.from(this.statisticByName.values())
|
||||||
.sort((a: any, b: any) => b[1].count - a[1].count)
|
.sort((a: any, b: any) => b.count - a.count);
|
||||||
.map((item: any) => item[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,16 @@ function getFolder(name?: string, path?: string[], file?: IDirtyFile): IFolder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFolderBy(folder: any, file: IDirtyFile, property: string) {
|
||||||
|
for (let author in file[property]) {
|
||||||
|
const folderAddedLinesByAuthor = folder[property][author];
|
||||||
|
const fileAddedLinesByAuthor = file[property][author];
|
||||||
|
folder[property][author] = folderAddedLinesByAuthor
|
||||||
|
? (folderAddedLinesByAuthor + fileAddedLinesByAuthor)
|
||||||
|
: fileAddedLinesByAuthor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateFolder(folder: any, file: IDirtyFile) {
|
function updateFolder(folder: any, file: IDirtyFile) {
|
||||||
folder.lastCommit = file.lastCommit;
|
folder.lastCommit = file.lastCommit;
|
||||||
folder.lines += file.lines;
|
folder.lines += file.lines;
|
||||||
|
@ -38,23 +48,9 @@ function updateFolder(folder: any, file: IDirtyFile) {
|
||||||
folder.removedLines += file.removedLines || 0;
|
folder.removedLines += file.removedLines || 0;
|
||||||
folder.changedLines += file.changedLines || 0;
|
folder.changedLines += file.changedLines || 0;
|
||||||
|
|
||||||
for (let author in file.addedLinesByAuthor) {
|
updateFolderBy(folder, file, 'addedLinesByAuthor');
|
||||||
folder.addedLinesByAuthor[author] = folder.addedLinesByAuthor[author]
|
updateFolderBy(folder, file, 'removedLinesByAuthor');
|
||||||
? (folder.addedLinesByAuthor[author] + file.addedLinesByAuthor[author])
|
updateFolderBy(folder, file, 'changedLinesByAuthor');
|
||||||
: file.addedLinesByAuthor[author];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let author in file.removedLinesByAuthor) {
|
|
||||||
folder.removedLinesByAuthor[author] = folder.removedLinesByAuthor[author]
|
|
||||||
? (folder.removedLinesByAuthor[author] + file.removedLinesByAuthor[author])
|
|
||||||
: file.removedLinesByAuthor[author];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let author in file.changedLinesByAuthor) {
|
|
||||||
folder.changedLinesByAuthor[author] = folder.changedLinesByAuthor[author]
|
|
||||||
? (folder.changedLinesByAuthor[author] + file.changedLinesByAuthor[author])
|
|
||||||
: file.changedLinesByAuthor[author];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FileGripByFolder {
|
export default class FileGripByFolder {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||||
|
import { increment } from 'ts/helpers/Math';
|
||||||
|
|
||||||
interface IStatByType {
|
interface IStatByType {
|
||||||
type: string; // type name
|
type: string; // type name
|
||||||
|
@ -15,11 +16,11 @@ interface IStatByType {
|
||||||
export default class FileGripByType {
|
export default class FileGripByType {
|
||||||
statistic: IStatByType[] = [];
|
statistic: IStatByType[] = [];
|
||||||
|
|
||||||
statisticByName: IHashMap<IStatByType> = {};
|
statisticByName: HashMap<IStatByType> = new Map();
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
this.statisticByName = {};
|
this.statisticByName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFile(file: IDirtyFile) {
|
addFile(file: IDirtyFile) {
|
||||||
|
@ -27,14 +28,13 @@ export default class FileGripByType {
|
||||||
|
|
||||||
if (!key || file?.name?.[0] === '.') return;
|
if (!key || file?.name?.[0] === '.') return;
|
||||||
|
|
||||||
if (!this.statisticByName.hasOwnProperty(key)) {
|
let type = this.statisticByName.get(key);
|
||||||
this.statisticByName[key] = this.#getNewType(file);
|
if (!type) {
|
||||||
|
type = this.#getNewType(file);
|
||||||
|
this.statisticByName.set(key, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this.statisticByName[key];
|
increment(type.extension, file?.extension);
|
||||||
type.extension[file?.extension] = type.extension[file?.extension]
|
|
||||||
? (type.extension[file?.extension] + 1)
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
if (file.action === 'D') {
|
if (file.action === 'D') {
|
||||||
type.removedFiles.push(file);
|
type.removedFiles.push(file);
|
||||||
|
@ -50,7 +50,7 @@ export default class FileGripByType {
|
||||||
type: file?.type,
|
type: file?.type,
|
||||||
task: file?.firstCommit?.task,
|
task: file?.firstCommit?.task,
|
||||||
path: file?.name,
|
path: file?.name,
|
||||||
extension: { [file?.extension]: 1 },
|
extension: {},
|
||||||
files: [],
|
files: [],
|
||||||
count: 0,
|
count: 0,
|
||||||
removedFiles: [],
|
removedFiles: [],
|
||||||
|
@ -59,8 +59,7 @@ export default class FileGripByType {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTotalInfo() {
|
updateTotalInfo() {
|
||||||
this.statistic = Object.entries(this.statisticByName)
|
this.statistic = Array.from(this.statisticByName.values())
|
||||||
.sort((a: any, b: any) => b[1].count - a[1].count)
|
.sort((a: any, b: any) => b.count - a.count);
|
||||||
.map((item: any) => item[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,5 +37,13 @@ export class WeightedAverage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function increment(object: Object, path: string) {
|
export function increment(object: Object, path: string) {
|
||||||
object[path] = (object[path] || 0) + 1;
|
if (path) object[path] = (object[path] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIncrement(key?: string, firstValue?: any) {
|
||||||
|
return key ? { [key]: firstValue || 1 } : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createHashMap(key?: string) {
|
||||||
|
return createIncrement(key, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
|
|
||||||
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
|
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
|
||||||
|
import getCompany from './getCompany';
|
||||||
|
|
||||||
const MASTER_BRANCH = {
|
const MASTER_BRANCH = {
|
||||||
master: true,
|
master: true,
|
||||||
|
@ -11,11 +12,11 @@ const MASTER_BRANCH = {
|
||||||
|
|
||||||
let prevDate = new Date();
|
let prevDate = new Date();
|
||||||
|
|
||||||
let refTimestampTime = {};
|
let refTimestampTime = new Map();
|
||||||
|
|
||||||
export function clearCache() {
|
export function clearCache() {
|
||||||
prevDate = new Date();
|
prevDate = new Date();
|
||||||
refTimestampTime = {};
|
refTimestampTime.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getCommitInfo(
|
export default function getCommitInfo(
|
||||||
|
@ -28,33 +29,44 @@ export default function getCommitInfo(
|
||||||
const sourceDate = parts[0] || '';
|
const sourceDate = parts[0] || '';
|
||||||
let date = new Date(sourceDate);
|
let date = new Date(sourceDate);
|
||||||
if (isNaN(date.getDay())) {
|
if (isNaN(date.getDay())) {
|
||||||
console.log(`PARSE ERROR: Date parse error for: "${logString}"`);
|
// console.log(`PARSE ERROR: Date parse error for: "${logString}"`);
|
||||||
date = prevDate;
|
date = prevDate;
|
||||||
}
|
}
|
||||||
prevDate = date;
|
prevDate = date;
|
||||||
const day = date.getDay() - 1;
|
const day = date.getDay() - 1;
|
||||||
const timestamp = sourceDate.substring(0, 10); // split('T')[0];
|
const timestamp = sourceDate.substring(0, 10); // split('T')[0];
|
||||||
if (!refTimestampTime[timestamp]) {
|
let milliseconds = refTimestampTime.get(timestamp);
|
||||||
refTimestampTime[timestamp] = (new Date(timestamp)).getTime();
|
if (!milliseconds) {
|
||||||
|
milliseconds = (new Date(timestamp)).getTime();
|
||||||
|
refTimestampTime.set(timestamp, milliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
let author = parts[1]?.replace(/[._]/gm, ' ') || '';
|
let author = parts[1]?.replace(/[._]/gm, ' ') || '';
|
||||||
let email = parts[2] || '';
|
let email = parts[2] || '';
|
||||||
if (email.indexOf('@') === -1) email = '';
|
if (email.indexOf('@') === -1) email = '';
|
||||||
|
|
||||||
|
const companyKey = `${author}>in>${email}`;
|
||||||
|
if (!refEmailAuthor[companyKey]) {
|
||||||
|
const companyForKey = getCompany(author, email);
|
||||||
|
// @ts-ignore
|
||||||
|
refEmailAuthor[companyKey] = { company: companyForKey };
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const company = refEmailAuthor[companyKey].company;
|
||||||
|
|
||||||
const authorID = author.replace(/\s|\t/gm, '');
|
const authorID = author.replace(/\s|\t/gm, '');
|
||||||
if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) {
|
if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) {
|
||||||
console.log(`PARSE WARNING: Rename "${author}" to "${refEmailAuthor[authorID]}"`);
|
// console.log(`PARSE WARNING: Rename "${author}" to "${refEmailAuthor[authorID]}"`);
|
||||||
author = refEmailAuthor[authorID];
|
author = refEmailAuthor[authorID];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email && refEmailAuthor[email] && refEmailAuthor[email] !== author) {
|
if (email && refEmailAuthor[email] && refEmailAuthor[email] !== author) {
|
||||||
console.log(`PARSE WARNING: Rename "${author}" to "${refEmailAuthor[email]}" by "${email}"`);
|
// console.log(`PARSE WARNING: Rename "${author}" to "${refEmailAuthor[email]}" by "${email}"`);
|
||||||
author = refEmailAuthor[email];
|
author = refEmailAuthor[email];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (author && refEmailAuthor[author] && refEmailAuthor[author] !== email) {
|
if (author && refEmailAuthor[author] && refEmailAuthor[author] !== email) {
|
||||||
console.log(`PARSE WARNING: Rename "${email}" to "${refEmailAuthor[author]}" by "${author}"`);
|
// console.log(`PARSE WARNING: Rename "${email}" to "${refEmailAuthor[author]}" by "${author}"`);
|
||||||
email = refEmailAuthor[author];
|
email = refEmailAuthor[author];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +87,12 @@ export default function getCommitInfo(
|
||||||
year: date.getUTCFullYear(),
|
year: date.getUTCFullYear(),
|
||||||
week: 0,
|
week: 0,
|
||||||
timestamp,
|
timestamp,
|
||||||
milliseconds: refTimestampTime[timestamp],
|
milliseconds,
|
||||||
|
|
||||||
author,
|
author,
|
||||||
email,
|
email,
|
||||||
message,
|
message,
|
||||||
|
company,
|
||||||
|
|
||||||
text: '',
|
text: '',
|
||||||
type: '—',
|
type: '—',
|
||||||
|
|
|
@ -14,6 +14,11 @@ const PUBLIC_SERVICES = [
|
||||||
'rambler',
|
'rambler',
|
||||||
'github',
|
'github',
|
||||||
'gitlab',
|
'gitlab',
|
||||||
|
'com',
|
||||||
|
'me',
|
||||||
|
'qq',
|
||||||
|
'dev',
|
||||||
|
'localhost',
|
||||||
];
|
];
|
||||||
|
|
||||||
const isPublicService = Object.fromEntries(
|
const isPublicService = Object.fromEntries(
|
||||||
|
@ -36,14 +41,32 @@ function getCompanyByName(author?: string): string {
|
||||||
|
|
||||||
function getCompanyByEmail(email?: string) {
|
function getCompanyByEmail(email?: string) {
|
||||||
const domain = (email || '').split('@').pop() || '';
|
const domain = (email || '').split('@').pop() || '';
|
||||||
const company = domain.split('.').shift() || '';
|
const parts = domain.split('.');
|
||||||
return company.toUpperCase();
|
parts.pop();
|
||||||
|
return (parts.pop() || '').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClearText(text: string) {
|
||||||
|
return (text || '')
|
||||||
|
.replace(/(\[[^\]]])+/gim, '')
|
||||||
|
.replace(/[\s\t._0-9]+/gim, '')
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUserName(author?: string, company?: string): boolean {
|
||||||
|
if (!author || !company) return false;
|
||||||
|
|
||||||
|
const clearAuthor = getClearText(author);
|
||||||
|
const clearCompany = getClearText(company);
|
||||||
|
if (!clearAuthor || !clearCompany) return false;
|
||||||
|
|
||||||
|
return !!clearAuthor.match(clearCompany);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCompany(author?: string, email?: string) {
|
function getCompany(author?: string, email?: string) {
|
||||||
const company = getCompanyByName(author) || getCompanyByEmail(email) || '';
|
const company = getCompanyByName(author) || getCompanyByEmail(email) || '';
|
||||||
const isMailService = company.indexOf('MAIL') !== -1;
|
const isMailService = company.indexOf('MAIL') !== -1;
|
||||||
return isPublicService[company] || isMailService
|
return isPublicService[company] || isMailService || isUserName(author, company)
|
||||||
? ''
|
? ''
|
||||||
: company;
|
: company;
|
||||||
}
|
}
|
|
@ -5,6 +5,8 @@ function getFilePath(path: string): string[] {
|
||||||
.replace(/"/gm, '')
|
.replace(/"/gm, '')
|
||||||
.replace(/\/\//gm, '/');
|
.replace(/\/\//gm, '/');
|
||||||
|
|
||||||
|
if (formattedPath.indexOf('{') === -1) return [formattedPath];
|
||||||
|
|
||||||
const parts = formattedPath.split(/(?:\{)|(?:\s=>\s)|(?:})/gm);
|
const parts = formattedPath.split(/(?:\{)|(?:\s=>\s)|(?:})/gm);
|
||||||
if (parts.length !== 2 && parts.length !== 4) return [formattedPath];
|
if (parts.length !== 2 && parts.length !== 4) return [formattedPath];
|
||||||
|
|
||||||
|
@ -19,9 +21,32 @@ function getFilePath(path: string): string[] {
|
||||||
return [oldPath, newPath];
|
return [oldPath, newPath];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fastNumStatSplit(message: string) {
|
||||||
|
let firstIndex = 0;
|
||||||
|
if (message[1] === '\t') firstIndex = 1;
|
||||||
|
else if (message[2] === '\t') firstIndex = 2;
|
||||||
|
else if (message[3] === '\t') firstIndex = 3;
|
||||||
|
else if (message[4] === '\t') firstIndex = 4;
|
||||||
|
else if (message[5] === '\t') firstIndex = 5;
|
||||||
|
|
||||||
|
let secondIndex = firstIndex + 2;
|
||||||
|
if (message[firstIndex + 2] === '\t') secondIndex = firstIndex + 2;
|
||||||
|
else if (message[firstIndex + 3] === '\t') secondIndex = firstIndex + 3;
|
||||||
|
else if (message[firstIndex + 4] === '\t') secondIndex = firstIndex + 4;
|
||||||
|
else if (message[firstIndex + 5] === '\t') secondIndex = firstIndex + 5;
|
||||||
|
else if (message[firstIndex + 6] === '\t') secondIndex = firstIndex + 6;
|
||||||
|
|
||||||
|
return [
|
||||||
|
message.substring(0, firstIndex),
|
||||||
|
message.substring(firstIndex + 1, secondIndex),
|
||||||
|
message.substring(secondIndex + 1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// "38 9 src/app.css" -> [38, 9, 'src/app.css']
|
// "38 9 src/app.css" -> [38, 9, 'src/app.css']
|
||||||
export function getNumStatInfo(message: string) {
|
export function getNumStatInfo(message: string) {
|
||||||
let [addedRaw, removedRaw, path] = message.split('\t');
|
let [addedRaw, removedRaw, path] = fastNumStatSplit(message);
|
||||||
|
// let [addedRaw, removedRaw, path] = message.split('\t');
|
||||||
|
|
||||||
let added = parseInt(addedRaw, 10) || 0;
|
let added = parseInt(addedRaw, 10) || 0;
|
||||||
let removed = parseInt(removedRaw, 10) || 0;
|
let removed = parseInt(removedRaw, 10) || 0;
|
||||||
|
@ -51,7 +76,7 @@ export function getNumStatInfo(message: string) {
|
||||||
// ":000000 100644 000000000 fc44b0a37 A public/logo192.png" -> ['A', 'public/logo192.png']
|
// ":000000 100644 000000000 fc44b0a37 A public/logo192.png" -> ['A', 'public/logo192.png']
|
||||||
export function getRawInfo(message: string) {
|
export function getRawInfo(message: string) {
|
||||||
return {
|
return {
|
||||||
action:message[35],
|
action: message[35],
|
||||||
path: message.substring(37),
|
path: message.substring(37),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import IHashMap from 'ts/interfaces/HashMap';
|
||||||
import { ONE_DAY, ONE_WEEK } from 'ts/helpers/formatter';
|
import { ONE_DAY, ONE_WEEK } from 'ts/helpers/formatter';
|
||||||
|
|
||||||
import getCommitInfo, { clearCache } from './getCommitInfo';
|
import getCommitInfo, { clearCache } from './getCommitInfo';
|
||||||
import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges';
|
import {
|
||||||
|
getInfoFromPath,
|
||||||
|
getNumStatInfo,
|
||||||
|
getRawInfo,
|
||||||
|
} from './getFileChanges';
|
||||||
|
|
||||||
function updateLineTotal(commit: any, line: any) {
|
function updateLineTotal(commit: any, line: any) {
|
||||||
commit.added += line.addedLines || 0;
|
commit.added += line.addedLines || 0;
|
||||||
|
@ -12,50 +16,60 @@ function updateLineTotal(commit: any, line: any) {
|
||||||
commit.changes += line.changedLines || 0;
|
commit.changes += line.changedLines || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNumStatLine(message: string) {
|
||||||
|
return message[1] === '\t'
|
||||||
|
|| message[2] === '\t'
|
||||||
|
|| message[3] === '\t'
|
||||||
|
|| message[4] === '\t'
|
||||||
|
|| message[5] === '\t'
|
||||||
|
|| message[6] === '\t'
|
||||||
|
|| message[7] === '\t';
|
||||||
|
}
|
||||||
|
|
||||||
export default function Parser(report: string[]) {
|
export default function Parser(report: string[]) {
|
||||||
let commit = null;
|
let commit = null;
|
||||||
const commits: Array<ICommit | ISystemCommit> = [];
|
const commits: Array<ICommit | ISystemCommit> = [];
|
||||||
|
|
||||||
let refEmailAuthor: IHashMap<string> = {};
|
let refEmailAuthor: IHashMap<string> = {};
|
||||||
let files: IHashMap<IFileChange> = {};
|
let files: Map<string, IFileChange> = new Map();
|
||||||
let fileChanges: IFileChange | null = null;
|
let fileChanges: IFileChange | null = null;
|
||||||
|
|
||||||
let firstMonday = 0;
|
let firstMonday = 0;
|
||||||
clearCache();
|
|
||||||
|
|
||||||
for (let i = 0, l = report.length; i < l; i += 1) {
|
for (let i = 0, l = report.length; i < l; i += 1) {
|
||||||
const message = report[i];
|
const message = report[i];
|
||||||
if (!message) continue;
|
if (!message) continue;
|
||||||
|
|
||||||
const index = message.indexOf('\t');
|
if (message[0] === ':') {
|
||||||
if (index > 0 && index < 10) {
|
// парсинг файлов формата --raw
|
||||||
|
// ":000000 100644 0000000 496d1ef A .browserlistrc"
|
||||||
|
const line = getRawInfo(message);
|
||||||
|
fileChanges = files.get(line.path) as IFileChange;
|
||||||
|
if (!fileChanges) {
|
||||||
|
fileChanges = getInfoFromPath(line.path);
|
||||||
|
files.set(line.path, fileChanges);
|
||||||
|
}
|
||||||
|
fileChanges.action = line.action;
|
||||||
|
|
||||||
|
} else if (isNumStatLine(message)) {
|
||||||
// парсинг файлов формата --num-stat
|
// парсинг файлов формата --num-stat
|
||||||
// "1 0 .browserlistrc"
|
// "1 0 .browserlistrc"
|
||||||
const line = getNumStatInfo(message);
|
const line = getNumStatInfo(message);
|
||||||
if (!files[line.path]) {
|
fileChanges = files.get(line.path) as IFileChange;
|
||||||
files[line.path] = getInfoFromPath(line.path);
|
if (!fileChanges) {
|
||||||
|
fileChanges = getInfoFromPath(line.path);
|
||||||
|
files.set(line.path, fileChanges);
|
||||||
}
|
}
|
||||||
fileChanges = files[line.path];
|
|
||||||
fileChanges.addedLines = line.addedLines;
|
fileChanges.addedLines = line.addedLines;
|
||||||
fileChanges.removedLines = line.removedLines;
|
fileChanges.removedLines = line.removedLines;
|
||||||
fileChanges.changedLines = line.changedLines;
|
fileChanges.changedLines = line.changedLines;
|
||||||
updateLineTotal(commit, line);
|
updateLineTotal(commit, line);
|
||||||
|
|
||||||
} else if (message[0] === ':') {
|
|
||||||
// парсинг файлов формата --raw
|
|
||||||
// ":000000 100644 0000000 496d1ef A .browserlistrc"
|
|
||||||
const line = getRawInfo(message);
|
|
||||||
if (!files[line.path]) {
|
|
||||||
files[line.path] = getInfoFromPath(line.path);
|
|
||||||
}
|
|
||||||
fileChanges = files[line.path];
|
|
||||||
fileChanges.action = line.action;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// парсинг коммита
|
// парсинг коммита
|
||||||
// "2021-02-09T16:08:15+03:00>Albert>instein@mail.de>feat(init): added the speed of light"
|
// "2021-02-09T16:08:15+03:00>Albert>instein@mail.de>feat(init): added the speed of light"
|
||||||
if (commit) commit.fileChanges = Object.values(files);
|
if (commit) commit.fileChanges = Array.from(files.values());
|
||||||
files = {};
|
files.clear();
|
||||||
commit = getCommitInfo(message, refEmailAuthor);
|
commit = getCommitInfo(message, refEmailAuthor);
|
||||||
|
|
||||||
const monday = commit.milliseconds - commit.day * ONE_DAY;
|
const monday = commit.milliseconds - commit.day * ONE_DAY;
|
||||||
|
@ -69,5 +83,7 @@ export default function Parser(report: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCache();
|
||||||
|
|
||||||
return commits;
|
return commits;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,9 @@ export interface ILog {
|
||||||
week: number; // 42,
|
week: number; // 42,
|
||||||
|
|
||||||
// user
|
// user
|
||||||
author: string; // "Frolov Ivan",
|
author: string; // "Dart Vader",
|
||||||
email: string; // "frolov@mail.ru",
|
email: string; // "d.vader@emap.com",
|
||||||
|
company: string; // "emap",
|
||||||
|
|
||||||
// task
|
// task
|
||||||
message: string; // "JIRA-0000 fix(profile): add new avatar",
|
message: string; // "JIRA-0000 fix(profile): add new avatar",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export default interface IHashMap<T> {
|
export default interface IHashMap<T> {
|
||||||
[key: string | number]: T;
|
[key: string | number]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HashMap<T> = Map<string | number | undefined | null, T>;
|
||||||
|
|
|
@ -28,14 +28,14 @@ import Recommendations from 'ts/components/Recommendations';
|
||||||
import { getMax, getMaxByLength } from 'ts/pages/Common/helpers/getMax';
|
import { getMax, getMaxByLength } from 'ts/pages/Common/helpers/getMax';
|
||||||
import Description from 'ts/components/Description';
|
import Description from 'ts/components/Description';
|
||||||
|
|
||||||
interface IAuthorViewProps {
|
interface AuthorViewProps {
|
||||||
response?: IPagination<any>;
|
response?: IPagination<any>;
|
||||||
updateSort?: Function;
|
updateSort?: Function;
|
||||||
rowsForExcel?: any[];
|
rowsForExcel?: any[];
|
||||||
mode?: string;
|
mode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewProps) {
|
export function AuthorView({ response, updateSort, rowsForExcel, mode }: AuthorViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (!response) return null;
|
if (!response) return null;
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewPro
|
||||||
rows={response.content}
|
rows={response.content}
|
||||||
sort={response.sort}
|
sort={response.sort}
|
||||||
updateSort={updateSort}
|
updateSort={updateSort}
|
||||||
|
mode={mode}
|
||||||
type={mode === 'print' ? 'cards' : undefined}
|
type={mode === 'print' ? 'cards' : undefined}
|
||||||
columnCount={mode === 'print' ? 3 : undefined}
|
columnCount={mode === 'print' ? 3 : undefined}
|
||||||
>
|
>
|
||||||
|
@ -84,6 +85,13 @@ function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewPro
|
||||||
template={(value: string) => <UiKitTags value={value} />}
|
template={(value: string) => <UiKitTags value={value} />}
|
||||||
width={100}
|
width={100}
|
||||||
/>
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable="company"
|
||||||
|
title="page.team.author.company"
|
||||||
|
properties="lastCompany"
|
||||||
|
template={(value: string) => <UiKitTags value={value} />}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
<Column
|
<Column
|
||||||
template={ColumnTypesEnum.STRING}
|
template={ColumnTypesEnum.STRING}
|
||||||
properties="firstCommit"
|
properties="firstCommit"
|
||||||
|
|
143
src/ts/pages/Team/components/Company/Companies.tsx
Normal file
143
src/ts/pages/Team/components/Company/Companies.tsx
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
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;
|
146
src/ts/pages/Team/components/Company/Employments.tsx
Normal file
146
src/ts/pages/Team/components/Company/Employments.tsx
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
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;
|
45
src/ts/pages/Team/components/Company/index.tsx
Normal file
45
src/ts/pages/Team/components/Company/index.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
|
import ISort from 'ts/interfaces/Sort';
|
||||||
|
import { IPaginationRequest } 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 Companies from './Companies';
|
||||||
|
|
||||||
|
const Company = observer(({
|
||||||
|
mode,
|
||||||
|
}: ICommonPageProps): React.ReactElement | null => {
|
||||||
|
const rows = dataGripStore.dataGrip.company.statistic;
|
||||||
|
|
||||||
|
if (!rows?.length) {
|
||||||
|
return mode !== 'print' ? (<NothingFound />) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title title="page.team.author.title"/>
|
||||||
|
<DataLoader
|
||||||
|
to="response"
|
||||||
|
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
|
||||||
|
content: rows, pagination, sort, mode,
|
||||||
|
})}
|
||||||
|
watch={`${mode}${dataGripStore.hash}`}
|
||||||
|
>
|
||||||
|
<Companies
|
||||||
|
mode={mode}
|
||||||
|
rowsForExcel={rows}
|
||||||
|
/>
|
||||||
|
<Pagination />
|
||||||
|
</DataLoader>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Company;
|
|
@ -50,7 +50,7 @@ function ReleaseView({ response, updateSort, rowsForExcel, mode }: IReleaseViewP
|
||||||
width={40}
|
width={40}
|
||||||
formatter={(row: any) => {
|
formatter={(row: any) => {
|
||||||
const content = row.pr.map((commit: any) => (
|
const content = row.pr.map((commit: any) => (
|
||||||
dataGripStore?.dataGrip?.pr?.pr?.[commit.prId]
|
dataGripStore?.dataGrip?.pr?.pr?.get(commit.prId)
|
||||||
)).filter((item: any) => item?.firstCommit);
|
)).filter((item: any) => item?.firstCommit);
|
||||||
return (
|
return (
|
||||||
<AllPR // @ts-ignore
|
<AllPR // @ts-ignore
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
import ISort from 'ts/interfaces/Sort';
|
import ISort from 'ts/interfaces/Sort';
|
||||||
|
@ -17,10 +17,14 @@ import LineChart from 'ts/components/LineChart';
|
||||||
import getOptions from 'ts/components/LineChart/helpers/getOptions';
|
import getOptions from 'ts/components/LineChart/helpers/getOptions';
|
||||||
import UiKitTags from 'ts/components/UiKit/components/Tags';
|
import UiKitTags from 'ts/components/UiKit/components/Tags';
|
||||||
import { PRLink, TaskLink } from 'ts/components/ExternalLink';
|
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 { getMax } from 'ts/pages/Common/helpers/getMax';
|
||||||
import { getDate } from 'ts/helpers/formatter';
|
import { getDate } from 'ts/helpers/formatter';
|
||||||
|
|
||||||
|
import TasksFilters from './TasksFilters';
|
||||||
|
|
||||||
interface ITasksViewProps {
|
interface ITasksViewProps {
|
||||||
response?: IPagination<any>;
|
response?: IPagination<any>;
|
||||||
updateSort?: Function;
|
updateSort?: Function;
|
||||||
|
@ -139,9 +143,19 @@ 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 });
|
||||||
|
|
||||||
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
|
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Title title="common.filters" />
|
||||||
|
<PageWrapper>
|
||||||
|
<TasksFilters
|
||||||
|
filters={filters}
|
||||||
|
onChange={setFilters}
|
||||||
|
/>
|
||||||
|
</PageWrapper>
|
||||||
<DataLoader
|
<DataLoader
|
||||||
to="response"
|
to="response"
|
||||||
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
|
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
|
||||||
|
@ -149,15 +163,13 @@ const Tasks = observer(({
|
||||||
})}
|
})}
|
||||||
watch={`${mode}${dataGripStore.hash}`}
|
watch={`${mode}${dataGripStore.hash}`}
|
||||||
>
|
>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<TasksView
|
<TasksView
|
||||||
mode={mode}
|
mode={mode}
|
||||||
rowsForExcel={rows}
|
rowsForExcel={rows}
|
||||||
/>
|
/>
|
||||||
<Pagination />
|
<Pagination />
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
60
src/ts/pages/Team/components/TasksFilters.tsx
Normal file
60
src/ts/pages/Team/components/TasksFilters.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
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;
|
|
@ -8,6 +8,7 @@ import fullScreen from 'ts/store/FullScreen';
|
||||||
|
|
||||||
import Author from './components/Author';
|
import Author from './components/Author';
|
||||||
import Commits from './components/Commits';
|
import Commits from './components/Commits';
|
||||||
|
import Company from './components/Company';
|
||||||
import Changes from './components/Changes';
|
import Changes from './components/Changes';
|
||||||
import Hours from './components/Hours';
|
import Hours from './components/Hours';
|
||||||
import PopularWords from './components/PopularWords';
|
import PopularWords from './components/PopularWords';
|
||||||
|
@ -37,6 +38,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
|
||||||
if (page === 'total') return <Total/>;
|
if (page === 'total') return <Total/>;
|
||||||
if (page === 'scope') return <Scope mode={mode}/>;
|
if (page === 'scope') return <Scope mode={mode}/>;
|
||||||
if (page === 'author') return <Author mode={mode}/>;
|
if (page === 'author') return <Author mode={mode}/>;
|
||||||
|
if (page === 'company') return <Company mode={mode}/>;
|
||||||
if (page === 'type') return <Type mode={mode}/>;
|
if (page === 'type') return <Type mode={mode}/>;
|
||||||
if (page === 'pr') return <Pr mode={mode}/>;
|
if (page === 'pr') return <Pr mode={mode}/>;
|
||||||
if (page === 'day') return <Tempo/>;
|
if (page === 'day') return <Tempo/>;
|
||||||
|
|
|
@ -94,7 +94,6 @@ const Main = observer(() => {
|
||||||
const view = viewNameStore.view;
|
const view = viewNameStore.view;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('main');
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const list = window?.report || [];
|
const list = window?.report || [];
|
||||||
if (list?.length && bugInReactWithDoubleInit !== list?.length) {
|
if (list?.length && bugInReactWithDoubleInit !== list?.length) {
|
||||||
|
|
|
@ -37,8 +37,8 @@ class DataGripStore {
|
||||||
hash: observable,
|
hash: observable,
|
||||||
isDepersonalized: observable,
|
isDepersonalized: observable,
|
||||||
asyncSetCommits: action,
|
asyncSetCommits: action,
|
||||||
processingStep01: action,
|
processingStringToCommit: action,
|
||||||
processingStep03: action,
|
processingDataAnalysis: action,
|
||||||
depersonalized: action,
|
depersonalized: action,
|
||||||
updateStatistic: action,
|
updateStatistic: action,
|
||||||
});
|
});
|
||||||
|
@ -47,10 +47,10 @@ class DataGripStore {
|
||||||
asyncSetCommits(dump?: string[]) {
|
asyncSetCommits(dump?: string[]) {
|
||||||
if (!dump?.length) return;
|
if (!dump?.length) return;
|
||||||
splashScreenStore.show();
|
splashScreenStore.show();
|
||||||
setTimeout(() => this.processingStep01(dump), PROCESSING_DELAY);
|
setTimeout(() => this.processingStringToCommit(dump), PROCESSING_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
processingStep01(dump?: string[]) {
|
processingStringToCommit(dump?: string[]) {
|
||||||
dataGrip.clear();
|
dataGrip.clear();
|
||||||
fileGrip.clear();
|
fileGrip.clear();
|
||||||
|
|
||||||
|
@ -60,20 +60,20 @@ class DataGripStore {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => this.processingStep02(commits), PROCESSING_DELAY);
|
setTimeout(() => this.processingCommitGrouping(commits), PROCESSING_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
processingStep02(commits: (ICommit | ISystemCommit)[]) {
|
processingCommitGrouping(commits: (ICommit | ISystemCommit)[]) {
|
||||||
commits.sort((a, b) => a.milliseconds - b.milliseconds);
|
commits.sort((a, b) => a.milliseconds - b.milliseconds);
|
||||||
commits.forEach((commit: ICommit | ISystemCommit) => {
|
commits.forEach((commit: ICommit | ISystemCommit) => {
|
||||||
dataGrip.addCommit(commit);
|
dataGrip.addCommit(commit);
|
||||||
fileGrip.addCommit(commit);
|
fileGrip.addCommit(commit);
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => this.processingStep03(commits), PROCESSING_DELAY);
|
setTimeout(() => this.processingDataAnalysis(commits), PROCESSING_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
processingStep03(commits: (ICommit | ISystemCommit)[]) {
|
processingDataAnalysis(commits: (ICommit | ISystemCommit)[]) {
|
||||||
fileGrip.updateTotalInfo();
|
fileGrip.updateTotalInfo();
|
||||||
this.commits = commits;
|
this.commits = commits;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
||||||
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
||||||
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: Parte de las estadísticas (la velocidad del trabajo, el dinero gastado, etc.) para los empleados con el tipo de "Asistente" no cuenta, ya que no es un rol permanente en el proyecto. Su trabajo es insignificante y puede ser ignorado.
|
§ page.team.author.description1: Parte de las estadísticas (la velocidad del trabajo, el dinero gastado, etc.) para los empleados con el tipo de "Asistente" no cuenta, ya que no es un rol permanente en el proyecto. Su trabajo es insignificante y puede ser ignorado.
|
||||||
§ page.team.author.description2: La clasificación predeterminada es la clasificación por número de tareas y grupos(empleados actuales, despedidos, ayudantes).
|
§ page.team.author.description2: La clasificación predeterminada es la clasificación por número de tareas y grupos(empleados actuales, despedidos, ayudantes).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: Partie des statistiques (vitesse de travail, argent dépensé, etc.) pour les collaborateurs de type “Assistant”, ce n’est pas une rôle permanente dans le projet. Leur travail est insignifiant et peut être ignoré.
|
§ page.team.author.description1: Partie des statistiques (vitesse de travail, argent dépensé, etc.) pour les collaborateurs de type “Assistant”, ce n’est pas une rôle permanente dans le projet. Leur travail est insignifiant et peut être ignoré.
|
||||||
§ page.team.author.description2: Le tri par défaut est le tri par nombre de tâches et de groupes (employés actuels, licenciés et aidants).
|
§ page.team.author.description2: Le tri par défaut est le tri par nombre de tâches et de groupes (employés actuels, licenciés et aidants).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
||||||
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
§ page.team.author.description1: *Part of the statistics* (work speed, costs, etc.) *for employees with the 'Assistant' type is not counted*, as it is an episodic role in the project. It is assumed that they do not affect the project, and their edits can be disregarded in the context of the overall volume of work.
|
||||||
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
§ page.team.author.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: *Часть статистики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помощник» не считается*, т.к. это эпизодическая роль в проекте. Предполагаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы.
|
§ page.team.author.description1: *Часть статистики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помощник» не считается*, т.к. это эпизодическая роль в проекте. Предполагаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы.
|
||||||
§ page.team.author.description2: *Сортировка по умолчанию* — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
§ page.team.author.description2: *Сортировка по умолчанию* — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
||||||
§ page.team.author.status: Статус
|
§ page.team.author.status: Статус
|
||||||
|
§ page.team.author.company: Компания
|
||||||
§ page.team.author.firstCommit: Первый коммит
|
§ page.team.author.firstCommit: Первый коммит
|
||||||
§ page.team.author.lastCommit: Последний
|
§ page.team.author.lastCommit: Последний
|
||||||
§ page.team.author.daysAll: Всего дней
|
§ page.team.author.daysAll: Всего дней
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
||||||
§ page.team.author.description1: Часть статистики (скорость работы, затраченные деньги и т.п.) по сотрудникам с типом «Помощник» не считается, т.к. это не постоянная роль в проекте. Их работа незначительно и её можно не учитывать.
|
§ page.team.author.description1: Часть статистики (скорость работы, затраченные деньги и т.п.) по сотрудникам с типом «Помощник» не считается, т.к. это не постоянная роль в проекте. Их работа незначительно и её можно не учитывать.
|
||||||
§ page.team.author.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
§ page.team.author.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
||||||
§ page.team.author.status: Status
|
§ page.team.author.status: Status
|
||||||
|
§ page.team.author.company: Company
|
||||||
§ page.team.author.firstCommit: First commit
|
§ page.team.author.firstCommit: First commit
|
||||||
§ page.team.author.lastCommit: Last
|
§ page.team.author.lastCommit: Last
|
||||||
§ page.team.author.daysAll: Total days
|
§ page.team.author.daysAll: Total days
|
||||||
|
|
Loading…
Reference in a new issue