update
|
@ -85,12 +85,12 @@ Read more about the format of this file you can [here](https://git-scm.com/docs/
|
|||
#### For online viewing
|
||||
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>
|
||||
#### 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`.
|
||||
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
|
||||
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>
|
||||
#### 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`.
|
||||
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
|
||||
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>
|
||||
#### 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`.
|
||||
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
|
||||
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>
|
||||
#### 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`.
|
||||
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
|
||||
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>
|
||||
#### 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`.
|
||||
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>
|
||||
#### インターネットなしで見るために
|
||||
```
|
||||
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`.
|
||||
このファイルには、レポートを構築するためのデータが含まれています。
|
||||
|
|
|
@ -81,12 +81,12 @@ Pode ler mais sobre o formato deste arquivo em [aqui](https://git-scm.com/docs/g
|
|||
#### Para visualização online
|
||||
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>
|
||||
#### 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`.
|
||||
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>
|
||||
#### Для офлайн просмотра
|
||||
```
|
||||
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`.
|
||||
Он содержит данные для построения отчёта.
|
||||
|
|
|
@ -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>
|
||||
#### 在没有互联网的情况下观看
|
||||
```
|
||||
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`.
|
||||
这个文件包含了构建报告的数据。
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
},
|
||||
{
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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": [
|
||||
"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
|
||||
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
|
||||
|
||||
```
|
||||
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`.
|
||||
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
|
||||
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>
|
||||
#### 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`.
|
||||
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
|
||||
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
|
||||
|
||||
```
|
||||
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`.
|
||||
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
|
||||
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
|
||||
|
||||
```
|
||||
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`.
|
||||
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`.
|
||||
このファイルには、レポートを構築するためのデータが含まれています。
|
||||
|
|
|
@ -47,12 +47,12 @@ Pode ler mais sobre o formato deste arquivo em [aqui](https://git-scm.com/docs/g
|
|||
#### Para visualização online
|
||||
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
|
||||
|
||||
```
|
||||
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`.
|
||||
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>
|
||||
#### Для офлайн просмотра
|
||||
|
||||
```
|
||||
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`.
|
||||
Он содержит данные для построения отчёта.
|
||||
|
|
|
@ -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`.
|
||||
Этот файл содержит данные для построения отчёта.
|
||||
|
|
|
@ -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`.
|
||||
这个文件包含了构建报告的数据。
|
||||
|
|
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 totalPages = Math.ceil(totalElements / size);
|
||||
|
||||
return Promise.resolve({
|
||||
const response = {
|
||||
size,
|
||||
number: page,
|
||||
totalPages,
|
||||
totalElements,
|
||||
sort: sort || [],
|
||||
content: sortedContent.slice(begin, end) || [],
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export class DataLoaderStore implements IDataLoaderStore {
|
|||
makeObservable(this, {
|
||||
state: observable,
|
||||
watchedValue: observable,
|
||||
response: observable,
|
||||
// response: observable,
|
||||
sort: observable,
|
||||
fetchData: action,
|
||||
successCallback: action,
|
||||
|
|
|
@ -11,7 +11,7 @@ interface IExtensionProps {
|
|||
function Extension({
|
||||
statistic,
|
||||
}: IExtensionProps): React.ReactElement | null {
|
||||
if (!statistic) return null;
|
||||
if (!statistic || true) return null;
|
||||
|
||||
const getValue = (more: any) => `${more.author} (${more.percent.toFixed(1)}%)`;
|
||||
|
||||
|
|
|
@ -22,11 +22,8 @@ function LineChart({
|
|||
className,
|
||||
}: ILineChartProps): React.ReactElement | null {
|
||||
if (!value) return null;
|
||||
if (options.suffix === 'stop') {
|
||||
console.log('xxx');
|
||||
}
|
||||
|
||||
const width = Math.round((value ?? 100) * (100 / options.max));
|
||||
let width = Math.round((value ?? 100) * (100 / options.max));
|
||||
|
||||
if (!details) {
|
||||
return (
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class DataGripByExtension {
|
|||
if (!group[file.extension]) {
|
||||
group[file.extension] = this.#getNewExtension(file);
|
||||
}
|
||||
group[file.extension][type].files[file.name] = file.firstName;
|
||||
group[file.extension][type].files[file.id] = file.name;
|
||||
group[file.extension][type].count += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,13 @@ export default class DataGripByPR {
|
|||
updateTotalByAuthor(authors: any, refAuthorPR: IHashMap<any>) {
|
||||
this.statisticByName = {};
|
||||
authors.map((name: string) => {
|
||||
|
||||
let maxDelayDays = 0;
|
||||
refAuthorPR[name].forEach((pr: any) => {
|
||||
if (pr.delayDays > maxDelayDays) maxDelayDays = pr.delayDays;
|
||||
});
|
||||
|
||||
// TODO: сложын и не интересные показатели. Гистаграмму?
|
||||
const delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays');
|
||||
const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10);
|
||||
|
||||
|
@ -151,6 +158,9 @@ export default class DataGripByPR {
|
|||
|
||||
this.statisticByName[name] = {
|
||||
author: name,
|
||||
maxDelayDays,
|
||||
numberMergedPr: refAuthorPR[name].length,
|
||||
|
||||
workDays: workDays.details,
|
||||
delayDays: delayDays.details,
|
||||
weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage,
|
||||
|
|
|
@ -7,9 +7,13 @@ export default class DataGripByTasks {
|
|||
|
||||
statistic: any = [];
|
||||
|
||||
// achievements
|
||||
longTaskByAuthor: IHashMap<number> = {};
|
||||
|
||||
clear() {
|
||||
this.commits = {};
|
||||
this.statistic = [];
|
||||
this.longTaskByAuthor = {};
|
||||
}
|
||||
|
||||
addCommit(commit: ICommit) {
|
||||
|
@ -67,6 +71,11 @@ export default class DataGripByTasks {
|
|||
const to = lastCommit.milliseconds;
|
||||
const daysInWork = Math.ceil((to - from) / settingsStore.ONE_DAY) + 1;
|
||||
|
||||
const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author];
|
||||
if (!longTaskByAuthor || longTaskByAuthor < daysInWork) {
|
||||
this.longTaskByAuthor[shortInfo.author] = daysInWork;
|
||||
}
|
||||
|
||||
return {
|
||||
...shortInfo,
|
||||
to: to !== from ? to : undefined,
|
||||
|
|
|
@ -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 { IDirtyFile } from 'ts/interfaces/FileInfo';
|
||||
|
||||
import settingsStore from 'ts/store/Settings';
|
||||
import Recommendations from 'ts/helpers/Recommendations';
|
||||
|
@ -11,7 +10,6 @@ import DataGripByType from './components/type';
|
|||
import DataGripByTimestamp from './components/timestamp';
|
||||
import DataGripByWeek from './components/week';
|
||||
import MinMaxCounter from './components/counter';
|
||||
import DataGripByExtension from './components/extension';
|
||||
import DataGripByGet from './components/get';
|
||||
import DataGripByPR from './components/pr';
|
||||
import DataGripByTasks from './components/tasks';
|
||||
|
@ -34,8 +32,6 @@ class DataGrip {
|
|||
|
||||
recommendations: any = new Recommendations();
|
||||
|
||||
extension: any = new DataGripByExtension();
|
||||
|
||||
get: any = new DataGripByGet();
|
||||
|
||||
pr: any = new DataGripByPR();
|
||||
|
@ -57,7 +53,6 @@ class DataGrip {
|
|||
this.timestamp.clear();
|
||||
this.week.clear();
|
||||
this.recommendations.clear();
|
||||
this.extension.clear();
|
||||
this.get.clear();
|
||||
this.pr.clear();
|
||||
this.tasks.clear();
|
||||
|
@ -114,10 +109,6 @@ class DataGrip {
|
|||
this.#updateTotalInfo();
|
||||
this.hash = Math.random();
|
||||
}
|
||||
|
||||
updateByFiles(fileList: IDirtyFile[], removedFileList: IDirtyFile[]) {
|
||||
this.extension.updateTotalInfo(fileList, removedFileList);
|
||||
}
|
||||
}
|
||||
|
||||
const dataGrip = new DataGrip();
|
||||
|
|
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';
|
||||
|
||||
export default function getUserInfo(logString: string): ICommit | ISystemCommit {
|
||||
export default function getCommitInfo(logString: string): ICommit | ISystemCommit {
|
||||
// "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile"
|
||||
const parts = logString.split('>');
|
||||
|
||||
|
@ -35,6 +35,7 @@ export default function getUserInfo(logString: string): ICommit | ISystemCommit
|
|||
text: '',
|
||||
type: 'не подписан',
|
||||
scope: 'неопределенна',
|
||||
fileChanges: [],
|
||||
};
|
||||
|
||||
const isSystemPR = message.indexOf('Pull request #') === 0;
|
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 ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||
import settingsStore from 'ts/store/Settings';
|
||||
|
||||
import getUserInfo from './user_info';
|
||||
import { getNewFileName, getFileList } from './files';
|
||||
import { getNewFileInfo } from './file_info';
|
||||
import getCommitInfo from './getCommitInfo';
|
||||
import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges';
|
||||
|
||||
const uniq = {};
|
||||
export default function Parser(report: string[]) {
|
||||
const allFiles: IHashMap<IDirtyFile> = {};
|
||||
const removedFiles: IHashMap<IDirtyFile> = {};
|
||||
let commit = null;
|
||||
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) {
|
||||
const message = report[i];
|
||||
if (!message) continue;
|
||||
|
||||
const index = message.indexOf('\t');
|
||||
if (index > 0 && index < 10) {
|
||||
let [addedRaw, removedRaw, fileName] = message.split('\t');
|
||||
const formattedFileName = fileName?.replace(/"/gm, '');
|
||||
fileName = getNewFileName(formattedFileName, allFiles);
|
||||
let added = parseInt(addedRaw, 10) || 0;
|
||||
let removed = parseInt(removedRaw, 10) || 0;
|
||||
const diff = added - removed;
|
||||
let changes = added > removed ? removed : added;
|
||||
if (index > 0 && index < 10) { // парсинг файлов формата --num-stat
|
||||
const line = getNumStatInfo(message);
|
||||
if (!files[line.path]) {
|
||||
files[line.path] = getInfoFromPath(line.path);
|
||||
}
|
||||
fileChanges = files[line.path];
|
||||
fileChanges.addedLines = line.addedLines;
|
||||
fileChanges.removedLines = line.removedLines;
|
||||
fileChanges.changedLines = line.changedLines;
|
||||
|
||||
if (!allFiles[fileName] && removedFiles[fileName]) {
|
||||
allFiles[fileName] = removedFiles[fileName];
|
||||
delete removedFiles[fileName];
|
||||
} else if (message[0] === ':') { // парсинг файлов формата --raw
|
||||
const line = getRawInfo(message);
|
||||
if (!files[line.path]) {
|
||||
files[line.path] = getInfoFromPath(line.path);
|
||||
}
|
||||
fileChanges = files[line.path];
|
||||
fileChanges.action = line.action;
|
||||
|
||||
if (allFiles[fileName]) {
|
||||
const fileInfo: IDirtyFile = allFiles[fileName];
|
||||
fileInfo.lastCommit = prev;
|
||||
fileInfo.lines += diff;
|
||||
if (!fileInfo.authors[prev?.author || '']) {
|
||||
fileInfo.authors[prev?.author || ''] = {
|
||||
added: 0,
|
||||
changes: 0,
|
||||
removed: 0,
|
||||
commits: 1,
|
||||
tasks: {},
|
||||
types: {},
|
||||
scopes: {},
|
||||
};
|
||||
}
|
||||
const authorInfo = fileInfo.authors[prev?.author || ''];
|
||||
authorInfo.changes = authorInfo.changes + changes;
|
||||
if (diff > 0) {
|
||||
authorInfo.added = authorInfo.added + diff;
|
||||
} else {
|
||||
authorInfo.removed = authorInfo.removed + diff * (-1);
|
||||
}
|
||||
authorInfo.commits += 1;
|
||||
authorInfo.tasks[prev?.task || ''] = (authorInfo.tasks[prev?.task || ''] || 0) + 1;
|
||||
authorInfo.types[prev?.type || ''] = (authorInfo.tasks[prev?.type || ''] || 0) + 1;
|
||||
authorInfo.scopes[prev?.scope || ''] = (authorInfo.tasks[prev?.scope || ''] || 0) + 1;
|
||||
if (allFiles[fileName].lines === 0) {
|
||||
removedFiles[fileName] = allFiles[fileName];
|
||||
delete allFiles[fileName];
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore
|
||||
allFiles[fileName] = getNewFileInfo(fileName, added, prev);
|
||||
}
|
||||
if (removed > added) {
|
||||
removed -= added;
|
||||
changes += added;
|
||||
added = 0;
|
||||
} else if (added > removed) {
|
||||
added -= removed;
|
||||
changes += removed;
|
||||
removed = 0;
|
||||
} else if (added === removed) {
|
||||
changes += added;
|
||||
added = 0;
|
||||
removed = 0;
|
||||
}
|
||||
if (prev) { // @ts-ignore
|
||||
prev.changes += changes; // @ts-ignore
|
||||
prev.added += added; // @ts-ignore
|
||||
prev.removed += removed;
|
||||
}
|
||||
} else {
|
||||
if (prev) {
|
||||
if (uniq[prev.date]) {
|
||||
// console.log(`double ${uniq[prev.date]} === ${i}`);
|
||||
}
|
||||
uniq[prev.date] = i;
|
||||
}
|
||||
|
||||
const next = getUserInfo(message);
|
||||
if (next.milliseconds > weekEndTime) {
|
||||
week += 1;
|
||||
weekEndTime = next.milliseconds + (settingsStore.ONE_DAY * (6 - next.day));
|
||||
}
|
||||
// @ts-ignore
|
||||
next.week = week;
|
||||
|
||||
prev = next;
|
||||
commits.push(prev); // @ts-ignore
|
||||
} else { // парсинг коммита
|
||||
if (commit) commit.fileChanges = Object.values(files);
|
||||
files = {};
|
||||
commit = getCommitInfo(message);
|
||||
commit.week = 1;
|
||||
commits.push(commit);
|
||||
}
|
||||
}
|
||||
|
||||
const { fileList, fileTree } = getFileList(allFiles);
|
||||
return {
|
||||
commits,
|
||||
fileList,
|
||||
fileTree,
|
||||
removed: getFileList(removedFiles),
|
||||
};
|
||||
return commits;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import localization from './Localization';
|
||||
|
||||
function getFormattedType(dataGrip: any): string {
|
||||
const popularType = dataGrip.extension.statistic?.[0] || {};
|
||||
function getFormattedType(fileGrip: any): string {
|
||||
const popularType = fileGrip.extension.statistic?.[0] || {};
|
||||
const extension = popularType?.extension || '';
|
||||
|
||||
if ([
|
||||
|
@ -30,7 +30,7 @@ function getFormattedType(dataGrip: any): string {
|
|||
'perl',
|
||||
'java',
|
||||
].includes(extension)) {
|
||||
const hasManifest = dataGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
|
||||
const hasManifest = fileGrip.extension.statisticByName?.xml?.files?.AndroidManifest;
|
||||
return hasManifest
|
||||
? 'Android'
|
||||
: 'Back';
|
||||
|
@ -45,12 +45,12 @@ function getFormattedType(dataGrip: any): string {
|
|||
return extension.toUpperCase();
|
||||
}
|
||||
|
||||
export default function getTitle(dataGrip: any, commits: any) {
|
||||
export default function getTitle(dataGrip: any, fileGrip: any, commits: any) {
|
||||
if (!commits.length) {
|
||||
return localization.get('common.title');
|
||||
}
|
||||
|
||||
const type = getFormattedType(dataGrip) || '';
|
||||
const type = getFormattedType(fileGrip) || '';
|
||||
const task = dataGrip.pr.statistic?.[0]?.task || '';
|
||||
const author = dataGrip.firstLastCommit.minData.author || '';
|
||||
const year = commits?.[0]?.year || '';
|
||||
|
|
|
@ -1,6 +1,37 @@
|
|||
import ALL_ACHIEVEMENTS from './constants/list';
|
||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||
|
||||
export default function getAchievementByAuthor(list: string[], statistic: any) {
|
||||
function getHoroscope(firstCommit: ICommit | ISystemCommit) {
|
||||
const month = firstCommit.month + 1;
|
||||
const dayInMonth = firstCommit.month;
|
||||
const horoscopeRange = [
|
||||
{ from: [22, 12], to: [20, 1] },
|
||||
{ from: [20, 1], to: [18, 2] },
|
||||
{ from: [19, 2], to: [20, 3] },
|
||||
{ from: [21, 3], to: [19, 4] },
|
||||
{ from: [20, 4], to: [20, 5] },
|
||||
{ from: [21, 5], to: [21, 6] },
|
||||
{ from: [22, 6], to: [22, 7] },
|
||||
{ from: [23, 7], to: [22, 8] },
|
||||
{ from: [23, 8], to: [22, 9] },
|
||||
{ from: [23, 9], to: [23, 10] },
|
||||
{ from: [24, 10], to: [22, 11] },
|
||||
{ from: [23, 11], to: [21, 12] },
|
||||
];
|
||||
|
||||
const achievementIndex = horoscopeRange.reduce((acc: string, item: any, index: number) => {
|
||||
if (acc) return acc;
|
||||
if ((item.from[1] === month && dayInMonth >= item.from[0])
|
||||
|| (item.to[1] === month && dayInMonth <= item.to[0])) return `${index + 1}`;
|
||||
return acc;
|
||||
}, '');
|
||||
|
||||
return `horoscope${achievementIndex}`;
|
||||
}
|
||||
|
||||
|
||||
export default function getAchievementByAuthor(list: string[], dataGrip: any, author: string) {
|
||||
const statistic = dataGrip.author.statisticByName[author];
|
||||
const commitByHours = statistic.commitsByHour;
|
||||
|
||||
if (statistic.commits > 20) {
|
||||
|
@ -40,6 +71,15 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
|
|||
if (statistic.allDaysInProject >= 666) list.push('more666DaysInProject');
|
||||
// Азино - отработал 777 дней на проекте
|
||||
if (statistic.allDaysInProject >= 777) list.push('more777DaysInProject');
|
||||
// Старожил - отработал 3 года на проекте
|
||||
if (statistic.allDaysInProject >= (3 * 365)) list.push('more3YearsInProject');
|
||||
// хоть раз работал на выходных
|
||||
if (statistic.commitsByDayAndHourTotal[5]
|
||||
|| statistic.commitsByDayAndHourTotal[6]) list.push('workOnWeekends');
|
||||
|
||||
// работал над задачей больше трех месяцев
|
||||
const daysInWork = dataGrip.tasks.longTaskByAuthor[author] || {};
|
||||
if (daysInWork > 92) list.push('longTask');
|
||||
}
|
||||
// Ни единого разрыва - 0 дней без коммитов
|
||||
if (statistic.lazyDays === 0) list.push('zeroLazyDays');
|
||||
|
@ -48,6 +88,11 @@ export default function getAchievementByAuthor(list: string[], statistic: any) {
|
|||
// Точно в цель - в среднем 1 коммит на таск
|
||||
if (statistic.tasks / statistic.commits) list.push('oneCommitOneTask');
|
||||
|
||||
list.push(getHoroscope(statistic.firstCommit));
|
||||
|
||||
const statisticByPr = dataGrip.pr.statisticByName[author] || {};
|
||||
if (statisticByPr?.maxDelayDays > 31) list.push('longWaitPR');
|
||||
|
||||
return list.reduce((acc: any, type: string) => {
|
||||
const index = ALL_ACHIEVEMENTS[type] - 1;
|
||||
acc[index].push(type);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
|
||||
import getAchievementByAuthor from './byAuthor';
|
||||
import getAchievementByFile from './byFile';
|
||||
|
||||
class AchievementsByAuthor {
|
||||
authors: IHashMap<string[]> = {};
|
||||
|
@ -10,11 +11,12 @@ class AchievementsByAuthor {
|
|||
}
|
||||
|
||||
add(authors: Array<[string, number]>, maxAchievementCode: string, minAchievementCode?: string) {
|
||||
const first = authors[0][0];
|
||||
const first = authors?.[0]?.[0];
|
||||
if (!first) return;
|
||||
this.authors?.[first]?.push(maxAchievementCode);
|
||||
|
||||
if (!minAchievementCode) return;
|
||||
const last = authors[authors.length - 1][0];
|
||||
const last = authors?.[authors.length - 1]?.[0];
|
||||
this.authors?.[last]?.push(minAchievementCode);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +24,7 @@ class AchievementsByAuthor {
|
|||
class AchievementsByCompetition {
|
||||
authors: IHashMap<Array<string[]>> = {};
|
||||
|
||||
updateByDataGrip(dataGrip: any) {
|
||||
updateByGrip(dataGrip: any, fileGrip: any) {
|
||||
const statisticByAuthor = dataGrip.author.statistic;
|
||||
const byAuthor: any = new AchievementsByAuthor();
|
||||
const total = this.#getMinMaxValue(statisticByAuthor, dataGrip, (statistic: any) => {
|
||||
|
@ -62,6 +64,12 @@ class AchievementsByCompetition {
|
|||
// Количество коммитов в день
|
||||
byAuthor.add(total.commitsInDay, 'moreCommits');
|
||||
|
||||
// Таможня даёт добро
|
||||
byAuthor.add(total.morePRMerge, 'morePRMerge');
|
||||
|
||||
// Давным давно, в далёкой галактике
|
||||
byAuthor.add(total.moreLongWaitPR, 'moreLongWaitPR');
|
||||
|
||||
// Первый и последний коммит
|
||||
const lastAuthor = dataGrip.firstLastCommit.maxData.author;
|
||||
const firstAuthor = dataGrip.firstLastCommit.minData.author;
|
||||
|
@ -72,10 +80,11 @@ class AchievementsByCompetition {
|
|||
byAuthor.authors[lastAuthor].push('lastCommit');
|
||||
}
|
||||
|
||||
console.dir(byAuthor);
|
||||
getAchievementByFile(fileGrip, byAuthor);
|
||||
|
||||
statisticByAuthor.forEach((statistic: any) => {
|
||||
const achievements = byAuthor.authors[statistic.author];
|
||||
this.authors[statistic.author] = getAchievementByAuthor(achievements, statistic);
|
||||
this.authors[statistic.author] = getAchievementByAuthor(achievements, dataGrip, statistic.author);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -85,9 +94,9 @@ class AchievementsByCompetition {
|
|||
statisticByAuthor.forEach((statistic: any) => {
|
||||
callback(statistic);
|
||||
|
||||
const addData = (property: string, count: number) => {
|
||||
const addData = (property: string, count?: number) => {
|
||||
if (!total[property]) total[property] = [];
|
||||
total[property].push([statistic.author, count]);
|
||||
total[property].push([statistic.author, count || 0]);
|
||||
};
|
||||
|
||||
addData('nameLength', statistic.author.length);
|
||||
|
@ -101,6 +110,10 @@ class AchievementsByCompetition {
|
|||
addData('tasksInDay', byTimestamp.tasksByTimestampCounter.max);
|
||||
addData('commitsInDay', byTimestamp.commitsByTimestampCounter.max);
|
||||
|
||||
const byPr = dataGrip.pr.statisticByName[statistic.author] || {};
|
||||
addData('moreLongWaitPR', byPr?.maxDelayDays);
|
||||
addData('morePRMerge', byPr?.numberMergedPr);
|
||||
|
||||
if (statistic.isStaff) return;
|
||||
addData('allDaysInProject', statistic.allDaysInProject);
|
||||
addData('lazyDays', statistic.lazyDays);
|
||||
|
|
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, // Чёрт
|
||||
more777DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Азино 3 топора
|
||||
moreRefactoring: ACHIEVEMENT_TYPE.GOOD, // Выпускающий редактор
|
||||
moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды
|
||||
|
||||
// нет картинки
|
||||
longestMessage: ACHIEVEMENT_TYPE.NORMAL, // А разговоров то было...
|
||||
|
@ -41,39 +42,50 @@ export default {
|
|||
noCommitOnDay: ACHIEVEMENT_TYPE.NORMAL, // Технический перерыв
|
||||
hasCommitEveryTime: ACHIEVEMENT_TYPE.BAD, // Умер на работе
|
||||
commitsAfter1800: ACHIEVEMENT_TYPE.GOOD, // Делу время
|
||||
more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Нужно чуть чуть потерпеть, отработал год и не уволился
|
||||
more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Годовасик, отработал год и не уволился
|
||||
more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте
|
||||
firstCommit: ACHIEVEMENT_TYPE.NORMAL, // Кто первый, того и тапки. первый коммит на проекте
|
||||
lastCommit: ACHIEVEMENT_TYPE.NORMAL, // Я закончил. последний коммит на проекте
|
||||
firstLastCommit: ACHIEVEMENT_TYPE.NORMAL, // От начала и до конца. первый и последний коммит на проекте
|
||||
moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json
|
||||
moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD
|
||||
moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование
|
||||
moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps
|
||||
longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением
|
||||
longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем
|
||||
moreAddedFolders: ACHIEVEMENT_TYPE.NORMAL, // Директор, создал больше всех дирректорий
|
||||
morePRMerge: ACHIEVEMENT_TYPE.NORMAL, // Таможня даёт добро,
|
||||
longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца
|
||||
moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике
|
||||
workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных
|
||||
longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев
|
||||
fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте
|
||||
|
||||
// Типаж Козерога, по месяцу первого коммита
|
||||
horoscope1: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope2: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope3: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope4: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope5: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope6: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope7: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope8: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope9: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope10: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope11: ACHIEVEMENT_TYPE.NORMAL,
|
||||
horoscope12: ACHIEVEMENT_TYPE.NORMAL,
|
||||
|
||||
// нет кода
|
||||
moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды
|
||||
lessWorkDays: ACHIEVEMENT_TYPE.BAD, // Дальше без меня
|
||||
moreOnHoliday: ACHIEVEMENT_TYPE.BAD, // Нет жизни
|
||||
moreCreateCode: ACHIEVEMENT_TYPE.NORMAL, // Созидатель -- переименовать?
|
||||
moreRemoveCode: ACHIEVEMENT_TYPE.NORMAL, // Разрушитель
|
||||
moreChangeCode: ACHIEVEMENT_TYPE.NORMAL, // Реформатор
|
||||
|
||||
morePRMerge: ACHIEVEMENT_TYPE.GOOD, // Таможня даёт добро,
|
||||
workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных
|
||||
longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца
|
||||
moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике
|
||||
more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте
|
||||
|
||||
oneExtension: ACHIEVEMENT_TYPE.NORMAL, // Один в поле воин. Только он работает с файлами определенного расширения
|
||||
fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте
|
||||
moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json
|
||||
moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD
|
||||
moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps
|
||||
moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование
|
||||
|
||||
allRelease: ACHIEVEMENT_TYPE.NORMAL, // Фулл хаус. есть релиз, собранный только из его задач
|
||||
longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением
|
||||
longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем
|
||||
|
||||
removeCreateFile: ACHIEVEMENT_TYPE.NORMAL, // Откопал стюардессу. востановил удаленный файл
|
||||
renameFile: ACHIEVEMENT_TYPE.NORMAL, // Астана Нур-Султан Астана. переименовывал туда-сюда файл
|
||||
longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев
|
||||
// Галя, у нас отмена - откатил назад
|
||||
// У меня работает - больше всего коммитов с текстом fix
|
||||
// 500я на проде - больше всего коммитов с префиксом hotfix
|
||||
|
@ -81,6 +93,4 @@ export default {
|
|||
// godFather: ACHIEVEMENT_TYPE.NORMAL, // Крёстный отец. Первый создал файлы небольшой группы.
|
||||
// Боярин - есть папка на 20 файлов, где правит только этот человек
|
||||
// Феодал - есть папка на 50 файлов, где правит только этот человек
|
||||
|
||||
// Психологический знак задиака по дате первого коммита
|
||||
};
|
||||
|
|
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 {
|
||||
// date
|
||||
date: string; // "2021-02-09T12:59:17+03:00",
|
||||
|
@ -22,6 +36,8 @@ export interface ILog {
|
|||
taskNumber: string; // "0000",
|
||||
type: string; // feat|fix|docs|style|refactor|test|chore
|
||||
scope: string; // table, sale, profile and etc.
|
||||
|
||||
fileChanges: IFileChange[];
|
||||
}
|
||||
|
||||
export const COMMIT_TYPE = {
|
||||
|
|
|
@ -1,37 +1,40 @@
|
|||
import ICommit, { ISystemCommit } from './Commit';
|
||||
import IHashMap from './HashMap';
|
||||
|
||||
export interface IDirtyFile {
|
||||
name: string; // ".gitignore",
|
||||
interface IFileStat {
|
||||
lines: number; // 38, line in file for this moment
|
||||
|
||||
addedLines: number;
|
||||
removedLines: number;
|
||||
changedLines: number;
|
||||
|
||||
addedLinesByAuthor: IHashMap<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,
|
||||
lastCommit: ICommit | ISystemCommit | null,
|
||||
path: string[],
|
||||
extension: string,
|
||||
firstName: string,
|
||||
authors: {
|
||||
[author: string]: {
|
||||
added: number; // 38,
|
||||
changes: number; // 38,
|
||||
removed: number; // 0,
|
||||
commits: number; // 1,
|
||||
tasks: {
|
||||
[taskName: string]: number,
|
||||
},
|
||||
types: {
|
||||
[typeName: string]: number,
|
||||
},
|
||||
scopes: {
|
||||
[scopeName: string]: number,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFileTree {
|
||||
export interface IDirtyFile extends IFileStat {
|
||||
id: string; // "src/mynewlogo.test.ts",
|
||||
path: string[]; // ['src']
|
||||
pathString: string; // 'src/MyNewLogo.test.ts'
|
||||
name: string; // "MyNewLogo.test.ts",
|
||||
extension: string; // "ts",
|
||||
type: string; // "test",
|
||||
action: string; // 'A' or 'M' or 'D'
|
||||
}
|
||||
|
||||
export interface IFolder extends IFileStat {
|
||||
id?: number;
|
||||
name?: string;
|
||||
firstCommit: ICommit | ISystemCommit | null,
|
||||
lastCommit: ICommit | ISystemCommit | null,
|
||||
path: string[]; // ['src']
|
||||
pathString: string; // 'src\\ts'
|
||||
content: IHashMap<IDirtyFile>,
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ import Console from 'ts/components/Console';
|
|||
import style from '../styles/index.module.scss';
|
||||
|
||||
function MailMap(): React.ReactElement | null {
|
||||
const items = dataGripStore.dataGrip.author.statistic.map((item: any) => (
|
||||
`${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`
|
||||
));
|
||||
const commands = items.map((text: string) => (<p key={text}>{text}</p>));
|
||||
const items = dataGripStore.dataGrip.author.statistic
|
||||
.map((item: any) => `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`)
|
||||
.sort();
|
||||
const commands = items.map((text: any) => (<p key={text}>{text}</p>));
|
||||
const commandsForCopy = items.join('\r\n');
|
||||
|
||||
return (
|
||||
|
|
|
@ -29,8 +29,8 @@ interface IFilesViewProps {
|
|||
function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) {
|
||||
if (!response) return null;
|
||||
|
||||
const current = getMax(response, 'current', 'count');
|
||||
const removed = getMax(response, 'removed', 'count');
|
||||
const current = getMax(response, 'count');
|
||||
const removed = getMax(response, 'removedCount');
|
||||
const max = Math.max(current, removed);
|
||||
const filesChart = getOptions({ max, suffix: 'page.team.extension.files' });
|
||||
|
||||
|
@ -47,14 +47,14 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP
|
|||
isFixed
|
||||
template={ColumnTypesEnum.STRING}
|
||||
title="page.team.extension.name"
|
||||
properties="extension"
|
||||
properties="type"
|
||||
width={200}
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.STRING}
|
||||
title="page.team.extension.path"
|
||||
width={350}
|
||||
properties="path"
|
||||
formatter={(row: any) => row.count === 1 || row.removedCount === 1 ? row.path : ''}
|
||||
/>
|
||||
{mode === 'print' ? (
|
||||
<Column
|
||||
|
@ -82,37 +82,35 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP
|
|||
)}
|
||||
<Column
|
||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||
properties="current"
|
||||
formatter={(value: any) => value.count}
|
||||
properties="count"
|
||||
/>
|
||||
<Column
|
||||
isSortable
|
||||
title="page.team.extension.current.count"
|
||||
properties="current"
|
||||
properties="count"
|
||||
width={170}
|
||||
minWidth={170}
|
||||
template={(value: any) => (
|
||||
template={(value: number) => (
|
||||
<LineChart
|
||||
options={filesChart}
|
||||
value={value.count}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Column
|
||||
template={ColumnTypesEnum.SHORT_NUMBER}
|
||||
properties="removed"
|
||||
formatter={(value: any) => value.count}
|
||||
properties="removedCount"
|
||||
/>
|
||||
<Column
|
||||
isSortable
|
||||
title="page.team.extension.removed.count"
|
||||
properties="removed"
|
||||
properties="removedCount"
|
||||
width={170}
|
||||
minWidth={170}
|
||||
template={(value: any) => (
|
||||
template={(value: number) => (
|
||||
<LineChart
|
||||
options={filesChart}
|
||||
value={value.count}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -127,7 +125,7 @@ ExtensionView.defaultProps = {
|
|||
const Extension = observer(({
|
||||
mode,
|
||||
}: ICommonPageProps): React.ReactElement | null => {
|
||||
const rows = dataGripStore.dataGrip.extension.statistic;
|
||||
const rows = dataGripStore.fileGrip.type.statistic;
|
||||
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
|
||||
|
||||
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 UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
|
||||
|
||||
import treeStore from '../store/Tree';
|
||||
import style from '../styles/filters.module.scss';
|
||||
import treeStore from '../../store/Tree';
|
||||
import style from '../../styles/filters.module.scss';
|
||||
|
||||
const TreeFilters = observer((): React.ReactElement => {
|
||||
const { t } = useTranslation();
|
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,
|
||||
});
|
||||
|
||||
console.log(commitsChart);
|
||||
return (
|
||||
<DataView
|
||||
rowsForExcel={rowsForExcel}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { getDate } from 'ts/helpers/formatter';
|
|||
import style from '../styles/quiz.module.scss';
|
||||
|
||||
const Top = observer((): React.ReactElement => {
|
||||
const extensions = dataGripStore.dataGrip.extension.statistic
|
||||
const extensions = dataGripStore.fileGrip.extension.statistic
|
||||
.slice(0, 4).map((statistic: any) => {
|
||||
return (
|
||||
<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 {
|
||||
name: string;
|
||||
path: string[];
|
||||
content: IFile[];
|
||||
}
|
||||
|
||||
export function getSubTreeByPath(tree: IFileTree, path: string[]) {
|
||||
let subTree: any = tree || { content: [] };
|
||||
(path || []).forEach((folderName: string) => {
|
||||
function getSubTree(tree: IFolder, path: string[]) {
|
||||
return (path || []).reduce((subTree: any, folderName: string) => {
|
||||
subTree = subTree.content[folderName] || { content: [] };
|
||||
});
|
||||
return subTree;
|
||||
}, tree || { content: [] });
|
||||
}
|
||||
|
||||
|
||||
function getButtonUp(file: IFile) {
|
||||
return file?.path?.length ? ({
|
||||
title: '..',
|
||||
path: file.path.slice(0, -1),
|
||||
}) : null;
|
||||
function getSortedContent(subTree: any) {
|
||||
return Object.values(subTree.content)
|
||||
.sort((a: any, b: any) => {
|
||||
if (a.content && !b.content) return -1;
|
||||
if (!a.content && b.content) return 1;
|
||||
if (a.name === b.name) return 0;
|
||||
return a.name > b.name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
function getFolderView(file: IFile) {
|
||||
return {
|
||||
file,
|
||||
title: `📁 ${file.name}`,
|
||||
path: file.path,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
export function getContentByPath(fileTree: IFolder, path: string[]) {
|
||||
return getSortedContent(getSubTree(fileTree, path));
|
||||
}
|
|
@ -12,8 +12,8 @@ import PopularWords from './components/PopularWords';
|
|||
import Scope from './components/Scope';
|
||||
import Tempo from './components/Tempo';
|
||||
import Total from './components/Total';
|
||||
import Tree from './components/Tree';
|
||||
import Extension from './components/Extension2';
|
||||
import Files from './components/Files';
|
||||
import FileAnalitics from './components/FileAnalitics';
|
||||
import Type from './components/Type';
|
||||
import Week from './components/Week';
|
||||
import Month from './components/Month';
|
||||
|
@ -34,9 +34,9 @@ function getViewById(page?: string) {
|
|||
if (page === 'week') return <Week mode={mode}/>;
|
||||
if (page === 'month') return <Month mode={mode}/>;
|
||||
if (page === 'hours') return <Hours mode={mode}/>;
|
||||
if (page === 'files') return <Tree/>;
|
||||
if (page === 'removedFiles') return <Tree type="removed" />;
|
||||
if (page === 'extension') return <Extension mode={mode}/>;
|
||||
if (page === 'files') return <Files/>;
|
||||
if (page === 'removedFiles') return <Files type="removed" />;
|
||||
if (page === 'extension') return <FileAnalitics mode={mode}/>;
|
||||
if (page === 'release') return <Release mode={mode}/>;
|
||||
if (page === 'commits') return <Commits/>;
|
||||
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() {
|
||||
const command = 'git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n';
|
||||
const command = 'git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n';
|
||||
return (
|
||||
<>
|
||||
{process.env.REACT_APP_TYPE !== 'local' && (<WarningInfo />)}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { makeObservable, observable, action } from 'mobx';
|
||||
|
||||
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
|
||||
import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo';
|
||||
|
||||
import achievements from 'ts/helpers/achievement/byCompetition';
|
||||
import dataGrip from 'ts/helpers/DataGrip';
|
||||
import getFileTreeWithStatistic from 'ts/helpers/DataGrip/helpers/tree';
|
||||
import fileGrip from 'ts/helpers/FileGrip';
|
||||
import Parser from 'ts/helpers/Parser';
|
||||
import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
|
||||
import getTitle from 'ts/helpers/Title';
|
||||
|
||||
import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
|
||||
import { applicationHasCustom } from 'ts/helpers/RPC';
|
||||
|
||||
import settingsStore from './Settings';
|
||||
|
@ -21,6 +22,7 @@ export enum DataParseStatusEnum {
|
|||
interface IDataGripStore {
|
||||
commits: ICommit[];
|
||||
dataGrip: any;
|
||||
fileGrip: any;
|
||||
status: DataParseStatusEnum;
|
||||
setCommits: (log?: string[]) => void;
|
||||
}
|
||||
|
@ -28,16 +30,10 @@ interface IDataGripStore {
|
|||
class DataGripStore implements IDataGripStore {
|
||||
commits: any[] = [];
|
||||
|
||||
fileList: IDirtyFile[] = [];
|
||||
|
||||
fileTree: IFileTree = {} as IFileTree;
|
||||
|
||||
removedFileList: IDirtyFile[] = [];
|
||||
|
||||
removedFileTree: IFileTree = {} as IFileTree;
|
||||
|
||||
dataGrip: any = null;
|
||||
|
||||
fileGrip: any = null;
|
||||
|
||||
status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING;
|
||||
|
||||
constructor() {
|
||||
|
@ -51,25 +47,18 @@ class DataGripStore implements IDataGripStore {
|
|||
|
||||
setCommits(dump?: string[]) {
|
||||
dataGrip.clear();
|
||||
const parser = Parser;
|
||||
fileGrip.clear();
|
||||
|
||||
const {
|
||||
commits,
|
||||
fileList,
|
||||
fileTree,
|
||||
removed,
|
||||
} = parser(dump || []);
|
||||
const commits = Parser(dump || []);
|
||||
|
||||
commits.sort((a, b) => a.milliseconds - b.milliseconds);
|
||||
commits.forEach((commit: ICommit | ISystemCommit) => {
|
||||
dataGrip.addCommit(commit);
|
||||
fileGrip.addCommit(commit);
|
||||
});
|
||||
fileGrip.updateTotalInfo();
|
||||
|
||||
this.commits = commits;
|
||||
this.fileList = fileList;
|
||||
this.fileTree = getFileTreeWithStatistic(fileTree);
|
||||
this.removedFileList = removed.fileList;
|
||||
this.removedFileTree = getFileTreeWithStatistic(removed.fileTree);
|
||||
|
||||
this.status = this.commits.length
|
||||
? DataParseStatusEnum.DONE
|
||||
|
@ -84,16 +73,17 @@ class DataGripStore implements IDataGripStore {
|
|||
);
|
||||
|
||||
dataGrip.updateByInitialization();
|
||||
dataGrip.updateByFiles(fileList, removed.fileList);
|
||||
achievements.updateByDataGrip(dataGrip);
|
||||
achievements.updateByGrip(dataGrip, fileGrip);
|
||||
}
|
||||
|
||||
this.dataGrip = null;
|
||||
this.dataGrip = dataGrip;
|
||||
this.fileGrip = fileGrip;
|
||||
|
||||
console.dir(this.dataGrip);
|
||||
console.dir(this.fileGrip);
|
||||
if (!applicationHasCustom.title) {
|
||||
document.title = getTitle(this.dataGrip, this.commits);
|
||||
document.title = getTitle(this.dataGrip, this.fileGrip, this.commits);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,7 +91,7 @@ class DataGripStore implements IDataGripStore {
|
|||
console.log('need update data TODO');
|
||||
dataGrip.updateByFilters();
|
||||
if (!dataGrip.author.list.length) return;
|
||||
achievements.updateByDataGrip(dataGrip);
|
||||
achievements.updateByGrip(dataGrip, fileGrip);
|
||||
this.dataGrip = null;
|
||||
this.dataGrip = dataGrip;
|
||||
}
|
||||
|
|
|
@ -45,24 +45,24 @@ export default `
|
|||
§ achievements.moreDaysForTask.description: работа по задачам идёт медленнее чем у остальных
|
||||
§ achievements.more2DaysForTask.title: Cо слоу
|
||||
§ achievements.more2DaysForTask.description: больше двух дней на задачу
|
||||
§ achievements.moreDaysInProject.title: Часть команды, часть коробля
|
||||
§ achievements.moreDaysInProject.description: больше всего дней на проекте
|
||||
§ achievements.more3YearsInProject.title: Старожил
|
||||
§ achievements.more3YearsInProject.description: больше трех лет на проекте
|
||||
§ achievements.lessDaysInProject.title: А это кто?
|
||||
§ achievements.lessDaysInProject.description: меньше всего дней на проекте
|
||||
§ achievements.more90DaysInProject.title: Добро пожаловать
|
||||
§ achievements.more90DaysInProject.description: не уволили на испытательном
|
||||
§ achievements.lessDaysForTask.title: Скорострел
|
||||
§ achievements.lessDaysForTask.description: одна задача занимает меньше дня
|
||||
§ achievements.adam.title: Адам
|
||||
§ achievements.adam.description: первый стабильный сотрудник на проекте
|
||||
§ achievements.more90DaysInProject.title: Добро пожаловать
|
||||
§ achievements.more90DaysInProject.description: не уволили на испытательном
|
||||
§ achievements.more365DaysInProject.title: Годовасик
|
||||
§ achievements.more365DaysInProject.description: отработал год на проекте
|
||||
§ achievements.more666DaysInProject.title: Чёрт
|
||||
§ achievements.more666DaysInProject.description: отработал 666 дней на проекте
|
||||
§ achievements.more777DaysInProject.title: Азино 3 топора
|
||||
§ achievements.more777DaysInProject.description: отработал 777 дней на проекте
|
||||
§ achievements.moreDaysInProject.title: Часть команды, часть коробля
|
||||
§ achievements.moreDaysInProject.description: больше всего дней на проекте
|
||||
§ achievements.moreRefactoring.title: Выпускающий редактор
|
||||
§ achievements.moreRefactoring.description: сделал больше всех меток «рефакторинг»
|
||||
§ achievements.longestMessage.title: А разговоров то было...
|
||||
|
|