update
|
@ -85,12 +85,12 @@ Read more about the format of this file you can [here](https://git-scm.com/docs/
|
||||||
#### For online viewing
|
#### For online viewing
|
||||||
In the root directory of your project run:
|
In the root directory of your project run:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-5"></a>
|
<a name="link-5"></a>
|
||||||
#### For offline viewing
|
#### For offline viewing
|
||||||
```
|
```
|
||||||
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 --raw --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 will create a file `log.txt`.
|
Git will create a file `log.txt`.
|
||||||
This file contains data for show a report.
|
This file contains data for show a report.
|
||||||
|
|
|
@ -80,12 +80,12 @@ Sie können mehr über das format dieser datei lesen[hier](https://git-scm.com/d
|
||||||
#### Für die onlineansicht
|
#### Für die onlineansicht
|
||||||
In der wurzelverzeichnis ihres projektes ausführen:
|
In der wurzelverzeichnis ihres projektes ausführen:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### Zum surfen ohne internet
|
#### Zum surfen ohne internet
|
||||||
```
|
```
|
||||||
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 erstellt eine datei `log.txt`.
|
Git erstellt eine datei `log.txt`.
|
||||||
Diese datei enthält die daten zum erstellen des berichts.
|
Diese datei enthält die daten zum erstellen des berichts.
|
||||||
|
|
|
@ -81,12 +81,12 @@ Read more about the format of this file you can [here](https://git-scm.com/docs/
|
||||||
#### For online viewing
|
#### For online viewing
|
||||||
In the root directory of your project run:
|
In the root directory of your project run:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### For offline viewing
|
#### For offline viewing
|
||||||
```
|
```
|
||||||
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 will create a file `log.txt`.
|
Git will create a file `log.txt`.
|
||||||
This file contains data for show a report.
|
This file contains data for show a report.
|
||||||
|
|
|
@ -81,12 +81,12 @@ Más información sobre el formato de este archivo se puede leer en [aquí](http
|
||||||
#### Para la visualización en línea
|
#### Para la visualización en línea
|
||||||
En el directorio raíz de su proyecto ejecutar:
|
En el directorio raíz de su proyecto ejecutar:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### Para ver sin conexión
|
#### Para ver sin conexión
|
||||||
```
|
```
|
||||||
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 creará un archivo `log.txt`.
|
Git creará un archivo `log.txt`.
|
||||||
contiene los datos para construir el informe.
|
contiene los datos para construir el informe.
|
||||||
|
|
|
@ -81,12 +81,12 @@ Vous pouvez en savoir plus sur le format de ce fichier en lisant la documentatio
|
||||||
#### Pour une visualisation en ligne
|
#### Pour une visualisation en ligne
|
||||||
Dans le répertoire racine de votre projet, exécutez:
|
Dans le répertoire racine de votre projet, exécutez:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### Pour la navigation hors ligne
|
#### Pour la navigation hors ligne
|
||||||
```
|
```
|
||||||
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 va créer le fichier `log.txt`.
|
Git va créer le fichier `log.txt`.
|
||||||
Son contenu est destiné à la création de rapports.
|
Son contenu est destiné à la création de rapports.
|
||||||
|
|
|
@ -81,12 +81,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### Дオンラインで見るため
|
#### Дオンラインで見るため
|
||||||
プロジェクトのルートディレクトリに次のコマンドを入力します:
|
プロジェクトのルートディレクトリに次のコマンドを入力します:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### インターネットなしで見るために
|
#### インターネットなしで見るために
|
||||||
```
|
```
|
||||||
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`.
|
Gitはファイルを作成します `log.txt`.
|
||||||
このファイルには、レポートを構築するためのデータが含まれています。
|
このファイルには、レポートを構築するためのデータが含まれています。
|
||||||
|
|
|
@ -81,12 +81,12 @@ Pode ler mais sobre o formato deste arquivo em [aqui](https://git-scm.com/docs/g
|
||||||
#### Para visualização online
|
#### Para visualização online
|
||||||
No diretório raiz do seu projeto executar:
|
No diretório raiz do seu projeto executar:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### Para ver sem internet
|
#### Para ver sem internet
|
||||||
```
|
```
|
||||||
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 criar um ficheiro `log.txt`.
|
Git criar um ficheiro `log.txt`.
|
||||||
Esse arquivo contém dados para construção de relatórios.
|
Esse arquivo contém dados para construção de relatórios.
|
||||||
|
|
|
@ -80,12 +80,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### Для онлайн просмотра
|
#### Для онлайн просмотра
|
||||||
В корневой директории вашего проекта выполнить:
|
В корневой директории вашего проекта выполнить:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### Для офлайн просмотра
|
#### Для офлайн просмотра
|
||||||
```
|
```
|
||||||
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`.
|
Git создаст файл `log.txt`.
|
||||||
Он содержит данные для построения отчёта.
|
Он содержит данные для построения отчёта.
|
||||||
|
|
|
@ -80,12 +80,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### 供网上浏览
|
#### 供网上浏览
|
||||||
在项目的根目录执行:
|
在项目的根目录执行:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-8"></a>
|
<a name="link-8"></a>
|
||||||
#### 在没有互联网的情况下观看
|
#### 在没有互联网的情况下观看
|
||||||
```
|
```
|
||||||
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`.
|
Git会创建一个文件 `log.txt`.
|
||||||
这个文件包含了构建报告的数据。
|
这个文件包含了构建报告的数据。
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pre": [
|
"pre": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,12 +45,12 @@ Sie können mehr über das format dieser datei lesen[hier](https://git-scm.com/d
|
||||||
#### Für die onlineansicht
|
#### Für die onlineansicht
|
||||||
In der wurzelverzeichnis ihres projektes ausführen:
|
In der wurzelverzeichnis ihres projektes ausführen:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
#### Zum surfen ohne internet
|
#### Zum surfen ohne internet
|
||||||
|
|
||||||
```
|
```
|
||||||
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 erstellt eine datei `log.txt`.
|
Git erstellt eine datei `log.txt`.
|
||||||
Diese datei enthält die daten zum erstellen des berichts.
|
Diese datei enthält die daten zum erstellen des berichts.
|
||||||
|
|
|
@ -51,12 +51,12 @@ Read more about the format of this file you can [here](https://git-scm.com/docs/
|
||||||
#### For online viewing
|
#### For online viewing
|
||||||
In the root directory of your project run:
|
In the root directory of your project run:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-5"></a>
|
<a name="link-5"></a>
|
||||||
#### For offline viewing
|
#### For offline viewing
|
||||||
```
|
```
|
||||||
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 will create a file `log.txt`.
|
Git will create a file `log.txt`.
|
||||||
This file contains data for show a report.
|
This file contains data for show a report.
|
||||||
|
|
|
@ -47,12 +47,12 @@ Más información sobre el formato de este archivo se puede leer en [aquí](http
|
||||||
#### Para la visualización en línea
|
#### Para la visualización en línea
|
||||||
En el directorio raíz de su proyecto ejecutar:
|
En el directorio raíz de su proyecto ejecutar:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
#### Para ver sin conexión
|
#### Para ver sin conexión
|
||||||
|
|
||||||
```
|
```
|
||||||
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 creará un archivo `log.txt`.
|
Git creará un archivo `log.txt`.
|
||||||
contiene los datos para construir el informe.
|
contiene los datos para construir el informe.
|
||||||
|
|
|
@ -47,12 +47,12 @@ Vous pouvez en savoir plus sur le format de ce fichier en lisant la documentatio
|
||||||
#### Pour une visualisation en ligne
|
#### Pour une visualisation en ligne
|
||||||
Dans le répertoire racine de votre projet, exécutez:
|
Dans le répertoire racine de votre projet, exécutez:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
#### Pour la navigation hors ligne
|
#### Pour la navigation hors ligne
|
||||||
|
|
||||||
```
|
```
|
||||||
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 va créer le fichier `log.txt`.
|
Git va créer le fichier `log.txt`.
|
||||||
Son contenu est destiné à la création de rapports.
|
Son contenu est destiné à la création de rapports.
|
||||||
|
|
|
@ -47,12 +47,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### Дオンラインで見るため
|
#### Дオンラインで見るため
|
||||||
プロジェクトのルートディレクトリに次のコマンドを入力します:
|
プロジェクトのルートディレクトリに次のコマンドを入力します:
|
||||||
```
|
```
|
||||||
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`.
|
Gitはファイルを作成します `log.txt`.
|
||||||
このファイルには、レポートを構築するためのデータが含まれています。
|
このファイルには、レポートを構築するためのデータが含まれています。
|
||||||
|
|
|
@ -47,12 +47,12 @@ Pode ler mais sobre o formato deste arquivo em [aqui](https://git-scm.com/docs/g
|
||||||
#### Para visualização online
|
#### Para visualização online
|
||||||
No diretório raiz do seu projeto executar:
|
No diretório raiz do seu projeto executar:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
#### Para ver sem internet
|
#### Para ver sem internet
|
||||||
|
|
||||||
```
|
```
|
||||||
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 criar um ficheiro `log.txt`.
|
Git criar um ficheiro `log.txt`.
|
||||||
Esse arquivo contém dados para construção de relatórios.
|
Esse arquivo contém dados para construção de relatórios.
|
||||||
|
|
|
@ -49,13 +49,13 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### Для онлайн просмотра
|
#### Для онлайн просмотра
|
||||||
В корневой директории вашего проекта выполнить:
|
В корневой директории вашего проекта выполнить:
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
<a name="link-5"></a>
|
<a name="link-5"></a>
|
||||||
#### Для офлайн просмотра
|
#### Для офлайн просмотра
|
||||||
|
|
||||||
```
|
```
|
||||||
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`.
|
Git создаст файл `log.txt`.
|
||||||
Он содержит данные для построения отчёта.
|
Он содержит данные для построения отчёта.
|
||||||
|
|
|
@ -50,12 +50,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### Для онлайн просмотра
|
#### Для онлайн просмотра
|
||||||
В корневой директории вашего проекта выполнить:
|
В корневой директории вашего проекта выполнить:
|
||||||
```
|
```
|
||||||
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`.
|
Git создаст файл `log.txt`.
|
||||||
Этот файл содержит данные для построения отчёта.
|
Этот файл содержит данные для построения отчёта.
|
||||||
|
|
|
@ -47,12 +47,12 @@ Alex B <alex@mail.uk> <man64@yahoo.com>
|
||||||
#### 供网上浏览
|
#### 供网上浏览
|
||||||
在项目的根目录执行:
|
在项目的根目录执行:
|
||||||
```
|
```
|
||||||
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`.
|
Git会创建一个文件 `log.txt`.
|
||||||
这个文件包含了构建报告的数据。
|
这个文件包含了构建报告的数据。
|
||||||
|
|
1
public/assets/achievements/more3YearsInProject.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M53.48,82.2c4.55-.83,9.56-2.3,10.81-3.6"/><path class="cls-1" d="M53.48,82.2c-1.62,7.46,8.51,7.81,10.81,4.67"/><path class="cls-1" d="M64.29,78.59c1.25,1.3,6.26,2.77,10.81,3.6"/><path class="cls-1" d="M64.29,86.87c2.3,3.14,12.43,2.79,10.81-4.67"/><g><g><path class="cls-1" d="M64.41,109.89c-26.76,0-28.81-18.57-28.93-41.24"/><path class="cls-1" d="M64.41,18.94c-26.76,0-28.81,11.58-28.93,25.72"/><path class="cls-1" d="M64.41,69.19c-7.99,0-13.18,.64-17.42,5.67"/><path class="cls-1" d="M35.48,44.66c-3.74,0-6.77,5.37-6.77,11.99s3.03,11.99,6.77,11.99"/><path class="cls-1" d="M39.76,26.74c.16-.1,2.27,4.62,4.51,6.67"/><path class="cls-1" d="M40.68,44.66s0-6.99,3.58-11.25"/><path class="cls-1" d="M40.68,63.52c0,6.82,6.31,11.34,6.31,11.34"/><line class="cls-1" x1="40.68" y1="44.66" x2="40.68" y2="63.52"/></g><g><path class="cls-1" d="M64.29,109.89c26.76,0,28.81-18.57,28.93-41.24"/><path class="cls-1" d="M93.35,44.66c3.74,0,6.77,5.37,6.77,11.99,0,6.62-3.03,11.99-6.77,11.99"/><path class="cls-1" d="M64.41,18.94c26.76,0,28.81,11.58,28.93,25.72"/><path class="cls-1" d="M64.41,69.19c7.99,0,13.18,.64,17.42,5.67"/><path class="cls-1" d="M89.07,26.74c-.16-.1-2.27,4.62-4.51,6.67"/><path class="cls-1" d="M88.14,44.66s0-6.99-3.58-11.25"/><path class="cls-1" d="M88.14,63.52c0,6.82-6.31,11.34-6.31,11.34"/><line class="cls-1" x1="88.14" y1="44.66" x2="88.14" y2="63.52"/></g></g><g><path class="cls-1" d="M45.18,50.66c6.64-2.47,14.92,.94,14.92,.94"/><path class="cls-1" d="M83.65,50.66c-6.64-2.47-14.92,.94-14.92,.94"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
1
public/assets/achievements/moreCreateCode.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><rect class="cls-1" x="70.58" y="78.58" width="27.71" height="11.8"/><rect class="cls-1" x="42.87" y="78.58" width="27.71" height="11.8"/><rect class="cls-1" x="84.44" y="90.38" width="13.85" height="11.8"/><rect class="cls-1" x="56.73" y="90.38" width="27.71" height="11.8"/><rect class="cls-1" x="84.44" y="66.77" width="13.85" height="11.8"/><rect class="cls-1" x="56.73" y="66.77" width="27.71" height="11.8"/><rect class="cls-1" x="29.02" y="90.38" width="27.71" height="11.8"/><path class="cls-1" d="M80.86,31.81h18.32c2.93,0,5.31,2.38,5.31,5.31h0c0,2.93-2.38,5.31-5.31,5.31h-18.32v-10.62h0Z"/><polygon class="cls-1" points="64.2 37.12 80.86 42.43 80.86 31.81 64.2 37.12"/><line class="cls-1" x1="64.2" y1="37.12" x2="56.8" y2="48.74"/><path class="cls-1" d="M56.8,48.74c0-6.59-5.34-11.94-11.94-11.94s-18.65,5.15-28.14,11.94c8.58,6,21.55,11.94,28.14,11.94s11.94-5.34,11.94-11.94Z"/><line class="cls-1" x1="56.8" y1="48.74" x2="36.76" y2="48.74"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
public/assets/achievements/moreRemoveCode.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M76.94,97.96h21.46c2.15,0,3.9-1.76,3.9-3.9V40.53"/><path class="cls-1" d="M80.44,20.86H36.33c-2.15,0-3.9,1.76-3.9,3.9v47.37"/><polyline class="cls-1" points="92.74 51.45 42.96 51.45 42.96 60.27 92.74 60.27 92.74 51.45"/><line class="cls-1" x1="80.44" y1="20.86" x2="102.3" y2="40.53"/><line class="cls-1" x1="32.42" y1="72.13" x2="42.96" y2="72.13"/><line class="cls-1" x1="47.05" y1="80.74" x2="42.96" y2="72.13"/><line class="cls-1" x1="47.05" y1="80.74" x2="57.91" y2="80.74"/><line class="cls-1" x1="62" y1="89.35" x2="57.91" y2="80.74"/><line class="cls-1" x1="62" y1="89.35" x2="72.85" y2="89.35"/><line class="cls-1" x1="76.94" y1="97.96" x2="72.85" y2="89.35"/><g><path class="cls-1" d="M26.53,82.13v21.93c0,2.15,1.76,3.9,3.9,3.9h40.61"/><line class="cls-1" x1="26.53" y1="82.13" x2="37.07" y2="82.13"/><line class="cls-1" x1="41.16" y1="90.74" x2="37.07" y2="82.13"/><line class="cls-1" x1="41.16" y1="90.74" x2="52.01" y2="90.74"/><line class="cls-1" x1="56.1" y1="99.35" x2="52.01" y2="90.74"/><line class="cls-1" x1="56.1" y1="99.35" x2="66.95" y2="99.35"/><line class="cls-1" x1="71.05" y1="107.96" x2="66.95" y2="99.35"/></g><path class="cls-1" d="M102.3,40.53h-18.75c-1.72,0-3.11-1.39-3.11-3.11V20.86"/><g><polyline class="cls-1" points="70.29 34.14 42.96 34.14 42.96 42.95 70.29 42.95"/><line class="cls-1" x1="70.29" y1="34.14" x2="70.29" y2="42.95"/></g><g><polyline class="cls-1" points="92.74 68.76 65.41 68.76 65.41 77.58 92.74 77.58"/><line class="cls-1" x1="92.74" y1="68.76" x2="92.74" y2="77.58"/></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
public/assets/achievements/moreStyle.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{stroke-width:2.93px;}.cls-1,.cls-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-2{stroke-width:2.66px;}</style></defs><path class="cls-2" d="M45.79,39.71c10.72,8.04,26.54,8.04,37.26,0m0,3.75v-9.65l6.83-9.99c1.06-1.55-.05-3.64-1.92-3.64H40.88c-1.87,0-2.98,2.1-1.92,3.64l6.83,9.99v9.65m37.26,3.02c0,11.19-8.34,20.27-18.63,20.27s-18.63-9.07-18.63-20.27v-12.67h37.26v12.67Zm-26.57,18.34v11.71m15.88-11.71v11.71m-15.88,0h-18.3c-8.71,0-15.77,7.06-15.77,15.77v12.75H106.43v-12.75c0-8.71-7.06-15.77-15.77-15.77h-18.3m-15.88,0l7.94,14.26m0,0l7.94-14.26m-26.57,.55l18.63,27.97m0,0l18.63-27.97m-16.12-49.72c0-1.38-1.12-2.5-2.5-2.5s-2.5,1.12-2.5,2.5,1.12,2.5,2.5,2.5,2.5-1.12,2.5-2.5Z"/><line class="cls-1" x1="47.62" y1="70.67" x2="26.78" y2="70.67"/><line class="cls-1" x1="102.02" y1="70.67" x2="81.18" y2="70.67"/></svg>
|
After Width: | Height: | Size: 977 B |
BIN
public/social/vk/awesomejs.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/social/vk/front_work.jpg
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
public/social/vk/front_work.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
public/social/vk/frontend_dev.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
public/social/vk/frontend_du2.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
public/social/vk/frontend_du2.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
public/social/vk/logo.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
public/social/vk/take_off_staff.jpg
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
public/social/vk/take_off_staff.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
28
src/ts/components/Banner/index.module.scss
Normal file
|
@ -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;
|
||||||
|
}
|
71
src/ts/components/Banner/index.tsx
Normal file
|
@ -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 (
|
||||||
|
<Link {...props}>
|
||||||
|
<div
|
||||||
|
className={style.banner}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${banner})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Link {...props}>
|
||||||
|
<div
|
||||||
|
title={title}
|
||||||
|
className={style.banner}
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
background,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{bannerText || textFromRef || ''}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Banner;
|
27
src/ts/components/CardWithIcon/Banner.tsx
Normal file
|
@ -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 (
|
||||||
|
<Banner className={`${className} ${style.card_with_icon_banner}`} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CardWithBanner.defaultProps = {
|
||||||
|
long: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardWithBanner;
|
|
@ -73,12 +73,14 @@ export default function getFakeLoader({
|
||||||
const totalElements = sortedContent.length;
|
const totalElements = sortedContent.length;
|
||||||
const totalPages = Math.ceil(totalElements / size);
|
const totalPages = Math.ceil(totalElements / size);
|
||||||
|
|
||||||
return Promise.resolve({
|
const response = {
|
||||||
size,
|
size,
|
||||||
number: page,
|
number: page,
|
||||||
totalPages,
|
totalPages,
|
||||||
totalElements,
|
totalElements,
|
||||||
sort: sort || [],
|
sort: sort || [],
|
||||||
content: sortedContent.slice(begin, end) || [],
|
content: sortedContent.slice(begin, end) || [],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
return Promise.resolve(response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ export class DataLoaderStore implements IDataLoaderStore {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
state: observable,
|
state: observable,
|
||||||
watchedValue: observable,
|
watchedValue: observable,
|
||||||
response: observable,
|
// response: observable,
|
||||||
sort: observable,
|
sort: observable,
|
||||||
fetchData: action,
|
fetchData: action,
|
||||||
successCallback: action,
|
successCallback: action,
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface IExtensionProps {
|
||||||
function Extension({
|
function Extension({
|
||||||
statistic,
|
statistic,
|
||||||
}: IExtensionProps): React.ReactElement | null {
|
}: IExtensionProps): React.ReactElement | null {
|
||||||
if (!statistic) return null;
|
if (!statistic || true) return null;
|
||||||
|
|
||||||
const getValue = (more: any) => `${more.author} (${more.percent.toFixed(1)}%)`;
|
const getValue = (more: any) => `${more.author} (${more.percent.toFixed(1)}%)`;
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,8 @@ function LineChart({
|
||||||
className,
|
className,
|
||||||
}: ILineChartProps): React.ReactElement | null {
|
}: ILineChartProps): React.ReactElement | null {
|
||||||
if (!value) return 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) {
|
if (!details) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default class DataGripByExtension {
|
||||||
if (!group[file.extension]) {
|
if (!group[file.extension]) {
|
||||||
group[file.extension] = this.#getNewExtension(file);
|
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;
|
group[file.extension][type].count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,13 @@ export default class DataGripByPR {
|
||||||
updateTotalByAuthor(authors: any, refAuthorPR: IHashMap<any>) {
|
updateTotalByAuthor(authors: any, refAuthorPR: IHashMap<any>) {
|
||||||
this.statisticByName = {};
|
this.statisticByName = {};
|
||||||
authors.map((name: string) => {
|
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 delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays');
|
||||||
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
|
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
|
||||||
|
|
||||||
|
@ -151,6 +158,9 @@ export default class DataGripByPR {
|
||||||
|
|
||||||
this.statisticByName[name] = {
|
this.statisticByName[name] = {
|
||||||
author: name,
|
author: name,
|
||||||
|
maxDelayDays,
|
||||||
|
numberMergedPr: refAuthorPR[name].length,
|
||||||
|
|
||||||
workDays: workDays.details,
|
workDays: workDays.details,
|
||||||
delayDays: delayDays.details,
|
delayDays: delayDays.details,
|
||||||
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
|
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
|
||||||
|
|
|
@ -7,9 +7,13 @@ export default class DataGripByTasks {
|
||||||
|
|
||||||
statistic: any = [];
|
statistic: any = [];
|
||||||
|
|
||||||
|
// achievements
|
||||||
|
longTaskByAuthor: IHashMap<number> = {};
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.commits = {};
|
this.commits = {};
|
||||||
this.statistic = [];
|
this.statistic = [];
|
||||||
|
this.longTaskByAuthor = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommit(commit: ICommit) {
|
addCommit(commit: ICommit) {
|
||||||
|
@ -67,6 +71,11 @@ export default class DataGripByTasks {
|
||||||
const to = lastCommit.milliseconds;
|
const to = lastCommit.milliseconds;
|
||||||
const daysInWork = Math.ceil((to - from) / settingsStore.ONE_DAY) + 1;
|
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 {
|
return {
|
||||||
...shortInfo,
|
...shortInfo,
|
||||||
to: to !== from ? to : undefined,
|
to: to !== from ? to : undefined,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||||
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
|
||||||
|
|
||||||
import settingsStore from 'ts/store/Settings';
|
import settingsStore from 'ts/store/Settings';
|
||||||
import Recommendations from 'ts/helpers/Recommendations';
|
import Recommendations from 'ts/helpers/Recommendations';
|
||||||
|
@ -11,7 +10,6 @@ import DataGripByType from './components/type';
|
||||||
import DataGripByTimestamp from './components/timestamp';
|
import DataGripByTimestamp from './components/timestamp';
|
||||||
import DataGripByWeek from './components/week';
|
import DataGripByWeek from './components/week';
|
||||||
import MinMaxCounter from './components/counter';
|
import MinMaxCounter from './components/counter';
|
||||||
import DataGripByExtension from './components/extension';
|
|
||||||
import DataGripByGet from './components/get';
|
import DataGripByGet from './components/get';
|
||||||
import DataGripByPR from './components/pr';
|
import DataGripByPR from './components/pr';
|
||||||
import DataGripByTasks from './components/tasks';
|
import DataGripByTasks from './components/tasks';
|
||||||
|
@ -34,8 +32,6 @@ class DataGrip {
|
||||||
|
|
||||||
recommendations: any = new Recommendations();
|
recommendations: any = new Recommendations();
|
||||||
|
|
||||||
extension: any = new DataGripByExtension();
|
|
||||||
|
|
||||||
get: any = new DataGripByGet();
|
get: any = new DataGripByGet();
|
||||||
|
|
||||||
pr: any = new DataGripByPR();
|
pr: any = new DataGripByPR();
|
||||||
|
@ -57,7 +53,6 @@ class DataGrip {
|
||||||
this.timestamp.clear();
|
this.timestamp.clear();
|
||||||
this.week.clear();
|
this.week.clear();
|
||||||
this.recommendations.clear();
|
this.recommendations.clear();
|
||||||
this.extension.clear();
|
|
||||||
this.get.clear();
|
this.get.clear();
|
||||||
this.pr.clear();
|
this.pr.clear();
|
||||||
this.tasks.clear();
|
this.tasks.clear();
|
||||||
|
@ -114,10 +109,6 @@ class DataGrip {
|
||||||
this.#updateTotalInfo();
|
this.#updateTotalInfo();
|
||||||
this.hash = Math.random();
|
this.hash = Math.random();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateByFiles(fileList: IDirtyFile[], removedFileList: IDirtyFile[]) {
|
|
||||||
this.extension.updateTotalInfo(fileList, removedFileList);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataGrip = new DataGrip();
|
const dataGrip = new DataGrip();
|
||||||
|
|
39
src/ts/helpers/FileGrip/components/FileBuilder/Common.ts
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
52
src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
90
src/ts/helpers/FileGrip/components/FileBuilder/index.ts
Normal file
|
@ -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<IDirtyFile> = {};
|
||||||
|
|
||||||
|
refRemovedFileIds: IHashMap<IDirtyFile> = {};
|
||||||
|
|
||||||
|
refExtensionType: IHashMap<IHashMap<number>> = {}; // 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
64
src/ts/helpers/FileGrip/components/extension.ts
Normal file
|
@ -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<any> = {};
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
109
src/ts/helpers/FileGrip/components/folder.ts
Normal file
|
@ -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<string[]> = {};
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
}
|
55
src/ts/helpers/FileGrip/components/type.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
|
import { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||||
|
|
||||||
|
export default class FileGripByType {
|
||||||
|
statistic: any = [];
|
||||||
|
|
||||||
|
statisticByName: IHashMap<any> = {};
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
12
src/ts/helpers/FileGrip/helpers/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
|
|
||||||
|
export function getValuesInPercent(list: IHashMap<number>, 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;
|
||||||
|
}
|
56
src/ts/helpers/FileGrip/index.ts
Normal file
|
@ -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;
|
|
@ -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),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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<IDirtyFile>) {
|
|
||||||
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 };
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit';
|
||||||
|
|
||||||
import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope';
|
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"
|
// "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile"
|
||||||
const parts = logString.split('>');
|
const parts = logString.split('>');
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ export default function getUserInfo(logString: string): ICommit | ISystemCommit
|
||||||
text: '',
|
text: '',
|
||||||
type: 'не подписан',
|
type: 'не подписан',
|
||||||
scope: 'неопределенна',
|
scope: 'неопределенна',
|
||||||
|
fileChanges: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSystemPR = message.indexOf('Pull request #') === 0;
|
const isSystemPR = message.indexOf('Pull request #') === 0;
|
75
src/ts/helpers/Parser/getFileChanges.ts
Normal file
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 IHashMap from 'ts/interfaces/HashMap';
|
||||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
|
||||||
import settingsStore from 'ts/store/Settings';
|
|
||||||
|
|
||||||
import getUserInfo from './user_info';
|
import getCommitInfo from './getCommitInfo';
|
||||||
import { getNewFileName, getFileList } from './files';
|
import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges';
|
||||||
import { getNewFileInfo } from './file_info';
|
|
||||||
|
|
||||||
const uniq = {};
|
|
||||||
export default function Parser(report: string[]) {
|
export default function Parser(report: string[]) {
|
||||||
const allFiles: IHashMap<IDirtyFile> = {};
|
let commit = null;
|
||||||
const removedFiles: IHashMap<IDirtyFile> = {};
|
|
||||||
const commits: Array<ICommit | ISystemCommit> = [];
|
const commits: Array<ICommit | ISystemCommit> = [];
|
||||||
let week: number = 0;
|
|
||||||
let weekEndTime: number = 0;
|
|
||||||
|
|
||||||
let prev = null;
|
let files: IHashMap<IFileChange> = {};
|
||||||
|
let fileChanges: IFileChange | null = null;
|
||||||
|
|
||||||
for (let i = 0, l = report.length; i < l; i += 1) {
|
for (let i = 0, l = report.length; i < l; i += 1) {
|
||||||
const message = report[i];
|
const message = report[i];
|
||||||
if (!message) continue;
|
if (!message) continue;
|
||||||
|
|
||||||
const index = message.indexOf('\t');
|
const index = message.indexOf('\t');
|
||||||
if (index > 0 && index < 10) {
|
if (index > 0 && index < 10) { // парсинг файлов формата --num-stat
|
||||||
let [addedRaw, removedRaw, fileName] = message.split('\t');
|
const line = getNumStatInfo(message);
|
||||||
const formattedFileName = fileName?.replace(/"/gm, '');
|
if (!files[line.path]) {
|
||||||
fileName = getNewFileName(formattedFileName, allFiles);
|
files[line.path] = getInfoFromPath(line.path);
|
||||||
let added = parseInt(addedRaw, 10) || 0;
|
}
|
||||||
let removed = parseInt(removedRaw, 10) || 0;
|
fileChanges = files[line.path];
|
||||||
const diff = added - removed;
|
fileChanges.addedLines = line.addedLines;
|
||||||
let changes = added > removed ? removed : added;
|
fileChanges.removedLines = line.removedLines;
|
||||||
|
fileChanges.changedLines = line.changedLines;
|
||||||
|
|
||||||
if (!allFiles[fileName] && removedFiles[fileName]) {
|
} else if (message[0] === ':') { // парсинг файлов формата --raw
|
||||||
allFiles[fileName] = removedFiles[fileName];
|
const line = getRawInfo(message);
|
||||||
delete removedFiles[fileName];
|
if (!files[line.path]) {
|
||||||
|
files[line.path] = getInfoFromPath(line.path);
|
||||||
}
|
}
|
||||||
|
fileChanges = files[line.path];
|
||||||
|
fileChanges.action = line.action;
|
||||||
|
|
||||||
if (allFiles[fileName]) {
|
} else { // парсинг коммита
|
||||||
const fileInfo: IDirtyFile = allFiles[fileName];
|
if (commit) commit.fileChanges = Object.values(files);
|
||||||
fileInfo.lastCommit = prev;
|
files = {};
|
||||||
fileInfo.lines += diff;
|
commit = getCommitInfo(message);
|
||||||
if (!fileInfo.authors[prev?.author || '']) {
|
commit.week = 1;
|
||||||
fileInfo.authors[prev?.author || ''] = {
|
commits.push(commit);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fileList, fileTree } = getFileList(allFiles);
|
return commits;
|
||||||
return {
|
|
||||||
commits,
|
|
||||||
fileList,
|
|
||||||
fileTree,
|
|
||||||
removed: getFileList(removedFiles),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import localization from './Localization';
|
import localization from './Localization';
|
||||||
|
|
||||||
function getFormattedType(dataGrip: any): string {
|
function getFormattedType(fileGrip: any): string {
|
||||||
const popularType = dataGrip.extension.statistic?.[0] || {};
|
const popularType = fileGrip.extension.statistic?.[0] || {};
|
||||||
const extension = popularType?.extension || '';
|
const extension = popularType?.extension || '';
|
||||||
|
|
||||||
if ([
|
if ([
|
||||||
|
@ -30,7 +30,7 @@ function getFormattedType(dataGrip: any): string {
|
||||||
'perl',
|
'perl',
|
||||||
'java',
|
'java',
|
||||||
].includes(extension)) {
|
].includes(extension)) {
|
||||||
const hasManifest = dataGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
|
const hasManifest = fileGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
|
||||||
return hasManifest
|
return hasManifest
|
||||||
? 'Android'
|
? 'Android'
|
||||||
: 'Back';
|
: 'Back';
|
||||||
|
@ -45,12 +45,12 @@ function getFormattedType(dataGrip: any): string {
|
||||||
return extension.toUpperCase();
|
return extension.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getTitle(dataGrip: any, commits: any) {
|
export default function getTitle(dataGrip: any, fileGrip: any, commits: any) {
|
||||||
if (!commits.length) {
|
if (!commits.length) {
|
||||||
return localization.get('common.title');
|
return localization.get('common.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = getFormattedType(dataGrip) || '';
|
const type = getFormattedType(fileGrip) || '';
|
||||||
const task = dataGrip.pr.statistic?.[0]?.task || '';
|
const task = dataGrip.pr.statistic?.[0]?.task || '';
|
||||||
const author = dataGrip.firstLastCommit.minData.author || '';
|
const author = dataGrip.firstLastCommit.minData.author || '';
|
||||||
const year = commits?.[0]?.year || '';
|
const year = commits?.[0]?.year || '';
|
||||||
|
|
|
@ -1,6 +1,37 @@
|
||||||
import ALL_ACHIEVEMENTS from './constants/list';
|
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;
|
const commitByHours = statistic.commitsByHour;
|
||||||
|
|
||||||
if (statistic.commits > 20) {
|
if (statistic.commits > 20) {
|
||||||
|
@ -40,6 +71,15 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
|
||||||
if (statistic.allDaysInProject >= 666) list.push('more666DaysInProject');
|
if (statistic.allDaysInProject >= 666) list.push('more666DaysInProject');
|
||||||
// Азино - отработал 777 дней на проекте
|
// Азино - отработал 777 дней на проекте
|
||||||
if (statistic.allDaysInProject >= 777) list.push('more777DaysInProject');
|
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 дней без коммитов
|
// Ни единого разрыва - 0 дней без коммитов
|
||||||
if (statistic.lazyDays === 0) list.push('zeroLazyDays');
|
if (statistic.lazyDays === 0) list.push('zeroLazyDays');
|
||||||
|
@ -48,6 +88,11 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
|
||||||
// Точно в цель - в среднем 1 коммит на таск
|
// Точно в цель - в среднем 1 коммит на таск
|
||||||
if (statistic.tasks / statistic.commits) list.push('oneCommitOneTask');
|
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) => {
|
return list.reduce((acc: any, type: string) => {
|
||||||
const index = ALL_ACHIEVEMENTS[type] - 1;
|
const index = ALL_ACHIEVEMENTS[type] - 1;
|
||||||
acc[index].push(type);
|
acc[index].push(type);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import IHashMap from 'ts/interfaces/HashMap';
|
import IHashMap from 'ts/interfaces/HashMap';
|
||||||
|
|
||||||
import getAchievementByAuthor from './byAuthor';
|
import getAchievementByAuthor from './byAuthor';
|
||||||
|
import getAchievementByFile from './byFile';
|
||||||
|
|
||||||
class AchievementsByAuthor {
|
class AchievementsByAuthor {
|
||||||
authors: IHashMap<string[]> = {};
|
authors: IHashMap<string[]> = {};
|
||||||
|
@ -10,11 +11,12 @@ class AchievementsByAuthor {
|
||||||
}
|
}
|
||||||
|
|
||||||
add(authors: Array<[string, number]>, maxAchievementCode: string, minAchievementCode?: string) {
|
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);
|
this.authors?.[first]?.push(maxAchievementCode);
|
||||||
|
|
||||||
if (!minAchievementCode) return;
|
if (!minAchievementCode) return;
|
||||||
const last = authors[authors.length - 1][0];
|
const last = authors?.[authors.length - 1]?.[0];
|
||||||
this.authors?.[last]?.push(minAchievementCode);
|
this.authors?.[last]?.push(minAchievementCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +24,7 @@ class AchievementsByAuthor {
|
||||||
class AchievementsByCompetition {
|
class AchievementsByCompetition {
|
||||||
authors: IHashMap<Array<string[]>> = {};
|
authors: IHashMap<Array<string[]>> = {};
|
||||||
|
|
||||||
updateByDataGrip(dataGrip: any) {
|
updateByGrip(dataGrip: any, fileGrip: any) {
|
||||||
const statisticByAuthor = dataGrip.author.statistic;
|
const statisticByAuthor = dataGrip.author.statistic;
|
||||||
const byAuthor: any = new AchievementsByAuthor();
|
const byAuthor: any = new AchievementsByAuthor();
|
||||||
const total = this.#getMinMaxValue(statisticByAuthor, dataGrip, (statistic: any) => {
|
const total = this.#getMinMaxValue(statisticByAuthor, dataGrip, (statistic: any) => {
|
||||||
|
@ -62,6 +64,12 @@ class AchievementsByCompetition {
|
||||||
// Количество коммитов в день
|
// Количество коммитов в день
|
||||||
byAuthor.add(total.commitsInDay, 'moreCommits');
|
byAuthor.add(total.commitsInDay, 'moreCommits');
|
||||||
|
|
||||||
|
// Таможня даёт добро
|
||||||
|
byAuthor.add(total.morePRMerge, 'morePRMerge');
|
||||||
|
|
||||||
|
// Давным давно, в далёкой галактике
|
||||||
|
byAuthor.add(total.moreLongWaitPR, 'moreLongWaitPR');
|
||||||
|
|
||||||
// Первый и последний коммит
|
// Первый и последний коммит
|
||||||
const lastAuthor = dataGrip.firstLastCommit.maxData.author;
|
const lastAuthor = dataGrip.firstLastCommit.maxData.author;
|
||||||
const firstAuthor = dataGrip.firstLastCommit.minData.author;
|
const firstAuthor = dataGrip.firstLastCommit.minData.author;
|
||||||
|
@ -72,10 +80,11 @@ class AchievementsByCompetition {
|
||||||
byAuthor.authors[lastAuthor].push('lastCommit');
|
byAuthor.authors[lastAuthor].push('lastCommit');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.dir(byAuthor);
|
getAchievementByFile(fileGrip, byAuthor);
|
||||||
|
|
||||||
statisticByAuthor.forEach((statistic: any) => {
|
statisticByAuthor.forEach((statistic: any) => {
|
||||||
const achievements = byAuthor.authors[statistic.author];
|
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) => {
|
statisticByAuthor.forEach((statistic: any) => {
|
||||||
callback(statistic);
|
callback(statistic);
|
||||||
|
|
||||||
const addData = (property: string, count: number) => {
|
const addData = (property: string, count?: number) => {
|
||||||
if (!total[property]) total[property] = [];
|
if (!total[property]) total[property] = [];
|
||||||
total[property].push([statistic.author, count]);
|
total[property].push([statistic.author, count || 0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
addData('nameLength', statistic.author.length);
|
addData('nameLength', statistic.author.length);
|
||||||
|
@ -101,6 +110,10 @@ class AchievementsByCompetition {
|
||||||
addData('tasksInDay', byTimestamp.tasksByTimestampCounter.max);
|
addData('tasksInDay', byTimestamp.tasksByTimestampCounter.max);
|
||||||
addData('commitsInDay', byTimestamp.commitsByTimestampCounter.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;
|
if (statistic.isStaff) return;
|
||||||
addData('allDaysInProject', statistic.allDaysInProject);
|
addData('allDaysInProject', statistic.allDaysInProject);
|
||||||
addData('lazyDays', statistic.lazyDays);
|
addData('lazyDays', statistic.lazyDays);
|
||||||
|
|
81
src/ts/helpers/achievement/byFile.ts
Normal file
|
@ -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<number> = {};
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ export default {
|
||||||
more666DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Чёрт
|
more666DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Чёрт
|
||||||
more777DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Азино 3 топора
|
more777DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Азино 3 топора
|
||||||
moreRefactoring: ACHIEVEMENT_TYPE.GOOD, // Выпускающий редактор
|
moreRefactoring: ACHIEVEMENT_TYPE.GOOD, // Выпускающий редактор
|
||||||
|
moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды
|
||||||
|
|
||||||
// нет картинки
|
// нет картинки
|
||||||
longestMessage: ACHIEVEMENT_TYPE.NORMAL, // А разговоров то было...
|
longestMessage: ACHIEVEMENT_TYPE.NORMAL, // А разговоров то было...
|
||||||
|
@ -41,39 +42,50 @@ export default {
|
||||||
noCommitOnDay: ACHIEVEMENT_TYPE.NORMAL, // Технический перерыв
|
noCommitOnDay: ACHIEVEMENT_TYPE.NORMAL, // Технический перерыв
|
||||||
hasCommitEveryTime: ACHIEVEMENT_TYPE.BAD, // Умер на работе
|
hasCommitEveryTime: ACHIEVEMENT_TYPE.BAD, // Умер на работе
|
||||||
commitsAfter1800: ACHIEVEMENT_TYPE.GOOD, // Делу время
|
commitsAfter1800: ACHIEVEMENT_TYPE.GOOD, // Делу время
|
||||||
more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Нужно чуть чуть потерпеть, отработал год и не уволился
|
more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Годовасик, отработал год и не уволился
|
||||||
|
more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте
|
||||||
firstCommit: ACHIEVEMENT_TYPE.NORMAL, // Кто первый, того и тапки. первый коммит на проекте
|
firstCommit: ACHIEVEMENT_TYPE.NORMAL, // Кто первый, того и тапки. первый коммит на проекте
|
||||||
lastCommit: ACHIEVEMENT_TYPE.NORMAL, // Я закончил. последний коммит на проекте
|
lastCommit: ACHIEVEMENT_TYPE.NORMAL, // Я закончил. последний коммит на проекте
|
||||||
firstLastCommit: 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, // Дальше без меня
|
lessWorkDays: ACHIEVEMENT_TYPE.BAD, // Дальше без меня
|
||||||
moreOnHoliday: ACHIEVEMENT_TYPE.BAD, // Нет жизни
|
moreOnHoliday: ACHIEVEMENT_TYPE.BAD, // Нет жизни
|
||||||
moreCreateCode: ACHIEVEMENT_TYPE.NORMAL, // Созидатель -- переименовать?
|
moreCreateCode: ACHIEVEMENT_TYPE.NORMAL, // Созидатель -- переименовать?
|
||||||
moreRemoveCode: ACHIEVEMENT_TYPE.NORMAL, // Разрушитель
|
moreRemoveCode: ACHIEVEMENT_TYPE.NORMAL, // Разрушитель
|
||||||
moreChangeCode: 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, // Один в поле воин. Только он работает с файлами определенного расширения
|
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, // Фулл хаус. есть релиз, собранный только из его задач
|
allRelease: ACHIEVEMENT_TYPE.NORMAL, // Фулл хаус. есть релиз, собранный только из его задач
|
||||||
longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением
|
|
||||||
longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем
|
|
||||||
|
|
||||||
removeCreateFile: ACHIEVEMENT_TYPE.NORMAL, // Откопал стюардессу. востановил удаленный файл
|
removeCreateFile: ACHIEVEMENT_TYPE.NORMAL, // Откопал стюардессу. востановил удаленный файл
|
||||||
renameFile: ACHIEVEMENT_TYPE.NORMAL, // Астана Нур-Султан Астана. переименовывал туда-сюда файл
|
renameFile: ACHIEVEMENT_TYPE.NORMAL, // Астана Нур-Султан Астана. переименовывал туда-сюда файл
|
||||||
longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев
|
|
||||||
// Галя, у нас отмена - откатил назад
|
// Галя, у нас отмена - откатил назад
|
||||||
// У меня работает - больше всего коммитов с текстом fix
|
// У меня работает - больше всего коммитов с текстом fix
|
||||||
// 500я на проде - больше всего коммитов с префиксом hotfix
|
// 500я на проде - больше всего коммитов с префиксом hotfix
|
||||||
|
@ -81,6 +93,4 @@ export default {
|
||||||
// godFather: ACHIEVEMENT_TYPE.NORMAL, // Крёстный отец. Первый создал файлы небольшой группы.
|
// godFather: ACHIEVEMENT_TYPE.NORMAL, // Крёстный отец. Первый создал файлы небольшой группы.
|
||||||
// Боярин - есть папка на 20 файлов, где правит только этот человек
|
// Боярин - есть папка на 20 файлов, где правит только этот человек
|
||||||
// Феодал - есть папка на 50 файлов, где правит только этот человек
|
// Феодал - есть папка на 50 файлов, где правит только этот человек
|
||||||
|
|
||||||
// Психологический знак задиака по дате первого коммита
|
|
||||||
};
|
};
|
||||||
|
|
20
src/ts/interfaces/Banner.ts
Normal file
|
@ -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;
|
|
@ -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 {
|
export interface ILog {
|
||||||
// date
|
// date
|
||||||
date: string; // "2021-02-09T12:59:17+03:00",
|
date: string; // "2021-02-09T12:59:17+03:00",
|
||||||
|
@ -22,6 +36,8 @@ export interface ILog {
|
||||||
taskNumber: string; // "0000",
|
taskNumber: string; // "0000",
|
||||||
type: string; // feat|fix|docs|style|refactor|test|chore
|
type: string; // feat|fix|docs|style|refactor|test|chore
|
||||||
scope: string; // table, sale, profile and etc.
|
scope: string; // table, sale, profile and etc.
|
||||||
|
|
||||||
|
fileChanges: IFileChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const COMMIT_TYPE = {
|
export const COMMIT_TYPE = {
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
import ICommit, { ISystemCommit } from './Commit';
|
import ICommit, { ISystemCommit } from './Commit';
|
||||||
import IHashMap from './HashMap';
|
import IHashMap from './HashMap';
|
||||||
|
|
||||||
export interface IDirtyFile {
|
interface IFileStat {
|
||||||
name: string; // ".gitignore",
|
|
||||||
lines: number; // 38, line in file for this moment
|
lines: number; // 38, line in file for this moment
|
||||||
|
|
||||||
|
addedLines: number;
|
||||||
|
removedLines: number;
|
||||||
|
changedLines: number;
|
||||||
|
|
||||||
|
addedLinesByAuthor: IHashMap<number>; // added lines by author
|
||||||
|
removedLinesByAuthor: IHashMap<number>; // removed lines by author
|
||||||
|
changedLinesByAuthor: IHashMap<number>; // removed lines by author
|
||||||
|
|
||||||
|
addedByAuthorInPercent: IHashMap<number>;
|
||||||
|
removedByAuthorInPercent: IHashMap<number>;
|
||||||
|
changedByAuthorInPercent: IHashMap<number>;
|
||||||
|
addedRemovedChangedInPercent: IHashMap<number>;
|
||||||
|
|
||||||
firstCommit: ICommit | ISystemCommit | null,
|
firstCommit: ICommit | ISystemCommit | null,
|
||||||
lastCommit: 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;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
firstCommit: ICommit | ISystemCommit | null,
|
path: string[]; // ['src']
|
||||||
lastCommit: ICommit | ISystemCommit | null,
|
pathString: string; // 'src\\ts'
|
||||||
content: IHashMap<IDirtyFile>,
|
content: IHashMap<IDirtyFile>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import Console from 'ts/components/Console';
|
||||||
import style from '../styles/index.module.scss';
|
import style from '../styles/index.module.scss';
|
||||||
|
|
||||||
function MailMap(): React.ReactElement | null {
|
function MailMap(): React.ReactElement | null {
|
||||||
const items = dataGripStore.dataGrip.author.statistic.map((item: any) => (
|
const items = dataGripStore.dataGrip.author.statistic
|
||||||
`${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`
|
.map((item: any) => `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`)
|
||||||
));
|
.sort();
|
||||||
const commands = items.map((text: string) => (<p key={text}>{text}</p>));
|
const commands = items.map((text: any) => (<p key={text}>{text}</p>));
|
||||||
const commandsForCopy = items.join('\r\n');
|
const commandsForCopy = items.join('\r\n');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -29,8 +29,8 @@ interface IFilesViewProps {
|
||||||
function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
|
function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
|
||||||
if (!response) return null;
|
if (!response) return null;
|
||||||
|
|
||||||
const current = getMax(response, 'current', 'count');
|
const current = getMax(response, 'count');
|
||||||
const removed = getMax(response, 'removed', 'count');
|
const removed = getMax(response, 'removedCount');
|
||||||
const max = Math.max(current, removed);
|
const max = Math.max(current, removed);
|
||||||
const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
|
const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
|
||||||
|
|
||||||
|
@ -47,14 +47,14 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP
|
||||||
isFixed
|
isFixed
|
||||||
template={ColumnTypesEnum.STRING}
|
template={ColumnTypesEnum.STRING}
|
||||||
title="page.team.extension.name"
|
title="page.team.extension.name"
|
||||||
properties="extension"
|
properties="type"
|
||||||
width={200}
|
width={200}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
template={ColumnTypesEnum.STRING}
|
template={ColumnTypesEnum.STRING}
|
||||||
title="page.team.extension.path"
|
title="page.team.extension.path"
|
||||||
width={350}
|
width={350}
|
||||||
properties="path"
|
formatter={(row: any) => row.count === 1 || row.removedCount === 1 ? row.path : ''}
|
||||||
/>
|
/>
|
||||||
{mode === 'print' ? (
|
{mode === 'print' ? (
|
||||||
<Column
|
<Column
|
||||||
|
@ -82,37 +82,35 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP
|
||||||
)}
|
)}
|
||||||
<Column
|
<Column
|
||||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
properties="current"
|
properties="count"
|
||||||
formatter={(value: any) => value.count}
|
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
isSortable
|
isSortable
|
||||||
title="page.team.extension.current.count"
|
title="page.team.extension.current.count"
|
||||||
properties="current"
|
properties="count"
|
||||||
width={170}
|
width={170}
|
||||||
minWidth={170}
|
minWidth={170}
|
||||||
template={(value: any) => (
|
template={(value: number) => (
|
||||||
<LineChart
|
<LineChart
|
||||||
options={filesChart}
|
options={filesChart}
|
||||||
value={value.count}
|
value={value}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
properties="removed"
|
properties="removedCount"
|
||||||
formatter={(value: any) => value.count}
|
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
isSortable
|
isSortable
|
||||||
title="page.team.extension.removed.count"
|
title="page.team.extension.removed.count"
|
||||||
properties="removed"
|
properties="removedCount"
|
||||||
width={170}
|
width={170}
|
||||||
minWidth={170}
|
minWidth={170}
|
||||||
template={(value: any) => (
|
template={(value: number) => (
|
||||||
<LineChart
|
<LineChart
|
||||||
options={filesChart}
|
options={filesChart}
|
||||||
value={value.count}
|
value={value}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -127,7 +125,7 @@ ExtensionView.defaultProps = {
|
||||||
const Extension = observer(({
|
const Extension = observer(({
|
||||||
mode,
|
mode,
|
||||||
}: ICommonPageProps): React.ReactElement | null => {
|
}: ICommonPageProps): React.ReactElement | null => {
|
||||||
const rows = dataGripStore.dataGrip.extension.statistic;
|
const rows = dataGripStore.fileGrip.type.statistic;
|
||||||
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
|
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
|
||||||
|
|
||||||
return (
|
return (
|
151
src/ts/pages/Team/components/FileAnalitics/Extension.tsx
Normal file
|
@ -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<any>;
|
||||||
|
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 (
|
||||||
|
<DataView
|
||||||
|
rowsForExcel={rowsForExcel}
|
||||||
|
rows={response.content}
|
||||||
|
sort={response.sort}
|
||||||
|
updateSort={updateSort}
|
||||||
|
type={mode === 'print' ? 'cards' : undefined}
|
||||||
|
columnCount={mode === 'print' ? 3 : undefined}
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
isFixed
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.extension.name"
|
||||||
|
properties="extension"
|
||||||
|
width={200}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.extension.path"
|
||||||
|
width={350}
|
||||||
|
formatter={(row: any) => row.count === 1 || row.removedCount === 1 ? row.path : ''}
|
||||||
|
/>
|
||||||
|
{mode === 'print' ? (
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.pr.task"
|
||||||
|
properties="task"
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={(value: string, row: any) => {
|
||||||
|
if (!row.path) return '';
|
||||||
|
return (
|
||||||
|
<ExternalLink
|
||||||
|
link={`${userSettings?.settings?.linksPrefix?.task || '/'}${value}`}
|
||||||
|
text={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
title="page.team.pr.task"
|
||||||
|
properties="task"
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
|
properties="count"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.extension.current.count"
|
||||||
|
properties="count"
|
||||||
|
width={170}
|
||||||
|
minWidth={170}
|
||||||
|
template={(value: number) => (
|
||||||
|
<LineChart
|
||||||
|
options={filesChart}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
|
properties="removedCount"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.extension.removed.count"
|
||||||
|
properties="removedCount"
|
||||||
|
width={170}
|
||||||
|
minWidth={170}
|
||||||
|
template={(value: number) => (
|
||||||
|
<LineChart
|
||||||
|
options={filesChart}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</DataView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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' ? (<NothingFound />) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title title="sidebar.team.extension"/>
|
||||||
|
<DataLoader
|
||||||
|
to="response"
|
||||||
|
loader={(pagination?: IPaginationRequest) => getFakeLoader({
|
||||||
|
content: rows, pagination, mode,
|
||||||
|
})}
|
||||||
|
watch={`${mode}${dataGripStore.dataGrip.hash}`}
|
||||||
|
>
|
||||||
|
<ExtensionView
|
||||||
|
mode={mode}
|
||||||
|
rowsForExcel={rows}
|
||||||
|
/>
|
||||||
|
<Pagination />
|
||||||
|
</DataLoader>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Extension;
|
151
src/ts/pages/Team/components/FileAnalitics/Type.tsx
Normal file
|
@ -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<any>;
|
||||||
|
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 (
|
||||||
|
<DataView
|
||||||
|
rowsForExcel={rowsForExcel}
|
||||||
|
rows={response.content}
|
||||||
|
sort={response.sort}
|
||||||
|
updateSort={updateSort}
|
||||||
|
type={mode === 'print' ? 'cards' : undefined}
|
||||||
|
columnCount={mode === 'print' ? 3 : undefined}
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
isFixed
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.extension.name"
|
||||||
|
properties="type"
|
||||||
|
width={200}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.extension.path"
|
||||||
|
width={350}
|
||||||
|
formatter={(row: any) => row.count === 1 || row.removedCount === 1 ? row.path : ''}
|
||||||
|
/>
|
||||||
|
{mode === 'print' ? (
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.pr.task"
|
||||||
|
properties="task"
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={(value: string, row: any) => {
|
||||||
|
if (!row.path) return '';
|
||||||
|
return (
|
||||||
|
<ExternalLink
|
||||||
|
link={`${userSettings?.settings?.linksPrefix?.task || '/'}${value}`}
|
||||||
|
text={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
title="page.team.pr.task"
|
||||||
|
properties="task"
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
|
properties="count"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.extension.current.count"
|
||||||
|
properties="count"
|
||||||
|
width={170}
|
||||||
|
minWidth={170}
|
||||||
|
template={(value: number) => (
|
||||||
|
<LineChart
|
||||||
|
options={filesChart}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
|
properties="removedCount"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
title="page.team.extension.removed.count"
|
||||||
|
properties="removedCount"
|
||||||
|
width={170}
|
||||||
|
minWidth={170}
|
||||||
|
template={(value: number) => (
|
||||||
|
<LineChart
|
||||||
|
options={filesChart}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</DataView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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' ? (<NothingFound />) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title title="sidebar.team.extension"/>
|
||||||
|
<DataLoader
|
||||||
|
to="response"
|
||||||
|
loader={(pagination?: IPaginationRequest) => getFakeLoader({
|
||||||
|
content: rows, pagination, mode,
|
||||||
|
})}
|
||||||
|
watch={`${mode}${dataGripStore.dataGrip.hash}`}
|
||||||
|
>
|
||||||
|
<TypeView
|
||||||
|
mode={mode}
|
||||||
|
rowsForExcel={rows}
|
||||||
|
/>
|
||||||
|
<Pagination />
|
||||||
|
</DataLoader>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Type;
|
20
src/ts/pages/Team/components/FileAnalitics/index.tsx
Normal file
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<Extension mode={mode} />
|
||||||
|
<Type mode={mode} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FileAnalitics;
|
48
src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx
Normal file
|
@ -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) => (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
key={`${dirName}.`}
|
||||||
|
className={style.file_breadcrumbs_text}
|
||||||
|
>
|
||||||
|
{'/'}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
key={dirName}
|
||||||
|
className={`${style.file_breadcrumbs_text} ${style.file_breadcrumbs_link}`}
|
||||||
|
onClick={() => {
|
||||||
|
const newPath = treeStore.selectedPath.slice(0, index + 1);
|
||||||
|
treeStore.updateFilter('selectedPath', newPath);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{dirName}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h3 className={style.file_breadcrumbs}>
|
||||||
|
<span className={style.file_breadcrumbs_text}>
|
||||||
|
Адрес:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`${style.file_breadcrumbs_text} ${style.file_breadcrumbs_link}`}
|
||||||
|
onClick={() => {
|
||||||
|
treeStore.updateFilter('selectedPath', []);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'..'}
|
||||||
|
</span>
|
||||||
|
{directories}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FileBreadcrumbs;
|
|
@ -6,8 +6,8 @@ import dataGripStore from 'ts/store/DataGrip';
|
||||||
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||||
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
|
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
|
||||||
|
|
||||||
import treeStore from '../store/Tree';
|
import treeStore from '../../store/Tree';
|
||||||
import style from '../styles/filters.module.scss';
|
import style from '../../styles/filters.module.scss';
|
||||||
|
|
||||||
const TreeFilters = observer((): React.ReactElement => {
|
const TreeFilters = observer((): React.ReactElement => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
156
src/ts/pages/Team/components/Files/Table.tsx
Normal file
|
@ -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<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Table
|
||||||
|
rows={response.content}
|
||||||
|
disabledRow={(row: any) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
isFixed
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
formatter={(row: any) => row?.content ? `📁 ${row?.name}` : `📄 ${row?.name}`}
|
||||||
|
minWidth={170}
|
||||||
|
onClick={(row: any) => {
|
||||||
|
if (!row.content) return;
|
||||||
|
treeStore.updateFilter('selectedPath', [...row.path, row.name]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
width={50}
|
||||||
|
properties="lines"
|
||||||
|
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
properties="lines"
|
||||||
|
minWidth={100}
|
||||||
|
template={(value: any) => (
|
||||||
|
<LineChart
|
||||||
|
options={fileSizeChart}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.pr.firstCommitTime"
|
||||||
|
formatter={(item: any) => getDate(item?.firstCommit?.timestamp)}
|
||||||
|
width={130}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.pr.author"
|
||||||
|
formatter={(item: any) => item?.firstCommit?.author || ''}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.pr.lastCommitTime"
|
||||||
|
formatter={(item: any) => getDate(item?.lastCommit?.timestamp)}
|
||||||
|
width={130}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
isSortable
|
||||||
|
template={ColumnTypesEnum.STRING}
|
||||||
|
title="page.team.pr.author"
|
||||||
|
formatter={(item: any) => item?.lastCommit?.author || ''}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
minWidth={200}
|
||||||
|
template={(file: IDirtyFile) => (
|
||||||
|
<LineChart
|
||||||
|
value={100}
|
||||||
|
options={addedRemovedChangedChart}
|
||||||
|
details={{
|
||||||
|
'page.team.tree.lineAdd': file?.addedRemovedChangedInPercent?.added || 0,
|
||||||
|
'page.team.tree.lineRemove': file?.addedRemovedChangedInPercent?.removed || 0,
|
||||||
|
'page.team.tree.lineChange': file?.addedRemovedChangedInPercent?.changed || 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="page.team.tree.add"
|
||||||
|
minWidth={200}
|
||||||
|
template={(file: IDirtyFile) => (
|
||||||
|
<LineChart
|
||||||
|
value={100}
|
||||||
|
options={addedLinesChart}
|
||||||
|
details={file?.addedByAuthorInPercent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="page.team.tree.change"
|
||||||
|
minWidth={200}
|
||||||
|
template={(file: IDirtyFile) => (
|
||||||
|
<LineChart
|
||||||
|
value={100}
|
||||||
|
options={addedLinesChart}
|
||||||
|
details={file?.changedByAuthorInPercent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="page.team.tree.remove"
|
||||||
|
minWidth={200}
|
||||||
|
template={(file: IDirtyFile) => (
|
||||||
|
<LineChart
|
||||||
|
value={100}
|
||||||
|
options={addedLinesChart}
|
||||||
|
details={file?.removedByAuthorInPercent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
View.defaultProps = {
|
||||||
|
response: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default View;
|
61
src/ts/pages/Team/components/Files/index.tsx
Normal file
|
@ -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 <NothingFound />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title title={t('common.filters')} />
|
||||||
|
<TreeFilters/>
|
||||||
|
<FileBreadcrumbs />
|
||||||
|
<PageWrapper template="table">
|
||||||
|
<DataLoader
|
||||||
|
to="response"
|
||||||
|
loader={(pagination?: IPaginationRequest) => getFakeLoader({
|
||||||
|
content, pagination: { ...pagination, size: 2000 },
|
||||||
|
})}
|
||||||
|
watch={`${treeStore.hash}${type}`}
|
||||||
|
>
|
||||||
|
<View />
|
||||||
|
<Pagination />
|
||||||
|
</DataLoader>
|
||||||
|
</PageWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Tree;
|
|
@ -36,7 +36,6 @@ function AllPR({
|
||||||
order: dataGripStore.dataGrip.author.list,
|
order: dataGripStore.dataGrip.author.list,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(commitsChart);
|
|
||||||
return (
|
return (
|
||||||
<DataView
|
<DataView
|
||||||
rowsForExcel={rowsForExcel}
|
rowsForExcel={rowsForExcel}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { getDate } from 'ts/helpers/formatter';
|
||||||
import style from '../styles/quiz.module.scss';
|
import style from '../styles/quiz.module.scss';
|
||||||
|
|
||||||
const Top = observer((): React.ReactElement => {
|
const Top = observer((): React.ReactElement => {
|
||||||
const extensions = dataGripStore.dataGrip.extension.statistic
|
const extensions = dataGripStore.fileGrip.extension.statistic
|
||||||
.slice(0, 4).map((statistic: any) => {
|
.slice(0, 4).map((statistic: any) => {
|
||||||
return (
|
return (
|
||||||
<Extension
|
<Extension
|
||||||
|
|
|
@ -1,199 +0,0 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { IPaginationRequest, IPagination } 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 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 NothingFound from 'ts/components/NothingFound';
|
|
||||||
|
|
||||||
import { getDate } from 'ts/helpers/formatter';
|
|
||||||
|
|
||||||
import TreeFilters from './TreeFilters';
|
|
||||||
import { getSubTreeByPath, getArrayFromTree } from '../helpers/tree';
|
|
||||||
import treeStore from '../store/Tree';
|
|
||||||
|
|
||||||
interface ITreeViewProps {
|
|
||||||
response?: IPagination<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Table
|
|
||||||
rows={response.content}
|
|
||||||
disabledRow={(row: any) => {
|
|
||||||
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);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Column
|
|
||||||
isFixed
|
|
||||||
template={ColumnTypesEnum.STRING}
|
|
||||||
properties="title"
|
|
||||||
minWidth={200}
|
|
||||||
onClick={(row: any) => {
|
|
||||||
treeStore.updateFilter('selectedPath', row.path || []);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
isSortable
|
|
||||||
template={ColumnTypesEnum.STRING}
|
|
||||||
title="page.team.pr.firstCommitTime"
|
|
||||||
formatter={(item: any) => getDate(item?.file?.firstCommit?.timestamp)}
|
|
||||||
width={130}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
isSortable
|
|
||||||
template={ColumnTypesEnum.STRING}
|
|
||||||
title="page.team.pr.author"
|
|
||||||
formatter={(item: any) => item?.file?.firstCommit?.author || ''}
|
|
||||||
width={150}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
isSortable
|
|
||||||
template={ColumnTypesEnum.STRING}
|
|
||||||
title="page.team.pr.lastCommitTime"
|
|
||||||
formatter={(item: any) => getDate(item?.file?.lastCommit?.timestamp)}
|
|
||||||
width={130}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
isSortable
|
|
||||||
template={ColumnTypesEnum.STRING}
|
|
||||||
title="page.team.pr.author"
|
|
||||||
formatter={(item: any) => item?.file?.lastCommit?.author || ''}
|
|
||||||
width={150}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
properties="file"
|
|
||||||
minWidth={200}
|
|
||||||
template={(file: any) => (
|
|
||||||
<LineChart
|
|
||||||
value={file ? 100 : 0}
|
|
||||||
options={rewriteChart}
|
|
||||||
details={{
|
|
||||||
'page.team.tree.lineAdd': file?.lines || 0,
|
|
||||||
'page.team.tree.lineRemove': (file?.total?.changes || 0) + (file?.total?.removed || 0),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="page.team.tree.add"
|
|
||||||
properties="file"
|
|
||||||
minWidth={200}
|
|
||||||
template={(file: any) => (
|
|
||||||
<LineChart
|
|
||||||
value={file?.total?.added ? 100 : 0}
|
|
||||||
options={fileChart}
|
|
||||||
details={getDetails(file, 'addedPercent')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="page.team.tree.change"
|
|
||||||
properties="file"
|
|
||||||
minWidth={200}
|
|
||||||
template={(file: any) => (
|
|
||||||
<LineChart
|
|
||||||
value={file?.total?.changes ? 100 : 0}
|
|
||||||
options={fileChart}
|
|
||||||
details={getDetails(file, 'changesPercent')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="page.team.tree.remove"
|
|
||||||
properties="file"
|
|
||||||
minWidth={200}
|
|
||||||
template={(file: any) => (
|
|
||||||
<LineChart
|
|
||||||
value={file?.total?.removed ? 100 : 0}
|
|
||||||
options={fileChart}
|
|
||||||
details={getDetails(file, 'removedPercent')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <NothingFound />;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
treeStore.updateFilter('selectedPath', []);
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title title={t('common.filters')} />
|
|
||||||
<TreeFilters/>
|
|
||||||
{false && treeStore.selectedPath?.join('/')}
|
|
||||||
<Title title="page.team.tree.title"/>
|
|
||||||
<PageWrapper template="table">
|
|
||||||
<DataLoader
|
|
||||||
to="response"
|
|
||||||
loader={(pagination?: IPaginationRequest) => getFakeLoader({
|
|
||||||
content: fileList, pagination: { ...pagination, size: 500 },
|
|
||||||
})}
|
|
||||||
watch={`${treeStore.hash}${type}`}
|
|
||||||
>
|
|
||||||
<TreeView />
|
|
||||||
<Pagination />
|
|
||||||
</DataLoader>
|
|
||||||
</PageWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Tree;
|
|
|
@ -1,58 +1,22 @@
|
||||||
import { IFileTree } from 'ts/interfaces/FileInfo';
|
import { IFolder } from 'ts/interfaces/FileInfo';
|
||||||
|
|
||||||
interface IFile {
|
function getSubTree(tree: IFolder, path: string[]) {
|
||||||
name: string;
|
return (path || []).reduce((subTree: any, folderName: string) => {
|
||||||
path: string[];
|
|
||||||
content: IFile[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSubTreeByPath(tree: IFileTree, path: string[]) {
|
|
||||||
let subTree: any = tree || { content: [] };
|
|
||||||
(path || []).forEach((folderName: string) => {
|
|
||||||
subTree = subTree.content[folderName] || { content: [] };
|
subTree = subTree.content[folderName] || { content: [] };
|
||||||
});
|
|
||||||
return subTree;
|
return subTree;
|
||||||
|
}, tree || { content: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSortedContent(subTree: any) {
|
||||||
function getButtonUp(file: IFile) {
|
return Object.values(subTree.content)
|
||||||
return file?.path?.length ? ({
|
.sort((a: any, b: any) => {
|
||||||
title: '..',
|
if (a.content && !b.content) return -1;
|
||||||
path: file.path.slice(0, -1),
|
if (!a.content && b.content) return 1;
|
||||||
}) : null;
|
if (a.name === b.name) return 0;
|
||||||
|
return a.name > b.name ? 1 : -1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFolderView(file: IFile) {
|
export function getContentByPath(fileTree: IFolder, path: string[]) {
|
||||||
return {
|
return getSortedContent(getSubTree(fileTree, path));
|
||||||
file,
|
|
||||||
title: `📁 ${file.name}`,
|
|
||||||
path: file.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);
|
|
||||||
}
|
}
|
|
@ -12,8 +12,8 @@ import PopularWords from './components/PopularWords';
|
||||||
import Scope from './components/Scope';
|
import Scope from './components/Scope';
|
||||||
import Tempo from './components/Tempo';
|
import Tempo from './components/Tempo';
|
||||||
import Total from './components/Total';
|
import Total from './components/Total';
|
||||||
import Tree from './components/Tree';
|
import Files from './components/Files';
|
||||||
import Extension from './components/Extension2';
|
import FileAnalitics from './components/FileAnalitics';
|
||||||
import Type from './components/Type';
|
import Type from './components/Type';
|
||||||
import Week from './components/Week';
|
import Week from './components/Week';
|
||||||
import Month from './components/Month';
|
import Month from './components/Month';
|
||||||
|
@ -34,9 +34,9 @@ function getViewById(page?: string) {
|
||||||
if (page === 'week') return <Week mode={mode}/>;
|
if (page === 'week') return <Week mode={mode}/>;
|
||||||
if (page === 'month') return <Month mode={mode}/>;
|
if (page === 'month') return <Month mode={mode}/>;
|
||||||
if (page === 'hours') return <Hours mode={mode}/>;
|
if (page === 'hours') return <Hours mode={mode}/>;
|
||||||
if (page === 'files') return <Tree/>;
|
if (page === 'files') return <Files/>;
|
||||||
if (page === 'removedFiles') return <Tree type="removed" />;
|
if (page === 'removedFiles') return <Files type="removed" />;
|
||||||
if (page === 'extension') return <Extension mode={mode}/>;
|
if (page === 'extension') return <FileAnalitics mode={mode}/>;
|
||||||
if (page === 'release') return <Release mode={mode}/>;
|
if (page === 'release') return <Release mode={mode}/>;
|
||||||
if (page === 'commits') return <Commits/>;
|
if (page === 'commits') return <Commits/>;
|
||||||
if (page === 'changes') return <Changes/>;
|
if (page === 'changes') return <Changes/>;
|
||||||
|
|
26
src/ts/pages/Team/styles/path.module.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ function WarningInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Welcome() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{process.env.REACT_APP_TYPE !== 'local' && (<WarningInfo />)}
|
{process.env.REACT_APP_TYPE !== 'local' && (<WarningInfo />)}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { makeObservable, observable, action } from 'mobx';
|
import { makeObservable, observable, action } from 'mobx';
|
||||||
|
|
||||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||||
import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo';
|
|
||||||
import achievements from 'ts/helpers/achievement/byCompetition';
|
import achievements from 'ts/helpers/achievement/byCompetition';
|
||||||
import dataGrip from 'ts/helpers/DataGrip';
|
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 Parser from 'ts/helpers/Parser';
|
||||||
import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
|
|
||||||
import getTitle from 'ts/helpers/Title';
|
import getTitle from 'ts/helpers/Title';
|
||||||
|
|
||||||
|
import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
|
||||||
import { applicationHasCustom } from 'ts/helpers/RPC';
|
import { applicationHasCustom } from 'ts/helpers/RPC';
|
||||||
|
|
||||||
import settingsStore from './Settings';
|
import settingsStore from './Settings';
|
||||||
|
@ -21,6 +22,7 @@ export enum DataParseStatusEnum {
|
||||||
interface IDataGripStore {
|
interface IDataGripStore {
|
||||||
commits: ICommit[];
|
commits: ICommit[];
|
||||||
dataGrip: any;
|
dataGrip: any;
|
||||||
|
fileGrip: any;
|
||||||
status: DataParseStatusEnum;
|
status: DataParseStatusEnum;
|
||||||
setCommits: (log?: string[]) => void;
|
setCommits: (log?: string[]) => void;
|
||||||
}
|
}
|
||||||
|
@ -28,16 +30,10 @@ interface IDataGripStore {
|
||||||
class DataGripStore implements IDataGripStore {
|
class DataGripStore implements IDataGripStore {
|
||||||
commits: any[] = [];
|
commits: any[] = [];
|
||||||
|
|
||||||
fileList: IDirtyFile[] = [];
|
|
||||||
|
|
||||||
fileTree: IFileTree = {} as IFileTree;
|
|
||||||
|
|
||||||
removedFileList: IDirtyFile[] = [];
|
|
||||||
|
|
||||||
removedFileTree: IFileTree = {} as IFileTree;
|
|
||||||
|
|
||||||
dataGrip: any = null;
|
dataGrip: any = null;
|
||||||
|
|
||||||
|
fileGrip: any = null;
|
||||||
|
|
||||||
status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING;
|
status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -51,25 +47,18 @@ class DataGripStore implements IDataGripStore {
|
||||||
|
|
||||||
setCommits(dump?: string[]) {
|
setCommits(dump?: string[]) {
|
||||||
dataGrip.clear();
|
dataGrip.clear();
|
||||||
const parser = Parser;
|
fileGrip.clear();
|
||||||
|
|
||||||
const {
|
const commits = Parser(dump || []);
|
||||||
commits,
|
|
||||||
fileList,
|
|
||||||
fileTree,
|
|
||||||
removed,
|
|
||||||
} = parser(dump || []);
|
|
||||||
|
|
||||||
commits.sort((a, b) => a.milliseconds - b.milliseconds);
|
commits.sort((a, b) => a.milliseconds - b.milliseconds);
|
||||||
commits.forEach((commit: ICommit | ISystemCommit) => {
|
commits.forEach((commit: ICommit | ISystemCommit) => {
|
||||||
dataGrip.addCommit(commit);
|
dataGrip.addCommit(commit);
|
||||||
|
fileGrip.addCommit(commit);
|
||||||
});
|
});
|
||||||
|
fileGrip.updateTotalInfo();
|
||||||
|
|
||||||
this.commits = commits;
|
this.commits = commits;
|
||||||
this.fileList = fileList;
|
|
||||||
this.fileTree = getFileTreeWithStatistic(fileTree);
|
|
||||||
this.removedFileList = removed.fileList;
|
|
||||||
this.removedFileTree = getFileTreeWithStatistic(removed.fileTree);
|
|
||||||
|
|
||||||
this.status = this.commits.length
|
this.status = this.commits.length
|
||||||
? DataParseStatusEnum.DONE
|
? DataParseStatusEnum.DONE
|
||||||
|
@ -84,16 +73,17 @@ class DataGripStore implements IDataGripStore {
|
||||||
);
|
);
|
||||||
|
|
||||||
dataGrip.updateByInitialization();
|
dataGrip.updateByInitialization();
|
||||||
dataGrip.updateByFiles(fileList, removed.fileList);
|
achievements.updateByGrip(dataGrip, fileGrip);
|
||||||
achievements.updateByDataGrip(dataGrip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataGrip = null;
|
this.dataGrip = null;
|
||||||
this.dataGrip = dataGrip;
|
this.dataGrip = dataGrip;
|
||||||
|
this.fileGrip = fileGrip;
|
||||||
|
|
||||||
console.dir(this.dataGrip);
|
console.dir(this.dataGrip);
|
||||||
|
console.dir(this.fileGrip);
|
||||||
if (!applicationHasCustom.title) {
|
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');
|
console.log('need update data TODO');
|
||||||
dataGrip.updateByFilters();
|
dataGrip.updateByFilters();
|
||||||
if (!dataGrip.author.list.length) return;
|
if (!dataGrip.author.list.length) return;
|
||||||
achievements.updateByDataGrip(dataGrip);
|
achievements.updateByGrip(dataGrip, fileGrip);
|
||||||
this.dataGrip = null;
|
this.dataGrip = null;
|
||||||
this.dataGrip = dataGrip;
|
this.dataGrip = dataGrip;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,24 +45,24 @@ export default `
|
||||||
§ achievements.moreDaysForTask.description: работа по задачам идёт медленнее чем у остальных
|
§ achievements.moreDaysForTask.description: работа по задачам идёт медленнее чем у остальных
|
||||||
§ achievements.more2DaysForTask.title: Cо слоу
|
§ achievements.more2DaysForTask.title: Cо слоу
|
||||||
§ achievements.more2DaysForTask.description: больше двух дней на задачу
|
§ achievements.more2DaysForTask.description: больше двух дней на задачу
|
||||||
§ achievements.moreDaysInProject.title: Часть команды, часть коробля
|
|
||||||
§ achievements.moreDaysInProject.description: больше всего дней на проекте
|
|
||||||
§ achievements.more3YearsInProject.title: Старожил
|
§ achievements.more3YearsInProject.title: Старожил
|
||||||
§ achievements.more3YearsInProject.description: больше трех лет на проекте
|
§ achievements.more3YearsInProject.description: больше трех лет на проекте
|
||||||
§ achievements.lessDaysInProject.title: А это кто?
|
§ achievements.lessDaysInProject.title: А это кто?
|
||||||
§ achievements.lessDaysInProject.description: меньше всего дней на проекте
|
§ achievements.lessDaysInProject.description: меньше всего дней на проекте
|
||||||
§ achievements.more90DaysInProject.title: Добро пожаловать
|
|
||||||
§ achievements.more90DaysInProject.description: не уволили на испытательном
|
|
||||||
§ achievements.lessDaysForTask.title: Скорострел
|
§ achievements.lessDaysForTask.title: Скорострел
|
||||||
§ achievements.lessDaysForTask.description: одна задача занимает меньше дня
|
§ achievements.lessDaysForTask.description: одна задача занимает меньше дня
|
||||||
§ achievements.adam.title: Адам
|
§ achievements.adam.title: Адам
|
||||||
§ achievements.adam.description: первый стабильный сотрудник на проекте
|
§ achievements.adam.description: первый стабильный сотрудник на проекте
|
||||||
|
§ achievements.more90DaysInProject.title: Добро пожаловать
|
||||||
|
§ achievements.more90DaysInProject.description: не уволили на испытательном
|
||||||
§ achievements.more365DaysInProject.title: Годовасик
|
§ achievements.more365DaysInProject.title: Годовасик
|
||||||
§ achievements.more365DaysInProject.description: отработал год на проекте
|
§ achievements.more365DaysInProject.description: отработал год на проекте
|
||||||
§ achievements.more666DaysInProject.title: Чёрт
|
§ achievements.more666DaysInProject.title: Чёрт
|
||||||
§ achievements.more666DaysInProject.description: отработал 666 дней на проекте
|
§ achievements.more666DaysInProject.description: отработал 666 дней на проекте
|
||||||
§ achievements.more777DaysInProject.title: Азино 3 топора
|
§ achievements.more777DaysInProject.title: Азино 3 топора
|
||||||
§ achievements.more777DaysInProject.description: отработал 777 дней на проекте
|
§ achievements.more777DaysInProject.description: отработал 777 дней на проекте
|
||||||
|
§ achievements.moreDaysInProject.title: Часть команды, часть коробля
|
||||||
|
§ achievements.moreDaysInProject.description: больше всего дней на проекте
|
||||||
§ achievements.moreRefactoring.title: Выпускающий редактор
|
§ achievements.moreRefactoring.title: Выпускающий редактор
|
||||||
§ achievements.moreRefactoring.description: сделал больше всех меток «рефакторинг»
|
§ achievements.moreRefactoring.description: сделал больше всех меток «рефакторинг»
|
||||||
§ achievements.longestMessage.title: А разговоров то было...
|
§ achievements.longestMessage.title: А разговоров то было...
|
||||||
|
|