mirror of
https://github.com/bakhirev/assayo.git
synced 2024-11-16 16:21:41 +00:00
JIRA-0003 feat(some): add new charts
This commit is contained in:
parent
fe82b15a2f
commit
42a6fbf363
18
README.md
18
README.md
|
@ -1,6 +1,6 @@
|
|||
# [Assayo](https://assayo.jp/)
|
||||
|
||||
Визуализация и анализ данных вашего git-репозитория ([демо](https://assayo.jp/demo/?dump=./test.git)).
|
||||
Визуализация и анализ данных вашего git-репозитория ([демо](https://assayo.jp/demo/?dump=./test.txt)).
|
||||
|
||||
##### Сотрудник может оценить новое место работы
|
||||
- темп работы;
|
||||
|
@ -44,26 +44,26 @@ Alex B <alex@mail.uk> <super_man@yahoo.com>
|
|||
|
||||
В корневой директории вашего проекта выполнить:
|
||||
```
|
||||
git --no-pager log --numstat --oneline --all --no-merges --reverse
|
||||
git --no-pager log --numstat --oneline --all --reverse
|
||||
--date=iso-strict --pretty=format:"%ad>%cN>%cE>%s"
|
||||
| sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g'
|
||||
| sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g'
|
||||
| sed 's/\$/_/g' > dump.git
|
||||
| sed 's/\$/_/g' > log.txt
|
||||
```
|
||||
Git создаст файл `dump.git`.
|
||||
Git создаст файл `log.txt`.
|
||||
Он содержит данные для построения отчёта.
|
||||
|
||||
### Как посмотреть отчёт онлайн?
|
||||
|
||||
- Перейти на [сайт](https://assayo.jp/)
|
||||
- Нажать кнопку «[Демо](https://assayo.jp/demo)»
|
||||
- Перетащить файл `dump.git` в окно браузера
|
||||
- Перетащить файл `log.txt` в окно браузера
|
||||
|
||||
### Как посмотреть отчёт офлайн?
|
||||
- Скачать этот репозиторий
|
||||
- Перетащить файл `dump.git` в папку `/build`
|
||||
- Перетащить файл `log.txt` в папку `/build`
|
||||
- Запустить `/build/index.html`
|
||||
- Или перетащить папку `/build` к себе в репозиторий (туда, где лежит `dump.git`). Можно сменить название. Например с `/build` на `/report`
|
||||
- Или перетащить папку `/build` к себе в репозиторий (туда, где лежит `log.txt`). Можно сменить название. Например с `/build` на `/report`
|
||||
|
||||
### Как пересобрать билд отчёта?
|
||||
- Скачать этот репозиторий
|
||||
|
@ -72,9 +72,9 @@ Git создаст файл `dump.git`.
|
|||
- Свежая сборка будет в папке `/build`
|
||||
|
||||
### Как посмотреть отчёт по группе микросервисов?
|
||||
- Сгенерировать для каждого микросервиса `dump.git` (`dump-1.git`, `dump-2.git`, `dump-3.git` и т.д.)
|
||||
- Сгенерировать для каждого микросервиса `log.txt` (`log-1.txt`, `log-2.txt`, `log-3.txt` и т.д.)
|
||||
- См. «Как посмотреть отчёт онлайн?». На последнем шаге перетащить сразу все файлы в окно браузера.
|
||||
- См. «Как посмотреть отчёт офлайн?». На втором шаге перетащить все файлы микросервисов (`dump-1.git`, `dump-2.git`, `dump-3.git` и т.д.) в папку отчета (`/build`).
|
||||
- См. «Как посмотреть отчёт офлайн?». На втором шаге перетащить все файлы микросервисов (`log-1.txt`, `log-2.txt`, `log-3.txt` и т.д.) в папку отчета (`/build`).
|
||||
|
||||
### Как подписывать коммиты?
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.27b68202.css",
|
||||
"main.js": "./static/js/main.fcc567df.js",
|
||||
"main.css": "./static/css/main.a871f7d5.css",
|
||||
"main.js": "./static/js/main.16986165.js",
|
||||
"static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png",
|
||||
"index.html": "./index.html",
|
||||
"static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg",
|
||||
"static/media/info.svg": "./static/media/info.954631f6b19e3fe9c495.svg",
|
||||
"static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg",
|
||||
"main.27b68202.css.map": "./static/css/main.27b68202.css.map",
|
||||
"main.fcc567df.js.map": "./static/js/main.fcc567df.js.map"
|
||||
"main.a871f7d5.css.map": "./static/css/main.a871f7d5.css.map",
|
||||
"main.16986165.js.map": "./static/js/main.16986165.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.27b68202.css",
|
||||
"static/js/main.fcc567df.js"
|
||||
"static/css/main.a871f7d5.css",
|
||||
"static/js/main.16986165.js"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="ru"><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="black-translucent"><script type="text/javascript">var report=[]</script><script src="/dump.git"></script><script src="./dump.git"></script><script src="../dump.git"></script><script src="./dump-0.git"></script><script src="./dump-1.git"></script><script src="./dump-2.git"></script><script src="./dump-3.git"></script><script src="./dump-4.git"></script><script src="./dump-5.git"></script><script src="./dump-6.git"></script><script src="./report/dump-0.git"></script><script src="./report/dump-1.git"></script><script src="./report/dump-2.git"></script><script src="./report/dump-3.git"></script><script src="./report/dump-4.git"></script><script src="./report/dump-5.git"></script><script src="./report/dump-6.git"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>ASSAYO</title><meta name="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta name="keywords" content="git, статистика, аудит, история, log, мониторинг, контроль сотрудников"><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 Статистика"><meta name="msapplication-tooltip" content="Простой и быстрый отчёт по истории коммитов в git."><meta property="og:title" content="GIT Статистика"><meta property="og:description" content="Простой и быстрый отчёт по истории коммитов в git."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="GIT Статистика"><meta name="twitter:description" content="Простой и быстрый отчёт по истории коммитов в git."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="GIT Статистика"><meta itemprop="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.fcc567df.js"></script><link href="./static/css/main.27b68202.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 lang="ru"><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="black-translucent"><script type="text/javascript">var report=[]</script><script src="/log.txt"></script><script src="./log.txt"></script><script src="../log.txt"></script><script src="./log-0.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="./report/log-0.txt"></script><script src="./report/log-1.txt"></script><script src="./report/log-2.txt"></script><script src="./report/log-3.txt"></script><script src="./report/log-4.txt"></script><script src="./report/log-5.txt"></script><script src="./report/log-6.txt"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>ASSAYO</title><meta name="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta name="keywords" content="git, статистика, аудит, история, log, мониторинг, контроль сотрудников"><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 Статистика"><meta name="msapplication-tooltip" content="Простой и быстрый отчёт по истории коммитов в git."><meta property="og:title" content="GIT Статистика"><meta property="og:description" content="Простой и быстрый отчёт по истории коммитов в git."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="GIT Статистика"><meta name="twitter:description" content="Простой и быстрый отчёт по истории коммитов в git."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="GIT Статистика"><meta itemprop="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.16986165.js"></script><link href="./static/css/main.a871f7d5.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
1
build/static/css/main.a871f7d5.css.map
Normal file
1
build/static/css/main.a871f7d5.css.map
Normal file
File diff suppressed because one or more lines are too long
3
build/static/js/main.16986165.js
Normal file
3
build/static/js/main.16986165.js
Normal file
File diff suppressed because one or more lines are too long
1
build/static/js/main.16986165.js.map
Normal file
1
build/static/js/main.16986165.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
@ -14,23 +14,23 @@
|
|||
<script type="text/javascript">
|
||||
var report = [];
|
||||
</script>
|
||||
<script src='/dump.git'></script>
|
||||
<script src='./dump.git'></script>
|
||||
<script src='../dump.git'></script>
|
||||
<script src='./dump-0.git'></script>
|
||||
<script src='./dump-1.git'></script>
|
||||
<script src='./dump-2.git'></script>
|
||||
<script src='./dump-3.git'></script>
|
||||
<script src='./dump-4.git'></script>
|
||||
<script src='./dump-5.git'></script>
|
||||
<script src='./dump-6.git'></script>
|
||||
<script src='./report/dump-0.git'></script>
|
||||
<script src='./report/dump-1.git'></script>
|
||||
<script src='./report/dump-2.git'></script>
|
||||
<script src='./report/dump-3.git'></script>
|
||||
<script src='./report/dump-4.git'></script>
|
||||
<script src='./report/dump-5.git'></script>
|
||||
<script src='./report/dump-6.git'></script>
|
||||
<script src='/log.txt'></script>
|
||||
<script src='./log.txt'></script>
|
||||
<script src='../log.txt'></script>
|
||||
<script src='./log-0.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='./report/log-0.txt'></script>
|
||||
<script src='./report/log-1.txt'></script>
|
||||
<script src='./report/log-2.txt'></script>
|
||||
<script src='./report/log-3.txt'></script>
|
||||
<script src='./report/log-4.txt'></script>
|
||||
<script src='./report/log-5.txt'></script>
|
||||
<script src='./report/log-6.txt'></script>
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
|
|
@ -60,24 +60,21 @@
|
|||
bottom: 16px;
|
||||
right: 16px;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 100;
|
||||
display: block;
|
||||
padding: 6px 12px;
|
||||
line-height: 13px;
|
||||
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
color: #8F8F8F;
|
||||
border: 1px solid #F2F2F2;
|
||||
background-color: #F2F2F2;
|
||||
|
||||
&:hover {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
background-color: #EDEDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.console {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
|
||||
&_body {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
|
||||
import Button from 'ts/components/UiKit/components/Button';
|
||||
|
||||
function copyInBuffer(value?: string) {
|
||||
if (!value) return;
|
||||
const copyTextarea = document.createElement('textarea');
|
||||
copyTextarea.style.position = 'fixed';
|
||||
copyTextarea.style.opacity = '0';
|
||||
copyTextarea.textContent = value;
|
||||
|
||||
document.body.appendChild(copyTextarea);
|
||||
copyTextarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyTextarea);
|
||||
}
|
||||
|
||||
import style from './index.module.scss';
|
||||
|
||||
interface IConsoleProps {
|
||||
textForCopy?: string;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
children?: ReactNode | string | number | null;
|
||||
}
|
||||
|
||||
function Console({ className, children }: IConsoleProps) {
|
||||
function Console({ className, textForCopy, children }: IConsoleProps) {
|
||||
return (
|
||||
<div className={`${style.console} ${className || ''}`}>
|
||||
<div className={`${style.console_header}`}>
|
||||
|
@ -16,16 +32,23 @@ function Console({ className, children }: IConsoleProps) {
|
|||
<span className={`${style.console_header_icon}`}></span>
|
||||
</div>
|
||||
<div className={`${style.console_body}`}>
|
||||
{children}
|
||||
{children || textForCopy}
|
||||
</div>
|
||||
<button className={`${style.console_copy}`}>
|
||||
<Button
|
||||
type="second"
|
||||
className={`${style.console_copy}`}
|
||||
onClick={() => {
|
||||
copyInBuffer(textForCopy);
|
||||
}}
|
||||
>
|
||||
Копировать
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Console.defaultProps = {
|
||||
textForCopy: undefined,
|
||||
children: undefined,
|
||||
className: '',
|
||||
};
|
||||
|
|
|
@ -54,6 +54,7 @@ export function getDayText(events: IHashMap<any>, timestamp: string): string {
|
|||
function getRefAuthorByTime(list: any[], property: string) {
|
||||
return list.reduce((refTimeAuthor: any, item: any) => {
|
||||
if (item.isStaff) return refTimeAuthor;
|
||||
if (property === 'lastCommit' && !item.isDismissed) return refTimeAuthor;
|
||||
const key = item?.[property]?.timestamp;
|
||||
if (!refTimeAuthor[key]) refTimeAuthor[key] = [];
|
||||
refTimeAuthor[key].push(item.author);
|
||||
|
|
|
@ -13,7 +13,7 @@ localization.parse('ru', `
|
|||
§ sidebar.team.timestamp: Все коммиты
|
||||
§ sidebar.team.changes: Все изменения
|
||||
§ sidebar.team.words: Популярные слова
|
||||
§ sidebar.team.top: Рейтинг
|
||||
§ sidebar.team.top: Викторина
|
||||
§ sidebar.person.total: Общая информация
|
||||
§ sidebar.person.money: Стоимость работы
|
||||
§ sidebar.person.speed: Скорость
|
||||
|
|
41
src/ts/helpers/DataGrip/components/pr.ts
Normal file
41
src/ts/helpers/DataGrip/components/pr.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
||||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
|
||||
export default class DataGripByPr {
|
||||
list: string[] = [];
|
||||
|
||||
pr: IHashMap<any> = {};
|
||||
|
||||
statistic: any = [];
|
||||
|
||||
clear() {
|
||||
this.list = [];
|
||||
this.pr = {};
|
||||
this.statistic = [];
|
||||
}
|
||||
|
||||
addCommit(commit: ISystemCommit) {
|
||||
if (commit.commitType === COMMIT_TYPE.AUTO_MERGE) return;
|
||||
if (this.pr[commit.prId]) {
|
||||
this.#updateCommitByPR(commit);
|
||||
} else {
|
||||
this.#addCommitByPR(commit);
|
||||
}
|
||||
}
|
||||
|
||||
#updateCommitByPR(commit: ISystemCommit) {
|
||||
const statistic = this.pr[commit.prId];
|
||||
const property = commit.commitType === COMMIT_TYPE.MERGE ? 'close' : 'open';
|
||||
statistic[property] = commit;
|
||||
statistic.delay = statistic.open.milliseconds - statistic.close.milliseconds;
|
||||
}
|
||||
|
||||
#addCommitByPR(commit: ISystemCommit) {
|
||||
const property = commit.commitType === COMMIT_TYPE.MERGE ? 'close' : 'open';
|
||||
this.pr[commit.prId] = { [property]: commit };
|
||||
}
|
||||
|
||||
updateTotalInfo() {
|
||||
this.statistic = [];
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import ICommit from 'ts/interfaces/Commit';
|
||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||
import settingsStore from 'ts/store/Settings';
|
||||
import Recommendations from 'ts/helpers/Recommendations';
|
||||
|
||||
|
@ -10,6 +10,7 @@ import DataGripByTimestamp from './components/timestamp';
|
|||
import DataGripByWeek from './components/week';
|
||||
import MinMaxCounter from './components/counter';
|
||||
import DataGripByExtension from './components/extension';
|
||||
import DataGripByPR from './components/pr';
|
||||
|
||||
class DataGrip {
|
||||
firstLastCommit: any = new MinMaxCounter();
|
||||
|
@ -30,6 +31,8 @@ class DataGrip {
|
|||
|
||||
extension: any = new DataGripByExtension();
|
||||
|
||||
pr: any = new DataGripByPR();
|
||||
|
||||
initializationInfo: any = {};
|
||||
|
||||
clear() {
|
||||
|
@ -42,16 +45,21 @@ class DataGrip {
|
|||
this.week.clear();
|
||||
this.recommendations.clear();
|
||||
this.extension.clear();
|
||||
this.pr.clear();
|
||||
}
|
||||
|
||||
addCommit(commit: ICommit) {
|
||||
if (commit.author === 'GitHub') return;
|
||||
this.firstLastCommit.update(commit.milliseconds, commit);
|
||||
this.author.addCommit(commit);
|
||||
this.scope.addCommit(commit);
|
||||
this.type.addCommit(commit);
|
||||
this.timestamp.addCommit(commit);
|
||||
this.week.addCommit(commit);
|
||||
addCommit(commit: ICommit | ISystemCommit) {
|
||||
if (commit.author === 'GitHub') return; // @ts-ignore
|
||||
if (commit.commitType) {
|
||||
this.pr.addCommit(commit);
|
||||
} else {
|
||||
this.firstLastCommit.update(commit.milliseconds, commit);
|
||||
this.author.addCommit(commit);
|
||||
this.scope.addCommit(commit);
|
||||
this.type.addCommit(commit);
|
||||
this.timestamp.addCommit(commit);
|
||||
this.week.addCommit(commit);
|
||||
}
|
||||
}
|
||||
|
||||
#updateTotalInfo() {
|
||||
|
@ -62,6 +70,7 @@ class DataGrip {
|
|||
this.timestamp.updateTotalInfo(this.author);
|
||||
this.week.updateTotalInfo(this.author);
|
||||
this.recommendations.updateTotalInfo(this);
|
||||
this.pr.updateTotalInfo(this);
|
||||
}
|
||||
|
||||
updateByInitialization() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
import ICommit from 'ts/interfaces/Commit';
|
||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||
import settingsStore from 'ts/store/Settings';
|
||||
|
||||
import getUserInfo from './user_info';
|
||||
|
@ -13,7 +13,7 @@ export default function Parser(
|
|||
parseCommit: Function,
|
||||
) {
|
||||
const allFiles: IHashMap<IDirtyFile> = {};
|
||||
const commits: ICommit[] = [];
|
||||
const commits: Array<ICommit | ISystemCommit> = [];
|
||||
let week: number = 0;
|
||||
let weekEndTime: number = 0;
|
||||
|
||||
|
@ -80,13 +80,12 @@ export default function Parser(
|
|||
added = 0;
|
||||
removed = 0;
|
||||
}
|
||||
if (prev) {
|
||||
prev.changes += changes;
|
||||
prev.added += added;
|
||||
if (prev) { // @ts-ignore
|
||||
prev.changes += changes; // @ts-ignore
|
||||
prev.added += added; // @ts-ignore
|
||||
prev.removed += removed;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (prev) {
|
||||
if (uniq[prev.date]) {
|
||||
// console.log(`double ${uniq[prev.date]} === ${i}`);
|
||||
|
@ -104,8 +103,8 @@ export default function Parser(
|
|||
next.week = week;
|
||||
|
||||
prev = next;
|
||||
commits.push(prev);
|
||||
isFileInfo = true;
|
||||
commits.push(prev); // @ts-ignore
|
||||
isFileInfo = !prev.commitType;
|
||||
}
|
||||
}
|
||||
if (prev) parseCommit(prev);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import ICommit from 'ts/interfaces/Commit';
|
||||
import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
||||
|
||||
function getTypeAndScope(messageParts: string[], task: string) {
|
||||
if (messageParts.length < 2) return ['', ''];
|
||||
|
@ -13,7 +13,12 @@ function getTypeAndScope(messageParts: string[], task: string) {
|
|||
: [type, scope];
|
||||
}
|
||||
|
||||
export default function getUserInfo(logString: string): ICommit {
|
||||
// ABC-123, #123, gh-123
|
||||
function getTask(message: string) {
|
||||
return ((message || '').match(/(([A-Z]+-)|(#)|(gh-)|(GH-))([0-9]+)/gm) || [])[0] || '';
|
||||
}
|
||||
|
||||
export default function getUserInfo(logString: string): ICommit | ISystemCommit {
|
||||
// "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile"
|
||||
const parts = logString.split('>');
|
||||
|
||||
|
@ -26,11 +31,8 @@ export default function getUserInfo(logString: string): ICommit {
|
|||
const email = parts.shift() || '';
|
||||
|
||||
const message = parts.join('>');
|
||||
const task = (message.match(/(([A-Z]+-)|(#)|(gh-)|(GH-))([0-9]+)/gm) || [])[0] || ''; // ABC-123, #123, gh-123
|
||||
const messageParts = message.split(':');
|
||||
const [type, scope] = getTypeAndScope(messageParts, task);
|
||||
|
||||
return {
|
||||
const commonInfo: any = {
|
||||
date: sourceDate,
|
||||
day: day < 0 ? 6 : day,
|
||||
dayInMonth: date.getDate(),
|
||||
|
@ -46,6 +48,50 @@ export default function getUserInfo(logString: string): ICommit {
|
|||
email,
|
||||
message,
|
||||
|
||||
type: 'не подписан',
|
||||
scope: 'неопределенна',
|
||||
};
|
||||
|
||||
const isSystemPR = message.indexOf('Pull request #') === 0;
|
||||
const isSystemMerge = message.indexOf('Merge pull request #') === 0;
|
||||
const fromGitHubToBitBucket = message.indexOf('Merge branch ') === 0;
|
||||
const isSystemCommit = isSystemPR
|
||||
|| isSystemMerge
|
||||
|| fromGitHubToBitBucket
|
||||
|| message.indexOf('Automatic merge from') === 0;
|
||||
|
||||
if (isSystemCommit) {
|
||||
let commitType = COMMIT_TYPE.AUTO_MERGE;
|
||||
let prId, repository, branch, toBranch, task;
|
||||
if (isSystemMerge) {
|
||||
commitType = COMMIT_TYPE.MERGE;
|
||||
[, prId, repository, branch, toBranch ] = message
|
||||
.replace(/(Merge\spull\srequest\s#)|(\sfrom\s)|(\sin\s)|(\sto\s)/gim, ',')
|
||||
.split(',');
|
||||
task = getTask(branch);
|
||||
} else if (isSystemPR) {
|
||||
commitType = COMMIT_TYPE.PR;
|
||||
const messageParts = message.substring(14, Infinity).split(':');
|
||||
prId = messageParts.shift();
|
||||
task = getTask(messageParts.join(':'));
|
||||
}
|
||||
|
||||
return {
|
||||
...commonInfo,
|
||||
prId: prId || '',
|
||||
task: task || '',
|
||||
repository: repository || '',
|
||||
branch: branch || '',
|
||||
toBranch: toBranch || '',
|
||||
commitType,
|
||||
};
|
||||
}
|
||||
|
||||
const messageParts = message.split(':');
|
||||
const task = getTask(message);
|
||||
const [type, scope] = getTypeAndScope(messageParts, task);
|
||||
return {
|
||||
...commonInfo,
|
||||
task,
|
||||
type: type || 'не подписан',
|
||||
scope: scope || 'неопределенна',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default interface ICommit {
|
||||
export interface ILog {
|
||||
// date
|
||||
date: string; // "2021-02-09T12:59:17+03:00",
|
||||
day: number; // 1,
|
||||
|
@ -20,7 +20,23 @@ export default interface ICommit {
|
|||
task: string; // "JIRA-0000,
|
||||
type: string; // feat|fix|docs|style|refactor|test|chore
|
||||
scope: string; // table, sale, profile and etc.
|
||||
}
|
||||
|
||||
export const COMMIT_TYPE = {
|
||||
PR: 'PR',
|
||||
MERGE: 'MERGE',
|
||||
AUTO_MERGE: 'AUTO_MERGE',
|
||||
};
|
||||
|
||||
export interface ISystemCommit extends ILog {
|
||||
prId: string; // "59"
|
||||
repository: string; // "ASSA/jira-frontend"
|
||||
branch: string; // "feature/JIRA-151-create-MainPage-without-Banners-slider"
|
||||
toBranch: string; // "master"
|
||||
commitType: string; // 'PR' | 'MERGE' | 'AUTO_MERGE';
|
||||
}
|
||||
|
||||
export default interface ICommit extends ILog {
|
||||
// files
|
||||
changes: number; // 0,
|
||||
added: number; // 0,
|
||||
|
|
|
@ -19,7 +19,7 @@ const TITLES = {
|
|||
timestamp: 'Все коммиты',
|
||||
week: 'Распределение коммитов по дням недели',
|
||||
words: 'Популярные слова в комментарии к коммиту',
|
||||
top: 'Рейтинг',
|
||||
top: 'Викторина',
|
||||
settings: 'Настройки',
|
||||
},
|
||||
person: {
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import localization from 'ts/helpers/Localization';
|
||||
import settingsForm from 'ts/pages/Settings/store/Form';
|
||||
|
||||
import style from '../../styles/sidebar.module.scss';
|
||||
|
||||
|
@ -26,6 +27,12 @@ function SideBarMenuItem({
|
|||
className={`${style.sidebar_item} ${isSelected ? style.selected : ''}`}
|
||||
to={link}
|
||||
id={`sidebar-menu-${id}`}
|
||||
onClick={() => {
|
||||
if (settingsForm.isEdited) {
|
||||
settingsForm.clear();
|
||||
settingsForm.setInitState(settingsForm.initState);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={style.sidebar_item_icon}
|
||||
|
|
|
@ -6,24 +6,19 @@ import Console from 'ts/components/Console';
|
|||
import style from '../styles/index.module.scss';
|
||||
|
||||
function MailMap(): React.ReactElement | null {
|
||||
const statistic = dataGripStore.dataGrip.author.statistic.map((item: any) => (
|
||||
<p key={item.author}>
|
||||
{`${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`}
|
||||
</p>
|
||||
const items = dataGripStore.dataGrip.author.statistic.map((item: any) => (
|
||||
`${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`
|
||||
));
|
||||
const commands = items.map((text: string) => (<p key={text}>{text}</p>));
|
||||
const commandsForCopy = items.join('\r\n');
|
||||
|
||||
return (
|
||||
<div className={style.races_track}>
|
||||
<Console>
|
||||
{statistic}
|
||||
<Console textForCopy={commandsForCopy}>
|
||||
{commands}
|
||||
</Console>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MailMap.defaultProps = {
|
||||
type: '',
|
||||
canStart: false,
|
||||
};
|
||||
|
||||
export default MailMap;
|
||||
|
|
|
@ -37,12 +37,12 @@ function ScopeView({ response }: IScopeViewProps) {
|
|||
width={200}
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||
template={ColumnTypesEnum.NUMBER}
|
||||
title="page.team.scope.days"
|
||||
properties="days"
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||
template={ColumnTypesEnum.NUMBER}
|
||||
title="page.team.scope.authorsDays"
|
||||
properties="authors"
|
||||
formatter={(authors: any) => {
|
||||
|
@ -58,7 +58,7 @@ function ScopeView({ response }: IScopeViewProps) {
|
|||
formatter={(v: any[]) => (v?.length || 0)}
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||
template={ColumnTypesEnum.NUMBER}
|
||||
title="page.team.scope.commits"
|
||||
properties="commits"
|
||||
/>
|
||||
|
|
|
@ -3,18 +3,21 @@ import { observer } from 'mobx-react-lite';
|
|||
|
||||
import dataGripStore from 'ts/store/DataGrip';
|
||||
|
||||
import Title from 'ts/components/Title';
|
||||
import Achievement from 'ts/components/Achievement/components/Item';
|
||||
import PageWrapper from 'ts/components/Page/wrapper';
|
||||
import Achievements from 'ts/components/Achievement';
|
||||
import Extension from 'ts/components/Extension';
|
||||
import Title from 'ts/components/Title';
|
||||
import Races from 'ts/components/Races';
|
||||
|
||||
import Tv100And1 from 'ts/components/Tv100And1';
|
||||
|
||||
import ACHIEVEMENT_TYPE from 'ts/helpers/achievement/constants/type';
|
||||
import getAchievementByAuthor from 'ts/helpers/achievement/byAuthor';
|
||||
import Description from 'ts/components/Description';
|
||||
import { getDate } from 'ts/helpers/formatter';
|
||||
|
||||
import style from '../styles/quiz.module.scss';
|
||||
|
||||
const Top = observer((): React.ReactElement => {
|
||||
const extensions = dataGripStore.dataGrip.extension.statistic
|
||||
.slice(0, 4).map((statistic: any) => {
|
||||
|
@ -43,17 +46,26 @@ const Top = observer((): React.ReactElement => {
|
|||
const achievements = getAchievementByAuthor(statistic.author);
|
||||
const from = getDate(statistic.firstCommit.date);
|
||||
const to = getDate(statistic.lastCommit.date);
|
||||
const achievementsList = [
|
||||
...achievements[ACHIEVEMENT_TYPE.GOOD],
|
||||
...achievements[ACHIEVEMENT_TYPE.NORMAL],
|
||||
...achievements[ACHIEVEMENT_TYPE.BAD],
|
||||
].map((type: string) => (
|
||||
<Achievement
|
||||
key={type}
|
||||
type={type}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div key={statistic.author}>
|
||||
<Title title={statistic.author}/>
|
||||
{`Всего коммитов: ${statistic.commits} `}
|
||||
{`Работал ${statistic.allDaysInProject} дней с ${from} по ${to} `}
|
||||
<Description text={`Всего коммитов: ${statistic.commits}`} />
|
||||
<Description text={`Работал с ${from} по ${to} (${statistic.allDaysInProject} дней)`} />
|
||||
<PageWrapper>
|
||||
<Achievements list={[
|
||||
...achievements[ACHIEVEMENT_TYPE.GOOD],
|
||||
...achievements[ACHIEVEMENT_TYPE.NORMAL],
|
||||
...achievements[ACHIEVEMENT_TYPE.BAD],
|
||||
]} />
|
||||
<div className={style.quiz_achievements}>
|
||||
{achievementsList}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
</div>
|
||||
);
|
||||
|
@ -65,12 +77,12 @@ const Top = observer((): React.ReactElement => {
|
|||
<Races tracks={tracks} />
|
||||
<Title title="Максимальная длинна подписи коммита"/>
|
||||
<Tv100And1 rows={maxMessageLength} />
|
||||
{authors}
|
||||
<PageWrapper>
|
||||
<div style={{ whiteSpace: 'normal' }} >
|
||||
{extensions}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
{authors}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
9
src/ts/pages/Team/styles/quiz.module.scss
Normal file
9
src/ts/pages/Team/styles/quiz.module.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
@import '../../../../styles/variables';
|
||||
|
||||
.quiz {
|
||||
&_achievements {
|
||||
margin: 12px 0 24px 0;
|
||||
column-count: 3;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import style from '../styles/console.module.scss';
|
||||
|
||||
function Console() {
|
||||
return (
|
||||
<div className={`${style.welcome_console}`}>
|
||||
<div className={`${style.welcome_console_header}`}>
|
||||
<span className={`${style.welcome_console_header_icon}`}></span>
|
||||
<span className={`${style.welcome_console_header_icon}`}></span>
|
||||
<span className={`${style.welcome_console_header_icon}`}></span>
|
||||
</div>
|
||||
<div className={`${style.welcome_console_body}`}>
|
||||
{'git --no-pager log --numstat --oneline --all --no-merges --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e \'s/\\\\/\\\\\\\\/g\' | sed -e \'s/`/"/g\' | sed -e \'s/^/report.push(\\`/g\' | sed \'s/$/\\`\\);/g\' | sed \'s/\\$/_/g\' > dump.git\n'}
|
||||
</div>
|
||||
<button className={`${style.welcome_console_copy}`}>
|
||||
Копировать
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Console;
|
|
@ -1,32 +1,36 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Console from './components/console';
|
||||
import Console from 'ts/components/Console';
|
||||
import style from './styles/index.module.scss';
|
||||
|
||||
function Welcome() {
|
||||
const command = 'git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e \'s/\\\\/\\\\\\\\/g\' | sed -e \'s/`/"/g\' | sed -e \'s/^/report.push(\\`/g\' | sed \'s/$/\\`\\);/g\' | sed \'s/\\$/_/g\' > log.txt\n';
|
||||
return (
|
||||
<section className={`${style.welcome}`}>
|
||||
<div className={`${style.welcome_row}`}>
|
||||
<h2 className={`${style.welcome__title_1}`}>
|
||||
<section className={style.welcome}>
|
||||
<div className={style.welcome_row}>
|
||||
<h2 className={style.welcome_first_title}>
|
||||
Выполните команду в корне вашего проекта
|
||||
</h2>
|
||||
<Console />
|
||||
<p className={`${style.welcome__description}`}>
|
||||
Git создаст файл dump.git.
|
||||
<Console
|
||||
className={style.welcome_console}
|
||||
textForCopy={command}
|
||||
/>
|
||||
<p className={style.welcome_description}>
|
||||
Git создаст файл log.txt.
|
||||
Он содержит данные для построения отчёта.
|
||||
Или git shortlog -s -n -e если отчёт вам не нужен.
|
||||
Советую добавить в проект файл
|
||||
Добавьте файл
|
||||
<Link
|
||||
className={`${style.welcome__description_link}`}
|
||||
className={`${style.welcome_link}`}
|
||||
target="_blank"
|
||||
to="https://git-scm.com/docs/gitmailmap">
|
||||
.mailmap
|
||||
</Link>
|
||||
{', чтобы обьединить статистику по пользователям.'}
|
||||
{' в проект, чтобы обьединить статистику по пользователям.'}
|
||||
</p>
|
||||
<h2 className={`${style.welcome__title_2}`}>
|
||||
Перетащите файл dump.git на эту страницу
|
||||
<h2 className={style.welcome_last_title}>
|
||||
Перетащите файл log.txt на эту страницу
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
@import '../../../../styles/variables';
|
||||
|
||||
.welcome_console {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.welcome_console_header,
|
||||
.welcome_console_body {
|
||||
font-size: var(--font-s);
|
||||
font-weight: 100;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.3;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.welcome_console_header {
|
||||
display: block;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
line-height: 20px;
|
||||
color: #8F8F8F;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
border: 1px solid #D4D4D4;
|
||||
border-bottom: none;
|
||||
cursor: default;
|
||||
background-color: #F2F2F2;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.welcome_console_body {
|
||||
height: 250px;
|
||||
padding: 8px 16px 16px;
|
||||
line-height: 1.3;
|
||||
color: #00B200;
|
||||
white-space: normal;
|
||||
background-color: #0C0C0C;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.welcome_console_header_icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 6px 8px 0 0;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #B5B5B5;
|
||||
background: linear-gradient(90deg, #D7D8DB 0%, #B5B5B5 100%);
|
||||
}
|
||||
|
||||
|
||||
.welcome_console_copy {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 100;
|
||||
display: block;
|
||||
padding: 6px 12px;
|
||||
line-height: 13px;
|
||||
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
color: #8F8F8F;
|
||||
border: 1px solid #F2F2F2;
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
.welcome_console_copy:hover {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
background-color: #EDEDED;
|
||||
}
|
|
@ -13,70 +13,52 @@
|
|||
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome_row {
|
||||
width: auto;
|
||||
}
|
||||
&_console {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.welcome_step__icon,
|
||||
.welcome_step__icon:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 38px;
|
||||
font-weight: 100;
|
||||
display: block;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
text-align: center;
|
||||
line-height: 70px;
|
||||
border: 1px solid black;
|
||||
background-color: transparent;
|
||||
}
|
||||
&_row {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.welcome_step__icon:after {
|
||||
content: '';
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
background-color: transparent;
|
||||
}
|
||||
&_first_title,
|
||||
&_last_title {
|
||||
font-size: 42px;
|
||||
font-weight: 100;
|
||||
margin: 46px auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.welcome__title_1,
|
||||
.welcome__title_2 {
|
||||
font-size: 42px;
|
||||
font-weight: 100;
|
||||
margin: 46px auto;
|
||||
padding: 0;
|
||||
}
|
||||
&_first_title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.welcome__title_1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
&_last_title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.welcome__title_2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&_link,
|
||||
&_description {
|
||||
font-size: var(--font-xs);
|
||||
|
||||
.welcome__description_link,
|
||||
.welcome__description {
|
||||
font-size: var(--font-xs);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
padding: 0;
|
||||
margin: 16px auto 0;
|
||||
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
padding: 0;
|
||||
margin: 16px auto 0;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #878FA1;
|
||||
}
|
||||
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #878FA1;
|
||||
}
|
||||
|
||||
.welcome__description_link {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
&_link {
|
||||
display: inline;
|
||||
margin: 16px 0 0 4px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
|
@ -84,24 +66,15 @@
|
|||
display: block;
|
||||
width: 100%;
|
||||
padding: 32px 0 0 0;
|
||||
}
|
||||
|
||||
.welcome__title_1,
|
||||
.welcome__title_2 {
|
||||
width: 90%;
|
||||
font-size: var(--font-l);
|
||||
}
|
||||
&_first_title,
|
||||
&_last_title {
|
||||
width: 90%;
|
||||
font-size: var(--font-l);
|
||||
}
|
||||
|
||||
.welcome__description {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.welcome_icons__console {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.welcome_icons__console_body {
|
||||
height: 200px;
|
||||
&_description {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue