#### 供网上浏览
在项目的根目录执行:
```
-git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt
+git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt
```
#### 在没有互联网的情况下观看
```
-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
+git --no-pager log --raw --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
```
Git会创建一个文件 `log.txt`.
这个文件包含了构建报告的数据。
diff --git a/public/assets/achievements/more3YearsInProject.svg b/public/assets/achievements/more3YearsInProject.svg
new file mode 100644
index 0000000..d328cc3
--- /dev/null
+++ b/public/assets/achievements/more3YearsInProject.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/achievements/moreCreateCode.svg b/public/assets/achievements/moreCreateCode.svg
new file mode 100644
index 0000000..c237f81
--- /dev/null
+++ b/public/assets/achievements/moreCreateCode.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/achievements/moreRemoveCode.svg b/public/assets/achievements/moreRemoveCode.svg
new file mode 100644
index 0000000..9e9fbc2
--- /dev/null
+++ b/public/assets/achievements/moreRemoveCode.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/achievements/moreStyle.svg b/public/assets/achievements/moreStyle.svg
new file mode 100644
index 0000000..a087a39
--- /dev/null
+++ b/public/assets/achievements/moreStyle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/social/vk/awesomejs.png b/public/social/vk/awesomejs.png
new file mode 100644
index 0000000..a98f110
Binary files /dev/null and b/public/social/vk/awesomejs.png differ
diff --git a/public/social/vk/front_work.jpg b/public/social/vk/front_work.jpg
new file mode 100644
index 0000000..aa0e46c
Binary files /dev/null and b/public/social/vk/front_work.jpg differ
diff --git a/public/social/vk/front_work.png b/public/social/vk/front_work.png
new file mode 100644
index 0000000..223d9d9
Binary files /dev/null and b/public/social/vk/front_work.png differ
diff --git a/public/social/vk/frontend_dev.png b/public/social/vk/frontend_dev.png
new file mode 100644
index 0000000..5b8ffa1
Binary files /dev/null and b/public/social/vk/frontend_dev.png differ
diff --git a/public/social/vk/frontend_du2.jpg b/public/social/vk/frontend_du2.jpg
new file mode 100644
index 0000000..7a849cc
Binary files /dev/null and b/public/social/vk/frontend_du2.jpg differ
diff --git a/public/social/vk/frontend_du2.png b/public/social/vk/frontend_du2.png
new file mode 100644
index 0000000..9d9e308
Binary files /dev/null and b/public/social/vk/frontend_du2.png differ
diff --git a/public/social/vk/logo.png b/public/social/vk/logo.png
new file mode 100644
index 0000000..8f4290b
Binary files /dev/null and b/public/social/vk/logo.png differ
diff --git a/public/social/vk/take_off_staff.jpg b/public/social/vk/take_off_staff.jpg
new file mode 100644
index 0000000..d120a82
Binary files /dev/null and b/public/social/vk/take_off_staff.jpg differ
diff --git a/public/social/vk/take_off_staff.png b/public/social/vk/take_off_staff.png
new file mode 100644
index 0000000..4d702fe
Binary files /dev/null and b/public/social/vk/take_off_staff.png differ
diff --git a/src/ts/components/Banner/index.module.scss b/src/ts/components/Banner/index.module.scss
new file mode 100644
index 0000000..699fa71
--- /dev/null
+++ b/src/ts/components/Banner/index.module.scss
@@ -0,0 +1,28 @@
+@import 'src/styles/variables';
+
+.banner {
+ border-left-width: 1px;
+ border-left-color: var(--color-border);
+
+ font-weight: 100;
+
+ width: 100%;
+ height: 100%;
+ padding: 0;
+
+ letter-spacing: 1px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-align: center;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ border-radius: var(--border-radius-s);
+
+ color: #FFFFFF;
+ background-color: #A7B6FE;
+ //background: linear-gradient(135deg, rgba(64,117,252,1) 0%, rgba(172,179,246,1) 100%);
+
+ background-repeat: no-repeat;
+ background-size: 180% auto;
+ background-position: center center;
+}
diff --git a/src/ts/components/Banner/index.tsx b/src/ts/components/Banner/index.tsx
new file mode 100644
index 0000000..419b847
--- /dev/null
+++ b/src/ts/components/Banner/index.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import themeSettings from 'ts/store/ThemeSettings';
+
+import style from './index.module.scss';
+
+interface IBannerProps {
+ className: string;
+}
+
+function Banner({ className }: IBannerProps) {
+ const config = themeSettings.getBanner();
+ if (!config) return null;
+
+ const {
+ ref,
+ link,
+ title,
+ banner,
+ bannerText,
+ color,
+ backgroundColor,
+ } = config;
+
+ const props = {
+ title,
+ to: link || '',
+ target: '_blank',
+ className,
+ };
+
+ if (banner) {
+ return (
+
+
+
+ );
+ }
+
+ if (!banner) {
+ const textFromRef = (ref || '').split('_').splice(1).join(' ').toUpperCase();
+ const background = backgroundColor
+ ? backgroundColor
+ : 'linear-gradient(135deg, rgba(64,117,252,1) 0%, rgba(172,179,246,1) 100%)';
+
+ return (
+
+
+ {bannerText || textFromRef || ''}
+
+
+ );
+ }
+
+ return null;
+}
+
+export default Banner;
diff --git a/src/ts/components/CardWithIcon/Banner.tsx b/src/ts/components/CardWithIcon/Banner.tsx
new file mode 100644
index 0000000..d125375
--- /dev/null
+++ b/src/ts/components/CardWithIcon/Banner.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import Banner from 'ts/components/Banner';
+
+import style from './index.module.scss';
+
+interface ICardWithBannerProps {
+ long?: boolean;
+}
+
+function CardWithBanner({
+ long = false,
+}: ICardWithBannerProps): React.ReactElement | null {
+ const className = long
+ ? style.card_with_icon_long
+ : style.card_with_icon;
+
+ return (
+
+ );
+}
+
+CardWithBanner.defaultProps = {
+ long: false,
+};
+
+export default CardWithBanner;
diff --git a/src/ts/components/DataLoader/helpers/formatter.ts b/src/ts/components/DataLoader/helpers/formatter.ts
index 850c6a7..c741980 100644
--- a/src/ts/components/DataLoader/helpers/formatter.ts
+++ b/src/ts/components/DataLoader/helpers/formatter.ts
@@ -73,12 +73,14 @@ export default function getFakeLoader({
const totalElements = sortedContent.length;
const totalPages = Math.ceil(totalElements / size);
- return Promise.resolve({
+ const response = {
size,
number: page,
totalPages,
totalElements,
sort: sort || [],
content: sortedContent.slice(begin, end) || [],
- });
+ };
+
+ return Promise.resolve(response);
}
diff --git a/src/ts/components/DataLoader/store/index.ts b/src/ts/components/DataLoader/store/index.ts
index c2d5ea0..448b094 100644
--- a/src/ts/components/DataLoader/store/index.ts
+++ b/src/ts/components/DataLoader/store/index.ts
@@ -78,7 +78,7 @@ export class DataLoaderStore implements IDataLoaderStore {
makeObservable(this, {
state: observable,
watchedValue: observable,
- response: observable,
+ // response: observable,
sort: observable,
fetchData: action,
successCallback: action,
diff --git a/src/ts/components/Extension/index.tsx b/src/ts/components/Extension/index.tsx
index 0618fba..ff928f5 100644
--- a/src/ts/components/Extension/index.tsx
+++ b/src/ts/components/Extension/index.tsx
@@ -11,7 +11,7 @@ interface IExtensionProps {
function Extension({
statistic,
}: IExtensionProps): React.ReactElement | null {
- if (!statistic) return null;
+ if (!statistic || true) return null;
const getValue = (more: any) => `${more.author} (${more.percent.toFixed(1)}%)`;
diff --git a/src/ts/components/LineChart/index.tsx b/src/ts/components/LineChart/index.tsx
index ff88ca7..97e18b4 100644
--- a/src/ts/components/LineChart/index.tsx
+++ b/src/ts/components/LineChart/index.tsx
@@ -22,11 +22,8 @@ function LineChart({
className,
}: ILineChartProps): React.ReactElement | null {
if (!value) return null;
- if (options.suffix === 'stop') {
- console.log('xxx');
- }
- const width = Math.round((value ?? 100) * (100 / options.max));
+ let width = Math.round((value ?? 100) * (100 / options.max));
if (!details) {
return (
diff --git a/src/ts/helpers/DataGrip/components/extension.ts b/src/ts/helpers/DataGrip/components/extension.ts
index d3960f9..a6b1946 100644
--- a/src/ts/helpers/DataGrip/components/extension.ts
+++ b/src/ts/helpers/DataGrip/components/extension.ts
@@ -53,7 +53,7 @@ export default class DataGripByExtension {
if (!group[file.extension]) {
group[file.extension] = this.#getNewExtension(file);
}
- group[file.extension][type].files[file.name] = file.firstName;
+ group[file.extension][type].files[file.id] = file.name;
group[file.extension][type].count += 1;
}
diff --git a/src/ts/helpers/DataGrip/components/pr.ts b/src/ts/helpers/DataGrip/components/pr.ts
index 34270ed..a6e863b 100644
--- a/src/ts/helpers/DataGrip/components/pr.ts
+++ b/src/ts/helpers/DataGrip/components/pr.ts
@@ -143,6 +143,13 @@ export default class DataGripByPR {
updateTotalByAuthor(authors: any, refAuthorPR: IHashMap) {
this.statisticByName = {};
authors.map((name: string) => {
+
+ let maxDelayDays = 0;
+ refAuthorPR[name].forEach((pr: any) => {
+ if (pr.delayDays > maxDelayDays) maxDelayDays = pr.delayDays;
+ });
+
+ // TODO: сложын и не интересные показатели. Гистаграмму?
const delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays');
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
@@ -151,6 +158,9 @@ export default class DataGripByPR {
this.statisticByName[name] = {
author: name,
+ maxDelayDays,
+ numberMergedPr: refAuthorPR[name].length,
+
workDays: workDays.details,
delayDays: delayDays.details,
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
diff --git a/src/ts/helpers/DataGrip/components/tasks.ts b/src/ts/helpers/DataGrip/components/tasks.ts
index 5835eb3..c7939e2 100644
--- a/src/ts/helpers/DataGrip/components/tasks.ts
+++ b/src/ts/helpers/DataGrip/components/tasks.ts
@@ -7,9 +7,13 @@ export default class DataGripByTasks {
statistic: any = [];
+ // achievements
+ longTaskByAuthor: IHashMap = {};
+
clear() {
this.commits = {};
this.statistic = [];
+ this.longTaskByAuthor = {};
}
addCommit(commit: ICommit) {
@@ -67,6 +71,11 @@ export default class DataGripByTasks {
const to = lastCommit.milliseconds;
const daysInWork = Math.ceil((to - from) / settingsStore.ONE_DAY) + 1;
+ const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author];
+ if (!longTaskByAuthor || longTaskByAuthor < daysInWork) {
+ this.longTaskByAuthor[shortInfo.author] = daysInWork;
+ }
+
return {
...shortInfo,
to: to !== from ? to : undefined,
diff --git a/src/ts/helpers/DataGrip/helpers/tree.ts b/src/ts/helpers/DataGrip/helpers/tree.ts
deleted file mode 100644
index 180daee..0000000
--- a/src/ts/helpers/DataGrip/helpers/tree.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-function addTotalInfo(folder: any) {
- folder.total = { added: 0, changes: 0, removed: 0, commits: 0 };
- const authors = Object.keys(folder.authors);
- authors.forEach(author => {
- folder.total.added += folder.authors[author].added;
- folder.total.changes += folder.authors[author].changes;
- folder.total.removed += folder.authors[author].removed;
- folder.total.commits += folder.authors[author].commits;
- });
- authors.forEach(author => {
- const authorInfo = folder.authors[author];
- authorInfo.addedPercent = Math.round(authorInfo.added * 100 / folder.total.added);
- authorInfo.changesPercent = Math.round(authorInfo.changes * 100 / folder.total.changes);
- authorInfo.removedPercent = Math.round(authorInfo.removed * 100 / folder.total.removed);
- authorInfo.commitsPercent = Math.round(authorInfo.commits * 100 / folder.total.commits);
- });
-}
-
-function addInfoFromFile(folderInfo: any, file: any) {
- folderInfo.lines += file.lines;
- for (let author in file.authors) {
- if (!folderInfo.authors[author]) {
- folderInfo.authors[author] = {
- added: 0,
- changes: 0,
- removed: 0,
- commits: 0,
- tasks: {},
- types: {},
- scopes: {},
- };
- }
- const folder = folderInfo.authors[author];
- const fileInfo = file.authors[author];
- folder.added += fileInfo.added;
- folder.changes += fileInfo.changes;
- folder.removed += fileInfo.removed;
- folder.commits += fileInfo.commits;
- }
-}
-
-function addInfoFromFolder(parentInfo: any, folder: any, path: string[]) {
- const folderInfo = { lines: 0, authors: {} };
- for (let fileName in folder.content) {
- if (folder.content[fileName].content) {
- addInfoFromFolder(folderInfo, folder.content[fileName], [...path, fileName]);
- } else {
- addInfoFromFile(folderInfo, folder.content[fileName]);
- addTotalInfo(folder.content[fileName]);
- }
- }
- addInfoFromFile(parentInfo, folderInfo);
- folder.path = path;
- folder.lines = folderInfo.lines;
- folder.authors = folderInfo.authors;
- addTotalInfo(folder);
-}
-
-export default function getFileTreeWithStatistic(rootTree: any) {
- const folderInfo = { lines: 0, authors: {}, path: [] };
- addInfoFromFolder(folderInfo, rootTree, []);
- return rootTree;
-}
\ No newline at end of file
diff --git a/src/ts/helpers/DataGrip/index.ts b/src/ts/helpers/DataGrip/index.ts
index 432179d..5380e3b 100644
--- a/src/ts/helpers/DataGrip/index.ts
+++ b/src/ts/helpers/DataGrip/index.ts
@@ -1,5 +1,4 @@
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
-import { IDirtyFile } from 'ts/interfaces/FileInfo';
import settingsStore from 'ts/store/Settings';
import Recommendations from 'ts/helpers/Recommendations';
@@ -11,7 +10,6 @@ import DataGripByType from './components/type';
import DataGripByTimestamp from './components/timestamp';
import DataGripByWeek from './components/week';
import MinMaxCounter from './components/counter';
-import DataGripByExtension from './components/extension';
import DataGripByGet from './components/get';
import DataGripByPR from './components/pr';
import DataGripByTasks from './components/tasks';
@@ -34,8 +32,6 @@ class DataGrip {
recommendations: any = new Recommendations();
- extension: any = new DataGripByExtension();
-
get: any = new DataGripByGet();
pr: any = new DataGripByPR();
@@ -57,7 +53,6 @@ class DataGrip {
this.timestamp.clear();
this.week.clear();
this.recommendations.clear();
- this.extension.clear();
this.get.clear();
this.pr.clear();
this.tasks.clear();
@@ -114,10 +109,6 @@ class DataGrip {
this.#updateTotalInfo();
this.hash = Math.random();
}
-
- updateByFiles(fileList: IDirtyFile[], removedFileList: IDirtyFile[]) {
- this.extension.updateTotalInfo(fileList, removedFileList);
- }
}
const dataGrip = new DataGrip();
diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts b/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts
new file mode 100644
index 0000000..a7ce3d7
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts
@@ -0,0 +1,39 @@
+import ICommit, { IFileChange } from 'ts/interfaces/Commit';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+function getNameTypeExtension(path?: string) {
+ const name = (path || '')?.split('/')?.pop() || '';
+ const parts = name.split('.') || [];
+ const extension = parts[parts.length - 1] || '';
+ const type = parts.length > 2 ? parts[parts.length - 2] : '';
+ return { name, type, extension };
+}
+
+export default class FileBuilderCommon {
+ static getProps(fileChange: IFileChange, commit: ICommit) {
+ return {
+ path: fileChange.path,
+ action: fileChange.action,
+ firstCommit: commit,
+ lastCommit: commit,
+ };
+ }
+
+ static updateProps(file: IDirtyFile, fileChange: IFileChange, commit: ICommit) {
+ file.action = fileChange.action;
+ file.lastCommit = commit;
+ }
+
+ static updateTotal(file: IDirtyFile) {
+ // @ts-ignore
+ const { name, type, extension } = getNameTypeExtension(file?.path);
+ file.name = name;
+ file.type = type;
+ file.extension = extension;
+
+ // @ts-ignore
+ const parts = file.path.split('/');
+ parts.pop();
+ file.path = parts;
+ }
+}
diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts
new file mode 100644
index 0000000..89e5c3f
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts
@@ -0,0 +1,52 @@
+import ICommit, { IFileChange } from 'ts/interfaces/Commit';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+import { getValuesInPercent } from '../../helpers';
+
+export default class FileBuilderLineStat {
+ static getProps(fileChange: IFileChange, commit: ICommit) {
+ return {
+ lines: fileChange.addedLines,
+
+ addedLines: fileChange.addedLines,
+ removedLines: fileChange.removedLines,
+ changedLines: fileChange.changedLines,
+
+ addedLinesByAuthor: { [commit.author]: fileChange.addedLines },
+ removedLinesByAuthor: { [commit.author]: fileChange.removedLines },
+ changedLinesByAuthor: { [commit.author]: fileChange.changedLines },
+ };
+ }
+
+ static updateProps(file: IDirtyFile, fileChange: IFileChange, commit: ICommit) {
+ file.lines += fileChange.addedLines;
+ file.lines -= fileChange.removedLines;
+ file.addedLines += fileChange.addedLines;
+ file.removedLines += fileChange.removedLines;
+ file.changedLines += fileChange.changedLines;
+
+ file.addedLinesByAuthor[commit.author] = file.addedLinesByAuthor[commit.author]
+ ? (file.addedLinesByAuthor[commit.author] + fileChange.addedLines)
+ : fileChange.addedLines;
+
+ file.removedLinesByAuthor[commit.author] = file.removedLinesByAuthor[commit.author]
+ ? (file.removedLinesByAuthor[commit.author] + fileChange.removedLines)
+ : fileChange.removedLines;
+
+ file.changedLinesByAuthor[commit.author] = file.changedLinesByAuthor[commit.author]
+ ? (file.changedLinesByAuthor[commit.author] + fileChange.changedLines)
+ : fileChange.changedLines;
+ }
+
+ static updateTotal(file: IDirtyFile) {
+ file.addedByAuthorInPercent = getValuesInPercent(file.addedLinesByAuthor, file.addedLines);
+ file.removedByAuthorInPercent = getValuesInPercent(file.removedLinesByAuthor, file.removedLines);
+ file.changedByAuthorInPercent = getValuesInPercent(file.changedLinesByAuthor, file.changedLines);
+
+ file.addedRemovedChangedInPercent = getValuesInPercent({
+ added: file.addedLines,
+ removed: file.removedLines,
+ changed: file.changedLines,
+ }, file.addedLines + file.removedLines + file.changedLines);
+ }
+}
diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/index.ts b/src/ts/helpers/FileGrip/components/FileBuilder/index.ts
new file mode 100644
index 0000000..e95f12a
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/FileBuilder/index.ts
@@ -0,0 +1,90 @@
+import ICommit, { IFileChange } from 'ts/interfaces/Commit';
+import IHashMap from 'ts/interfaces/HashMap';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+import FileBuilderCommon from './Common';
+import FileBuilderLineStat from './LineStat';
+
+export default class FileGripByPaths {
+ list: IDirtyFile[] = [];
+
+ refFileIds: IHashMap = {};
+
+ refRemovedFileIds: IHashMap = {};
+
+ refExtensionType: IHashMap> = {}; // TODO: remove me?
+
+ clear() {
+ this.list = [];
+ this.refFileIds = {};
+ this.refRemovedFileIds = {};
+ }
+
+ addCommit(fileChange: IFileChange, commit: ICommit) {
+ let file = this.refFileIds[fileChange.id] || this.refFileIds[fileChange.newId || ''];
+ if (file) {
+ this.#updateDirtyFile(file, fileChange, commit);
+ } else {
+ this.refFileIds[fileChange.id] = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
+ }
+
+ if (fileChange.newId) {
+ this.#renameFile(file, fileChange.newId);
+ }
+ }
+
+ #getNewDirtyFile(fileChange: IFileChange, commit: ICommit): any {
+ const commonProps = FileBuilderCommon.getProps(fileChange, commit);
+ const statProps = FileBuilderLineStat.getProps(fileChange, commit);
+
+ return {
+ id: fileChange.id,
+ ...commonProps,
+ ...statProps,
+ };
+ }
+
+ #updateDirtyFile(file: any, fileChange: IFileChange, commit: ICommit) {
+ FileBuilderCommon.updateProps(file, fileChange, commit);
+ FileBuilderLineStat.updateProps(file, fileChange, commit);
+ }
+
+ #renameFile(file: any, newId: string) {
+ this.refFileIds[newId] = this.refFileIds[file.id];
+ delete this.refFileIds[file.id];
+ file.id = newId;
+ }
+
+ #removeFile(file: any) {
+ this.refRemovedFileIds[file.id] = this.refFileIds[file.id];
+ this.refRemovedFileIds[file.id].action = 'D';
+ delete this.refFileIds[file.id];
+ }
+
+ updateTotalInfo(callback?: Function) {
+ this.list = Object.values(this.refFileIds);
+ this.list.forEach((temp: any) => {
+ const file = temp;
+
+ FileBuilderCommon.updateTotal(file);
+ 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;
+ }
+
+ if (file.lines === 0
+ || file.action === 'D'
+ || file.action === 'A') {
+ this.#removeFile(file);
+ }
+
+ if (callback) {
+ callback(file);
+ }
+ });
+ }
+}
diff --git a/src/ts/helpers/FileGrip/components/extension.ts b/src/ts/helpers/FileGrip/components/extension.ts
new file mode 100644
index 0000000..08d2ed6
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/extension.ts
@@ -0,0 +1,64 @@
+import IHashMap from 'ts/interfaces/HashMap';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+const IGNORE_LIST = [
+ '.eslintrc',
+ '.gitignore',
+ 'package.json',
+ 'package-lock.json',
+ 'tsconfig.json',
+];
+
+export default class FileGripByExtension {
+ statistic: any = [];
+
+ statisticByName: IHashMap = {};
+
+ property: string = '';
+
+ constructor(property?: string) {
+ this.property = property || 'extension';
+ }
+
+ clear() {
+ this.statistic = [];
+ this.statisticByName = {};
+ }
+
+ addFile(file: IDirtyFile) {
+ const key = file?.extension;
+
+ if (!key || IGNORE_LIST.includes(file.name)) return;
+
+ if (!this.statisticByName[key]) {
+ this.statisticByName[key] = this.#getNewExtension(file);
+ }
+
+ const extensions = this.statisticByName[key];
+ if (file.action === 'D') {
+ extensions.removedFiles.push(file);
+ extensions.removedCount += 1;
+ } else {
+ extensions.files.push(file);
+ extensions.count += 1;
+ }
+ }
+
+ #getNewExtension(file: IDirtyFile) {
+ return {
+ extension: file?.extension,
+ task: file?.firstCommit?.task,
+ path: file?.name,
+ files: [],
+ count: 0,
+ removedFiles: [],
+ removedCount: 0,
+ };
+ }
+
+ updateTotalInfo() {
+ this.statistic = Object.entries(this.statisticByName)
+ .sort((a: any, b: any) => b[1].count - a[1].count)
+ .map((item: any) => item[1]);
+ }
+}
diff --git a/src/ts/helpers/FileGrip/components/folder.ts b/src/ts/helpers/FileGrip/components/folder.ts
new file mode 100644
index 0000000..0e4bc98
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/folder.ts
@@ -0,0 +1,109 @@
+import { IDirtyFile, IFolder } from 'ts/interfaces/FileInfo';
+import IHashMap from 'ts/interfaces/HashMap';
+
+import { getValuesInPercent } from '../helpers';
+
+function getFolder(name?: string, path?: string[], file?: IDirtyFile): IFolder {
+ return {
+ id: Math.random(),
+ name: name || '', // @ts-ignore
+ path: path || [],
+ pathString: `${(path || []).join('/')}/${name || ''}`,
+ content: {},
+
+ lines: file?.lines || 0,
+
+ addedLines: file?.addedLines || 0,
+ removedLines: file?.removedLines || 0,
+ changedLines: file?.changedLines || 0,
+
+ addedLinesByAuthor: { ...(file?.addedLinesByAuthor || {}) },
+ removedLinesByAuthor: { ...(file?.removedLinesByAuthor || {}) },
+ changedLinesByAuthor: { ...(file?.changedLinesByAuthor || {}) },
+
+ addedByAuthorInPercent: {},
+ removedByAuthorInPercent: {},
+ changedByAuthorInPercent: {},
+ addedRemovedChangedInPercent: {},
+
+ firstCommit: file?.firstCommit || null,
+ lastCommit: file?.firstCommit || null,
+ };
+}
+
+function updateFolder(folder: any, file: IDirtyFile) {
+ folder.lastCommit = file.lastCommit;
+ folder.lines += file.lines;
+
+ folder.addedLines += file.addedLines || 0;
+ 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];
+ }
+}
+
+export default class FileGripByFolder {
+ tree: IFolder = getFolder();
+
+ folders: IFolder[] = [];
+
+ // achievements
+ addedFoldersByAuthor: IHashMap = {};
+
+ clear() {
+ this.tree = getFolder();
+ this.folders = [];
+ }
+
+ addFile(file: IDirtyFile) {
+ let prev: any = this.tree.content;
+ file.path.forEach((folderName: any, index: number) => {
+ let folder = prev[folderName];
+ if (!folder || !folder.content) {
+ const path = file.path.slice(0, index);
+ prev[folderName] = getFolder(folderName, path, file);
+ this.folders.push(prev[folderName]);
+ } else {
+ updateFolder(folder, file);
+ }
+ prev = prev[folderName].content;
+ });
+ prev[file.name] = file;
+ }
+
+ updateTotalInfo() {
+ this.folders.forEach((folder: IFolder) => {
+ folder.addedByAuthorInPercent = getValuesInPercent(folder.addedLinesByAuthor, folder.addedLines);
+ folder.removedByAuthorInPercent = getValuesInPercent(folder.removedLinesByAuthor, folder.removedLines);
+ folder.changedByAuthorInPercent = getValuesInPercent(folder.changedLinesByAuthor, folder.changedLines);
+
+ folder.addedRemovedChangedInPercent = getValuesInPercent({
+ added: folder.addedLines,
+ removed: folder.removedLines,
+ changed: folder.changedLines,
+ }, folder.addedLines + folder.removedLines + folder.changedLines);
+
+ const author = folder.firstCommit?.author || '';
+
+ if (!this.addedFoldersByAuthor[author]) this.addedFoldersByAuthor[author] = [];
+ this.addedFoldersByAuthor[author].push(folder.pathString);
+ });
+ this.folders = [];
+ }
+}
diff --git a/src/ts/helpers/FileGrip/components/type.ts b/src/ts/helpers/FileGrip/components/type.ts
new file mode 100644
index 0000000..df871f9
--- /dev/null
+++ b/src/ts/helpers/FileGrip/components/type.ts
@@ -0,0 +1,55 @@
+import IHashMap from 'ts/interfaces/HashMap';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+export default class FileGripByType {
+ statistic: any = [];
+
+ statisticByName: IHashMap = {};
+
+ clear() {
+ this.statistic = [];
+ this.statisticByName = {};
+ }
+
+ addFile(file: IDirtyFile) {
+ const key = file?.type;
+
+ if (!key || file?.name?.[0] === '.') return;
+
+ if (!this.statisticByName[key]) {
+ this.statisticByName[key] = this.#getNewType(file);
+ }
+
+ const type = this.statisticByName[key];
+ type.extension[file?.extension] = type.extension[file?.extension]
+ ? (type.extension[file?.extension] + 1)
+ : 1;
+
+ if (file.action === 'D') {
+ type.removedFiles.push(file);
+ type.removedCount += 1;
+ } else {
+ type.files.push(file);
+ type.count += 1;
+ }
+ }
+
+ #getNewType(file: IDirtyFile) {
+ return {
+ type: file?.type,
+ task: file?.firstCommit?.task,
+ path: file?.name,
+ extension: { [file?.extension]: 1 },
+ files: [],
+ count: 0,
+ removedFiles: [],
+ removedCount: 0,
+ };
+ }
+
+ updateTotalInfo() {
+ this.statistic = Object.entries(this.statisticByName)
+ .sort((a: any, b: any) => b[1].count - a[1].count)
+ .map((item: any) => item[1]);
+ }
+}
diff --git a/src/ts/helpers/FileGrip/helpers/index.ts b/src/ts/helpers/FileGrip/helpers/index.ts
new file mode 100644
index 0000000..35a7b18
--- /dev/null
+++ b/src/ts/helpers/FileGrip/helpers/index.ts
@@ -0,0 +1,12 @@
+import IHashMap from 'ts/interfaces/HashMap';
+
+export function getValuesInPercent(list: IHashMap, maxValue: number) {
+ const percent = 100 / maxValue;
+ const valuesInPercent = {};
+ for (let name in list) {
+ if (list[name]) {
+ valuesInPercent[name] = Math.round((list[name] ?? 100) * percent);
+ }
+ }
+ return valuesInPercent;
+}
diff --git a/src/ts/helpers/FileGrip/index.ts b/src/ts/helpers/FileGrip/index.ts
new file mode 100644
index 0000000..e2a1175
--- /dev/null
+++ b/src/ts/helpers/FileGrip/index.ts
@@ -0,0 +1,56 @@
+import ICommit, { ISystemCommit, IFileChange } from 'ts/interfaces/Commit';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+
+import FileBuilder from './components/FileBuilder';
+import FileGripByExtension from './components/extension';
+import FileGripByType from './components/type';
+import FileGripByFolder from './components/folder';
+
+class FileGrip {
+ files: any = new FileBuilder();
+
+ extension: any = new FileGripByExtension();
+
+ type: any = new FileGripByType();
+
+ tree: any = new FileGripByFolder();
+
+ removedTree: any = new FileGripByFolder();
+
+ clear() {
+ this.files.clear();
+ this.extension.clear();
+ this.type.clear();
+ this.tree.clear();
+ this.removedTree.clear();
+ }
+
+ addCommit(commit: ICommit | ISystemCommit) {
+ if (!commit?.fileChanges?.length) return;
+
+ commit.fileChanges.forEach((fileChange: IFileChange) => {
+ this.files.addCommit(fileChange, commit);
+ });
+ }
+
+ updateTotalInfo() {
+ this.files.updateTotalInfo((file: IDirtyFile) => {
+ this.extension.addFile(file);
+ this.type.addFile(file);
+ if (file.action !== 'D') {
+ this.tree.addFile(file);
+ } else {
+ this.removedTree.addFile(file);
+ }
+ });
+
+ this.extension.updateTotalInfo();
+ this.type.updateTotalInfo();
+ this.tree.updateTotalInfo();
+ this.removedTree.updateTotalInfo();
+ }
+}
+
+const fileGrip = new FileGrip();
+
+export default fileGrip;
diff --git a/src/ts/helpers/Parser/file_info.ts b/src/ts/helpers/Parser/file_info.ts
deleted file mode 100644
index a1ebdcf..0000000
--- a/src/ts/helpers/Parser/file_info.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import ICommit from 'ts/interfaces/Commit';
-
-export function getNewFileAuthor(
- addedLines: number,
- prev?: ICommit | null,
-) {
- return {
- added: addedLines,
- changes: addedLines,
- removed: 0,
- commits: 1,
- tasks: { [prev?.task || '']: 1 },
- types: { [prev?.type || '']: 1 },
- scopes: { [prev?.scope || '']: 1 },
- };
-}
-
-export function getNewFileInfo(
- name: string,
- addedLines: number,
- commit?: ICommit | null,
-) {
- const nameParts = name?.split('/')?.pop()?.split('.') || [];
- return {
- name,
- extension: nameParts.pop(),
- firstName: nameParts.shift(),
- suffixes: nameParts,
- lines: addedLines,
- firstCommit: commit,
- lastCommit: commit,
- authors: {
- [commit?.author || '']: getNewFileAuthor(addedLines, commit),
- },
- };
-}
diff --git a/src/ts/helpers/Parser/files.ts b/src/ts/helpers/Parser/files.ts
deleted file mode 100644
index 2a464c9..0000000
--- a/src/ts/helpers/Parser/files.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo';
-import IHashMap from 'ts/interfaces/HashMap';
-
-export function getNewFileName(fileName: string, allFiles: any) {
- const hasRename = (/\s=>\s/gim).test(fileName);
- if (!hasRename) return fileName;
-
- let changedName = fileName.match(/\{[^}]+\}/gim)?.pop();
- if (!changedName) changedName = fileName;
-
- const [oldName, newName] = changedName
- ? changedName.replace(/[{}]/gim, '').split(' => ')
- : fileName.split(' => ');
-
- let oldPath = fileName.replace(changedName, oldName);
- if (!oldName) oldPath = oldPath.replace('//', '/');
-
- const newPath = fileName.replace(changedName, newName);
- if (!allFiles[oldPath]) return newPath;
-
- allFiles[newPath] = allFiles[oldPath];
- allFiles[newPath].name = newPath;
-
- return newPath;
-}
-
-function getFolder(name: string, file: IDirtyFile): IFileTree {
- return {
- id: Math.random(),
- name: name || '',
- firstCommit: file?.firstCommit,
- lastCommit: file?.firstCommit,
- content: {},
- };
-}
-
-function getFolderTree(fileTree: any, file: IDirtyFile) {
- let prev = fileTree;
- let fileName: string = file.path.pop() || '';
- file.path.forEach((folder: any) => {
- if (!prev[folder] || !prev[folder].content) {
- prev[folder] = getFolder(folder, file);
- } else {
- prev[folder].lastCommit = file?.lastCommit;
- }
- prev = prev[folder].content;
- });
- prev[fileName] = file;
-}
-
-
-export function getFileList(allFiles: IHashMap) {
- const fileList = Object.values(allFiles); // @ts-ignore
- const fileTree: IFileTree = getFolder();
-
- fileList.forEach((file: IDirtyFile) => {
- if (!file.name) return;
- file.path = file.name.split('/');
- getFolderTree(fileTree.content, file);
- });
-
- return { fileList, fileTree };
-}
diff --git a/src/ts/helpers/Parser/user_info.ts b/src/ts/helpers/Parser/getCommitInfo.ts
similarity index 96%
rename from src/ts/helpers/Parser/user_info.ts
rename to src/ts/helpers/Parser/getCommitInfo.ts
index bb22a05..f7d232a 100644
--- a/src/ts/helpers/Parser/user_info.ts
+++ b/src/ts/helpers/Parser/getCommitInfo.ts
@@ -2,7 +2,7 @@ import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
-export default function getUserInfo(logString: string): ICommit | ISystemCommit {
+export default function getCommitInfo(logString: string): ICommit | ISystemCommit {
// "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile"
const parts = logString.split('>');
@@ -35,6 +35,7 @@ export default function getUserInfo(logString: string): ICommit | ISystemCommit
text: '',
type: 'не подписан',
scope: 'неопределенна',
+ fileChanges: [],
};
const isSystemPR = message.indexOf('Pull request #') === 0;
diff --git a/src/ts/helpers/Parser/getFileChanges.ts b/src/ts/helpers/Parser/getFileChanges.ts
new file mode 100644
index 0000000..c334d5a
--- /dev/null
+++ b/src/ts/helpers/Parser/getFileChanges.ts
@@ -0,0 +1,75 @@
+import { IFileChange } from 'ts/interfaces/Commit';
+
+function getFilePath(path: string): string[] {
+ const formattedPath = path
+ .replace(/"/gm, '')
+ .replace(/\/\//gm, '/');
+
+ const parts = formattedPath.split(/(?:\{)|(?:\s=>\s)|(?:})/gm);
+ if (parts.length !== 2 && parts.length !== 4) return [formattedPath];
+
+ if (parts.length === 2) parts.unshift('');
+
+ let oldPath = `${parts[0] || ''}${parts[1] || ''}${parts[3] || ''}`;
+ let newPath = `${parts[0] || ''}${parts[2] || ''}${parts[3] || ''}`;
+
+ if (!parts[1]) oldPath = oldPath.replace(/\/\//gm, '/');
+ if (!parts[2]) newPath = newPath.replace(/\/\//gm, '/');
+
+ return [oldPath, newPath];
+}
+
+// "38 9 src/app.css" -> [38, 9, 9, 'src/app.css']
+export function getNumStatInfo(message: string) {
+ let [addedRaw, removedRaw, path] = message.split('\t');
+
+ let added = parseInt(addedRaw, 10) || 0;
+ let removed = parseInt(removedRaw, 10) || 0;
+
+ let changes = 0;
+ if (added > removed) {
+ added = added - removed;
+ changes = removed;
+ removed = 0;
+ } else if (removed > added) {
+ removed = removed - added;
+ changes = added;
+ added = 0;
+ } else {
+ changes = added;
+ added = 0;
+ removed = 0;
+ }
+
+ return {
+ path,
+ addedLines: added,
+ removedLines: removed,
+ changedLines: changes,
+ };
+}
+// ":000000 100644 000000000 fc44b0a37 A public/logo192.png" -> ['A', 'public/logo192.png']
+export function getRawInfo(message: string) {
+ const action = message[35];
+ const path = message.split('\t')[1];
+ return { path, action };
+}
+
+// "src/AppGit.css" -> { id: 'src/appgit.css', path: 'src/AppGit.css' }
+export function getInfoFromPath(path: string): IFileChange {
+ const [oldPath, newPath] = getFilePath(path);
+
+ const id = oldPath.toLowerCase();
+ const newId = newPath?.toLowerCase();
+
+ return {
+ id,
+ newId: (newId && id !== newId) ? newId : undefined,
+ path: newPath || oldPath,
+
+ action: '',
+ addedLines: 0,
+ removedLines: 0,
+ changedLines: 0,
+ };
+}
diff --git a/src/ts/helpers/Parser/index.ts b/src/ts/helpers/Parser/index.ts
index 3f3328a..d2b8a90 100644
--- a/src/ts/helpers/Parser/index.ts
+++ b/src/ts/helpers/Parser/index.ts
@@ -1,118 +1,48 @@
-import { IDirtyFile } from 'ts/interfaces/FileInfo';
+import ICommit, { IFileChange, ISystemCommit } from 'ts/interfaces/Commit';
+
import IHashMap from 'ts/interfaces/HashMap';
-import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
-import settingsStore from 'ts/store/Settings';
-import getUserInfo from './user_info';
-import { getNewFileName, getFileList } from './files';
-import { getNewFileInfo } from './file_info';
+import getCommitInfo from './getCommitInfo';
+import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges';
-const uniq = {};
export default function Parser(report: string[]) {
- const allFiles: IHashMap = {};
- const removedFiles: IHashMap = {};
+ let commit = null;
const commits: Array = [];
- let week: number = 0;
- let weekEndTime: number = 0;
- let prev = null;
+ let files: IHashMap = {};
+ let fileChanges: IFileChange | null = null;
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) {
- let [addedRaw, removedRaw, fileName] = message.split('\t');
- const formattedFileName = fileName?.replace(/"/gm, '');
- fileName = getNewFileName(formattedFileName, allFiles);
- let added = parseInt(addedRaw, 10) || 0;
- let removed = parseInt(removedRaw, 10) || 0;
- const diff = added - removed;
- let changes = added > removed ? removed : added;
+ if (index > 0 && index < 10) { // парсинг файлов формата --num-stat
+ const line = getNumStatInfo(message);
+ if (!files[line.path]) {
+ files[line.path] = getInfoFromPath(line.path);
+ }
+ fileChanges = files[line.path];
+ fileChanges.addedLines = line.addedLines;
+ fileChanges.removedLines = line.removedLines;
+ fileChanges.changedLines = line.changedLines;
- if (!allFiles[fileName] && removedFiles[fileName]) {
- allFiles[fileName] = removedFiles[fileName];
- delete removedFiles[fileName];
+ } else if (message[0] === ':') { // парсинг файлов формата --raw
+ const line = getRawInfo(message);
+ if (!files[line.path]) {
+ files[line.path] = getInfoFromPath(line.path);
}
+ fileChanges = files[line.path];
+ fileChanges.action = line.action;
- if (allFiles[fileName]) {
- const fileInfo: IDirtyFile = allFiles[fileName];
- fileInfo.lastCommit = prev;
- fileInfo.lines += diff;
- if (!fileInfo.authors[prev?.author || '']) {
- fileInfo.authors[prev?.author || ''] = {
- added: 0,
- changes: 0,
- removed: 0,
- commits: 1,
- tasks: {},
- types: {},
- scopes: {},
- };
- }
- const authorInfo = fileInfo.authors[prev?.author || ''];
- authorInfo.changes = authorInfo.changes + changes;
- if (diff > 0) {
- authorInfo.added = authorInfo.added + diff;
- } else {
- authorInfo.removed = authorInfo.removed + diff * (-1);
- }
- authorInfo.commits += 1;
- authorInfo.tasks[prev?.task || ''] = (authorInfo.tasks[prev?.task || ''] || 0) + 1;
- authorInfo.types[prev?.type || ''] = (authorInfo.tasks[prev?.type || ''] || 0) + 1;
- authorInfo.scopes[prev?.scope || ''] = (authorInfo.tasks[prev?.scope || ''] || 0) + 1;
- if (allFiles[fileName].lines === 0) {
- removedFiles[fileName] = allFiles[fileName];
- delete allFiles[fileName];
- }
- } else {
- // @ts-ignore
- allFiles[fileName] = getNewFileInfo(fileName, added, prev);
- }
- if (removed > added) {
- removed -= added;
- changes += added;
- added = 0;
- } else if (added > removed) {
- added -= removed;
- changes += removed;
- removed = 0;
- } else if (added === removed) {
- changes += added;
- added = 0;
- removed = 0;
- }
- 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}`);
- }
- uniq[prev.date] = i;
- }
-
- const next = getUserInfo(message);
- if (next.milliseconds > weekEndTime) {
- week += 1;
- weekEndTime = next.milliseconds + (settingsStore.ONE_DAY * (6 - next.day));
- }
- // @ts-ignore
- next.week = week;
-
- prev = next;
- commits.push(prev); // @ts-ignore
+ } else { // парсинг коммита
+ if (commit) commit.fileChanges = Object.values(files);
+ files = {};
+ commit = getCommitInfo(message);
+ commit.week = 1;
+ commits.push(commit);
}
}
- const { fileList, fileTree } = getFileList(allFiles);
- return {
- commits,
- fileList,
- fileTree,
- removed: getFileList(removedFiles),
- };
+ return commits;
}
diff --git a/src/ts/helpers/Title.ts b/src/ts/helpers/Title.ts
index fb3a9ee..0877ece 100644
--- a/src/ts/helpers/Title.ts
+++ b/src/ts/helpers/Title.ts
@@ -1,7 +1,7 @@
import localization from './Localization';
-function getFormattedType(dataGrip: any): string {
- const popularType = dataGrip.extension.statistic?.[0] || {};
+function getFormattedType(fileGrip: any): string {
+ const popularType = fileGrip.extension.statistic?.[0] || {};
const extension = popularType?.extension || '';
if ([
@@ -30,7 +30,7 @@ function getFormattedType(dataGrip: any): string {
'perl',
'java',
].includes(extension)) {
- const hasManifest = dataGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
+ const hasManifest = fileGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
return hasManifest
? 'Android'
: 'Back';
@@ -45,12 +45,12 @@ function getFormattedType(dataGrip: any): string {
return extension.toUpperCase();
}
-export default function getTitle(dataGrip: any, commits: any) {
+export default function getTitle(dataGrip: any, fileGrip: any, commits: any) {
if (!commits.length) {
return localization.get('common.title');
}
- const type = getFormattedType(dataGrip) || '';
+ const type = getFormattedType(fileGrip) || '';
const task = dataGrip.pr.statistic?.[0]?.task || '';
const author = dataGrip.firstLastCommit.minData.author || '';
const year = commits?.[0]?.year || '';
@@ -59,4 +59,4 @@ export default function getTitle(dataGrip: any, commits: any) {
const formattedAuthor = author.split(' ').shift() || '';
return `${type} ${formattedTask} (${year}, ${formattedAuthor})`;
-}
\ No newline at end of file
+}
diff --git a/src/ts/helpers/achievement/byAuthor.ts b/src/ts/helpers/achievement/byAuthor.ts
index f019944..411d9de 100644
--- a/src/ts/helpers/achievement/byAuthor.ts
+++ b/src/ts/helpers/achievement/byAuthor.ts
@@ -1,6 +1,37 @@
import ALL_ACHIEVEMENTS from './constants/list';
+import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
-export default function getAchievementByAuthor(list: string[], statistic: any) {
+function getHoroscope(firstCommit: ICommit | ISystemCommit) {
+ const month = firstCommit.month + 1;
+ const dayInMonth = firstCommit.month;
+ const horoscopeRange = [
+ { from: [22, 12], to: [20, 1] },
+ { from: [20, 1], to: [18, 2] },
+ { from: [19, 2], to: [20, 3] },
+ { from: [21, 3], to: [19, 4] },
+ { from: [20, 4], to: [20, 5] },
+ { from: [21, 5], to: [21, 6] },
+ { from: [22, 6], to: [22, 7] },
+ { from: [23, 7], to: [22, 8] },
+ { from: [23, 8], to: [22, 9] },
+ { from: [23, 9], to: [23, 10] },
+ { from: [24, 10], to: [22, 11] },
+ { from: [23, 11], to: [21, 12] },
+ ];
+
+ const achievementIndex = horoscopeRange.reduce((acc: string, item: any, index: number) => {
+ if (acc) return acc;
+ if ((item.from[1] === month && dayInMonth >= item.from[0])
+ || (item.to[1] === month && dayInMonth <= item.to[0])) return `${index + 1}`;
+ return acc;
+ }, '');
+
+ return `horoscope${achievementIndex}`;
+}
+
+
+export default function getAchievementByAuthor(list: string[], dataGrip: any, author: string) {
+ const statistic = dataGrip.author.statisticByName[author];
const commitByHours = statistic.commitsByHour;
if (statistic.commits > 20) {
@@ -40,6 +71,15 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
if (statistic.allDaysInProject >= 666) list.push('more666DaysInProject');
// Азино - отработал 777 дней на проекте
if (statistic.allDaysInProject >= 777) list.push('more777DaysInProject');
+ // Старожил - отработал 3 года на проекте
+ if (statistic.allDaysInProject >= (3 * 365)) list.push('more3YearsInProject');
+ // хоть раз работал на выходных
+ if (statistic.commitsByDayAndHourTotal[5]
+ || statistic.commitsByDayAndHourTotal[6]) list.push('workOnWeekends');
+
+ // работал над задачей больше трех месяцев
+ const daysInWork = dataGrip.tasks.longTaskByAuthor[author] || {};
+ if (daysInWork > 92) list.push('longTask');
}
// Ни единого разрыва - 0 дней без коммитов
if (statistic.lazyDays === 0) list.push('zeroLazyDays');
@@ -48,6 +88,11 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
// Точно в цель - в среднем 1 коммит на таск
if (statistic.tasks / statistic.commits) list.push('oneCommitOneTask');
+ list.push(getHoroscope(statistic.firstCommit));
+
+ const statisticByPr = dataGrip.pr.statisticByName[author] || {};
+ if (statisticByPr?.maxDelayDays > 31) list.push('longWaitPR');
+
return list.reduce((acc: any, type: string) => {
const index = ALL_ACHIEVEMENTS[type] - 1;
acc[index].push(type);
diff --git a/src/ts/helpers/achievement/byCompetition.ts b/src/ts/helpers/achievement/byCompetition.ts
index 4eb738b..63bbfcd 100644
--- a/src/ts/helpers/achievement/byCompetition.ts
+++ b/src/ts/helpers/achievement/byCompetition.ts
@@ -1,6 +1,7 @@
import IHashMap from 'ts/interfaces/HashMap';
import getAchievementByAuthor from './byAuthor';
+import getAchievementByFile from './byFile';
class AchievementsByAuthor {
authors: IHashMap = {};
@@ -10,11 +11,12 @@ class AchievementsByAuthor {
}
add(authors: Array<[string, number]>, maxAchievementCode: string, minAchievementCode?: string) {
- const first = authors[0][0];
+ const first = authors?.[0]?.[0];
+ if (!first) return;
this.authors?.[first]?.push(maxAchievementCode);
if (!minAchievementCode) return;
- const last = authors[authors.length - 1][0];
+ const last = authors?.[authors.length - 1]?.[0];
this.authors?.[last]?.push(minAchievementCode);
}
}
@@ -22,7 +24,7 @@ class AchievementsByAuthor {
class AchievementsByCompetition {
authors: IHashMap> = {};
- updateByDataGrip(dataGrip: any) {
+ updateByGrip(dataGrip: any, fileGrip: any) {
const statisticByAuthor = dataGrip.author.statistic;
const byAuthor: any = new AchievementsByAuthor();
const total = this.#getMinMaxValue(statisticByAuthor, dataGrip, (statistic: any) => {
@@ -62,6 +64,12 @@ class AchievementsByCompetition {
// Количество коммитов в день
byAuthor.add(total.commitsInDay, 'moreCommits');
+ // Таможня даёт добро
+ byAuthor.add(total.morePRMerge, 'morePRMerge');
+
+ // Давным давно, в далёкой галактике
+ byAuthor.add(total.moreLongWaitPR, 'moreLongWaitPR');
+
// Первый и последний коммит
const lastAuthor = dataGrip.firstLastCommit.maxData.author;
const firstAuthor = dataGrip.firstLastCommit.minData.author;
@@ -72,10 +80,11 @@ class AchievementsByCompetition {
byAuthor.authors[lastAuthor].push('lastCommit');
}
- console.dir(byAuthor);
+ getAchievementByFile(fileGrip, byAuthor);
+
statisticByAuthor.forEach((statistic: any) => {
const achievements = byAuthor.authors[statistic.author];
- this.authors[statistic.author] = getAchievementByAuthor(achievements, statistic);
+ this.authors[statistic.author] = getAchievementByAuthor(achievements, dataGrip, statistic.author);
});
}
@@ -85,9 +94,9 @@ class AchievementsByCompetition {
statisticByAuthor.forEach((statistic: any) => {
callback(statistic);
- const addData = (property: string, count: number) => {
+ const addData = (property: string, count?: number) => {
if (!total[property]) total[property] = [];
- total[property].push([statistic.author, count]);
+ total[property].push([statistic.author, count || 0]);
};
addData('nameLength', statistic.author.length);
@@ -101,6 +110,10 @@ class AchievementsByCompetition {
addData('tasksInDay', byTimestamp.tasksByTimestampCounter.max);
addData('commitsInDay', byTimestamp.commitsByTimestampCounter.max);
+ const byPr = dataGrip.pr.statisticByName[statistic.author] || {};
+ addData('moreLongWaitPR', byPr?.maxDelayDays);
+ addData('morePRMerge', byPr?.numberMergedPr);
+
if (statistic.isStaff) return;
addData('allDaysInProject', statistic.allDaysInProject);
addData('lazyDays', statistic.lazyDays);
diff --git a/src/ts/helpers/achievement/byFile.ts b/src/ts/helpers/achievement/byFile.ts
new file mode 100644
index 0000000..48ba5f4
--- /dev/null
+++ b/src/ts/helpers/achievement/byFile.ts
@@ -0,0 +1,81 @@
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+import IHashMap from 'ts/interfaces/HashMap';
+
+function getHashMap(list: string[]) {
+ return Object.fromEntries(list.map((code: string) => [code, true]));
+}
+
+const IS_LINT_HINT = getHashMap(['.eslintrc', '.stylelintrc.json']);
+const IS_CSS = getHashMap(['css', 'scss', 'less', 'style']);
+const IS_TEST = getHashMap(['test', 'mock', 'snap']);
+const IS_CI_CD = getHashMap([
+ 'Dockerfile',
+ 'gradlew',
+ 'gradlew.bat',
+ 'gradle.properties',
+ 'docker-compose.yml',
+]);
+
+function getAddedChangedLines(file: IDirtyFile) {
+ return [
+ Object.entries(file?.addedLinesByAuthor || {}),
+ Object.entries(file?.changedLinesByAuthor || {}),
+ ];
+}
+
+function getTopUser(listOfChanges: any) {
+ const total = listOfChanges.reduce((acc: any, item: any) => {
+ acc[item[0]] = acc[item[0]] ? (acc[item[0]] + item[1]) : item[1];
+ return acc;
+ }, {});
+
+ return Object.entries(total).sort((a: any, b: any) => b[1] - a[1]);
+}
+
+export default function getAchievementByFile(fileGrip: any, byAuthor: any) {
+ const moreLintHint: any = [];
+ const moreReadMe: any = [];
+ const moreStyle: any = [];
+ const moreTests: any = [];
+ const moreDevOps: any = [];
+ const longFilePath: any = { author: '', length: 0 };
+ const longFileName: any = { author: '', length: 0 };
+ const fileRush: IHashMap = {};
+
+ fileGrip.files.list.forEach((file: IDirtyFile) => {
+ if (IS_LINT_HINT[file.name]) moreLintHint.push(getAddedChangedLines(file));
+ if (file.extension === 'md') moreReadMe.push(getAddedChangedLines(file));
+ if (IS_CSS[file.extension]) moreStyle.push(getAddedChangedLines(file));
+ if (IS_TEST[file.extension] || IS_TEST[file.type]) moreTests.push(getAddedChangedLines(file));
+ if (IS_CI_CD[file.name]) moreDevOps.push(getAddedChangedLines(file));
+
+ fileRush[file.firstCommit?.author || ''] = fileRush[file.firstCommit?.author || '']
+ ? (fileRush[file.firstCommit?.author || ''] + 1)
+ : 1;
+
+ if (file.name.length > longFileName.length) {
+ longFileName.author = file.firstCommit?.author;
+ longFileName.length = file.name.length;
+ }
+ if (file.path.length > longFilePath.length) {
+ longFilePath.author = file.firstCommit?.author;
+ longFilePath.length = file.name.length;
+ }
+ });
+
+ const userFileRush = Object.entries(fileRush).sort((a: any, b: any) => b[1] - a[1]);
+
+ const addedFoldersByAuthor = Object
+ .entries(fileGrip.tree.addedFoldersByAuthor)
+ .map((item: any) => [item[0], item[1].length]);
+
+ byAuthor.add(getTopUser(userFileRush), 'fileRush');
+ byAuthor.add(getTopUser(addedFoldersByAuthor), 'moreAddedFolders');
+ byAuthor.add(getTopUser(moreLintHint.flat(2)), 'moreLintHint');
+ byAuthor.add(getTopUser(moreReadMe.flat(2)), 'moreReadMe');
+ byAuthor.add(getTopUser(moreStyle.flat(2)), 'moreStyle');
+ byAuthor.add(getTopUser(moreTests.flat(2)), 'moreTests');
+ byAuthor.add(getTopUser(moreDevOps.flat(2)), 'moreDevOps');
+ byAuthor.authors[longFilePath.author].push('longFilePath');
+ byAuthor.authors[longFileName.author].push('longFileName');
+}
diff --git a/src/ts/helpers/achievement/constants/list.ts b/src/ts/helpers/achievement/constants/list.ts
index 2b8f22d..96b6919 100644
--- a/src/ts/helpers/achievement/constants/list.ts
+++ b/src/ts/helpers/achievement/constants/list.ts
@@ -33,6 +33,7 @@ export default {
more666DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Чёрт
more777DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Азино 3 топора
moreRefactoring: ACHIEVEMENT_TYPE.GOOD, // Выпускающий редактор
+ moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды
// нет картинки
longestMessage: ACHIEVEMENT_TYPE.NORMAL, // А разговоров то было...
@@ -41,39 +42,50 @@ export default {
noCommitOnDay: ACHIEVEMENT_TYPE.NORMAL, // Технический перерыв
hasCommitEveryTime: ACHIEVEMENT_TYPE.BAD, // Умер на работе
commitsAfter1800: ACHIEVEMENT_TYPE.GOOD, // Делу время
- more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Нужно чуть чуть потерпеть, отработал год и не уволился
+ more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Годовасик, отработал год и не уволился
+ more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте
firstCommit: ACHIEVEMENT_TYPE.NORMAL, // Кто первый, того и тапки. первый коммит на проекте
lastCommit: ACHIEVEMENT_TYPE.NORMAL, // Я закончил. последний коммит на проекте
firstLastCommit: ACHIEVEMENT_TYPE.NORMAL, // От начала и до конца. первый и последний коммит на проекте
+ moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json
+ moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD
+ moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование
+ moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps
+ longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением
+ longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем
+ moreAddedFolders: ACHIEVEMENT_TYPE.NORMAL, // Директор, создал больше всех дирректорий
+ morePRMerge: ACHIEVEMENT_TYPE.NORMAL, // Таможня даёт добро,
+ longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца
+ moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике
+ workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных
+ longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев
+ fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте
+
+ // Типаж Козерога, по месяцу первого коммита
+ horoscope1: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope2: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope3: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope4: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope5: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope6: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope7: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope8: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope9: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope10: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope11: ACHIEVEMENT_TYPE.NORMAL,
+ horoscope12: ACHIEVEMENT_TYPE.NORMAL,
// нет кода
- moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды
lessWorkDays: ACHIEVEMENT_TYPE.BAD, // Дальше без меня
moreOnHoliday: ACHIEVEMENT_TYPE.BAD, // Нет жизни
moreCreateCode: ACHIEVEMENT_TYPE.NORMAL, // Созидатель -- переименовать?
moreRemoveCode: ACHIEVEMENT_TYPE.NORMAL, // Разрушитель
moreChangeCode: ACHIEVEMENT_TYPE.NORMAL, // Реформатор
- morePRMerge: ACHIEVEMENT_TYPE.GOOD, // Таможня даёт добро,
- workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных
- longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца
- moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике
- more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте
-
oneExtension: ACHIEVEMENT_TYPE.NORMAL, // Один в поле воин. Только он работает с файлами определенного расширения
- fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте
- moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json
- moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD
- moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps
- moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование
-
allRelease: ACHIEVEMENT_TYPE.NORMAL, // Фулл хаус. есть релиз, собранный только из его задач
- longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением
- longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем
-
removeCreateFile: ACHIEVEMENT_TYPE.NORMAL, // Откопал стюардессу. востановил удаленный файл
renameFile: ACHIEVEMENT_TYPE.NORMAL, // Астана Нур-Султан Астана. переименовывал туда-сюда файл
- longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев
// Галя, у нас отмена - откатил назад
// У меня работает - больше всего коммитов с текстом fix
// 500я на проде - больше всего коммитов с префиксом hotfix
@@ -81,6 +93,4 @@ export default {
// godFather: ACHIEVEMENT_TYPE.NORMAL, // Крёстный отец. Первый создал файлы небольшой группы.
// Боярин - есть папка на 20 файлов, где правит только этот человек
// Феодал - есть папка на 50 файлов, где правит только этот человек
-
- // Психологический знак задиака по дате первого коммита
};
diff --git a/src/ts/interfaces/Banner.ts b/src/ts/interfaces/Banner.ts
new file mode 100644
index 0000000..637a360
--- /dev/null
+++ b/src/ts/interfaces/Banner.ts
@@ -0,0 +1,20 @@
+interface IBanner {
+ isDefault?: boolean;
+ ref?: string;
+ link?: string;
+ isOpenInNewTab?: boolean;
+
+ /* Логотип */
+ icon?: string;
+ title?: string;
+
+ /* Картинка баннера */
+ banner?: string;
+
+ /* Текстовы баннер */
+ bannerText?: string;
+ color?: string;
+ backgroundColor?: string;
+}
+
+export default IBanner;
diff --git a/src/ts/interfaces/Commit.ts b/src/ts/interfaces/Commit.ts
index eefe444..c443022 100644
--- a/src/ts/interfaces/Commit.ts
+++ b/src/ts/interfaces/Commit.ts
@@ -1,3 +1,17 @@
+export interface IFileChange {
+ id: string; // регистро-независимый путь в качестве ID
+ path: string; // актуальный путь с учётом регистра
+
+ newId?: string; // новый ID, если файл переименовали
+ newPath?: string; // новый путь с учётом регистра
+
+ action: string; // тип действия с файлом: добавили, изменили, удалили
+
+ addedLines: number;
+ removedLines: number;
+ changedLines: number;
+}
+
export interface ILog {
// date
date: string; // "2021-02-09T12:59:17+03:00",
@@ -22,6 +36,8 @@ export interface ILog {
taskNumber: string; // "0000",
type: string; // feat|fix|docs|style|refactor|test|chore
scope: string; // table, sale, profile and etc.
+
+ fileChanges: IFileChange[];
}
export const COMMIT_TYPE = {
diff --git a/src/ts/interfaces/FileInfo.ts b/src/ts/interfaces/FileInfo.ts
index e914080..44c3042 100644
--- a/src/ts/interfaces/FileInfo.ts
+++ b/src/ts/interfaces/FileInfo.ts
@@ -1,37 +1,40 @@
import ICommit, { ISystemCommit } from './Commit';
import IHashMap from './HashMap';
-export interface IDirtyFile {
- name: string; // ".gitignore",
+interface IFileStat {
lines: number; // 38, line in file for this moment
+
+ addedLines: number;
+ removedLines: number;
+ changedLines: number;
+
+ addedLinesByAuthor: IHashMap; // added lines by author
+ removedLinesByAuthor: IHashMap; // removed lines by author
+ changedLinesByAuthor: IHashMap; // removed lines by author
+
+ addedByAuthorInPercent: IHashMap;
+ removedByAuthorInPercent: IHashMap;
+ changedByAuthorInPercent: IHashMap;
+ addedRemovedChangedInPercent: IHashMap;
+
firstCommit: ICommit | ISystemCommit | null,
lastCommit: ICommit | ISystemCommit | null,
- path: string[],
- extension: string,
- firstName: string,
- authors: {
- [author: string]: {
- added: number; // 38,
- changes: number; // 38,
- removed: number; // 0,
- commits: number; // 1,
- tasks: {
- [taskName: string]: number,
- },
- types: {
- [typeName: string]: number,
- },
- scopes: {
- [scopeName: string]: number,
- }
- }
- }
}
-export interface IFileTree {
+export interface IDirtyFile extends IFileStat {
+ id: string; // "src/mynewlogo.test.ts",
+ path: string[]; // ['src']
+ pathString: string; // 'src/MyNewLogo.test.ts'
+ name: string; // "MyNewLogo.test.ts",
+ extension: string; // "ts",
+ type: string; // "test",
+ action: string; // 'A' or 'M' or 'D'
+}
+
+export interface IFolder extends IFileStat {
id?: number;
name?: string;
- firstCommit: ICommit | ISystemCommit | null,
- lastCommit: ICommit | ISystemCommit | null,
+ path: string[]; // ['src']
+ pathString: string; // 'src\\ts'
content: IHashMap,
}
diff --git a/src/ts/pages/Common/helpers/getMax.ts b/src/ts/pages/Common/helpers/getMax.ts
index 0394e5e..aea2174 100644
--- a/src/ts/pages/Common/helpers/getMax.ts
+++ b/src/ts/pages/Common/helpers/getMax.ts
@@ -9,4 +9,4 @@ export function getMax(response: IPagination, property: string, subProperty
export function getMaxByLength(response: IPagination, property: string) {
return getMax(response, property, 'length');
-}
\ No newline at end of file
+}
diff --git a/src/ts/pages/Settings/components/MailMap.tsx b/src/ts/pages/Settings/components/MailMap.tsx
index b9ab83a..3b244f0 100644
--- a/src/ts/pages/Settings/components/MailMap.tsx
+++ b/src/ts/pages/Settings/components/MailMap.tsx
@@ -6,10 +6,10 @@ import Console from 'ts/components/Console';
import style from '../styles/index.module.scss';
function MailMap(): React.ReactElement | null {
- const items = dataGripStore.dataGrip.author.statistic.map((item: any) => (
- `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`
- ));
- const commands = items.map((text: string) => ({text}
));
+ const items = dataGripStore.dataGrip.author.statistic
+ .map((item: any) => `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`)
+ .sort();
+ const commands = items.map((text: any) => ({text}
));
const commandsForCopy = items.join('\r\n');
return (
diff --git a/src/ts/pages/Team/components/Extension2.tsx b/src/ts/pages/Team/components/Extension.tsx
similarity index 87%
rename from src/ts/pages/Team/components/Extension2.tsx
rename to src/ts/pages/Team/components/Extension.tsx
index e3348a2..64c9b17 100644
--- a/src/ts/pages/Team/components/Extension2.tsx
+++ b/src/ts/pages/Team/components/Extension.tsx
@@ -29,8 +29,8 @@ interface IFilesViewProps {
function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
if (!response) return null;
- const current = getMax(response, 'current', 'count');
- const removed = getMax(response, 'removed', 'count');
+ const current = getMax(response, 'count');
+ const removed = getMax(response, 'removedCount');
const max = Math.max(current, removed);
const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
@@ -47,14 +47,14 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP
isFixed
template={ColumnTypesEnum.STRING}
title="page.team.extension.name"
- properties="extension"
+ properties="type"
width={200}
/>
row.count === 1 || row.removedCount === 1 ? row.path : ''}
/>
{mode === 'print' ? (
value.count}
+ properties="count"
/>
(
+ template={(value: number) => (
)}
/>
value.count}
+ properties="removedCount"
/>
(
+ template={(value: number) => (
)}
/>
@@ -127,7 +125,7 @@ ExtensionView.defaultProps = {
const Extension = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
- const rows = dataGripStore.dataGrip.extension.statistic;
+ const rows = dataGripStore.fileGrip.type.statistic;
if (rows?.length < 2) return mode !== 'print' ? () : null;
return (
diff --git a/src/ts/pages/Team/components/FileAnalitics/Extension.tsx b/src/ts/pages/Team/components/FileAnalitics/Extension.tsx
new file mode 100644
index 0000000..5c5db3f
--- /dev/null
+++ b/src/ts/pages/Team/components/FileAnalitics/Extension.tsx
@@ -0,0 +1,151 @@
+import React from 'react';
+import { observer } from 'mobx-react-lite';
+
+import { IPaginationRequest, IPagination } 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 DataView from 'ts/components/DataView';
+import Column from 'ts/components/Table/components/Column';
+import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
+import getOptions from 'ts/components/LineChart/helpers/getOptions';
+import LineChart from 'ts/components/LineChart';
+import { getMax } from 'ts/pages/Common/helpers/getMax';
+import ExternalLink from 'ts/components/ExternalLink';
+import userSettings from 'ts/store/UserSettings';
+
+interface IFilesViewProps {
+ response?: IPagination;
+ updateSort?: Function;
+ rowsForExcel?: any[];
+ mode?: string;
+}
+
+function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
+ if (!response) return null;
+
+ const current = getMax(response, 'count');
+ const removed = getMax(response, 'removedCount');
+ const max = Math.max(current, removed);
+ const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
+
+ return (
+
+
+ row.count === 1 || row.removedCount === 1 ? row.path : ''}
+ />
+ {mode === 'print' ? (
+
+ ) : (
+ {
+ if (!row.path) return '';
+ return (
+
+ );
+ }}
+ title="page.team.pr.task"
+ properties="task"
+ width={120}
+ />
+ )}
+
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ );
+}
+
+ExtensionView.defaultProps = {
+ response: undefined,
+};
+
+const Extension = observer(({
+ mode,
+}: ICommonPageProps): React.ReactElement | null => {
+ const rows = dataGripStore.fileGrip.extension.statistic;
+ if (rows?.length < 2) return mode !== 'print' ? () : null;
+
+ return (
+ <>
+
+ getFakeLoader({
+ content: rows, pagination, mode,
+ })}
+ watch={`${mode}${dataGripStore.dataGrip.hash}`}
+ >
+
+
+
+ >
+ );
+});
+
+export default Extension;
diff --git a/src/ts/pages/Team/components/FileAnalitics/Type.tsx b/src/ts/pages/Team/components/FileAnalitics/Type.tsx
new file mode 100644
index 0000000..2e2168d
--- /dev/null
+++ b/src/ts/pages/Team/components/FileAnalitics/Type.tsx
@@ -0,0 +1,151 @@
+import React from 'react';
+import { observer } from 'mobx-react-lite';
+
+import { IPaginationRequest, IPagination } 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 DataView from 'ts/components/DataView';
+import Column from 'ts/components/Table/components/Column';
+import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
+import getOptions from 'ts/components/LineChart/helpers/getOptions';
+import LineChart from 'ts/components/LineChart';
+import { getMax } from 'ts/pages/Common/helpers/getMax';
+import ExternalLink from 'ts/components/ExternalLink';
+import userSettings from 'ts/store/UserSettings';
+
+interface IFilesViewProps {
+ response?: IPagination;
+ updateSort?: Function;
+ rowsForExcel?: any[];
+ mode?: string;
+}
+
+function TypeView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
+ if (!response) return null;
+
+ const current = getMax(response, 'count');
+ const removed = getMax(response, 'removedCount');
+ const max = Math.max(current, removed);
+ const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
+
+ return (
+
+
+ row.count === 1 || row.removedCount === 1 ? row.path : ''}
+ />
+ {mode === 'print' ? (
+
+ ) : (
+ {
+ if (!row.path) return '';
+ return (
+
+ );
+ }}
+ title="page.team.pr.task"
+ properties="task"
+ width={120}
+ />
+ )}
+
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ );
+}
+
+TypeView.defaultProps = {
+ response: undefined,
+};
+
+const Type = observer(({
+ mode,
+}: ICommonPageProps): React.ReactElement | null => {
+ const rows = dataGripStore.fileGrip.type.statistic;
+ if (rows?.length < 2) return mode !== 'print' ? () : null;
+
+ return (
+ <>
+
+ getFakeLoader({
+ content: rows, pagination, mode,
+ })}
+ watch={`${mode}${dataGripStore.dataGrip.hash}`}
+ >
+
+
+
+ >
+ );
+});
+
+export default Type;
diff --git a/src/ts/pages/Team/components/FileAnalitics/index.tsx b/src/ts/pages/Team/components/FileAnalitics/index.tsx
new file mode 100644
index 0000000..8d55210
--- /dev/null
+++ b/src/ts/pages/Team/components/FileAnalitics/index.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { observer } from 'mobx-react-lite';
+
+import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
+
+import Extension from './Extension';
+import Type from './Type';
+
+const FileAnalitics = observer(({
+ mode,
+}: ICommonPageProps): React.ReactElement | null => {
+ return (
+ <>
+
+
+ >
+ );
+});
+
+export default FileAnalitics;
diff --git a/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx b/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx
new file mode 100644
index 0000000..4561c1f
--- /dev/null
+++ b/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { observer } from 'mobx-react-lite';
+
+import style from '../../styles/path.module.scss';
+import treeStore from '../../store/Tree';
+
+const FileBreadcrumbs = observer((): React.ReactElement => {
+ const directories = treeStore.selectedPath
+ .map((dirName: string, index: number) => (
+ <>
+
+ {'/'}
+
+ {
+ const newPath = treeStore.selectedPath.slice(0, index + 1);
+ treeStore.updateFilter('selectedPath', newPath);
+ }}
+ >
+ {dirName}
+
+ >
+ ));
+
+ return (
+
+
+ Адрес:
+
+ {
+ treeStore.updateFilter('selectedPath', []);
+ }}
+ >
+ {'..'}
+
+ {directories}
+
+ );
+});
+
+export default FileBreadcrumbs;
diff --git a/src/ts/pages/Team/components/TreeFilters.tsx b/src/ts/pages/Team/components/Files/Filters.tsx
similarity index 93%
rename from src/ts/pages/Team/components/TreeFilters.tsx
rename to src/ts/pages/Team/components/Files/Filters.tsx
index 083a77f..a2e4b51 100644
--- a/src/ts/pages/Team/components/TreeFilters.tsx
+++ b/src/ts/pages/Team/components/Files/Filters.tsx
@@ -6,8 +6,8 @@ import dataGripStore from 'ts/store/DataGrip';
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
-import treeStore from '../store/Tree';
-import style from '../styles/filters.module.scss';
+import treeStore from '../../store/Tree';
+import style from '../../styles/filters.module.scss';
const TreeFilters = observer((): React.ReactElement => {
const { t } = useTranslation();
diff --git a/src/ts/pages/Team/components/Files/Table.tsx b/src/ts/pages/Team/components/Files/Table.tsx
new file mode 100644
index 0000000..6292df2
--- /dev/null
+++ b/src/ts/pages/Team/components/Files/Table.tsx
@@ -0,0 +1,156 @@
+import React from 'react';
+
+import { IPagination } from 'ts/interfaces/Pagination';
+import { IDirtyFile } from 'ts/interfaces/FileInfo';
+import dataGripStore from 'ts/store/DataGrip';
+
+import Table from 'ts/components/Table';
+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 { getDate } from 'ts/helpers/formatter';
+
+import treeStore from '../../store/Tree';
+
+interface IViewProps {
+ response?: IPagination;
+}
+
+function View({ response }: IViewProps) {
+ if (!response) return null;
+
+ const fileSizeChart = getOptions({ max: getMax(response, 'lines'), suffix: 'page.team.tree.line' });
+ const addedLinesChart = getOptions({ order: dataGripStore.dataGrip.author.list, suffix: 'page.team.tree.line' });
+ const addedRemovedChangedChart = getOptions({ order: [
+ 'page.team.tree.lineAdd',
+ 'page.team.tree.lineChange',
+ 'page.team.tree.lineRemove',
+ ], suffix: 'page.team.tree.line' });
+
+ return (
+ {
+ if (row?.title === '..') return false;
+ else return true;
+ const limit = treeStore.minCommits || 0;
+ const name = dataGripStore.dataGrip.author.list[treeStore.authorId || ''] || '';
+ const author = row.file?.authors[name];
+ const commits = author?.commits || 0;
+ return (treeStore.authorId && !author) || (commits < limit);
+ }}
+ >
+ row?.content ? `📁 ${row?.name}` : `📄 ${row?.name}`}
+ minWidth={170}
+ onClick={(row: any) => {
+ if (!row.content) return;
+ treeStore.updateFilter('selectedPath', [...row.path, row.name]);
+ }}
+ />
+
+ (
+
+ )}
+ />
+ getDate(item?.firstCommit?.timestamp)}
+ width={130}
+ />
+ item?.firstCommit?.author || ''}
+ width={150}
+ />
+ getDate(item?.lastCommit?.timestamp)}
+ width={130}
+ />
+ item?.lastCommit?.author || ''}
+ width={150}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+ );
+}
+
+View.defaultProps = {
+ response: undefined,
+};
+
+export default View;
diff --git a/src/ts/pages/Team/components/Files/index.tsx b/src/ts/pages/Team/components/Files/index.tsx
new file mode 100644
index 0000000..932bd36
--- /dev/null
+++ b/src/ts/pages/Team/components/Files/index.tsx
@@ -0,0 +1,61 @@
+import React, { useEffect } from 'react';
+import { observer } from 'mobx-react-lite';
+import { useTranslation } from 'react-i18next';
+
+import { IPaginationRequest } from 'ts/interfaces/Pagination';
+import dataGripStore from 'ts/store/DataGrip';
+
+import PageWrapper from 'ts/components/Page/wrapper';
+import DataLoader from 'ts/components/DataLoader';
+import Pagination from 'ts/components/DataLoader/components/Pagination';
+import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
+import Title from 'ts/components/Title';
+import NothingFound from 'ts/components/NothingFound';
+
+import TreeFilters from './Filters';
+import FileBreadcrumbs from './FileBreadcrumbs';
+import View from './Table';
+import { getContentByPath } from '../../helpers/tree';
+import treeStore from '../../store/Tree';
+
+interface ITreeProps {
+ type?: string
+}
+
+const Tree = observer(({ type }: ITreeProps): React.ReactElement => {
+ const { t } = useTranslation();
+ const fileTree = type === 'removed'
+ ? dataGripStore.fileGrip.removedTree.tree
+ : dataGripStore.fileGrip.tree.tree;
+
+ useEffect(() => {
+ treeStore.updateFilter('selectedPath', []);
+ }, [type]);
+
+ const content = getContentByPath(fileTree, treeStore.selectedPath);
+ if (!content?.length) {
+ return ;
+ }
+
+ return (
+ <>
+
+
+
+
+ getFakeLoader({
+ content, pagination: { ...pagination, size: 2000 },
+ })}
+ watch={`${treeStore.hash}${type}`}
+ >
+
+
+
+
+ >
+ );
+});
+
+export default Tree;
diff --git a/src/ts/pages/Team/components/PR/All.tsx b/src/ts/pages/Team/components/PR/All.tsx
index c52671e..a490285 100644
--- a/src/ts/pages/Team/components/PR/All.tsx
+++ b/src/ts/pages/Team/components/PR/All.tsx
@@ -36,7 +36,6 @@ function AllPR({
order: dataGripStore.dataGrip.author.list,
});
- console.log(commitsChart);
return (
{
- const extensions = dataGripStore.dataGrip.extension.statistic
+ const extensions = dataGripStore.fileGrip.extension.statistic
.slice(0, 4).map((statistic: any) => {
return (
;
-}
-
-function TreeView({ response }: ITreeViewProps) {
- if (!response) return null;
-
- const getDetails = (file: any, property: string) => {
- if (!file) return {};
- return Object.keys(file.authors || {})
- .reduce((details: any, name: any) => {
- details[name] = file.authors[name][property];
- return details;
- }, {});
- };
-
- const fileChart = getOptions({ order: dataGripStore.dataGrip.author.list, suffix: 'строк' });
- const rewriteChart = getOptions({ order: [
- 'page.team.tree.lineAdd',
- 'page.team.tree.lineRemove',
- ], suffix: 'page.team.tree.line' });
-
- console.log(response.content);
- return (
- {
- if (row?.title === '..') return false;
- const limit = treeStore.minCommits || 0;
- const name = dataGripStore.dataGrip.author.list[treeStore.authorId || ''] || '';
- const author = row.file?.authors[name];
- const commits = author?.commits || 0;
- return (treeStore.authorId && !author) || (commits < limit);
- }}
- >
- {
- treeStore.updateFilter('selectedPath', row.path || []);
- }}
- />
- getDate(item?.file?.firstCommit?.timestamp)}
- width={130}
- />
- item?.file?.firstCommit?.author || ''}
- width={150}
- />
- getDate(item?.file?.lastCommit?.timestamp)}
- width={130}
- />
- item?.file?.lastCommit?.author || ''}
- width={150}
- />
- (
-
- )}
- />
- (
-
- )}
- />
- (
-
- )}
- />
- (
-
- )}
- />
-
- );
-}
-
-TreeView.defaultProps = {
- response: undefined,
-};
-
-interface ITreeProps {
- type?: string
-}
-
-const Tree = observer(({ type }: ITreeProps): React.ReactElement => {
- const { t } = useTranslation();
- const fileTree = type === 'removed'
- ? dataGripStore.removedFileTree
- : dataGripStore.fileTree;
- const subTree = getSubTreeByPath(fileTree, treeStore.selectedPath);
- const fileList = getArrayFromTree(subTree);
- console.dir(dataGripStore.removedFileTree);
- console.dir(fileList);
-
- // @ts-ignore
- if (!fileTree?.lines) return ;
-
- useEffect(() => {
- treeStore.updateFilter('selectedPath', []);
- }, [type]);
-
- return (
- <>
-
-
- {false && treeStore.selectedPath?.join('/')}
-
-
- getFakeLoader({
- content: fileList, pagination: { ...pagination, size: 500 },
- })}
- watch={`${treeStore.hash}${type}`}
- >
-
-
-
-
- >
- );
-});
-
-export default Tree;
diff --git a/src/ts/pages/Team/helpers/tree.ts b/src/ts/pages/Team/helpers/tree.ts
index d5bcbda..a8d8a91 100644
--- a/src/ts/pages/Team/helpers/tree.ts
+++ b/src/ts/pages/Team/helpers/tree.ts
@@ -1,58 +1,22 @@
-import { IFileTree } from 'ts/interfaces/FileInfo';
+import { IFolder } from 'ts/interfaces/FileInfo';
-interface IFile {
- name: string;
- path: string[];
- content: IFile[];
-}
-
-export function getSubTreeByPath(tree: IFileTree, path: string[]) {
- let subTree: any = tree || { content: [] };
- (path || []).forEach((folderName: string) => {
+function getSubTree(tree: IFolder, path: string[]) {
+ return (path || []).reduce((subTree: any, folderName: string) => {
subTree = subTree.content[folderName] || { content: [] };
- });
- return subTree;
+ return subTree;
+ }, tree || { content: [] });
}
-
-function getButtonUp(file: IFile) {
- return file?.path?.length ? ({
- title: '..',
- path: file.path.slice(0, -1),
- }) : null;
+function getSortedContent(subTree: any) {
+ return Object.values(subTree.content)
+ .sort((a: any, b: any) => {
+ if (a.content && !b.content) return -1;
+ if (!a.content && b.content) return 1;
+ if (a.name === b.name) return 0;
+ return a.name > b.name ? 1 : -1;
+ });
}
-function getFolderView(file: IFile) {
- return {
- file,
- title: `📁 ${file.name}`,
- path: file.path,
- };
+export function getContentByPath(fileTree: IFolder, path: string[]) {
+ return getSortedContent(getSubTree(fileTree, path));
}
-
-function getFileView(file: IFile) {
- return {
- file,
- title: `📄 ${file.name.split('/').pop() || ''}`,
- };
-}
-
-export function getArrayFromTree(tree: any) {
- const folders = [];
- const files = [];
-
- for (let name in tree.content) {
- const file = tree.content[name];
- if (file.content) {
- folders.push(getFolderView(file));
- } else {
- files.push(getFileView(file));
- }
- }
-
- return [
- getButtonUp(tree),
- ...folders,
- ...files,
- ].filter(v => v);
-}
\ No newline at end of file
diff --git a/src/ts/pages/Team/index.tsx b/src/ts/pages/Team/index.tsx
index ef1bc7b..3e5a38c 100644
--- a/src/ts/pages/Team/index.tsx
+++ b/src/ts/pages/Team/index.tsx
@@ -12,8 +12,8 @@ import PopularWords from './components/PopularWords';
import Scope from './components/Scope';
import Tempo from './components/Tempo';
import Total from './components/Total';
-import Tree from './components/Tree';
-import Extension from './components/Extension2';
+import Files from './components/Files';
+import FileAnalitics from './components/FileAnalitics';
import Type from './components/Type';
import Week from './components/Week';
import Month from './components/Month';
@@ -34,9 +34,9 @@ function getViewById(page?: string) {
if (page === 'week') return ;
if (page === 'month') return ;
if (page === 'hours') return ;
- if (page === 'files') return ;
- if (page === 'removedFiles') return ;
- if (page === 'extension') return ;
+ if (page === 'files') return ;
+ if (page === 'removedFiles') return ;
+ if (page === 'extension') return ;
if (page === 'release') return ;
if (page === 'commits') return ;
if (page === 'changes') return ;
diff --git a/src/ts/pages/Team/styles/path.module.scss b/src/ts/pages/Team/styles/path.module.scss
new file mode 100644
index 0000000..153c682
--- /dev/null
+++ b/src/ts/pages/Team/styles/path.module.scss
@@ -0,0 +1,26 @@
+@import 'src/styles/variables';
+
+.file_breadcrumbs {
+ margin: 0 0 24px 0;
+
+ &_text {
+ font-size: var(--font-l);
+ font-weight: 100;
+
+ display: inline-block;
+ margin-right: var(--space-m);
+
+ text-decoration: none;
+ vertical-align: top;
+
+ color: var(--color-black);
+ }
+
+ &_link {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/ts/pages/Welcome/index.tsx b/src/ts/pages/Welcome/index.tsx
index 583aa2b..4dd7892 100644
--- a/src/ts/pages/Welcome/index.tsx
+++ b/src/ts/pages/Welcome/index.tsx
@@ -27,7 +27,7 @@ function WarningInfo() {
}
function Welcome() {
- const command = 'git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n';
+ const command = 'git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n';
return (
<>
{process.env.REACT_APP_TYPE !== 'local' && ()}
diff --git a/src/ts/store/DataGrip.ts b/src/ts/store/DataGrip.ts
index 8d80be8..a8723c1 100644
--- a/src/ts/store/DataGrip.ts
+++ b/src/ts/store/DataGrip.ts
@@ -1,13 +1,14 @@
import { makeObservable, observable, action } from 'mobx';
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
-import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo';
+
import achievements from 'ts/helpers/achievement/byCompetition';
import dataGrip from 'ts/helpers/DataGrip';
-import getFileTreeWithStatistic from 'ts/helpers/DataGrip/helpers/tree';
+import fileGrip from 'ts/helpers/FileGrip';
import Parser from 'ts/helpers/Parser';
-import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
import getTitle from 'ts/helpers/Title';
+
+import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
import { applicationHasCustom } from 'ts/helpers/RPC';
import settingsStore from './Settings';
@@ -21,6 +22,7 @@ export enum DataParseStatusEnum {
interface IDataGripStore {
commits: ICommit[];
dataGrip: any;
+ fileGrip: any;
status: DataParseStatusEnum;
setCommits: (log?: string[]) => void;
}
@@ -28,16 +30,10 @@ interface IDataGripStore {
class DataGripStore implements IDataGripStore {
commits: any[] = [];
- fileList: IDirtyFile[] = [];
-
- fileTree: IFileTree = {} as IFileTree;
-
- removedFileList: IDirtyFile[] = [];
-
- removedFileTree: IFileTree = {} as IFileTree;
-
dataGrip: any = null;
+ fileGrip: any = null;
+
status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING;
constructor() {
@@ -51,25 +47,18 @@ class DataGripStore implements IDataGripStore {
setCommits(dump?: string[]) {
dataGrip.clear();
- const parser = Parser;
+ fileGrip.clear();
- const {
- commits,
- fileList,
- fileTree,
- removed,
- } = parser(dump || []);
+ const commits = Parser(dump || []);
commits.sort((a, b) => a.milliseconds - b.milliseconds);
commits.forEach((commit: ICommit | ISystemCommit) => {
dataGrip.addCommit(commit);
+ fileGrip.addCommit(commit);
});
+ fileGrip.updateTotalInfo();
this.commits = commits;
- this.fileList = fileList;
- this.fileTree = getFileTreeWithStatistic(fileTree);
- this.removedFileList = removed.fileList;
- this.removedFileTree = getFileTreeWithStatistic(removed.fileTree);
this.status = this.commits.length
? DataParseStatusEnum.DONE
@@ -84,16 +73,17 @@ class DataGripStore implements IDataGripStore {
);
dataGrip.updateByInitialization();
- dataGrip.updateByFiles(fileList, removed.fileList);
- achievements.updateByDataGrip(dataGrip);
+ achievements.updateByGrip(dataGrip, fileGrip);
}
this.dataGrip = null;
this.dataGrip = dataGrip;
+ this.fileGrip = fileGrip;
console.dir(this.dataGrip);
+ console.dir(this.fileGrip);
if (!applicationHasCustom.title) {
- document.title = getTitle(this.dataGrip, this.commits);
+ document.title = getTitle(this.dataGrip, this.fileGrip, this.commits);
}
}
@@ -101,7 +91,7 @@ class DataGripStore implements IDataGripStore {
console.log('need update data TODO');
dataGrip.updateByFilters();
if (!dataGrip.author.list.length) return;
- achievements.updateByDataGrip(dataGrip);
+ achievements.updateByGrip(dataGrip, fileGrip);
this.dataGrip = null;
this.dataGrip = dataGrip;
}
diff --git a/src/ts/translations/ru/achievements.ts b/src/ts/translations/ru/achievements.ts
index 5e0a03c..b85a0b8 100644
--- a/src/ts/translations/ru/achievements.ts
+++ b/src/ts/translations/ru/achievements.ts
@@ -45,24 +45,24 @@ export default `
§ achievements.moreDaysForTask.description: работа по задачам идёт медленнее чем у остальных
§ achievements.more2DaysForTask.title: Cо слоу
§ achievements.more2DaysForTask.description: больше двух дней на задачу
-§ achievements.moreDaysInProject.title: Часть команды, часть коробля
-§ achievements.moreDaysInProject.description: больше всего дней на проекте
§ achievements.more3YearsInProject.title: Старожил
§ achievements.more3YearsInProject.description: больше трех лет на проекте
§ achievements.lessDaysInProject.title: А это кто?
§ achievements.lessDaysInProject.description: меньше всего дней на проекте
-§ achievements.more90DaysInProject.title: Добро пожаловать
-§ achievements.more90DaysInProject.description: не уволили на испытательном
§ achievements.lessDaysForTask.title: Скорострел
§ achievements.lessDaysForTask.description: одна задача занимает меньше дня
§ achievements.adam.title: Адам
§ achievements.adam.description: первый стабильный сотрудник на проекте
+§ achievements.more90DaysInProject.title: Добро пожаловать
+§ achievements.more90DaysInProject.description: не уволили на испытательном
§ achievements.more365DaysInProject.title: Годовасик
§ achievements.more365DaysInProject.description: отработал год на проекте
§ achievements.more666DaysInProject.title: Чёрт
§ achievements.more666DaysInProject.description: отработал 666 дней на проекте
§ achievements.more777DaysInProject.title: Азино 3 топора
§ achievements.more777DaysInProject.description: отработал 777 дней на проекте
+§ achievements.moreDaysInProject.title: Часть команды, часть коробля
+§ achievements.moreDaysInProject.description: больше всего дней на проекте
§ achievements.moreRefactoring.title: Выпускающий редактор
§ achievements.moreRefactoring.description: сделал больше всех меток «рефакторинг»
§ achievements.longestMessage.title: А разговоров то было...