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": [
|
||||
"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">
|
||||
var report = [];
|
||||
var f = String.raw.bind(String);
|
||||
function r(t) {
|
||||
report.push(t);
|
||||
}
|
||||
var f = String.raw.bind(String);
|
||||
function R(t) {
|
||||
report = report.concat(t.split("\n"));
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<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 {
|
||||
rowsForExcel?: any[];
|
||||
rows: any[];
|
||||
mode?: string;
|
||||
type?: string;
|
||||
sort?: ISort[];
|
||||
columnCount?: number,
|
||||
|
@ -32,6 +33,7 @@ function DataView({
|
|||
rows = [],
|
||||
sort = [],
|
||||
type,
|
||||
mode,
|
||||
columnCount,
|
||||
className,
|
||||
fullScreenMode = '',
|
||||
|
@ -58,6 +60,7 @@ function DataView({
|
|||
|
||||
return (
|
||||
<>
|
||||
{mode !== 'details' && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div className={style.data_view_buttons}>
|
||||
{!isMobile && (
|
||||
|
@ -95,8 +98,9 @@ function DataView({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{localType === 'table' && (
|
||||
{localType === 'table' && mode !== 'details' && (
|
||||
<PageWrapper template="table">
|
||||
<Table
|
||||
rows={rows}
|
||||
|
@ -109,6 +113,17 @@ function DataView({
|
|||
</PageWrapper>
|
||||
)}
|
||||
|
||||
{localType === 'table' && mode === 'details' && (
|
||||
<Table
|
||||
rows={rows}
|
||||
sort={sort}
|
||||
disabledRow={disabledRow}
|
||||
updateSort={updateSort}
|
||||
>
|
||||
{children}
|
||||
</Table>
|
||||
)}
|
||||
|
||||
{localType === 'cards' && (
|
||||
<Cards
|
||||
items={rows}
|
||||
|
|
|
@ -39,7 +39,7 @@ function CommitInfo({ commits }: { commits: ICommit[] }): React.ReactElement {
|
|||
function TaskInfo({ tasks }: { tasks: ITask }): React.ReactElement {
|
||||
const items = Object.entries(tasks)
|
||||
.map(([task, commits]: [string, any]) => {
|
||||
const prId = dataGrip.pr.prByTask[task];
|
||||
const prId = dataGrip.pr.prByTask.get(task);
|
||||
return (
|
||||
<>
|
||||
<div className={style.day_info_link}>
|
||||
|
|
|
@ -8,28 +8,31 @@
|
|||
// onChange('meta', { byTaskId });
|
||||
// }
|
||||
|
||||
import splashScreenStore from 'ts/components/SplashScreen/store';
|
||||
|
||||
function getGlobalValue() { // @ts-ignore
|
||||
return window.report;
|
||||
}
|
||||
|
||||
function setGlobalValue(value?: any) { // @ts-ignore
|
||||
window.report = value || [];
|
||||
splashScreenStore.setDelay((value || [])?.length);
|
||||
}
|
||||
|
||||
export function getStringsForParser(text: string) {
|
||||
let temp = getGlobalValue();
|
||||
setGlobalValue([]);
|
||||
|
||||
const firstText = text.slice(0, 3);
|
||||
if (firstText === 'rep' || firstText === 'r(f') {
|
||||
try {
|
||||
eval(text);
|
||||
} catch (e) {
|
||||
setGlobalValue(temp);
|
||||
return;
|
||||
const isNeedClear = {
|
||||
'rep': true,
|
||||
'r(f': true,
|
||||
'R(f': true,
|
||||
}[firstText];
|
||||
|
||||
if (isNeedClear) {
|
||||
text = text.replace(/(R\(f`)|(r\(f`)|(report\.push\(`)|(`\);)/gim, '');
|
||||
}
|
||||
} else {
|
||||
setGlobalValue(text.split('\n'));
|
||||
}
|
||||
|
||||
return getGlobalValue();
|
||||
}
|
||||
|
@ -56,7 +59,6 @@ export function getOnDrop(setLoading: Function, onChange: Function) {
|
|||
.map((file: any) => file.kind === 'file' ? file?.getAsFile() : null)
|
||||
.filter(file => file);
|
||||
|
||||
console.log(files);
|
||||
setLoading(false);
|
||||
if (!files.length) return;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ function GetItem({ commit, mode }: IGetItemProps) {
|
|||
const className = size > 5
|
||||
? style.get_list_big_number
|
||||
: '';
|
||||
const prId = dataGrip.pr.prByTask[commit.task];
|
||||
const prId = dataGrip.pr.prByTask.get(commit.task);
|
||||
|
||||
return (
|
||||
<div className={style.get_list}>
|
||||
|
|
|
@ -12,16 +12,27 @@ const SplashScreen = observer((): React.ReactElement | null => {
|
|||
if (!splashScreenStore.isOpen) return;
|
||||
setTimeout(() => {
|
||||
splashScreenStore.hide();
|
||||
}, 5400);
|
||||
}, splashScreenStore.delay);
|
||||
}, [splashScreenStore.isOpen]);
|
||||
|
||||
if (!splashScreenStore.isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={style.splash_screen}>
|
||||
<div className={style.splash_screen_container}>
|
||||
<div
|
||||
className={style.splash_screen}
|
||||
style={{ animationDelay: splashScreenStore.getDelay(100) }}
|
||||
>
|
||||
<div
|
||||
className={style.splash_screen_container}
|
||||
style={{ animationDelay: splashScreenStore.getDelay(-1400) }}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
background-color: #404148;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
&_line {
|
||||
font-size: 0.8em;
|
||||
display: block;
|
||||
width: 0;
|
||||
|
@ -33,11 +32,11 @@
|
|||
}
|
||||
|
||||
.progress_bar,
|
||||
.progress_bar:after {
|
||||
.progress_bar_line {
|
||||
transition: background-color 0.7s, width 0.5s;
|
||||
}
|
||||
|
||||
.progress_bar:after {
|
||||
.progress_bar_line {
|
||||
animation: progress_bar 4.3s linear forwards;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,41 @@
|
|||
import { observable, action, makeObservable } from 'mobx';
|
||||
import globalScroll from 'ts/helpers/globalScroll';
|
||||
|
||||
const DEFAULT_DELAY = 3400;
|
||||
|
||||
class SplashScreenStore {
|
||||
isOpen: boolean = false;
|
||||
|
||||
delay: number = DEFAULT_DELAY;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
isOpen: observable,
|
||||
delay: observable,
|
||||
show: action,
|
||||
hide: action,
|
||||
setDelay: action,
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.isOpen = true;
|
||||
globalScroll.off(5400);
|
||||
globalScroll.off(this.delay);
|
||||
}
|
||||
|
||||
hide() {
|
||||
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();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { IColumn, IRowsConfig } from '../../interfaces/Column';
|
||||
import getClassName from '../../helpers/getClassName';
|
||||
import style from '../../styles/index.module.scss';
|
||||
|
||||
interface IDefaultCellProps {
|
||||
|
@ -24,14 +25,12 @@ function DetailsCell({
|
|||
|
||||
const left = column?.isFixed ? marginLeft : 0;
|
||||
|
||||
const columnClassName = typeof column.className === 'function'
|
||||
? column.className('body', row)
|
||||
: column.className;
|
||||
|
||||
const iconClassName = config?.details
|
||||
? style.table_cell_icon_open
|
||||
: style.table_cell_icon_close;
|
||||
|
||||
const localClassName = getClassName(style.table_cell, column, ['body', row], className);
|
||||
|
||||
const hasIcon = ((column.properties && row[column.properties])
|
||||
|| !column.properties
|
||||
|| !column.properties?.length)
|
||||
|
@ -47,13 +46,13 @@ function DetailsCell({
|
|||
|
||||
return (
|
||||
<div
|
||||
key={column.title} // @ts-ignore
|
||||
className={`${style.table_cell} ${className || ''} ${columnClassName || ''}`}
|
||||
key={column.title}
|
||||
className={localClassName}
|
||||
style={{
|
||||
left,
|
||||
width: column.width,
|
||||
cursor: 'pointer',
|
||||
left,
|
||||
}} // @ts-ignore
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{hasIcon && (
|
||||
|
|
|
@ -41,11 +41,13 @@
|
|||
position: relative;
|
||||
font-weight: 100;
|
||||
display: block;
|
||||
padding-top: 24px;
|
||||
padding-left: 12px;
|
||||
padding: 0 var(--space-m) var(--space-xxl) 42px;
|
||||
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
break-inside: auto;
|
||||
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
border-radius: var(--border-radius-m);
|
||||
}
|
||||
|
||||
&_cell,
|
||||
|
@ -62,6 +64,8 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
|
||||
&_fixed {
|
||||
|
@ -97,11 +101,11 @@
|
|||
margin-top: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.5s;
|
||||
transform: rotate(0);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
&_open {
|
||||
transform: rotate(-180deg);
|
||||
transform: rotate(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ interface ITaskProps {
|
|||
|
||||
function Task({ title, commits }: ITaskProps) {
|
||||
const { t } = useTranslation();
|
||||
const prId = dataGrip.pr.prByTask[title];
|
||||
const prId = dataGrip.pr.prByTask.get(title);
|
||||
return (
|
||||
<div
|
||||
key={title}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import ICommit from 'ts/interfaces/Commit';
|
||||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||
|
||||
import { ONE_DAY } from 'ts/helpers/formatter';
|
||||
import { increment } from 'ts/helpers/Math';
|
||||
import getCompany from '../helpers/getCompany';
|
||||
import { createHashMap, createIncrement, increment } from 'ts/helpers/Math';
|
||||
import getCompany from 'ts/helpers/Parser/getCompany';
|
||||
|
||||
import userSettings from 'ts/store/UserSettings';
|
||||
|
||||
export default class DataGripByAuthor {
|
||||
list: string[] = [];
|
||||
|
||||
commits: IHashMap<any> = {};
|
||||
commits: HashMap<any> = new Map();
|
||||
|
||||
statistic: any = [];
|
||||
|
||||
|
@ -20,22 +20,22 @@ export default class DataGripByAuthor {
|
|||
|
||||
clear() {
|
||||
this.list = [];
|
||||
this.commits = {};
|
||||
this.commits.clear();
|
||||
this.statistic = [];
|
||||
this.statisticByName = {};
|
||||
}
|
||||
|
||||
addCommit(commit: ICommit) {
|
||||
if (this.commits.hasOwnProperty(commit.author)) {
|
||||
this.#updateCommitByAuthor(commit);
|
||||
const statistic = this.commits.get(commit.author);
|
||||
if (statistic) {
|
||||
this.#updateCommitByAuthor(statistic, commit);
|
||||
} else {
|
||||
this.#addCommitByAuthor(commit);
|
||||
}
|
||||
this.#setMoneyByMonth(commit);
|
||||
}
|
||||
|
||||
#updateCommitByAuthor(commit: ICommit) {
|
||||
const statistic = this.commits[commit.author];
|
||||
#updateCommitByAuthor(statistic: any, commit: ICommit) {
|
||||
statistic.commits += 1;
|
||||
statistic.lastCommit = commit;
|
||||
statistic.days[commit.timestamp] = true;
|
||||
|
@ -56,6 +56,11 @@ export default class DataGripByAuthor {
|
|||
}
|
||||
statistic.commitsByHour[commit.hours] += 1;
|
||||
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) {
|
||||
|
@ -65,16 +70,20 @@ export default class DataGripByAuthor {
|
|||
const commitsByHour = new Array(24).fill(0);
|
||||
commitsByHour[commit.hours] += 1;
|
||||
|
||||
this.commits[commit.author] = {
|
||||
this.commits.set(commit.author, {
|
||||
author: commit.author,
|
||||
commits: 1,
|
||||
firstCommit: commit,
|
||||
lastCommit: commit,
|
||||
days: { [commit.timestamp]: true },
|
||||
days: createHashMap(commit.timestamp),
|
||||
tasks: { [commit.task]: commit.added + commit.changes + commit.removed },
|
||||
types: { [commit.type]: 1 },
|
||||
scopes: { [commit.scope]: 1 },
|
||||
types: createIncrement(commit.type),
|
||||
scopes: createIncrement(commit.scope),
|
||||
hours: [commit.hours],
|
||||
company: commit.company
|
||||
? [{ title: commit.company, from: commit.timestamp }]
|
||||
: [],
|
||||
lastCompany: commit.company,
|
||||
commitsByDayAndHour,
|
||||
commitsByHour,
|
||||
messageLength: [commit.text.length || 0],
|
||||
|
@ -82,12 +91,12 @@ export default class DataGripByAuthor {
|
|||
maxMessageLength: commit.text.length || 0,
|
||||
wordStatistics: DataGripByAuthor.#updateWordStatistics(commit),
|
||||
moneyByMonth: {},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#setMoneyByMonth(commit: ICommit) {
|
||||
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);
|
||||
} else {
|
||||
this.#addMoneyByMonth(commit, key);
|
||||
|
@ -95,7 +104,7 @@ export default class DataGripByAuthor {
|
|||
}
|
||||
|
||||
#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;
|
||||
statistic.alreadyAdded[commit.milliseconds] = true;
|
||||
|
||||
|
@ -110,7 +119,7 @@ export default class DataGripByAuthor {
|
|||
#addMoneyByMonth(commit: ICommit, key: string) {
|
||||
const contract = userSettings.getEmploymentContract(commit.author, commit.milliseconds);
|
||||
const isWorkDay = contract.workDaysInWeek[commit.day];
|
||||
this.commits[commit.author].moneyByMonth[key] = {
|
||||
this.commits.get(commit.author).moneyByMonth[key] = {
|
||||
workDay: isWorkDay ? 1 : 0,
|
||||
weekDay: isWorkDay ? 0 : 1,
|
||||
alreadyAdded: {
|
||||
|
@ -147,7 +156,7 @@ export default class DataGripByAuthor {
|
|||
active: [],
|
||||
};
|
||||
|
||||
this.statistic = Object.values(this.commits)
|
||||
this.statistic = Array.from(this.commits.values())
|
||||
.sort((dotA: any, dotB: any) => dotB.commits - dotA.commits)
|
||||
.map((dot: any) => {
|
||||
const from = dot.firstCommit.milliseconds;
|
||||
|
@ -186,7 +195,7 @@ export default class DataGripByAuthor {
|
|||
daysForTask: isStaff ? 0 : workDays / tasks.length,
|
||||
taskInDay: isStaff ? 0 : tasks.length / workDays,
|
||||
changesForTask: DataGripByAuthor.getMiddleValue(tasksSize),
|
||||
company: getCompany(dot.author, dot.lastCommit.email),
|
||||
lastCompany: getCompany(dot.author, dot.lastCommit.email),
|
||||
|
||||
days: workDays,
|
||||
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 IHashMap from 'ts/interfaces/HashMap';
|
||||
import { increment, WeightedAverage } from 'ts/helpers/Math';
|
||||
import IHashMap, { HashMap } from 'ts/interfaces/HashMap';
|
||||
import { createIncrement, increment, WeightedAverage } from 'ts/helpers/Math';
|
||||
|
||||
const IS_PR = {
|
||||
[COMMIT_TYPE.PR_BITBUCKET]: true,
|
||||
|
@ -9,71 +9,69 @@ const IS_PR = {
|
|||
};
|
||||
|
||||
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[] = [];
|
||||
|
||||
statisticByName: IHashMap<any> = [];
|
||||
|
||||
clear() {
|
||||
this.pr = {};
|
||||
this.prByTask = {};
|
||||
this.lastCommitByTaskNumber = {};
|
||||
this.pr.clear();
|
||||
this.prByTask.clear();
|
||||
this.lastCommitByTaskNumber.clear();
|
||||
this.statistic = [];
|
||||
}
|
||||
|
||||
addCommit(commit: ISystemCommit) {
|
||||
if (!commit.commitType) {
|
||||
if (!this.lastCommitByTaskNumber[commit.task]) {
|
||||
this.#addCommitByTaskNumber(commit);
|
||||
const commitByTaskNumber = this.lastCommitByTaskNumber.get(commit.task);
|
||||
if (commitByTaskNumber) {
|
||||
this.#updateCommitByTaskNumber(commitByTaskNumber, commit);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
#addCommitByTaskNumber(commit: ISystemCommit) {
|
||||
this.lastCommitByTaskNumber[commit.task] = {
|
||||
this.lastCommitByTaskNumber.set(commit.task, {
|
||||
commits : 1,
|
||||
beginTaskTime: commit.milliseconds,
|
||||
endTaskTime: commit.milliseconds,
|
||||
commitsByAuthors: {
|
||||
[commit.author]: 1,
|
||||
},
|
||||
commitsByAuthors: createIncrement(commit.author),
|
||||
firstCommit: commit,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#updateCommitByTaskNumber(commit: ISystemCommit) {
|
||||
const statistic = this.lastCommitByTaskNumber[commit.task];
|
||||
#updateCommitByTaskNumber(statistic: any, commit: ISystemCommit) {
|
||||
statistic.endTaskTime = commit.milliseconds;
|
||||
statistic.commits += 1;
|
||||
increment(statistic.commitsByAuthors, commit.author);
|
||||
}
|
||||
|
||||
#addCommitByPR(commit: ISystemCommit) {
|
||||
const lastCommit = this.lastCommitByTaskNumber[commit.task];
|
||||
const lastCommit = this.lastCommitByTaskNumber.get(commit.task);
|
||||
if (lastCommit) {
|
||||
// коммиты после влития PR сгорают, чтобы не засчитать технические PR мержи веток
|
||||
delete this.lastCommitByTaskNumber[commit.task];
|
||||
this.lastCommitByTaskNumber.delete(commit.task);
|
||||
const delay = commit.milliseconds - lastCommit.endTaskTime;
|
||||
const work = lastCommit.endTaskTime - lastCommit.beginTaskTime;
|
||||
this.pr[commit.prId] = {
|
||||
this.pr.set(commit.prId, {
|
||||
...commit,
|
||||
...lastCommit,
|
||||
delay,
|
||||
delayDays: delay / (24 * 60 * 60 * 1000),
|
||||
workDays: work === 0 ? 1 : (work / (24 * 60 * 60 * 1000)),
|
||||
};
|
||||
this.prByTask[commit.task] = commit.prId;
|
||||
});
|
||||
this.prByTask.set(commit.task, commit.prId);
|
||||
} 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.updateTotalByAuthor(authors, refAuthorPR);
|
||||
|
||||
this.lastCommitByTaskNumber = {};
|
||||
this.lastCommitByTaskNumber.clear();
|
||||
}
|
||||
|
||||
static getPRByGroups(list: any, propertyName: string) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import ICommit from 'ts/interfaces/Commit';
|
||||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
import userSettings from 'ts/store/UserSettings';
|
||||
import { increment } from 'ts/helpers/Math';
|
||||
import { createHashMap, createIncrement, increment } from 'ts/helpers/Math';
|
||||
|
||||
interface IStatByAuthor {
|
||||
commits: number; // number of commits by author in this scope
|
||||
|
@ -60,10 +60,10 @@ export default class DataGripByScope {
|
|||
this.commits[commit.scope] = {
|
||||
scope: commit.scope,
|
||||
commits: 1,
|
||||
days: { [commit.timestamp]: true },
|
||||
tasks: { [commit.task]: true },
|
||||
types: { [commit.type]: 1 },
|
||||
authors: { [commit.author]: this.#getDefaultAuthorForScope(commit) },
|
||||
days: createHashMap(commit.timestamp),
|
||||
tasks: createHashMap(commit.task),
|
||||
types: createIncrement(commit.type),
|
||||
authors: createIncrement(commit.author, this.#getDefaultAuthorForScope(commit)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class DataGripByTasks {
|
|||
const firstCommit = commits[0];
|
||||
const lastCommit = commits[commits.length - 1];
|
||||
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 = {
|
||||
task,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
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 { increment } from 'ts/helpers/Math';
|
||||
|
||||
import MinMaxCounter from './counter';
|
||||
|
||||
export default class DataGripByTimestamp {
|
||||
commits: IHashMap<any> = {};
|
||||
commits: HashMap<any> = new Map();
|
||||
|
||||
commitsByAuthor: IHashMap<any> = {};
|
||||
commitsByAuthor: HashMap<any> = new Map();
|
||||
|
||||
statistic: any = [];
|
||||
|
||||
|
@ -19,29 +19,35 @@ export default class DataGripByTimestamp {
|
|||
}
|
||||
|
||||
clear() {
|
||||
this.commits = {};
|
||||
this.commitsByAuthor = {};
|
||||
this.commits.clear();
|
||||
this.commitsByAuthor.clear();
|
||||
this.statistic = [];
|
||||
this.statisticByAuthor = {};
|
||||
}
|
||||
|
||||
addCommit(commit: ICommit) {
|
||||
if (this.commits[commit.milliseconds]) {
|
||||
this.#updateCommitByTimestamp(commit, this.commits[commit.milliseconds]);
|
||||
const commitByMilliseconds = this.commits.get(commit.milliseconds);
|
||||
if (commitByMilliseconds) {
|
||||
this.#updateCommitByTimestamp(commitByMilliseconds, commit);
|
||||
} 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 {
|
||||
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.addedAndChanges += commit.added + commit.changes;
|
||||
increment(statistic.tasks, commit.task);
|
||||
|
@ -76,16 +82,16 @@ export default class DataGripByTimestamp {
|
|||
updateTotalInfo(dataGripByAuthor: any) {
|
||||
this.statistic = this.#getTotalInfo(this.commits);
|
||||
this.statistic.weekendPayment = 0;
|
||||
for (let author in this.commitsByAuthor) {
|
||||
const statistic = this.#getTotalInfo(this.commitsByAuthor[author]);
|
||||
statistic.weekendPayment = this.#getWeekendPaymentByAuthor(statistic, dataGripByAuthor.statisticByName[author]);
|
||||
this.statisticByAuthor[author] = statistic; // TODO: странный результат, неверный расчёт?
|
||||
for (let author of this.commitsByAuthor.keys()) {
|
||||
const statistic = this.#getTotalInfo(this.commitsByAuthor.get(author));
|
||||
statistic.weekendPayment = this.#getWeekendPaymentByAuthor(statistic, dataGripByAuthor.statisticByName[author || '']);
|
||||
this.statisticByAuthor[author || ''] = statistic; // TODO: странный результат, неверный расчёт?
|
||||
this.statistic.weekendPayment += statistic.weekendPayment;
|
||||
}
|
||||
}
|
||||
|
||||
#getTotalInfo(uniqCommitsByTimestamp: any) {
|
||||
const allCommitsByTimestamp = Object.values(uniqCommitsByTimestamp);
|
||||
#getTotalInfo(uniqCommitsByTimestamp: HashMap<any>) {
|
||||
const allCommitsByTimestamp = Array.from(uniqCommitsByTimestamp.values());
|
||||
|
||||
const commitsCounter = 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) {
|
||||
if (dataGripByAuthor.isStaff) return 0;
|
||||
const salaryInMonth = userSettings.getCurrentSalaryInMonth(dataGripByAuthor.author);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ICommit from 'ts/interfaces/Commit';
|
||||
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';
|
||||
|
||||
export default class DataGripByType {
|
||||
|
@ -39,10 +39,12 @@ export default class DataGripByType {
|
|||
this.commits[commit.type] = {
|
||||
type: commit.type,
|
||||
commits: 1,
|
||||
days: { [commit.timestamp]: true },
|
||||
tasks: { [commit.task]: true },
|
||||
commitsByAuthors: { [commit.author]: 1 },
|
||||
daysByAuthors: { [commit.author]: { [commit.timestamp]: true } },
|
||||
days: createIncrement(commit.timestamp, true),
|
||||
tasks: createIncrement(commit.task, true),
|
||||
commitsByAuthors: createIncrement(commit.author, true),
|
||||
daysByAuthors: {
|
||||
[commit.author]: createIncrement(commit.timestamp, true),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,15 @@ import DataGripByPR from './components/pr';
|
|||
import DataGripByTasks from './components/tasks';
|
||||
import DataGripByRelease from './components/release';
|
||||
import DataGripByScoring from './components/scoring';
|
||||
import DataGripByCompany from './components/company';
|
||||
|
||||
class DataGrip {
|
||||
firstLastCommit: any = new MinMaxCounter();
|
||||
|
||||
author: any = new DataGripByAuthor();
|
||||
|
||||
company: any = new DataGripByCompany();
|
||||
|
||||
team: any = new DataGripByTeam();
|
||||
|
||||
scope: any = new DataGripByScope();
|
||||
|
@ -45,6 +48,7 @@ class DataGrip {
|
|||
clear() {
|
||||
this.firstLastCommit.clear();
|
||||
this.author.clear();
|
||||
this.company.clear();
|
||||
this.team.clear();
|
||||
this.scope.clear();
|
||||
this.type.clear();
|
||||
|
@ -71,6 +75,7 @@ class DataGrip {
|
|||
this.get.addCommit(commit);
|
||||
this.week.addCommit(commit);
|
||||
this.tasks.addCommit(commit);
|
||||
this.company.addCommit(commit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +91,7 @@ class DataGrip {
|
|||
this.tasks.updateTotalInfo(this.pr);
|
||||
this.release.updateTotalInfo();
|
||||
this.scoring.updateTotalInfo(this.author, this.timestamp);
|
||||
this.company.updateTotalInfo(this.author);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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 { increment } from 'ts/helpers/Math';
|
||||
|
||||
import FileBuilderCommon from './Common';
|
||||
import FileBuilderLineStat from './LineStat';
|
||||
|
@ -8,25 +9,26 @@ import FileBuilderLineStat from './LineStat';
|
|||
export default class FileGripByPaths {
|
||||
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() {
|
||||
this.list = [];
|
||||
this.refFileIds = {};
|
||||
this.refRemovedFileIds = {};
|
||||
this.refFileIds.clear();
|
||||
this.refRemovedFileIds.clear();
|
||||
this.refExtensionType.clear();
|
||||
}
|
||||
|
||||
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) {
|
||||
this.#updateDirtyFile(file, fileChange, commit);
|
||||
} else {
|
||||
file = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
|
||||
this.refFileIds[fileChange.id] = file;
|
||||
this.refFileIds.set(fileChange.id, file);
|
||||
}
|
||||
|
||||
if (fileChange.newId) {
|
||||
|
@ -51,20 +53,22 @@ export default class FileGripByPaths {
|
|||
}
|
||||
|
||||
#renameFile(file: any, newId: string) {
|
||||
this.refFileIds[newId] = this.refFileIds[file.id];
|
||||
delete this.refFileIds[file.id];
|
||||
const oldFile = this.refFileIds.get(file.id) as IDirtyFile;
|
||||
this.refFileIds.set(newId, oldFile);
|
||||
this.refFileIds.delete(file.id);
|
||||
file.id = newId;
|
||||
}
|
||||
|
||||
#removeFile(file: any) {
|
||||
file.action = 'D';
|
||||
this.refRemovedFileIds[file.id] = this.refFileIds[file.id];
|
||||
this.refRemovedFileIds[file.id].action = 'D';
|
||||
delete this.refFileIds[file.id];
|
||||
const oldFile = this.refFileIds.get(file.id) as IDirtyFile;
|
||||
oldFile.action = 'D';
|
||||
this.refRemovedFileIds.set(file.id, oldFile);
|
||||
this.refFileIds.delete(file.id);
|
||||
}
|
||||
|
||||
updateTotalInfo(callback?: Function) {
|
||||
this.list = Object.values(this.refFileIds);
|
||||
this.list = Array.from(this.refFileIds.values());
|
||||
this.list.forEach((temp: any) => {
|
||||
const file = temp;
|
||||
|
||||
|
@ -72,10 +76,12 @@ export default class FileGripByPaths {
|
|||
FileBuilderLineStat.updateTotal(file);
|
||||
|
||||
if (file.type) {
|
||||
if (!this.refExtensionType[file.extension]) this.refExtensionType[file.extension] = {};
|
||||
this.refExtensionType[file.extension][file.type] = this.refExtensionType[file.extension][file.type]
|
||||
? (this.refExtensionType[file.extension][file.type] + 1)
|
||||
: 1;
|
||||
let refExtensionType = this.refExtensionType.get(file.extension);
|
||||
if (!refExtensionType) {
|
||||
refExtensionType = {};
|
||||
this.refExtensionType.set(file.extension, refExtensionType);
|
||||
}
|
||||
increment(refExtensionType, file.type);
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
interface IStatByAuthor {
|
||||
|
@ -8,12 +8,12 @@ interface IStatByAuthor {
|
|||
}
|
||||
|
||||
export default class FileGripByAuthor {
|
||||
statisticByName: IHashMap<IStatByAuthor> = {};
|
||||
statisticByName: HashMap<IStatByAuthor> = new Map();
|
||||
|
||||
totalAddedFiles: number = 0;
|
||||
|
||||
clear() {
|
||||
this.statisticByName = {};
|
||||
this.statisticByName.clear();
|
||||
}
|
||||
|
||||
addFile(file: IDirtyFile) {
|
||||
|
@ -28,17 +28,17 @@ export default class FileGripByAuthor {
|
|||
}
|
||||
|
||||
#addCommitByAuthor(author: string) {
|
||||
if (this.statisticByName[author]) return;
|
||||
this.statisticByName[author] = {
|
||||
if (this.statisticByName.has(author)) return;
|
||||
this.statisticByName.set(author, {
|
||||
addedFiles: 0,
|
||||
removedFiles: 0,
|
||||
addedWithoutRemoveFiles: 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#updateCommitByAuthor(file: IDirtyFile, firstAuthor: string, lastAuthor: string) {
|
||||
const createStatistic = this.statisticByName[firstAuthor];
|
||||
const removeStatistic = this.statisticByName[lastAuthor];
|
||||
const createStatistic = this.statisticByName.get(firstAuthor) as IStatByAuthor;
|
||||
const removeStatistic = this.statisticByName.get(lastAuthor) as IStatByAuthor;
|
||||
|
||||
createStatistic.addedWithoutRemoveFiles += 1;
|
||||
if (file.action === 'D') {
|
||||
|
@ -49,7 +49,7 @@ export default class FileGripByAuthor {
|
|||
}
|
||||
|
||||
updateTotalInfo() {
|
||||
this.totalAddedFiles = Object.values(this.statisticByName)
|
||||
this.totalAddedFiles = Array.from(this.statisticByName.values())
|
||||
.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';
|
||||
|
||||
interface IStatByExtension {
|
||||
|
@ -22,7 +22,7 @@ const IGNORE_LIST = [
|
|||
export default class FileGripByExtension {
|
||||
statistic: IStatByExtension[] = [];
|
||||
|
||||
statisticByName: IHashMap<IStatByExtension> = {};
|
||||
statisticByName: HashMap<IStatByExtension> = new Map();
|
||||
|
||||
property: string = '';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default class FileGripByExtension {
|
|||
|
||||
clear() {
|
||||
this.statistic = [];
|
||||
this.statisticByName = {};
|
||||
this.statisticByName.clear();
|
||||
}
|
||||
|
||||
addFile(file: IDirtyFile) {
|
||||
|
@ -40,17 +40,18 @@ export default class FileGripByExtension {
|
|||
|
||||
if (!key || IGNORE_LIST.includes(file.name)) return;
|
||||
|
||||
if (!this.statisticByName[key]) {
|
||||
this.statisticByName[key] = this.#getNewExtension(file);
|
||||
let extension = this.statisticByName.get(key);
|
||||
if (!extension) {
|
||||
extension = this.#getNewExtension(file);
|
||||
this.statisticByName.set(key, extension);
|
||||
}
|
||||
|
||||
const extensions = this.statisticByName[key];
|
||||
if (file.action === 'D') {
|
||||
extensions.removedFiles.push(file);
|
||||
extensions.removedCount += 1;
|
||||
extension.removedFiles.push(file);
|
||||
extension.removedCount += 1;
|
||||
} else {
|
||||
extensions.files.push(file);
|
||||
extensions.count += 1;
|
||||
extension.files.push(file);
|
||||
extension.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,8 +68,7 @@ export default class FileGripByExtension {
|
|||
}
|
||||
|
||||
updateTotalInfo() {
|
||||
this.statistic = Object.entries(this.statisticByName)
|
||||
.sort((a: any, b: any) => b[1].count - a[1].count)
|
||||
.map((item: any) => item[1]);
|
||||
this.statistic = Array.from(this.statisticByName.values())
|
||||
.sort((a: any, b: any) => b.count - a.count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
folder.lastCommit = file.lastCommit;
|
||||
folder.lines += file.lines;
|
||||
|
@ -38,23 +48,9 @@ function updateFolder(folder: any, file: IDirtyFile) {
|
|||
folder.removedLines += file.removedLines || 0;
|
||||
folder.changedLines += file.changedLines || 0;
|
||||
|
||||
for (let author in file.addedLinesByAuthor) {
|
||||
folder.addedLinesByAuthor[author] = folder.addedLinesByAuthor[author]
|
||||
? (folder.addedLinesByAuthor[author] + file.addedLinesByAuthor[author])
|
||||
: 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];
|
||||
}
|
||||
updateFolderBy(folder, file, 'addedLinesByAuthor');
|
||||
updateFolderBy(folder, file, 'removedLinesByAuthor');
|
||||
updateFolderBy(folder, file, 'changedLinesByAuthor');
|
||||
}
|
||||
|
||||
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 { increment } from 'ts/helpers/Math';
|
||||
|
||||
interface IStatByType {
|
||||
type: string; // type name
|
||||
|
@ -15,11 +16,11 @@ interface IStatByType {
|
|||
export default class FileGripByType {
|
||||
statistic: IStatByType[] = [];
|
||||
|
||||
statisticByName: IHashMap<IStatByType> = {};
|
||||
statisticByName: HashMap<IStatByType> = new Map();
|
||||
|
||||
clear() {
|
||||
this.statistic = [];
|
||||
this.statisticByName = {};
|
||||
this.statisticByName.clear();
|
||||
}
|
||||
|
||||
addFile(file: IDirtyFile) {
|
||||
|
@ -27,14 +28,13 @@ export default class FileGripByType {
|
|||
|
||||
if (!key || file?.name?.[0] === '.') return;
|
||||
|
||||
if (!this.statisticByName.hasOwnProperty(key)) {
|
||||
this.statisticByName[key] = this.#getNewType(file);
|
||||
let type = this.statisticByName.get(key);
|
||||
if (!type) {
|
||||
type = this.#getNewType(file);
|
||||
this.statisticByName.set(key, type);
|
||||
}
|
||||
|
||||
const type = this.statisticByName[key];
|
||||
type.extension[file?.extension] = type.extension[file?.extension]
|
||||
? (type.extension[file?.extension] + 1)
|
||||
: 1;
|
||||
increment(type.extension, file?.extension);
|
||||
|
||||
if (file.action === 'D') {
|
||||
type.removedFiles.push(file);
|
||||
|
@ -50,7 +50,7 @@ export default class FileGripByType {
|
|||
type: file?.type,
|
||||
task: file?.firstCommit?.task,
|
||||
path: file?.name,
|
||||
extension: { [file?.extension]: 1 },
|
||||
extension: {},
|
||||
files: [],
|
||||
count: 0,
|
||||
removedFiles: [],
|
||||
|
@ -59,8 +59,7 @@ export default class FileGripByType {
|
|||
}
|
||||
|
||||
updateTotalInfo() {
|
||||
this.statistic = Object.entries(this.statisticByName)
|
||||
.sort((a: any, b: any) => b[1].count - a[1].count)
|
||||
.map((item: any) => item[1]);
|
||||
this.statistic = Array.from(this.statisticByName.values())
|
||||
.sort((a: any, b: any) => b.count - a.count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,5 +37,13 @@ export class WeightedAverage {
|
|||
}
|
||||
|
||||
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 { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
|
||||
import getCompany from './getCompany';
|
||||
|
||||
const MASTER_BRANCH = {
|
||||
master: true,
|
||||
|
@ -11,11 +12,11 @@ const MASTER_BRANCH = {
|
|||
|
||||
let prevDate = new Date();
|
||||
|
||||
let refTimestampTime = {};
|
||||
let refTimestampTime = new Map();
|
||||
|
||||
export function clearCache() {
|
||||
prevDate = new Date();
|
||||
refTimestampTime = {};
|
||||
refTimestampTime.clear();
|
||||
}
|
||||
|
||||
export default function getCommitInfo(
|
||||
|
@ -28,33 +29,44 @@ export default function getCommitInfo(
|
|||
const sourceDate = parts[0] || '';
|
||||
let date = new Date(sourceDate);
|
||||
if (isNaN(date.getDay())) {
|
||||
console.log(`PARSE ERROR: Date parse error for: "${logString}"`);
|
||||
// console.log(`PARSE ERROR: Date parse error for: "${logString}"`);
|
||||
date = prevDate;
|
||||
}
|
||||
prevDate = date;
|
||||
const day = date.getDay() - 1;
|
||||
const timestamp = sourceDate.substring(0, 10); // split('T')[0];
|
||||
if (!refTimestampTime[timestamp]) {
|
||||
refTimestampTime[timestamp] = (new Date(timestamp)).getTime();
|
||||
let milliseconds = refTimestampTime.get(timestamp);
|
||||
if (!milliseconds) {
|
||||
milliseconds = (new Date(timestamp)).getTime();
|
||||
refTimestampTime.set(timestamp, milliseconds);
|
||||
}
|
||||
|
||||
let author = parts[1]?.replace(/[._]/gm, ' ') || '';
|
||||
let email = parts[2] || '';
|
||||
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, '');
|
||||
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];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
@ -75,11 +87,12 @@ export default function getCommitInfo(
|
|||
year: date.getUTCFullYear(),
|
||||
week: 0,
|
||||
timestamp,
|
||||
milliseconds: refTimestampTime[timestamp],
|
||||
milliseconds,
|
||||
|
||||
author,
|
||||
email,
|
||||
message,
|
||||
company,
|
||||
|
||||
text: '',
|
||||
type: '—',
|
||||
|
|
|
@ -14,6 +14,11 @@ const PUBLIC_SERVICES = [
|
|||
'rambler',
|
||||
'github',
|
||||
'gitlab',
|
||||
'com',
|
||||
'me',
|
||||
'qq',
|
||||
'dev',
|
||||
'localhost',
|
||||
];
|
||||
|
||||
const isPublicService = Object.fromEntries(
|
||||
|
@ -36,14 +41,32 @@ function getCompanyByName(author?: string): string {
|
|||
|
||||
function getCompanyByEmail(email?: string) {
|
||||
const domain = (email || '').split('@').pop() || '';
|
||||
const company = domain.split('.').shift() || '';
|
||||
return company.toUpperCase();
|
||||
const parts = domain.split('.');
|
||||
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) {
|
||||
const company = getCompanyByName(author) || getCompanyByEmail(email) || '';
|
||||
const isMailService = company.indexOf('MAIL') !== -1;
|
||||
return isPublicService[company] || isMailService
|
||||
return isPublicService[company] || isMailService || isUserName(author, company)
|
||||
? ''
|
||||
: company;
|
||||
}
|
|
@ -5,6 +5,8 @@ function getFilePath(path: string): string[] {
|
|||
.replace(/"/gm, '')
|
||||
.replace(/\/\//gm, '/');
|
||||
|
||||
if (formattedPath.indexOf('{') === -1) return [formattedPath];
|
||||
|
||||
const parts = formattedPath.split(/(?:\{)|(?:\s=>\s)|(?:})/gm);
|
||||
if (parts.length !== 2 && parts.length !== 4) return [formattedPath];
|
||||
|
||||
|
@ -19,9 +21,32 @@ function getFilePath(path: string): string[] {
|
|||
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']
|
||||
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 removed = parseInt(removedRaw, 10) || 0;
|
||||
|
|
|
@ -4,7 +4,11 @@ import IHashMap from 'ts/interfaces/HashMap';
|
|||
import { ONE_DAY, ONE_WEEK } from 'ts/helpers/formatter';
|
||||
|
||||
import getCommitInfo, { clearCache } from './getCommitInfo';
|
||||
import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges';
|
||||
import {
|
||||
getInfoFromPath,
|
||||
getNumStatInfo,
|
||||
getRawInfo,
|
||||
} from './getFileChanges';
|
||||
|
||||
function updateLineTotal(commit: any, line: any) {
|
||||
commit.added += line.addedLines || 0;
|
||||
|
@ -12,50 +16,60 @@ function updateLineTotal(commit: any, line: any) {
|
|||
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[]) {
|
||||
let commit = null;
|
||||
const commits: Array<ICommit | ISystemCommit> = [];
|
||||
|
||||
let refEmailAuthor: IHashMap<string> = {};
|
||||
let files: IHashMap<IFileChange> = {};
|
||||
let files: Map<string, IFileChange> = new Map();
|
||||
let fileChanges: IFileChange | null = null;
|
||||
|
||||
let firstMonday = 0;
|
||||
clearCache();
|
||||
|
||||
for (let i = 0, l = report.length; i < l; i += 1) {
|
||||
const message = report[i];
|
||||
if (!message) continue;
|
||||
|
||||
const index = message.indexOf('\t');
|
||||
if (index > 0 && index < 10) {
|
||||
if (message[0] === ':') {
|
||||
// парсинг файлов формата --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
|
||||
// "1 0 .browserlistrc"
|
||||
const line = getNumStatInfo(message);
|
||||
if (!files[line.path]) {
|
||||
files[line.path] = getInfoFromPath(line.path);
|
||||
fileChanges = files.get(line.path) as IFileChange;
|
||||
if (!fileChanges) {
|
||||
fileChanges = getInfoFromPath(line.path);
|
||||
files.set(line.path, fileChanges);
|
||||
}
|
||||
fileChanges = files[line.path];
|
||||
fileChanges.addedLines = line.addedLines;
|
||||
fileChanges.removedLines = line.removedLines;
|
||||
fileChanges.changedLines = line.changedLines;
|
||||
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 {
|
||||
// парсинг коммита
|
||||
// "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);
|
||||
files = {};
|
||||
if (commit) commit.fileChanges = Array.from(files.values());
|
||||
files.clear();
|
||||
commit = getCommitInfo(message, refEmailAuthor);
|
||||
|
||||
const monday = commit.milliseconds - commit.day * ONE_DAY;
|
||||
|
@ -69,5 +83,7 @@ export default function Parser(report: string[]) {
|
|||
}
|
||||
}
|
||||
|
||||
clearCache();
|
||||
|
||||
return commits;
|
||||
}
|
||||
|
|
|
@ -26,8 +26,9 @@ export interface ILog {
|
|||
week: number; // 42,
|
||||
|
||||
// user
|
||||
author: string; // "Frolov Ivan",
|
||||
email: string; // "frolov@mail.ru",
|
||||
author: string; // "Dart Vader",
|
||||
email: string; // "d.vader@emap.com",
|
||||
company: string; // "emap",
|
||||
|
||||
// task
|
||||
message: string; // "JIRA-0000 fix(profile): add new avatar",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export default interface IHashMap<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 Description from 'ts/components/Description';
|
||||
|
||||
interface IAuthorViewProps {
|
||||
interface AuthorViewProps {
|
||||
response?: IPagination<any>;
|
||||
updateSort?: Function;
|
||||
rowsForExcel?: any[];
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewProps) {
|
||||
export function AuthorView({ response, updateSort, rowsForExcel, mode }: AuthorViewProps) {
|
||||
const { t } = useTranslation();
|
||||
if (!response) return null;
|
||||
|
||||
|
@ -58,6 +58,7 @@ function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewPro
|
|||
rows={response.content}
|
||||
sort={response.sort}
|
||||
updateSort={updateSort}
|
||||
mode={mode}
|
||||
type={mode === 'print' ? 'cards' : undefined}
|
||||
columnCount={mode === 'print' ? 3 : undefined}
|
||||
>
|
||||
|
@ -84,6 +85,13 @@ function AuthorView({ response, updateSort, rowsForExcel, mode }: IAuthorViewPro
|
|||
template={(value: string) => <UiKitTags value={value} />}
|
||||
width={100}
|
||||
/>
|
||||
<Column
|
||||
isSortable="company"
|
||||
title="page.team.author.company"
|
||||
properties="lastCompany"
|
||||
template={(value: string) => <UiKitTags value={value} />}
|
||||
width={150}
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.STRING}
|
||||
properties="firstCommit"
|
||||
|
|
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}
|
||||
formatter={(row: 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);
|
||||
return (
|
||||
<AllPR // @ts-ignore
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
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 UiKitTags from 'ts/components/UiKit/components/Tags';
|
||||
import { PRLink, TaskLink } from 'ts/components/ExternalLink';
|
||||
import Title from 'ts/components/Title';
|
||||
import PageWrapper from 'ts/components/Page/wrapper';
|
||||
|
||||
import { getMax } from 'ts/pages/Common/helpers/getMax';
|
||||
import { getDate } from 'ts/helpers/formatter';
|
||||
|
||||
import TasksFilters from './TasksFilters';
|
||||
|
||||
interface ITasksViewProps {
|
||||
response?: IPagination<any>;
|
||||
updateSort?: Function;
|
||||
|
@ -139,9 +143,19 @@ const Tasks = observer(({
|
|||
mode,
|
||||
}: ICommonPageProps): React.ReactElement | null => {
|
||||
const rows = dataGripStore.dataGrip.tasks.statistic;
|
||||
const [filters, setFilters] = useState<any>({ user: 0, company: 0 });
|
||||
|
||||
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title="common.filters" />
|
||||
<PageWrapper>
|
||||
<TasksFilters
|
||||
filters={filters}
|
||||
onChange={setFilters}
|
||||
/>
|
||||
</PageWrapper>
|
||||
<DataLoader
|
||||
to="response"
|
||||
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
|
||||
|
@ -149,15 +163,13 @@ const Tasks = observer(({
|
|||
})}
|
||||
watch={`${mode}${dataGripStore.hash}`}
|
||||
>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<TasksView
|
||||
mode={mode}
|
||||
rowsForExcel={rows}
|
||||
/>
|
||||
<Pagination />
|
||||
</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 Commits from './components/Commits';
|
||||
import Company from './components/Company';
|
||||
import Changes from './components/Changes';
|
||||
import Hours from './components/Hours';
|
||||
import PopularWords from './components/PopularWords';
|
||||
|
@ -37,6 +38,7 @@ const View = observer(({ page }: ViewProps): React.ReactElement => {
|
|||
if (page === 'total') return <Total/>;
|
||||
if (page === 'scope') return <Scope 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 === 'pr') return <Pr mode={mode}/>;
|
||||
if (page === 'day') return <Tempo/>;
|
||||
|
|
|
@ -94,7 +94,6 @@ const Main = observer(() => {
|
|||
const view = viewNameStore.view;
|
||||
|
||||
useEffect(() => {
|
||||
console.log('main');
|
||||
// @ts-ignore
|
||||
const list = window?.report || [];
|
||||
if (list?.length && bugInReactWithDoubleInit !== list?.length) {
|
||||
|
|
|
@ -37,8 +37,8 @@ class DataGripStore {
|
|||
hash: observable,
|
||||
isDepersonalized: observable,
|
||||
asyncSetCommits: action,
|
||||
processingStep01: action,
|
||||
processingStep03: action,
|
||||
processingStringToCommit: action,
|
||||
processingDataAnalysis: action,
|
||||
depersonalized: action,
|
||||
updateStatistic: action,
|
||||
});
|
||||
|
@ -47,10 +47,10 @@ class DataGripStore {
|
|||
asyncSetCommits(dump?: string[]) {
|
||||
if (!dump?.length) return;
|
||||
splashScreenStore.show();
|
||||
setTimeout(() => this.processingStep01(dump), PROCESSING_DELAY);
|
||||
setTimeout(() => this.processingStringToCommit(dump), PROCESSING_DELAY);
|
||||
}
|
||||
|
||||
processingStep01(dump?: string[]) {
|
||||
processingStringToCommit(dump?: string[]) {
|
||||
dataGrip.clear();
|
||||
fileGrip.clear();
|
||||
|
||||
|
@ -60,20 +60,20 @@ class DataGripStore {
|
|||
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.forEach((commit: ICommit | ISystemCommit) => {
|
||||
dataGrip.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();
|
||||
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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||
§ page.team.author.status: Status
|
||||
§ page.team.author.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||
§ page.team.author.status: Status
|
||||
§ page.team.author.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ 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.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.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ 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.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.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||
§ page.team.author.status: Status
|
||||
§ page.team.author.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ 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.description2: *Default sorting* is by the number of tasks and groups (current, fired, assisting employees).
|
||||
§ page.team.author.status: Status
|
||||
§ page.team.author.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ page.team.author.daysAll: Total days
|
||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
|||
§ page.team.author.description1: *Часть статистики* (скорость работы, затраченные деньги и т.п.) *по сотрудникам с типом «Помощник» не считается*, т.к. это эпизодическая роль в проекте. Предполагаем, что они не влияют на проект, а их правками можно пренебречь на фоне общего объема работы.
|
||||
§ page.team.author.description2: *Сортировка по умолчанию* — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
||||
§ page.team.author.status: Статус
|
||||
§ page.team.author.company: Компания
|
||||
§ page.team.author.firstCommit: Первый коммит
|
||||
§ page.team.author.lastCommit: Последний
|
||||
§ page.team.author.daysAll: Всего дней
|
||||
|
|
|
@ -24,6 +24,7 @@ export default `
|
|||
§ page.team.author.description1: Часть статистики (скорость работы, затраченные деньги и т.п.) по сотрудникам с типом «Помощник» не считается, т.к. это не постоянная роль в проекте. Их работа незначительно и её можно не учитывать.
|
||||
§ page.team.author.description2: Сортировка по умолчанию — это сортировка по количеству задач и группам (текущие, уволенные, помогающие сотрудники).
|
||||
§ page.team.author.status: Status
|
||||
§ page.team.author.company: Company
|
||||
§ page.team.author.firstCommit: First commit
|
||||
§ page.team.author.lastCommit: Last
|
||||
§ page.team.author.daysAll: Total days
|
||||
|
|
Loading…
Reference in a new issue