diff --git a/README.md b/README.md index 8682143..fdf558f 100644 --- a/README.md +++ b/README.md @@ -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 ``` #### 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. diff --git a/documents/DE.md b/documents/DE.md index 8bb4948..bc2126e 100644 --- a/documents/DE.md +++ b/documents/DE.md @@ -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 ``` #### 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. diff --git a/documents/EN.md b/documents/EN.md index 518abce..02c1131 100644 --- a/documents/EN.md +++ b/documents/EN.md @@ -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 ``` #### 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. diff --git a/documents/ES.md b/documents/ES.md index 1621772..e0019d9 100644 --- a/documents/ES.md +++ b/documents/ES.md @@ -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 ``` #### 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. diff --git a/documents/FR.md b/documents/FR.md index 5768983..c085c6c 100644 --- a/documents/FR.md +++ b/documents/FR.md @@ -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 ``` #### 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. diff --git a/documents/JA.md b/documents/JA.md index b91b98b..36bf7de 100644 --- a/documents/JA.md +++ b/documents/JA.md @@ -81,12 +81,12 @@ Alex B #### Дオンラインで見るため プロジェクトのルートディレクトリに次のコマンドを入力します: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### インターネットなしで見るために ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Gitはファイルを作成します `log.txt`. このファイルには、レポートを構築するためのデータが含まれています。 diff --git a/documents/PT.md b/documents/PT.md index 1140ddb..ddae08f 100644 --- a/documents/PT.md +++ b/documents/PT.md @@ -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 ``` #### 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. diff --git a/documents/RU.md b/documents/RU.md index 4cd0099..8d61992 100644 --- a/documents/RU.md +++ b/documents/RU.md @@ -80,12 +80,12 @@ Alex B #### Для онлайн просмотра В корневой директории вашего проекта выполнить: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### Для офлайн просмотра ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Git создаст файл `log.txt`. Он содержит данные для построения отчёта. diff --git a/documents/ZH.md b/documents/ZH.md index 54185a0..6423f18 100644 --- a/documents/ZH.md +++ b/documents/ZH.md @@ -80,12 +80,12 @@ Alex B #### 供网上浏览 在项目的根目录执行: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### 在没有互联网的情况下观看 ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Git会创建一个文件 `log.txt`. 这个文件包含了构建报告的数据。 diff --git a/documents/src/json/de.json b/documents/src/json/de.json index 66ddc52..1ef1ae5 100644 --- a/documents/src/json/de.json +++ b/documents/src/json/de.json @@ -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" ] }, { diff --git a/documents/src/json/en.json b/documents/src/json/en.json index bfb3e32..219e3c4 100644 --- a/documents/src/json/en.json +++ b/documents/src/json/en.json @@ -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" ] }, { diff --git a/documents/src/json/es.json b/documents/src/json/es.json index 785f28d..4fd33e4 100644 --- a/documents/src/json/es.json +++ b/documents/src/json/es.json @@ -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" ] }, { diff --git a/documents/src/json/fr.json b/documents/src/json/fr.json index a90ca3f..6d31ccf 100644 --- a/documents/src/json/fr.json +++ b/documents/src/json/fr.json @@ -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" ] }, { diff --git a/documents/src/json/ja.json b/documents/src/json/ja.json index b124d03..d8a33f7 100644 --- a/documents/src/json/ja.json +++ b/documents/src/json/ja.json @@ -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" ] }, { diff --git a/documents/src/json/pt.json b/documents/src/json/pt.json index 0eab9c6..7bc3d34 100644 --- a/documents/src/json/pt.json +++ b/documents/src/json/pt.json @@ -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" ] }, { diff --git a/documents/src/json/ru.json b/documents/src/json/ru.json index be3eef5..fa24be3 100644 --- a/documents/src/json/ru.json +++ b/documents/src/json/ru.json @@ -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" ] }, { diff --git a/documents/src/json/zh.json b/documents/src/json/zh.json index 3577b7f..50c0e86 100644 --- a/documents/src/json/zh.json +++ b/documents/src/json/zh.json @@ -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" ] }, { diff --git a/documents/src/md/DE.md b/documents/src/md/DE.md index 833e823..d919499 100644 --- a/documents/src/md/DE.md +++ b/documents/src/md/DE.md @@ -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. diff --git a/documents/src/md/EN.md b/documents/src/md/EN.md index bfb38ae..1816ca2 100644 --- a/documents/src/md/EN.md +++ b/documents/src/md/EN.md @@ -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 ``` #### 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. diff --git a/documents/src/md/ES.md b/documents/src/md/ES.md index 6c712ea..eedab4e 100644 --- a/documents/src/md/ES.md +++ b/documents/src/md/ES.md @@ -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. diff --git a/documents/src/md/FR.md b/documents/src/md/FR.md index 1057fda..b82d86e 100644 --- a/documents/src/md/FR.md +++ b/documents/src/md/FR.md @@ -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. diff --git a/documents/src/md/JA.md b/documents/src/md/JA.md index b72e8dc..eca6855 100644 --- a/documents/src/md/JA.md +++ b/documents/src/md/JA.md @@ -47,12 +47,12 @@ Alex B #### Дオンラインで見るため プロジェクトのルートディレクトリに次のコマンドを入力します: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### インターネットなしで見るために ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Gitはファイルを作成します `log.txt`. このファイルには、レポートを構築するためのデータが含まれています。 diff --git a/documents/src/md/PT.md b/documents/src/md/PT.md index 635398a..73e56a7 100644 --- a/documents/src/md/PT.md +++ b/documents/src/md/PT.md @@ -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. diff --git a/documents/src/md/RU.md b/documents/src/md/RU.md index 0f1b08c..0031cdc 100644 --- a/documents/src/md/RU.md +++ b/documents/src/md/RU.md @@ -49,13 +49,13 @@ Alex B #### Для онлайн просмотра В корневой директории вашего проекта выполнить: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### Для офлайн просмотра ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Git создаст файл `log.txt`. Он содержит данные для построения отчёта. diff --git a/documents/src/md/SIMPLE_RU.md b/documents/src/md/SIMPLE_RU.md index 56e1161..30b884a 100644 --- a/documents/src/md/SIMPLE_RU.md +++ b/documents/src/md/SIMPLE_RU.md @@ -50,12 +50,12 @@ Alex B #### Для онлайн просмотра В корневой директории вашего проекта выполнить: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### Для просмотра без интернета ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Git создаст файл `log.txt`. Этот файл содержит данные для построения отчёта. diff --git a/documents/src/md/ZH.md b/documents/src/md/ZH.md index e9a2ad4..60ad476 100644 --- a/documents/src/md/ZH.md +++ b/documents/src/md/ZH.md @@ -47,12 +47,12 @@ Alex B #### 供网上浏览 在项目的根目录执行: ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt ``` #### 在没有互联网的情况下观看 ``` -git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt +git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt ``` Git会创建一个文件 `log.txt`. 这个文件包含了构建报告的数据。 diff --git a/public/assets/achievements/more3YearsInProject.svg b/public/assets/achievements/more3YearsInProject.svg new file mode 100644 index 0000000..d328cc3 --- /dev/null +++ b/public/assets/achievements/more3YearsInProject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/achievements/moreCreateCode.svg b/public/assets/achievements/moreCreateCode.svg new file mode 100644 index 0000000..c237f81 --- /dev/null +++ b/public/assets/achievements/moreCreateCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/achievements/moreRemoveCode.svg b/public/assets/achievements/moreRemoveCode.svg new file mode 100644 index 0000000..9e9fbc2 --- /dev/null +++ b/public/assets/achievements/moreRemoveCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/achievements/moreStyle.svg b/public/assets/achievements/moreStyle.svg new file mode 100644 index 0000000..a087a39 --- /dev/null +++ b/public/assets/achievements/moreStyle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/social/vk/awesomejs.png b/public/social/vk/awesomejs.png new file mode 100644 index 0000000..a98f110 Binary files /dev/null and b/public/social/vk/awesomejs.png differ diff --git a/public/social/vk/front_work.jpg b/public/social/vk/front_work.jpg new file mode 100644 index 0000000..aa0e46c Binary files /dev/null and b/public/social/vk/front_work.jpg differ diff --git a/public/social/vk/front_work.png b/public/social/vk/front_work.png new file mode 100644 index 0000000..223d9d9 Binary files /dev/null and b/public/social/vk/front_work.png differ diff --git a/public/social/vk/frontend_dev.png b/public/social/vk/frontend_dev.png new file mode 100644 index 0000000..5b8ffa1 Binary files /dev/null and b/public/social/vk/frontend_dev.png differ diff --git a/public/social/vk/frontend_du2.jpg b/public/social/vk/frontend_du2.jpg new file mode 100644 index 0000000..7a849cc Binary files /dev/null and b/public/social/vk/frontend_du2.jpg differ diff --git a/public/social/vk/frontend_du2.png b/public/social/vk/frontend_du2.png new file mode 100644 index 0000000..9d9e308 Binary files /dev/null and b/public/social/vk/frontend_du2.png differ diff --git a/public/social/vk/logo.png b/public/social/vk/logo.png new file mode 100644 index 0000000..8f4290b Binary files /dev/null and b/public/social/vk/logo.png differ diff --git a/public/social/vk/take_off_staff.jpg b/public/social/vk/take_off_staff.jpg new file mode 100644 index 0000000..d120a82 Binary files /dev/null and b/public/social/vk/take_off_staff.jpg differ diff --git a/public/social/vk/take_off_staff.png b/public/social/vk/take_off_staff.png new file mode 100644 index 0000000..4d702fe Binary files /dev/null and b/public/social/vk/take_off_staff.png differ diff --git a/src/ts/components/Banner/index.module.scss b/src/ts/components/Banner/index.module.scss new file mode 100644 index 0000000..699fa71 --- /dev/null +++ b/src/ts/components/Banner/index.module.scss @@ -0,0 +1,28 @@ +@import 'src/styles/variables'; + +.banner { + border-left-width: 1px; + border-left-color: var(--color-border); + + font-weight: 100; + + width: 100%; + height: 100%; + padding: 0; + + letter-spacing: 1px; + white-space: nowrap; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + text-decoration: none; + border-radius: var(--border-radius-s); + + color: #FFFFFF; + background-color: #A7B6FE; + //background: linear-gradient(135deg, rgba(64,117,252,1) 0%, rgba(172,179,246,1) 100%); + + background-repeat: no-repeat; + background-size: 180% auto; + background-position: center center; +} diff --git a/src/ts/components/Banner/index.tsx b/src/ts/components/Banner/index.tsx new file mode 100644 index 0000000..419b847 --- /dev/null +++ b/src/ts/components/Banner/index.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import themeSettings from 'ts/store/ThemeSettings'; + +import style from './index.module.scss'; + +interface IBannerProps { + className: string; +} + +function Banner({ className }: IBannerProps) { + const config = themeSettings.getBanner(); + if (!config) return null; + + const { + ref, + link, + title, + banner, + bannerText, + color, + backgroundColor, + } = config; + + const props = { + title, + to: link || '', + target: '_blank', + className, + }; + + if (banner) { + return ( + +
+ + ); + } + + if (!banner) { + const textFromRef = (ref || '').split('_').splice(1).join(' ').toUpperCase(); + const background = backgroundColor + ? backgroundColor + : 'linear-gradient(135deg, rgba(64,117,252,1) 0%, rgba(172,179,246,1) 100%)'; + + return ( + +
+ {bannerText || textFromRef || ''} +
+ + ); + } + + return null; +} + +export default Banner; diff --git a/src/ts/components/CardWithIcon/Banner.tsx b/src/ts/components/CardWithIcon/Banner.tsx new file mode 100644 index 0000000..d125375 --- /dev/null +++ b/src/ts/components/CardWithIcon/Banner.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import Banner from 'ts/components/Banner'; + +import style from './index.module.scss'; + +interface ICardWithBannerProps { + long?: boolean; +} + +function CardWithBanner({ + long = false, +}: ICardWithBannerProps): React.ReactElement | null { + const className = long + ? style.card_with_icon_long + : style.card_with_icon; + + return ( + + ); +} + +CardWithBanner.defaultProps = { + long: false, +}; + +export default CardWithBanner; diff --git a/src/ts/components/DataLoader/helpers/formatter.ts b/src/ts/components/DataLoader/helpers/formatter.ts index 850c6a7..c741980 100644 --- a/src/ts/components/DataLoader/helpers/formatter.ts +++ b/src/ts/components/DataLoader/helpers/formatter.ts @@ -73,12 +73,14 @@ export default function getFakeLoader({ const totalElements = sortedContent.length; const totalPages = Math.ceil(totalElements / size); - return Promise.resolve({ + const response = { size, number: page, totalPages, totalElements, sort: sort || [], content: sortedContent.slice(begin, end) || [], - }); + }; + + return Promise.resolve(response); } diff --git a/src/ts/components/DataLoader/store/index.ts b/src/ts/components/DataLoader/store/index.ts index c2d5ea0..448b094 100644 --- a/src/ts/components/DataLoader/store/index.ts +++ b/src/ts/components/DataLoader/store/index.ts @@ -78,7 +78,7 @@ export class DataLoaderStore implements IDataLoaderStore { makeObservable(this, { state: observable, watchedValue: observable, - response: observable, + // response: observable, sort: observable, fetchData: action, successCallback: action, diff --git a/src/ts/components/Extension/index.tsx b/src/ts/components/Extension/index.tsx index 0618fba..ff928f5 100644 --- a/src/ts/components/Extension/index.tsx +++ b/src/ts/components/Extension/index.tsx @@ -11,7 +11,7 @@ interface IExtensionProps { function Extension({ statistic, }: IExtensionProps): React.ReactElement | null { - if (!statistic) return null; + if (!statistic || true) return null; const getValue = (more: any) => `${more.author} (${more.percent.toFixed(1)}%)`; diff --git a/src/ts/components/LineChart/index.tsx b/src/ts/components/LineChart/index.tsx index ff88ca7..97e18b4 100644 --- a/src/ts/components/LineChart/index.tsx +++ b/src/ts/components/LineChart/index.tsx @@ -22,11 +22,8 @@ function LineChart({ className, }: ILineChartProps): React.ReactElement | null { if (!value) return null; - if (options.suffix === 'stop') { - console.log('xxx'); - } - const width = Math.round((value ?? 100) * (100 / options.max)); + let width = Math.round((value ?? 100) * (100 / options.max)); if (!details) { return ( diff --git a/src/ts/helpers/DataGrip/components/extension.ts b/src/ts/helpers/DataGrip/components/extension.ts index d3960f9..a6b1946 100644 --- a/src/ts/helpers/DataGrip/components/extension.ts +++ b/src/ts/helpers/DataGrip/components/extension.ts @@ -53,7 +53,7 @@ export default class DataGripByExtension { if (!group[file.extension]) { group[file.extension] = this.#getNewExtension(file); } - group[file.extension][type].files[file.name] = file.firstName; + group[file.extension][type].files[file.id] = file.name; group[file.extension][type].count += 1; } diff --git a/src/ts/helpers/DataGrip/components/pr.ts b/src/ts/helpers/DataGrip/components/pr.ts index 34270ed..a6e863b 100644 --- a/src/ts/helpers/DataGrip/components/pr.ts +++ b/src/ts/helpers/DataGrip/components/pr.ts @@ -143,6 +143,13 @@ export default class DataGripByPR { updateTotalByAuthor(authors: any, refAuthorPR: IHashMap) { this.statisticByName = {}; authors.map((name: string) => { + + let maxDelayDays = 0; + refAuthorPR[name].forEach((pr: any) => { + if (pr.delayDays > maxDelayDays) maxDelayDays = pr.delayDays; + }); + + // TODO: сложын и не интересные показатели. Гистаграмму? const delayDays = DataGripByPR.getPRByGroups(refAuthorPR[name], 'delayDays'); const delayDaysWeightedAverage = parseInt(delayDays.weightedAverage.toFixed(1), 10); @@ -151,6 +158,9 @@ export default class DataGripByPR { this.statisticByName[name] = { author: name, + maxDelayDays, + numberMergedPr: refAuthorPR[name].length, + workDays: workDays.details, delayDays: delayDays.details, weightedAverage: workDaysWeightedAverage + delayDaysWeightedAverage, diff --git a/src/ts/helpers/DataGrip/components/tasks.ts b/src/ts/helpers/DataGrip/components/tasks.ts index 5835eb3..c7939e2 100644 --- a/src/ts/helpers/DataGrip/components/tasks.ts +++ b/src/ts/helpers/DataGrip/components/tasks.ts @@ -7,9 +7,13 @@ export default class DataGripByTasks { statistic: any = []; + // achievements + longTaskByAuthor: IHashMap = {}; + clear() { this.commits = {}; this.statistic = []; + this.longTaskByAuthor = {}; } addCommit(commit: ICommit) { @@ -67,6 +71,11 @@ export default class DataGripByTasks { const to = lastCommit.milliseconds; const daysInWork = Math.ceil((to - from) / settingsStore.ONE_DAY) + 1; + const longTaskByAuthor = this.longTaskByAuthor[shortInfo.author]; + if (!longTaskByAuthor || longTaskByAuthor < daysInWork) { + this.longTaskByAuthor[shortInfo.author] = daysInWork; + } + return { ...shortInfo, to: to !== from ? to : undefined, diff --git a/src/ts/helpers/DataGrip/helpers/tree.ts b/src/ts/helpers/DataGrip/helpers/tree.ts deleted file mode 100644 index 180daee..0000000 --- a/src/ts/helpers/DataGrip/helpers/tree.ts +++ /dev/null @@ -1,63 +0,0 @@ -function addTotalInfo(folder: any) { - folder.total = { added: 0, changes: 0, removed: 0, commits: 0 }; - const authors = Object.keys(folder.authors); - authors.forEach(author => { - folder.total.added += folder.authors[author].added; - folder.total.changes += folder.authors[author].changes; - folder.total.removed += folder.authors[author].removed; - folder.total.commits += folder.authors[author].commits; - }); - authors.forEach(author => { - const authorInfo = folder.authors[author]; - authorInfo.addedPercent = Math.round(authorInfo.added * 100 / folder.total.added); - authorInfo.changesPercent = Math.round(authorInfo.changes * 100 / folder.total.changes); - authorInfo.removedPercent = Math.round(authorInfo.removed * 100 / folder.total.removed); - authorInfo.commitsPercent = Math.round(authorInfo.commits * 100 / folder.total.commits); - }); -} - -function addInfoFromFile(folderInfo: any, file: any) { - folderInfo.lines += file.lines; - for (let author in file.authors) { - if (!folderInfo.authors[author]) { - folderInfo.authors[author] = { - added: 0, - changes: 0, - removed: 0, - commits: 0, - tasks: {}, - types: {}, - scopes: {}, - }; - } - const folder = folderInfo.authors[author]; - const fileInfo = file.authors[author]; - folder.added += fileInfo.added; - folder.changes += fileInfo.changes; - folder.removed += fileInfo.removed; - folder.commits += fileInfo.commits; - } -} - -function addInfoFromFolder(parentInfo: any, folder: any, path: string[]) { - const folderInfo = { lines: 0, authors: {} }; - for (let fileName in folder.content) { - if (folder.content[fileName].content) { - addInfoFromFolder(folderInfo, folder.content[fileName], [...path, fileName]); - } else { - addInfoFromFile(folderInfo, folder.content[fileName]); - addTotalInfo(folder.content[fileName]); - } - } - addInfoFromFile(parentInfo, folderInfo); - folder.path = path; - folder.lines = folderInfo.lines; - folder.authors = folderInfo.authors; - addTotalInfo(folder); -} - -export default function getFileTreeWithStatistic(rootTree: any) { - const folderInfo = { lines: 0, authors: {}, path: [] }; - addInfoFromFolder(folderInfo, rootTree, []); - return rootTree; -} \ No newline at end of file diff --git a/src/ts/helpers/DataGrip/index.ts b/src/ts/helpers/DataGrip/index.ts index 432179d..5380e3b 100644 --- a/src/ts/helpers/DataGrip/index.ts +++ b/src/ts/helpers/DataGrip/index.ts @@ -1,5 +1,4 @@ import ICommit, { ISystemCommit } from 'ts/interfaces/Commit'; -import { IDirtyFile } from 'ts/interfaces/FileInfo'; import settingsStore from 'ts/store/Settings'; import Recommendations from 'ts/helpers/Recommendations'; @@ -11,7 +10,6 @@ import DataGripByType from './components/type'; import DataGripByTimestamp from './components/timestamp'; import DataGripByWeek from './components/week'; import MinMaxCounter from './components/counter'; -import DataGripByExtension from './components/extension'; import DataGripByGet from './components/get'; import DataGripByPR from './components/pr'; import DataGripByTasks from './components/tasks'; @@ -34,8 +32,6 @@ class DataGrip { recommendations: any = new Recommendations(); - extension: any = new DataGripByExtension(); - get: any = new DataGripByGet(); pr: any = new DataGripByPR(); @@ -57,7 +53,6 @@ class DataGrip { this.timestamp.clear(); this.week.clear(); this.recommendations.clear(); - this.extension.clear(); this.get.clear(); this.pr.clear(); this.tasks.clear(); @@ -114,10 +109,6 @@ class DataGrip { this.#updateTotalInfo(); this.hash = Math.random(); } - - updateByFiles(fileList: IDirtyFile[], removedFileList: IDirtyFile[]) { - this.extension.updateTotalInfo(fileList, removedFileList); - } } const dataGrip = new DataGrip(); diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts b/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts new file mode 100644 index 0000000..a7ce3d7 --- /dev/null +++ b/src/ts/helpers/FileGrip/components/FileBuilder/Common.ts @@ -0,0 +1,39 @@ +import ICommit, { IFileChange } from 'ts/interfaces/Commit'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +function getNameTypeExtension(path?: string) { + const name = (path || '')?.split('/')?.pop() || ''; + const parts = name.split('.') || []; + const extension = parts[parts.length - 1] || ''; + const type = parts.length > 2 ? parts[parts.length - 2] : ''; + return { name, type, extension }; +} + +export default class FileBuilderCommon { + static getProps(fileChange: IFileChange, commit: ICommit) { + return { + path: fileChange.path, + action: fileChange.action, + firstCommit: commit, + lastCommit: commit, + }; + } + + static updateProps(file: IDirtyFile, fileChange: IFileChange, commit: ICommit) { + file.action = fileChange.action; + file.lastCommit = commit; + } + + static updateTotal(file: IDirtyFile) { + // @ts-ignore + const { name, type, extension } = getNameTypeExtension(file?.path); + file.name = name; + file.type = type; + file.extension = extension; + + // @ts-ignore + const parts = file.path.split('/'); + parts.pop(); + file.path = parts; + } +} diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts new file mode 100644 index 0000000..89e5c3f --- /dev/null +++ b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts @@ -0,0 +1,52 @@ +import ICommit, { IFileChange } from 'ts/interfaces/Commit'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +import { getValuesInPercent } from '../../helpers'; + +export default class FileBuilderLineStat { + static getProps(fileChange: IFileChange, commit: ICommit) { + return { + lines: fileChange.addedLines, + + addedLines: fileChange.addedLines, + removedLines: fileChange.removedLines, + changedLines: fileChange.changedLines, + + addedLinesByAuthor: { [commit.author]: fileChange.addedLines }, + removedLinesByAuthor: { [commit.author]: fileChange.removedLines }, + changedLinesByAuthor: { [commit.author]: fileChange.changedLines }, + }; + } + + static updateProps(file: IDirtyFile, fileChange: IFileChange, commit: ICommit) { + file.lines += fileChange.addedLines; + file.lines -= fileChange.removedLines; + file.addedLines += fileChange.addedLines; + file.removedLines += fileChange.removedLines; + file.changedLines += fileChange.changedLines; + + file.addedLinesByAuthor[commit.author] = file.addedLinesByAuthor[commit.author] + ? (file.addedLinesByAuthor[commit.author] + fileChange.addedLines) + : fileChange.addedLines; + + file.removedLinesByAuthor[commit.author] = file.removedLinesByAuthor[commit.author] + ? (file.removedLinesByAuthor[commit.author] + fileChange.removedLines) + : fileChange.removedLines; + + file.changedLinesByAuthor[commit.author] = file.changedLinesByAuthor[commit.author] + ? (file.changedLinesByAuthor[commit.author] + fileChange.changedLines) + : fileChange.changedLines; + } + + static updateTotal(file: IDirtyFile) { + file.addedByAuthorInPercent = getValuesInPercent(file.addedLinesByAuthor, file.addedLines); + file.removedByAuthorInPercent = getValuesInPercent(file.removedLinesByAuthor, file.removedLines); + file.changedByAuthorInPercent = getValuesInPercent(file.changedLinesByAuthor, file.changedLines); + + file.addedRemovedChangedInPercent = getValuesInPercent({ + added: file.addedLines, + removed: file.removedLines, + changed: file.changedLines, + }, file.addedLines + file.removedLines + file.changedLines); + } +} diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/index.ts b/src/ts/helpers/FileGrip/components/FileBuilder/index.ts new file mode 100644 index 0000000..e95f12a --- /dev/null +++ b/src/ts/helpers/FileGrip/components/FileBuilder/index.ts @@ -0,0 +1,90 @@ +import ICommit, { IFileChange } from 'ts/interfaces/Commit'; +import IHashMap from 'ts/interfaces/HashMap'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +import FileBuilderCommon from './Common'; +import FileBuilderLineStat from './LineStat'; + +export default class FileGripByPaths { + list: IDirtyFile[] = []; + + refFileIds: IHashMap = {}; + + refRemovedFileIds: IHashMap = {}; + + refExtensionType: IHashMap> = {}; // TODO: remove me? + + clear() { + this.list = []; + this.refFileIds = {}; + this.refRemovedFileIds = {}; + } + + addCommit(fileChange: IFileChange, commit: ICommit) { + let file = this.refFileIds[fileChange.id] || this.refFileIds[fileChange.newId || '']; + if (file) { + this.#updateDirtyFile(file, fileChange, commit); + } else { + this.refFileIds[fileChange.id] = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile; + } + + if (fileChange.newId) { + this.#renameFile(file, fileChange.newId); + } + } + + #getNewDirtyFile(fileChange: IFileChange, commit: ICommit): any { + const commonProps = FileBuilderCommon.getProps(fileChange, commit); + const statProps = FileBuilderLineStat.getProps(fileChange, commit); + + return { + id: fileChange.id, + ...commonProps, + ...statProps, + }; + } + + #updateDirtyFile(file: any, fileChange: IFileChange, commit: ICommit) { + FileBuilderCommon.updateProps(file, fileChange, commit); + FileBuilderLineStat.updateProps(file, fileChange, commit); + } + + #renameFile(file: any, newId: string) { + this.refFileIds[newId] = this.refFileIds[file.id]; + delete this.refFileIds[file.id]; + file.id = newId; + } + + #removeFile(file: any) { + this.refRemovedFileIds[file.id] = this.refFileIds[file.id]; + this.refRemovedFileIds[file.id].action = 'D'; + delete this.refFileIds[file.id]; + } + + updateTotalInfo(callback?: Function) { + this.list = Object.values(this.refFileIds); + this.list.forEach((temp: any) => { + const file = temp; + + FileBuilderCommon.updateTotal(file); + FileBuilderLineStat.updateTotal(file); + + if (file.type) { + if (!this.refExtensionType[file.extension]) this.refExtensionType[file.extension] = {}; + this.refExtensionType[file.extension][file.type] = this.refExtensionType[file.extension][file.type] + ? (this.refExtensionType[file.extension][file.type] + 1) + : 1; + } + + if (file.lines === 0 + || file.action === 'D' + || file.action === 'A') { + this.#removeFile(file); + } + + if (callback) { + callback(file); + } + }); + } +} diff --git a/src/ts/helpers/FileGrip/components/extension.ts b/src/ts/helpers/FileGrip/components/extension.ts new file mode 100644 index 0000000..08d2ed6 --- /dev/null +++ b/src/ts/helpers/FileGrip/components/extension.ts @@ -0,0 +1,64 @@ +import IHashMap from 'ts/interfaces/HashMap'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +const IGNORE_LIST = [ + '.eslintrc', + '.gitignore', + 'package.json', + 'package-lock.json', + 'tsconfig.json', +]; + +export default class FileGripByExtension { + statistic: any = []; + + statisticByName: IHashMap = {}; + + property: string = ''; + + constructor(property?: string) { + this.property = property || 'extension'; + } + + clear() { + this.statistic = []; + this.statisticByName = {}; + } + + addFile(file: IDirtyFile) { + const key = file?.extension; + + if (!key || IGNORE_LIST.includes(file.name)) return; + + if (!this.statisticByName[key]) { + this.statisticByName[key] = this.#getNewExtension(file); + } + + const extensions = this.statisticByName[key]; + if (file.action === 'D') { + extensions.removedFiles.push(file); + extensions.removedCount += 1; + } else { + extensions.files.push(file); + extensions.count += 1; + } + } + + #getNewExtension(file: IDirtyFile) { + return { + extension: file?.extension, + task: file?.firstCommit?.task, + path: file?.name, + files: [], + count: 0, + removedFiles: [], + removedCount: 0, + }; + } + + updateTotalInfo() { + this.statistic = Object.entries(this.statisticByName) + .sort((a: any, b: any) => b[1].count - a[1].count) + .map((item: any) => item[1]); + } +} diff --git a/src/ts/helpers/FileGrip/components/folder.ts b/src/ts/helpers/FileGrip/components/folder.ts new file mode 100644 index 0000000..0e4bc98 --- /dev/null +++ b/src/ts/helpers/FileGrip/components/folder.ts @@ -0,0 +1,109 @@ +import { IDirtyFile, IFolder } from 'ts/interfaces/FileInfo'; +import IHashMap from 'ts/interfaces/HashMap'; + +import { getValuesInPercent } from '../helpers'; + +function getFolder(name?: string, path?: string[], file?: IDirtyFile): IFolder { + return { + id: Math.random(), + name: name || '', // @ts-ignore + path: path || [], + pathString: `${(path || []).join('/')}/${name || ''}`, + content: {}, + + lines: file?.lines || 0, + + addedLines: file?.addedLines || 0, + removedLines: file?.removedLines || 0, + changedLines: file?.changedLines || 0, + + addedLinesByAuthor: { ...(file?.addedLinesByAuthor || {}) }, + removedLinesByAuthor: { ...(file?.removedLinesByAuthor || {}) }, + changedLinesByAuthor: { ...(file?.changedLinesByAuthor || {}) }, + + addedByAuthorInPercent: {}, + removedByAuthorInPercent: {}, + changedByAuthorInPercent: {}, + addedRemovedChangedInPercent: {}, + + firstCommit: file?.firstCommit || null, + lastCommit: file?.firstCommit || null, + }; +} + +function updateFolder(folder: any, file: IDirtyFile) { + folder.lastCommit = file.lastCommit; + folder.lines += file.lines; + + folder.addedLines += file.addedLines || 0; + folder.removedLines += file.removedLines || 0; + folder.changedLines += file.changedLines || 0; + + for (let author in file.addedLinesByAuthor) { + folder.addedLinesByAuthor[author] = folder.addedLinesByAuthor[author] + ? (folder.addedLinesByAuthor[author] + file.addedLinesByAuthor[author]) + : file.addedLinesByAuthor[author]; + } + + for (let author in file.removedLinesByAuthor) { + folder.removedLinesByAuthor[author] = folder.removedLinesByAuthor[author] + ? (folder.removedLinesByAuthor[author] + file.removedLinesByAuthor[author]) + : file.removedLinesByAuthor[author]; + } + + for (let author in file.changedLinesByAuthor) { + folder.changedLinesByAuthor[author] = folder.changedLinesByAuthor[author] + ? (folder.changedLinesByAuthor[author] + file.changedLinesByAuthor[author]) + : file.changedLinesByAuthor[author]; + } +} + +export default class FileGripByFolder { + tree: IFolder = getFolder(); + + folders: IFolder[] = []; + + // achievements + addedFoldersByAuthor: IHashMap = {}; + + clear() { + this.tree = getFolder(); + this.folders = []; + } + + addFile(file: IDirtyFile) { + let prev: any = this.tree.content; + file.path.forEach((folderName: any, index: number) => { + let folder = prev[folderName]; + if (!folder || !folder.content) { + const path = file.path.slice(0, index); + prev[folderName] = getFolder(folderName, path, file); + this.folders.push(prev[folderName]); + } else { + updateFolder(folder, file); + } + prev = prev[folderName].content; + }); + prev[file.name] = file; + } + + updateTotalInfo() { + this.folders.forEach((folder: IFolder) => { + folder.addedByAuthorInPercent = getValuesInPercent(folder.addedLinesByAuthor, folder.addedLines); + folder.removedByAuthorInPercent = getValuesInPercent(folder.removedLinesByAuthor, folder.removedLines); + folder.changedByAuthorInPercent = getValuesInPercent(folder.changedLinesByAuthor, folder.changedLines); + + folder.addedRemovedChangedInPercent = getValuesInPercent({ + added: folder.addedLines, + removed: folder.removedLines, + changed: folder.changedLines, + }, folder.addedLines + folder.removedLines + folder.changedLines); + + const author = folder.firstCommit?.author || ''; + + if (!this.addedFoldersByAuthor[author]) this.addedFoldersByAuthor[author] = []; + this.addedFoldersByAuthor[author].push(folder.pathString); + }); + this.folders = []; + } +} diff --git a/src/ts/helpers/FileGrip/components/type.ts b/src/ts/helpers/FileGrip/components/type.ts new file mode 100644 index 0000000..df871f9 --- /dev/null +++ b/src/ts/helpers/FileGrip/components/type.ts @@ -0,0 +1,55 @@ +import IHashMap from 'ts/interfaces/HashMap'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +export default class FileGripByType { + statistic: any = []; + + statisticByName: IHashMap = {}; + + clear() { + this.statistic = []; + this.statisticByName = {}; + } + + addFile(file: IDirtyFile) { + const key = file?.type; + + if (!key || file?.name?.[0] === '.') return; + + if (!this.statisticByName[key]) { + this.statisticByName[key] = this.#getNewType(file); + } + + const type = this.statisticByName[key]; + type.extension[file?.extension] = type.extension[file?.extension] + ? (type.extension[file?.extension] + 1) + : 1; + + if (file.action === 'D') { + type.removedFiles.push(file); + type.removedCount += 1; + } else { + type.files.push(file); + type.count += 1; + } + } + + #getNewType(file: IDirtyFile) { + return { + type: file?.type, + task: file?.firstCommit?.task, + path: file?.name, + extension: { [file?.extension]: 1 }, + files: [], + count: 0, + removedFiles: [], + removedCount: 0, + }; + } + + updateTotalInfo() { + this.statistic = Object.entries(this.statisticByName) + .sort((a: any, b: any) => b[1].count - a[1].count) + .map((item: any) => item[1]); + } +} diff --git a/src/ts/helpers/FileGrip/helpers/index.ts b/src/ts/helpers/FileGrip/helpers/index.ts new file mode 100644 index 0000000..35a7b18 --- /dev/null +++ b/src/ts/helpers/FileGrip/helpers/index.ts @@ -0,0 +1,12 @@ +import IHashMap from 'ts/interfaces/HashMap'; + +export function getValuesInPercent(list: IHashMap, maxValue: number) { + const percent = 100 / maxValue; + const valuesInPercent = {}; + for (let name in list) { + if (list[name]) { + valuesInPercent[name] = Math.round((list[name] ?? 100) * percent); + } + } + return valuesInPercent; +} diff --git a/src/ts/helpers/FileGrip/index.ts b/src/ts/helpers/FileGrip/index.ts new file mode 100644 index 0000000..e2a1175 --- /dev/null +++ b/src/ts/helpers/FileGrip/index.ts @@ -0,0 +1,56 @@ +import ICommit, { ISystemCommit, IFileChange } from 'ts/interfaces/Commit'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; + +import FileBuilder from './components/FileBuilder'; +import FileGripByExtension from './components/extension'; +import FileGripByType from './components/type'; +import FileGripByFolder from './components/folder'; + +class FileGrip { + files: any = new FileBuilder(); + + extension: any = new FileGripByExtension(); + + type: any = new FileGripByType(); + + tree: any = new FileGripByFolder(); + + removedTree: any = new FileGripByFolder(); + + clear() { + this.files.clear(); + this.extension.clear(); + this.type.clear(); + this.tree.clear(); + this.removedTree.clear(); + } + + addCommit(commit: ICommit | ISystemCommit) { + if (!commit?.fileChanges?.length) return; + + commit.fileChanges.forEach((fileChange: IFileChange) => { + this.files.addCommit(fileChange, commit); + }); + } + + updateTotalInfo() { + this.files.updateTotalInfo((file: IDirtyFile) => { + this.extension.addFile(file); + this.type.addFile(file); + if (file.action !== 'D') { + this.tree.addFile(file); + } else { + this.removedTree.addFile(file); + } + }); + + this.extension.updateTotalInfo(); + this.type.updateTotalInfo(); + this.tree.updateTotalInfo(); + this.removedTree.updateTotalInfo(); + } +} + +const fileGrip = new FileGrip(); + +export default fileGrip; diff --git a/src/ts/helpers/Parser/file_info.ts b/src/ts/helpers/Parser/file_info.ts deleted file mode 100644 index a1ebdcf..0000000 --- a/src/ts/helpers/Parser/file_info.ts +++ /dev/null @@ -1,36 +0,0 @@ -import ICommit from 'ts/interfaces/Commit'; - -export function getNewFileAuthor( - addedLines: number, - prev?: ICommit | null, -) { - return { - added: addedLines, - changes: addedLines, - removed: 0, - commits: 1, - tasks: { [prev?.task || '']: 1 }, - types: { [prev?.type || '']: 1 }, - scopes: { [prev?.scope || '']: 1 }, - }; -} - -export function getNewFileInfo( - name: string, - addedLines: number, - commit?: ICommit | null, -) { - const nameParts = name?.split('/')?.pop()?.split('.') || []; - return { - name, - extension: nameParts.pop(), - firstName: nameParts.shift(), - suffixes: nameParts, - lines: addedLines, - firstCommit: commit, - lastCommit: commit, - authors: { - [commit?.author || '']: getNewFileAuthor(addedLines, commit), - }, - }; -} diff --git a/src/ts/helpers/Parser/files.ts b/src/ts/helpers/Parser/files.ts deleted file mode 100644 index 2a464c9..0000000 --- a/src/ts/helpers/Parser/files.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo'; -import IHashMap from 'ts/interfaces/HashMap'; - -export function getNewFileName(fileName: string, allFiles: any) { - const hasRename = (/\s=>\s/gim).test(fileName); - if (!hasRename) return fileName; - - let changedName = fileName.match(/\{[^}]+\}/gim)?.pop(); - if (!changedName) changedName = fileName; - - const [oldName, newName] = changedName - ? changedName.replace(/[{}]/gim, '').split(' => ') - : fileName.split(' => '); - - let oldPath = fileName.replace(changedName, oldName); - if (!oldName) oldPath = oldPath.replace('//', '/'); - - const newPath = fileName.replace(changedName, newName); - if (!allFiles[oldPath]) return newPath; - - allFiles[newPath] = allFiles[oldPath]; - allFiles[newPath].name = newPath; - - return newPath; -} - -function getFolder(name: string, file: IDirtyFile): IFileTree { - return { - id: Math.random(), - name: name || '', - firstCommit: file?.firstCommit, - lastCommit: file?.firstCommit, - content: {}, - }; -} - -function getFolderTree(fileTree: any, file: IDirtyFile) { - let prev = fileTree; - let fileName: string = file.path.pop() || ''; - file.path.forEach((folder: any) => { - if (!prev[folder] || !prev[folder].content) { - prev[folder] = getFolder(folder, file); - } else { - prev[folder].lastCommit = file?.lastCommit; - } - prev = prev[folder].content; - }); - prev[fileName] = file; -} - - -export function getFileList(allFiles: IHashMap) { - const fileList = Object.values(allFiles); // @ts-ignore - const fileTree: IFileTree = getFolder(); - - fileList.forEach((file: IDirtyFile) => { - if (!file.name) return; - file.path = file.name.split('/'); - getFolderTree(fileTree.content, file); - }); - - return { fileList, fileTree }; -} diff --git a/src/ts/helpers/Parser/user_info.ts b/src/ts/helpers/Parser/getCommitInfo.ts similarity index 96% rename from src/ts/helpers/Parser/user_info.ts rename to src/ts/helpers/Parser/getCommitInfo.ts index bb22a05..f7d232a 100644 --- a/src/ts/helpers/Parser/user_info.ts +++ b/src/ts/helpers/Parser/getCommitInfo.ts @@ -2,7 +2,7 @@ import ICommit, { COMMIT_TYPE, ISystemCommit } from 'ts/interfaces/Commit'; import { getTypeAndScope, getTask, getTaskNumber } from './getTypeAndScope'; -export default function getUserInfo(logString: string): ICommit | ISystemCommit { +export default function getCommitInfo(logString: string): ICommit | ISystemCommit { // "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile" const parts = logString.split('>'); @@ -35,6 +35,7 @@ export default function getUserInfo(logString: string): ICommit | ISystemCommit text: '', type: 'не подписан', scope: 'неопределенна', + fileChanges: [], }; const isSystemPR = message.indexOf('Pull request #') === 0; diff --git a/src/ts/helpers/Parser/getFileChanges.ts b/src/ts/helpers/Parser/getFileChanges.ts new file mode 100644 index 0000000..c334d5a --- /dev/null +++ b/src/ts/helpers/Parser/getFileChanges.ts @@ -0,0 +1,75 @@ +import { IFileChange } from 'ts/interfaces/Commit'; + +function getFilePath(path: string): string[] { + const formattedPath = path + .replace(/"/gm, '') + .replace(/\/\//gm, '/'); + + const parts = formattedPath.split(/(?:\{)|(?:\s=>\s)|(?:})/gm); + if (parts.length !== 2 && parts.length !== 4) return [formattedPath]; + + if (parts.length === 2) parts.unshift(''); + + let oldPath = `${parts[0] || ''}${parts[1] || ''}${parts[3] || ''}`; + let newPath = `${parts[0] || ''}${parts[2] || ''}${parts[3] || ''}`; + + if (!parts[1]) oldPath = oldPath.replace(/\/\//gm, '/'); + if (!parts[2]) newPath = newPath.replace(/\/\//gm, '/'); + + return [oldPath, newPath]; +} + +// "38 9 src/app.css" -> [38, 9, 9, 'src/app.css'] +export function getNumStatInfo(message: string) { + let [addedRaw, removedRaw, path] = message.split('\t'); + + let added = parseInt(addedRaw, 10) || 0; + let removed = parseInt(removedRaw, 10) || 0; + + let changes = 0; + if (added > removed) { + added = added - removed; + changes = removed; + removed = 0; + } else if (removed > added) { + removed = removed - added; + changes = added; + added = 0; + } else { + changes = added; + added = 0; + removed = 0; + } + + return { + path, + addedLines: added, + removedLines: removed, + changedLines: changes, + }; +} +// ":000000 100644 000000000 fc44b0a37 A public/logo192.png" -> ['A', 'public/logo192.png'] +export function getRawInfo(message: string) { + const action = message[35]; + const path = message.split('\t')[1]; + return { path, action }; +} + +// "src/AppGit.css" -> { id: 'src/appgit.css', path: 'src/AppGit.css' } +export function getInfoFromPath(path: string): IFileChange { + const [oldPath, newPath] = getFilePath(path); + + const id = oldPath.toLowerCase(); + const newId = newPath?.toLowerCase(); + + return { + id, + newId: (newId && id !== newId) ? newId : undefined, + path: newPath || oldPath, + + action: '', + addedLines: 0, + removedLines: 0, + changedLines: 0, + }; +} diff --git a/src/ts/helpers/Parser/index.ts b/src/ts/helpers/Parser/index.ts index 3f3328a..d2b8a90 100644 --- a/src/ts/helpers/Parser/index.ts +++ b/src/ts/helpers/Parser/index.ts @@ -1,118 +1,48 @@ -import { IDirtyFile } from 'ts/interfaces/FileInfo'; +import ICommit, { IFileChange, ISystemCommit } from 'ts/interfaces/Commit'; + import IHashMap from 'ts/interfaces/HashMap'; -import ICommit, { ISystemCommit } from 'ts/interfaces/Commit'; -import settingsStore from 'ts/store/Settings'; -import getUserInfo from './user_info'; -import { getNewFileName, getFileList } from './files'; -import { getNewFileInfo } from './file_info'; +import getCommitInfo from './getCommitInfo'; +import { getInfoFromPath, getNumStatInfo, getRawInfo } from './getFileChanges'; -const uniq = {}; export default function Parser(report: string[]) { - const allFiles: IHashMap = {}; - const removedFiles: IHashMap = {}; + let commit = null; const commits: Array = []; - let week: number = 0; - let weekEndTime: number = 0; - let prev = null; + let files: IHashMap = {}; + let fileChanges: IFileChange | null = null; for (let i = 0, l = report.length; i < l; i += 1) { const message = report[i]; if (!message) continue; + const index = message.indexOf('\t'); - if (index > 0 && index < 10) { - let [addedRaw, removedRaw, fileName] = message.split('\t'); - const formattedFileName = fileName?.replace(/"/gm, ''); - fileName = getNewFileName(formattedFileName, allFiles); - let added = parseInt(addedRaw, 10) || 0; - let removed = parseInt(removedRaw, 10) || 0; - const diff = added - removed; - let changes = added > removed ? removed : added; + if (index > 0 && index < 10) { // парсинг файлов формата --num-stat + const line = getNumStatInfo(message); + if (!files[line.path]) { + files[line.path] = getInfoFromPath(line.path); + } + fileChanges = files[line.path]; + fileChanges.addedLines = line.addedLines; + fileChanges.removedLines = line.removedLines; + fileChanges.changedLines = line.changedLines; - if (!allFiles[fileName] && removedFiles[fileName]) { - allFiles[fileName] = removedFiles[fileName]; - delete removedFiles[fileName]; + } else if (message[0] === ':') { // парсинг файлов формата --raw + const line = getRawInfo(message); + if (!files[line.path]) { + files[line.path] = getInfoFromPath(line.path); } + fileChanges = files[line.path]; + fileChanges.action = line.action; - if (allFiles[fileName]) { - const fileInfo: IDirtyFile = allFiles[fileName]; - fileInfo.lastCommit = prev; - fileInfo.lines += diff; - if (!fileInfo.authors[prev?.author || '']) { - fileInfo.authors[prev?.author || ''] = { - added: 0, - changes: 0, - removed: 0, - commits: 1, - tasks: {}, - types: {}, - scopes: {}, - }; - } - const authorInfo = fileInfo.authors[prev?.author || '']; - authorInfo.changes = authorInfo.changes + changes; - if (diff > 0) { - authorInfo.added = authorInfo.added + diff; - } else { - authorInfo.removed = authorInfo.removed + diff * (-1); - } - authorInfo.commits += 1; - authorInfo.tasks[prev?.task || ''] = (authorInfo.tasks[prev?.task || ''] || 0) + 1; - authorInfo.types[prev?.type || ''] = (authorInfo.tasks[prev?.type || ''] || 0) + 1; - authorInfo.scopes[prev?.scope || ''] = (authorInfo.tasks[prev?.scope || ''] || 0) + 1; - if (allFiles[fileName].lines === 0) { - removedFiles[fileName] = allFiles[fileName]; - delete allFiles[fileName]; - } - } else { - // @ts-ignore - allFiles[fileName] = getNewFileInfo(fileName, added, prev); - } - if (removed > added) { - removed -= added; - changes += added; - added = 0; - } else if (added > removed) { - added -= removed; - changes += removed; - removed = 0; - } else if (added === removed) { - changes += added; - added = 0; - removed = 0; - } - if (prev) { // @ts-ignore - prev.changes += changes; // @ts-ignore - prev.added += added; // @ts-ignore - prev.removed += removed; - } - } else { - if (prev) { - if (uniq[prev.date]) { - // console.log(`double ${uniq[prev.date]} === ${i}`); - } - uniq[prev.date] = i; - } - - const next = getUserInfo(message); - if (next.milliseconds > weekEndTime) { - week += 1; - weekEndTime = next.milliseconds + (settingsStore.ONE_DAY * (6 - next.day)); - } - // @ts-ignore - next.week = week; - - prev = next; - commits.push(prev); // @ts-ignore + } else { // парсинг коммита + if (commit) commit.fileChanges = Object.values(files); + files = {}; + commit = getCommitInfo(message); + commit.week = 1; + commits.push(commit); } } - const { fileList, fileTree } = getFileList(allFiles); - return { - commits, - fileList, - fileTree, - removed: getFileList(removedFiles), - }; + return commits; } diff --git a/src/ts/helpers/Title.ts b/src/ts/helpers/Title.ts index fb3a9ee..0877ece 100644 --- a/src/ts/helpers/Title.ts +++ b/src/ts/helpers/Title.ts @@ -1,7 +1,7 @@ import localization from './Localization'; -function getFormattedType(dataGrip: any): string { - const popularType = dataGrip.extension.statistic?.[0] || {}; +function getFormattedType(fileGrip: any): string { + const popularType = fileGrip.extension.statistic?.[0] || {}; const extension = popularType?.extension || ''; if ([ @@ -30,7 +30,7 @@ function getFormattedType(dataGrip: any): string { 'perl', 'java', ].includes(extension)) { - const hasManifest = dataGrip.extension.statisticByName?.xml?.files?.AndroidManifest; + const hasManifest = fileGrip.extension.statisticByName?.xml?.files?.AndroidManifest; return hasManifest ? 'Android' : 'Back'; @@ -45,12 +45,12 @@ function getFormattedType(dataGrip: any): string { return extension.toUpperCase(); } -export default function getTitle(dataGrip: any, commits: any) { +export default function getTitle(dataGrip: any, fileGrip: any, commits: any) { if (!commits.length) { return localization.get('common.title'); } - const type = getFormattedType(dataGrip) || ''; + const type = getFormattedType(fileGrip) || ''; const task = dataGrip.pr.statistic?.[0]?.task || ''; const author = dataGrip.firstLastCommit.minData.author || ''; const year = commits?.[0]?.year || ''; @@ -59,4 +59,4 @@ export default function getTitle(dataGrip: any, commits: any) { const formattedAuthor = author.split(' ').shift() || ''; return `${type} ${formattedTask} (${year}, ${formattedAuthor})`; -} \ No newline at end of file +} diff --git a/src/ts/helpers/achievement/byAuthor.ts b/src/ts/helpers/achievement/byAuthor.ts index f019944..411d9de 100644 --- a/src/ts/helpers/achievement/byAuthor.ts +++ b/src/ts/helpers/achievement/byAuthor.ts @@ -1,6 +1,37 @@ import ALL_ACHIEVEMENTS from './constants/list'; +import ICommit, { ISystemCommit } from 'ts/interfaces/Commit'; -export default function getAchievementByAuthor(list: string[], statistic: any) { +function getHoroscope(firstCommit: ICommit | ISystemCommit) { + const month = firstCommit.month + 1; + const dayInMonth = firstCommit.month; + const horoscopeRange = [ + { from: [22, 12], to: [20, 1] }, + { from: [20, 1], to: [18, 2] }, + { from: [19, 2], to: [20, 3] }, + { from: [21, 3], to: [19, 4] }, + { from: [20, 4], to: [20, 5] }, + { from: [21, 5], to: [21, 6] }, + { from: [22, 6], to: [22, 7] }, + { from: [23, 7], to: [22, 8] }, + { from: [23, 8], to: [22, 9] }, + { from: [23, 9], to: [23, 10] }, + { from: [24, 10], to: [22, 11] }, + { from: [23, 11], to: [21, 12] }, + ]; + + const achievementIndex = horoscopeRange.reduce((acc: string, item: any, index: number) => { + if (acc) return acc; + if ((item.from[1] === month && dayInMonth >= item.from[0]) + || (item.to[1] === month && dayInMonth <= item.to[0])) return `${index + 1}`; + return acc; + }, ''); + + return `horoscope${achievementIndex}`; +} + + +export default function getAchievementByAuthor(list: string[], dataGrip: any, author: string) { + const statistic = dataGrip.author.statisticByName[author]; const commitByHours = statistic.commitsByHour; if (statistic.commits > 20) { @@ -40,6 +71,15 @@ export default function getAchievementByAuthor(list: string[], statistic: any) { if (statistic.allDaysInProject >= 666) list.push('more666DaysInProject'); // Азино - отработал 777 дней на проекте if (statistic.allDaysInProject >= 777) list.push('more777DaysInProject'); + // Старожил - отработал 3 года на проекте + if (statistic.allDaysInProject >= (3 * 365)) list.push('more3YearsInProject'); + // хоть раз работал на выходных + if (statistic.commitsByDayAndHourTotal[5] + || statistic.commitsByDayAndHourTotal[6]) list.push('workOnWeekends'); + + // работал над задачей больше трех месяцев + const daysInWork = dataGrip.tasks.longTaskByAuthor[author] || {}; + if (daysInWork > 92) list.push('longTask'); } // Ни единого разрыва - 0 дней без коммитов if (statistic.lazyDays === 0) list.push('zeroLazyDays'); @@ -48,6 +88,11 @@ export default function getAchievementByAuthor(list: string[], statistic: any) { // Точно в цель - в среднем 1 коммит на таск if (statistic.tasks / statistic.commits) list.push('oneCommitOneTask'); + list.push(getHoroscope(statistic.firstCommit)); + + const statisticByPr = dataGrip.pr.statisticByName[author] || {}; + if (statisticByPr?.maxDelayDays > 31) list.push('longWaitPR'); + return list.reduce((acc: any, type: string) => { const index = ALL_ACHIEVEMENTS[type] - 1; acc[index].push(type); diff --git a/src/ts/helpers/achievement/byCompetition.ts b/src/ts/helpers/achievement/byCompetition.ts index 4eb738b..63bbfcd 100644 --- a/src/ts/helpers/achievement/byCompetition.ts +++ b/src/ts/helpers/achievement/byCompetition.ts @@ -1,6 +1,7 @@ import IHashMap from 'ts/interfaces/HashMap'; import getAchievementByAuthor from './byAuthor'; +import getAchievementByFile from './byFile'; class AchievementsByAuthor { authors: IHashMap = {}; @@ -10,11 +11,12 @@ class AchievementsByAuthor { } add(authors: Array<[string, number]>, maxAchievementCode: string, minAchievementCode?: string) { - const first = authors[0][0]; + const first = authors?.[0]?.[0]; + if (!first) return; this.authors?.[first]?.push(maxAchievementCode); if (!minAchievementCode) return; - const last = authors[authors.length - 1][0]; + const last = authors?.[authors.length - 1]?.[0]; this.authors?.[last]?.push(minAchievementCode); } } @@ -22,7 +24,7 @@ class AchievementsByAuthor { class AchievementsByCompetition { authors: IHashMap> = {}; - updateByDataGrip(dataGrip: any) { + updateByGrip(dataGrip: any, fileGrip: any) { const statisticByAuthor = dataGrip.author.statistic; const byAuthor: any = new AchievementsByAuthor(); const total = this.#getMinMaxValue(statisticByAuthor, dataGrip, (statistic: any) => { @@ -62,6 +64,12 @@ class AchievementsByCompetition { // Количество коммитов в день byAuthor.add(total.commitsInDay, 'moreCommits'); + // Таможня даёт добро + byAuthor.add(total.morePRMerge, 'morePRMerge'); + + // Давным давно, в далёкой галактике + byAuthor.add(total.moreLongWaitPR, 'moreLongWaitPR'); + // Первый и последний коммит const lastAuthor = dataGrip.firstLastCommit.maxData.author; const firstAuthor = dataGrip.firstLastCommit.minData.author; @@ -72,10 +80,11 @@ class AchievementsByCompetition { byAuthor.authors[lastAuthor].push('lastCommit'); } - console.dir(byAuthor); + getAchievementByFile(fileGrip, byAuthor); + statisticByAuthor.forEach((statistic: any) => { const achievements = byAuthor.authors[statistic.author]; - this.authors[statistic.author] = getAchievementByAuthor(achievements, statistic); + this.authors[statistic.author] = getAchievementByAuthor(achievements, dataGrip, statistic.author); }); } @@ -85,9 +94,9 @@ class AchievementsByCompetition { statisticByAuthor.forEach((statistic: any) => { callback(statistic); - const addData = (property: string, count: number) => { + const addData = (property: string, count?: number) => { if (!total[property]) total[property] = []; - total[property].push([statistic.author, count]); + total[property].push([statistic.author, count || 0]); }; addData('nameLength', statistic.author.length); @@ -101,6 +110,10 @@ class AchievementsByCompetition { addData('tasksInDay', byTimestamp.tasksByTimestampCounter.max); addData('commitsInDay', byTimestamp.commitsByTimestampCounter.max); + const byPr = dataGrip.pr.statisticByName[statistic.author] || {}; + addData('moreLongWaitPR', byPr?.maxDelayDays); + addData('morePRMerge', byPr?.numberMergedPr); + if (statistic.isStaff) return; addData('allDaysInProject', statistic.allDaysInProject); addData('lazyDays', statistic.lazyDays); diff --git a/src/ts/helpers/achievement/byFile.ts b/src/ts/helpers/achievement/byFile.ts new file mode 100644 index 0000000..48ba5f4 --- /dev/null +++ b/src/ts/helpers/achievement/byFile.ts @@ -0,0 +1,81 @@ +import { IDirtyFile } from 'ts/interfaces/FileInfo'; +import IHashMap from 'ts/interfaces/HashMap'; + +function getHashMap(list: string[]) { + return Object.fromEntries(list.map((code: string) => [code, true])); +} + +const IS_LINT_HINT = getHashMap(['.eslintrc', '.stylelintrc.json']); +const IS_CSS = getHashMap(['css', 'scss', 'less', 'style']); +const IS_TEST = getHashMap(['test', 'mock', 'snap']); +const IS_CI_CD = getHashMap([ + 'Dockerfile', + 'gradlew', + 'gradlew.bat', + 'gradle.properties', + 'docker-compose.yml', +]); + +function getAddedChangedLines(file: IDirtyFile) { + return [ + Object.entries(file?.addedLinesByAuthor || {}), + Object.entries(file?.changedLinesByAuthor || {}), + ]; +} + +function getTopUser(listOfChanges: any) { + const total = listOfChanges.reduce((acc: any, item: any) => { + acc[item[0]] = acc[item[0]] ? (acc[item[0]] + item[1]) : item[1]; + return acc; + }, {}); + + return Object.entries(total).sort((a: any, b: any) => b[1] - a[1]); +} + +export default function getAchievementByFile(fileGrip: any, byAuthor: any) { + const moreLintHint: any = []; + const moreReadMe: any = []; + const moreStyle: any = []; + const moreTests: any = []; + const moreDevOps: any = []; + const longFilePath: any = { author: '', length: 0 }; + const longFileName: any = { author: '', length: 0 }; + const fileRush: IHashMap = {}; + + fileGrip.files.list.forEach((file: IDirtyFile) => { + if (IS_LINT_HINT[file.name]) moreLintHint.push(getAddedChangedLines(file)); + if (file.extension === 'md') moreReadMe.push(getAddedChangedLines(file)); + if (IS_CSS[file.extension]) moreStyle.push(getAddedChangedLines(file)); + if (IS_TEST[file.extension] || IS_TEST[file.type]) moreTests.push(getAddedChangedLines(file)); + if (IS_CI_CD[file.name]) moreDevOps.push(getAddedChangedLines(file)); + + fileRush[file.firstCommit?.author || ''] = fileRush[file.firstCommit?.author || ''] + ? (fileRush[file.firstCommit?.author || ''] + 1) + : 1; + + if (file.name.length > longFileName.length) { + longFileName.author = file.firstCommit?.author; + longFileName.length = file.name.length; + } + if (file.path.length > longFilePath.length) { + longFilePath.author = file.firstCommit?.author; + longFilePath.length = file.name.length; + } + }); + + const userFileRush = Object.entries(fileRush).sort((a: any, b: any) => b[1] - a[1]); + + const addedFoldersByAuthor = Object + .entries(fileGrip.tree.addedFoldersByAuthor) + .map((item: any) => [item[0], item[1].length]); + + byAuthor.add(getTopUser(userFileRush), 'fileRush'); + byAuthor.add(getTopUser(addedFoldersByAuthor), 'moreAddedFolders'); + byAuthor.add(getTopUser(moreLintHint.flat(2)), 'moreLintHint'); + byAuthor.add(getTopUser(moreReadMe.flat(2)), 'moreReadMe'); + byAuthor.add(getTopUser(moreStyle.flat(2)), 'moreStyle'); + byAuthor.add(getTopUser(moreTests.flat(2)), 'moreTests'); + byAuthor.add(getTopUser(moreDevOps.flat(2)), 'moreDevOps'); + byAuthor.authors[longFilePath.author].push('longFilePath'); + byAuthor.authors[longFileName.author].push('longFileName'); +} diff --git a/src/ts/helpers/achievement/constants/list.ts b/src/ts/helpers/achievement/constants/list.ts index 2b8f22d..96b6919 100644 --- a/src/ts/helpers/achievement/constants/list.ts +++ b/src/ts/helpers/achievement/constants/list.ts @@ -33,6 +33,7 @@ export default { more666DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Чёрт more777DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Азино 3 топора moreRefactoring: ACHIEVEMENT_TYPE.GOOD, // Выпускающий редактор + moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды // нет картинки longestMessage: ACHIEVEMENT_TYPE.NORMAL, // А разговоров то было... @@ -41,39 +42,50 @@ export default { noCommitOnDay: ACHIEVEMENT_TYPE.NORMAL, // Технический перерыв hasCommitEveryTime: ACHIEVEMENT_TYPE.BAD, // Умер на работе commitsAfter1800: ACHIEVEMENT_TYPE.GOOD, // Делу время - more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Нужно чуть чуть потерпеть, отработал год и не уволился + more365DaysInProject: ACHIEVEMENT_TYPE.GOOD, // Годовасик, отработал год и не уволился + more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте firstCommit: ACHIEVEMENT_TYPE.NORMAL, // Кто первый, того и тапки. первый коммит на проекте lastCommit: ACHIEVEMENT_TYPE.NORMAL, // Я закончил. последний коммит на проекте firstLastCommit: ACHIEVEMENT_TYPE.NORMAL, // От начала и до конца. первый и последний коммит на проекте + moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json + moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD + moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование + moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps + longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением + longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем + moreAddedFolders: ACHIEVEMENT_TYPE.NORMAL, // Директор, создал больше всех дирректорий + morePRMerge: ACHIEVEMENT_TYPE.NORMAL, // Таможня даёт добро, + longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца + moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике + workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных + longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев + fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте + + // Типаж Козерога, по месяцу первого коммита + horoscope1: ACHIEVEMENT_TYPE.NORMAL, + horoscope2: ACHIEVEMENT_TYPE.NORMAL, + horoscope3: ACHIEVEMENT_TYPE.NORMAL, + horoscope4: ACHIEVEMENT_TYPE.NORMAL, + horoscope5: ACHIEVEMENT_TYPE.NORMAL, + horoscope6: ACHIEVEMENT_TYPE.NORMAL, + horoscope7: ACHIEVEMENT_TYPE.NORMAL, + horoscope8: ACHIEVEMENT_TYPE.NORMAL, + horoscope9: ACHIEVEMENT_TYPE.NORMAL, + horoscope10: ACHIEVEMENT_TYPE.NORMAL, + horoscope11: ACHIEVEMENT_TYPE.NORMAL, + horoscope12: ACHIEVEMENT_TYPE.NORMAL, // нет кода - moreStyle: ACHIEVEMENT_TYPE.GOOD, // Полиция моды lessWorkDays: ACHIEVEMENT_TYPE.BAD, // Дальше без меня moreOnHoliday: ACHIEVEMENT_TYPE.BAD, // Нет жизни moreCreateCode: ACHIEVEMENT_TYPE.NORMAL, // Созидатель -- переименовать? moreRemoveCode: ACHIEVEMENT_TYPE.NORMAL, // Разрушитель moreChangeCode: ACHIEVEMENT_TYPE.NORMAL, // Реформатор - morePRMerge: ACHIEVEMENT_TYPE.GOOD, // Таможня даёт добро, - workOnWeekends: ACHIEVEMENT_TYPE.BAD, // Работа не walk. хоть раз работал на выходных - longWaitPR: ACHIEVEMENT_TYPE.BAD, // Обещать не значит жениться, ожидание PR больше месяца - moreLongWaitPR: ACHIEVEMENT_TYPE.BAD, // Давным давно, в далёкой галактике - more3YearsInProject: ACHIEVEMENT_TYPE.GOOD, // Старожил. больше 3х лет на проекте - oneExtension: ACHIEVEMENT_TYPE.NORMAL, // Один в поле воин. Только он работает с файлами определенного расширения - fileRush: ACHIEVEMENT_TYPE.NORMAL, // Зерг Раш. Создал больше всех файлов в проекте - moreLintHint: ACHIEVEMENT_TYPE.GOOD, // Грамар-наци. Больше всех внес в .eslintrc .stylelintrc.json - moreReadMe: ACHIEVEMENT_TYPE.GOOD, // Летописец. Больше остальных внес в .MD - moreDevOps: ACHIEVEMENT_TYPE.GOOD, // DevOps. Больше остальных внес в DevOps - moreTests: ACHIEVEMENT_TYPE.GOOD, // Тестировщик. Больше остальных внес в тестирование - allRelease: ACHIEVEMENT_TYPE.NORMAL, // Фулл хаус. есть релиз, собранный только из его задач - longFilePath: ACHIEVEMENT_TYPE.NORMAL, // Закрома родины. первый создал файл с самым глубоким вложением - longFileName: ACHIEVEMENT_TYPE.NORMAL, // Размер имеет значение. создал файл с самым длинным именем - removeCreateFile: ACHIEVEMENT_TYPE.NORMAL, // Откопал стюардессу. востановил удаленный файл renameFile: ACHIEVEMENT_TYPE.NORMAL, // Астана Нур-Султан Астана. переименовывал туда-сюда файл - longTask: ACHIEVEMENT_TYPE.BAD, // Вроде изян. работал над задачей больше трех месяцев // Галя, у нас отмена - откатил назад // У меня работает - больше всего коммитов с текстом fix // 500я на проде - больше всего коммитов с префиксом hotfix @@ -81,6 +93,4 @@ export default { // godFather: ACHIEVEMENT_TYPE.NORMAL, // Крёстный отец. Первый создал файлы небольшой группы. // Боярин - есть папка на 20 файлов, где правит только этот человек // Феодал - есть папка на 50 файлов, где правит только этот человек - - // Психологический знак задиака по дате первого коммита }; diff --git a/src/ts/interfaces/Banner.ts b/src/ts/interfaces/Banner.ts new file mode 100644 index 0000000..637a360 --- /dev/null +++ b/src/ts/interfaces/Banner.ts @@ -0,0 +1,20 @@ +interface IBanner { + isDefault?: boolean; + ref?: string; + link?: string; + isOpenInNewTab?: boolean; + + /* Логотип */ + icon?: string; + title?: string; + + /* Картинка баннера */ + banner?: string; + + /* Текстовы баннер */ + bannerText?: string; + color?: string; + backgroundColor?: string; +} + +export default IBanner; diff --git a/src/ts/interfaces/Commit.ts b/src/ts/interfaces/Commit.ts index eefe444..c443022 100644 --- a/src/ts/interfaces/Commit.ts +++ b/src/ts/interfaces/Commit.ts @@ -1,3 +1,17 @@ +export interface IFileChange { + id: string; // регистро-независимый путь в качестве ID + path: string; // актуальный путь с учётом регистра + + newId?: string; // новый ID, если файл переименовали + newPath?: string; // новый путь с учётом регистра + + action: string; // тип действия с файлом: добавили, изменили, удалили + + addedLines: number; + removedLines: number; + changedLines: number; +} + export interface ILog { // date date: string; // "2021-02-09T12:59:17+03:00", @@ -22,6 +36,8 @@ export interface ILog { taskNumber: string; // "0000", type: string; // feat|fix|docs|style|refactor|test|chore scope: string; // table, sale, profile and etc. + + fileChanges: IFileChange[]; } export const COMMIT_TYPE = { diff --git a/src/ts/interfaces/FileInfo.ts b/src/ts/interfaces/FileInfo.ts index e914080..44c3042 100644 --- a/src/ts/interfaces/FileInfo.ts +++ b/src/ts/interfaces/FileInfo.ts @@ -1,37 +1,40 @@ import ICommit, { ISystemCommit } from './Commit'; import IHashMap from './HashMap'; -export interface IDirtyFile { - name: string; // ".gitignore", +interface IFileStat { lines: number; // 38, line in file for this moment + + addedLines: number; + removedLines: number; + changedLines: number; + + addedLinesByAuthor: IHashMap; // added lines by author + removedLinesByAuthor: IHashMap; // removed lines by author + changedLinesByAuthor: IHashMap; // removed lines by author + + addedByAuthorInPercent: IHashMap; + removedByAuthorInPercent: IHashMap; + changedByAuthorInPercent: IHashMap; + addedRemovedChangedInPercent: IHashMap; + firstCommit: ICommit | ISystemCommit | null, lastCommit: ICommit | ISystemCommit | null, - path: string[], - extension: string, - firstName: string, - authors: { - [author: string]: { - added: number; // 38, - changes: number; // 38, - removed: number; // 0, - commits: number; // 1, - tasks: { - [taskName: string]: number, - }, - types: { - [typeName: string]: number, - }, - scopes: { - [scopeName: string]: number, - } - } - } } -export interface IFileTree { +export interface IDirtyFile extends IFileStat { + id: string; // "src/mynewlogo.test.ts", + path: string[]; // ['src'] + pathString: string; // 'src/MyNewLogo.test.ts' + name: string; // "MyNewLogo.test.ts", + extension: string; // "ts", + type: string; // "test", + action: string; // 'A' or 'M' or 'D' +} + +export interface IFolder extends IFileStat { id?: number; name?: string; - firstCommit: ICommit | ISystemCommit | null, - lastCommit: ICommit | ISystemCommit | null, + path: string[]; // ['src'] + pathString: string; // 'src\\ts' content: IHashMap, } diff --git a/src/ts/pages/Common/helpers/getMax.ts b/src/ts/pages/Common/helpers/getMax.ts index 0394e5e..aea2174 100644 --- a/src/ts/pages/Common/helpers/getMax.ts +++ b/src/ts/pages/Common/helpers/getMax.ts @@ -9,4 +9,4 @@ export function getMax(response: IPagination, property: string, subProperty export function getMaxByLength(response: IPagination, property: string) { return getMax(response, property, 'length'); -} \ No newline at end of file +} diff --git a/src/ts/pages/Settings/components/MailMap.tsx b/src/ts/pages/Settings/components/MailMap.tsx index b9ab83a..3b244f0 100644 --- a/src/ts/pages/Settings/components/MailMap.tsx +++ b/src/ts/pages/Settings/components/MailMap.tsx @@ -6,10 +6,10 @@ import Console from 'ts/components/Console'; import style from '../styles/index.module.scss'; function MailMap(): React.ReactElement | null { - const items = dataGripStore.dataGrip.author.statistic.map((item: any) => ( - `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>` - )); - const commands = items.map((text: string) => (

{text}

)); + const items = dataGripStore.dataGrip.author.statistic + .map((item: any) => `${item.author} <${item.firstCommit.email}> <${item.firstCommit.email}>`) + .sort(); + const commands = items.map((text: any) => (

{text}

)); const commandsForCopy = items.join('\r\n'); return ( diff --git a/src/ts/pages/Team/components/Extension2.tsx b/src/ts/pages/Team/components/Extension.tsx similarity index 87% rename from src/ts/pages/Team/components/Extension2.tsx rename to src/ts/pages/Team/components/Extension.tsx index e3348a2..64c9b17 100644 --- a/src/ts/pages/Team/components/Extension2.tsx +++ b/src/ts/pages/Team/components/Extension.tsx @@ -29,8 +29,8 @@ interface IFilesViewProps { function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) { if (!response) return null; - const current = getMax(response, 'current', 'count'); - const removed = getMax(response, 'removed', 'count'); + const current = getMax(response, 'count'); + const removed = getMax(response, 'removedCount'); const max = Math.max(current, removed); const filesChart = getOptions({ max, suffix: 'page.team.extension.files' }); @@ -47,14 +47,14 @@ function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewP isFixed template={ColumnTypesEnum.STRING} title="page.team.extension.name" - properties="extension" + properties="type" width={200} /> row.count === 1 || row.removedCount === 1 ? row.path : ''} /> {mode === 'print' ? ( value.count} + properties="count" /> ( + template={(value: number) => ( )} /> value.count} + properties="removedCount" /> ( + template={(value: number) => ( )} /> @@ -127,7 +125,7 @@ ExtensionView.defaultProps = { const Extension = observer(({ mode, }: ICommonPageProps): React.ReactElement | null => { - const rows = dataGripStore.dataGrip.extension.statistic; + const rows = dataGripStore.fileGrip.type.statistic; if (rows?.length < 2) return mode !== 'print' ? () : null; return ( diff --git a/src/ts/pages/Team/components/FileAnalitics/Extension.tsx b/src/ts/pages/Team/components/FileAnalitics/Extension.tsx new file mode 100644 index 0000000..5c5db3f --- /dev/null +++ b/src/ts/pages/Team/components/FileAnalitics/Extension.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; + +import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination'; +import dataGripStore from 'ts/store/DataGrip'; + +import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps'; +import DataLoader from 'ts/components/DataLoader'; +import Pagination from 'ts/components/DataLoader/components/Pagination'; +import getFakeLoader from 'ts/components/DataLoader/helpers/formatter'; +import NothingFound from 'ts/components/NothingFound'; +import Title from 'ts/components/Title'; +import DataView from 'ts/components/DataView'; +import Column from 'ts/components/Table/components/Column'; +import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column'; +import getOptions from 'ts/components/LineChart/helpers/getOptions'; +import LineChart from 'ts/components/LineChart'; +import { getMax } from 'ts/pages/Common/helpers/getMax'; +import ExternalLink from 'ts/components/ExternalLink'; +import userSettings from 'ts/store/UserSettings'; + +interface IFilesViewProps { + response?: IPagination; + updateSort?: Function; + rowsForExcel?: any[]; + mode?: string; +} + +function ExtensionView({ response, updateSort, rowsForExcel, mode }: IFilesViewProps) { + if (!response) return null; + + const current = getMax(response, 'count'); + const removed = getMax(response, 'removedCount'); + const max = Math.max(current, removed); + const filesChart = getOptions({ max, suffix: 'page.team.extension.files' }); + + return ( + + + row.count === 1 || row.removedCount === 1 ? row.path : ''} + /> + {mode === 'print' ? ( + + ) : ( + { + if (!row.path) return ''; + return ( + + ); + }} + title="page.team.pr.task" + properties="task" + width={120} + /> + )} + + ( + + )} + /> + + ( + + )} + /> + + ); +} + +ExtensionView.defaultProps = { + response: undefined, +}; + +const Extension = observer(({ + mode, +}: ICommonPageProps): React.ReactElement | null => { + const rows = dataGripStore.fileGrip.extension.statistic; + if (rows?.length < 2) return mode !== 'print' ? () : null; + + return ( + <> + + <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; diff --git a/src/ts/pages/Team/components/FileAnalitics/Type.tsx b/src/ts/pages/Team/components/FileAnalitics/Type.tsx new file mode 100644 index 0000000..2e2168d --- /dev/null +++ b/src/ts/pages/Team/components/FileAnalitics/Type.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; + +import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination'; +import dataGripStore from 'ts/store/DataGrip'; + +import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps'; +import DataLoader from 'ts/components/DataLoader'; +import Pagination from 'ts/components/DataLoader/components/Pagination'; +import getFakeLoader from 'ts/components/DataLoader/helpers/formatter'; +import NothingFound from 'ts/components/NothingFound'; +import Title from 'ts/components/Title'; +import DataView from 'ts/components/DataView'; +import Column from 'ts/components/Table/components/Column'; +import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column'; +import getOptions from 'ts/components/LineChart/helpers/getOptions'; +import LineChart from 'ts/components/LineChart'; +import { getMax } from 'ts/pages/Common/helpers/getMax'; +import ExternalLink from 'ts/components/ExternalLink'; +import userSettings from 'ts/store/UserSettings'; + +interface IFilesViewProps { + response?: IPagination<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; diff --git a/src/ts/pages/Team/components/FileAnalitics/index.tsx b/src/ts/pages/Team/components/FileAnalitics/index.tsx new file mode 100644 index 0000000..8d55210 --- /dev/null +++ b/src/ts/pages/Team/components/FileAnalitics/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; + +import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps'; + +import Extension from './Extension'; +import Type from './Type'; + +const FileAnalitics = observer(({ + mode, +}: ICommonPageProps): React.ReactElement | null => { + return ( + <> + <Extension mode={mode} /> + <Type mode={mode} /> + </> + ); +}); + +export default FileAnalitics; diff --git a/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx b/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx new file mode 100644 index 0000000..4561c1f --- /dev/null +++ b/src/ts/pages/Team/components/Files/FileBreadcrumbs.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; + +import style from '../../styles/path.module.scss'; +import treeStore from '../../store/Tree'; + +const FileBreadcrumbs = observer((): React.ReactElement => { + const directories = treeStore.selectedPath + .map((dirName: string, index: number) => ( + <> + <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; diff --git a/src/ts/pages/Team/components/TreeFilters.tsx b/src/ts/pages/Team/components/Files/Filters.tsx similarity index 93% rename from src/ts/pages/Team/components/TreeFilters.tsx rename to src/ts/pages/Team/components/Files/Filters.tsx index 083a77f..a2e4b51 100644 --- a/src/ts/pages/Team/components/TreeFilters.tsx +++ b/src/ts/pages/Team/components/Files/Filters.tsx @@ -6,8 +6,8 @@ import dataGripStore from 'ts/store/DataGrip'; import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons'; import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber'; -import treeStore from '../store/Tree'; -import style from '../styles/filters.module.scss'; +import treeStore from '../../store/Tree'; +import style from '../../styles/filters.module.scss'; const TreeFilters = observer((): React.ReactElement => { const { t } = useTranslation(); diff --git a/src/ts/pages/Team/components/Files/Table.tsx b/src/ts/pages/Team/components/Files/Table.tsx new file mode 100644 index 0000000..6292df2 --- /dev/null +++ b/src/ts/pages/Team/components/Files/Table.tsx @@ -0,0 +1,156 @@ +import React from 'react'; + +import { IPagination } from 'ts/interfaces/Pagination'; +import { IDirtyFile } from 'ts/interfaces/FileInfo'; +import dataGripStore from 'ts/store/DataGrip'; + +import Table from 'ts/components/Table'; +import Column from 'ts/components/Table/components/Column'; +import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column'; +import LineChart from 'ts/components/LineChart'; +import getOptions from 'ts/components/LineChart/helpers/getOptions'; + +import { getMax } from 'ts/pages/Common/helpers/getMax'; +import { getDate } from 'ts/helpers/formatter'; + +import treeStore from '../../store/Tree'; + +interface IViewProps { + response?: IPagination<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; diff --git a/src/ts/pages/Team/components/Files/index.tsx b/src/ts/pages/Team/components/Files/index.tsx new file mode 100644 index 0000000..932bd36 --- /dev/null +++ b/src/ts/pages/Team/components/Files/index.tsx @@ -0,0 +1,61 @@ +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; + +import { IPaginationRequest } from 'ts/interfaces/Pagination'; +import dataGripStore from 'ts/store/DataGrip'; + +import PageWrapper from 'ts/components/Page/wrapper'; +import DataLoader from 'ts/components/DataLoader'; +import Pagination from 'ts/components/DataLoader/components/Pagination'; +import getFakeLoader from 'ts/components/DataLoader/helpers/formatter'; +import Title from 'ts/components/Title'; +import NothingFound from 'ts/components/NothingFound'; + +import TreeFilters from './Filters'; +import FileBreadcrumbs from './FileBreadcrumbs'; +import View from './Table'; +import { getContentByPath } from '../../helpers/tree'; +import treeStore from '../../store/Tree'; + +interface ITreeProps { + type?: string +} + +const Tree = observer(({ type }: ITreeProps): React.ReactElement => { + const { t } = useTranslation(); + const fileTree = type === 'removed' + ? dataGripStore.fileGrip.removedTree.tree + : dataGripStore.fileGrip.tree.tree; + + useEffect(() => { + treeStore.updateFilter('selectedPath', []); + }, [type]); + + const content = getContentByPath(fileTree, treeStore.selectedPath); + if (!content?.length) { + return <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; diff --git a/src/ts/pages/Team/components/PR/All.tsx b/src/ts/pages/Team/components/PR/All.tsx index c52671e..a490285 100644 --- a/src/ts/pages/Team/components/PR/All.tsx +++ b/src/ts/pages/Team/components/PR/All.tsx @@ -36,7 +36,6 @@ function AllPR({ order: dataGripStore.dataGrip.author.list, }); - console.log(commitsChart); return ( <DataView rowsForExcel={rowsForExcel} diff --git a/src/ts/pages/Team/components/Top.tsx b/src/ts/pages/Team/components/Top.tsx index 9c687c0..365dc23 100644 --- a/src/ts/pages/Team/components/Top.tsx +++ b/src/ts/pages/Team/components/Top.tsx @@ -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 diff --git a/src/ts/pages/Team/components/Tree.tsx b/src/ts/pages/Team/components/Tree.tsx deleted file mode 100644 index ba3992c..0000000 --- a/src/ts/pages/Team/components/Tree.tsx +++ /dev/null @@ -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; diff --git a/src/ts/pages/Team/helpers/tree.ts b/src/ts/pages/Team/helpers/tree.ts index d5bcbda..a8d8a91 100644 --- a/src/ts/pages/Team/helpers/tree.ts +++ b/src/ts/pages/Team/helpers/tree.ts @@ -1,58 +1,22 @@ -import { IFileTree } from 'ts/interfaces/FileInfo'; +import { IFolder } from 'ts/interfaces/FileInfo'; -interface IFile { - name: string; - path: string[]; - content: IFile[]; -} - -export function getSubTreeByPath(tree: IFileTree, path: string[]) { - let subTree: any = tree || { content: [] }; - (path || []).forEach((folderName: string) => { +function getSubTree(tree: IFolder, path: string[]) { + return (path || []).reduce((subTree: any, folderName: string) => { subTree = subTree.content[folderName] || { content: [] }; - }); - return subTree; + return subTree; + }, tree || { content: [] }); } - -function getButtonUp(file: IFile) { - return file?.path?.length ? ({ - title: '..', - path: file.path.slice(0, -1), - }) : null; +function getSortedContent(subTree: any) { + return Object.values(subTree.content) + .sort((a: any, b: any) => { + if (a.content && !b.content) return -1; + if (!a.content && b.content) return 1; + if (a.name === b.name) return 0; + return a.name > b.name ? 1 : -1; + }); } -function getFolderView(file: IFile) { - return { - file, - title: `📁 ${file.name}`, - path: file.path, - }; +export function getContentByPath(fileTree: IFolder, path: string[]) { + return getSortedContent(getSubTree(fileTree, path)); } - -function getFileView(file: IFile) { - return { - file, - title: `📄 ${file.name.split('/').pop() || ''}`, - }; -} - -export function getArrayFromTree(tree: any) { - const folders = []; - const files = []; - - for (let name in tree.content) { - const file = tree.content[name]; - if (file.content) { - folders.push(getFolderView(file)); - } else { - files.push(getFileView(file)); - } - } - - return [ - getButtonUp(tree), - ...folders, - ...files, - ].filter(v => v); -} \ No newline at end of file diff --git a/src/ts/pages/Team/index.tsx b/src/ts/pages/Team/index.tsx index ef1bc7b..3e5a38c 100644 --- a/src/ts/pages/Team/index.tsx +++ b/src/ts/pages/Team/index.tsx @@ -12,8 +12,8 @@ import PopularWords from './components/PopularWords'; import Scope from './components/Scope'; import Tempo from './components/Tempo'; import Total from './components/Total'; -import Tree from './components/Tree'; -import Extension from './components/Extension2'; +import Files from './components/Files'; +import FileAnalitics from './components/FileAnalitics'; import Type from './components/Type'; import Week from './components/Week'; import Month from './components/Month'; @@ -34,9 +34,9 @@ function getViewById(page?: string) { if (page === 'week') return <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/>; diff --git a/src/ts/pages/Team/styles/path.module.scss b/src/ts/pages/Team/styles/path.module.scss new file mode 100644 index 0000000..153c682 --- /dev/null +++ b/src/ts/pages/Team/styles/path.module.scss @@ -0,0 +1,26 @@ +@import 'src/styles/variables'; + +.file_breadcrumbs { + margin: 0 0 24px 0; + + &_text { + font-size: var(--font-l); + font-weight: 100; + + display: inline-block; + margin-right: var(--space-m); + + text-decoration: none; + vertical-align: top; + + color: var(--color-black); + } + + &_link { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/ts/pages/Welcome/index.tsx b/src/ts/pages/Welcome/index.tsx index 583aa2b..4dd7892 100644 --- a/src/ts/pages/Welcome/index.tsx +++ b/src/ts/pages/Welcome/index.tsx @@ -27,7 +27,7 @@ function WarningInfo() { } function Welcome() { - const command = 'git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n'; + const command = 'git --no-pager log --raw --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt\n'; return ( <> {process.env.REACT_APP_TYPE !== 'local' && (<WarningInfo />)} diff --git a/src/ts/store/DataGrip.ts b/src/ts/store/DataGrip.ts index 8d80be8..a8723c1 100644 --- a/src/ts/store/DataGrip.ts +++ b/src/ts/store/DataGrip.ts @@ -1,13 +1,14 @@ import { makeObservable, observable, action } from 'mobx'; import ICommit, { ISystemCommit } from 'ts/interfaces/Commit'; -import { IDirtyFile, IFileTree } from 'ts/interfaces/FileInfo'; + import achievements from 'ts/helpers/achievement/byCompetition'; import dataGrip from 'ts/helpers/DataGrip'; -import getFileTreeWithStatistic from 'ts/helpers/DataGrip/helpers/tree'; +import fileGrip from 'ts/helpers/FileGrip'; import Parser from 'ts/helpers/Parser'; -import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings'; import getTitle from 'ts/helpers/Title'; + +import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings'; import { applicationHasCustom } from 'ts/helpers/RPC'; import settingsStore from './Settings'; @@ -21,6 +22,7 @@ export enum DataParseStatusEnum { interface IDataGripStore { commits: ICommit[]; dataGrip: any; + fileGrip: any; status: DataParseStatusEnum; setCommits: (log?: string[]) => void; } @@ -28,16 +30,10 @@ interface IDataGripStore { class DataGripStore implements IDataGripStore { commits: any[] = []; - fileList: IDirtyFile[] = []; - - fileTree: IFileTree = {} as IFileTree; - - removedFileList: IDirtyFile[] = []; - - removedFileTree: IFileTree = {} as IFileTree; - dataGrip: any = null; + fileGrip: any = null; + status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING; constructor() { @@ -51,25 +47,18 @@ class DataGripStore implements IDataGripStore { setCommits(dump?: string[]) { dataGrip.clear(); - const parser = Parser; + fileGrip.clear(); - const { - commits, - fileList, - fileTree, - removed, - } = parser(dump || []); + const commits = Parser(dump || []); commits.sort((a, b) => a.milliseconds - b.milliseconds); commits.forEach((commit: ICommit | ISystemCommit) => { dataGrip.addCommit(commit); + fileGrip.addCommit(commit); }); + fileGrip.updateTotalInfo(); this.commits = commits; - this.fileList = fileList; - this.fileTree = getFileTreeWithStatistic(fileTree); - this.removedFileList = removed.fileList; - this.removedFileTree = getFileTreeWithStatistic(removed.fileTree); this.status = this.commits.length ? DataParseStatusEnum.DONE @@ -84,16 +73,17 @@ class DataGripStore implements IDataGripStore { ); dataGrip.updateByInitialization(); - dataGrip.updateByFiles(fileList, removed.fileList); - achievements.updateByDataGrip(dataGrip); + achievements.updateByGrip(dataGrip, fileGrip); } this.dataGrip = null; this.dataGrip = dataGrip; + this.fileGrip = fileGrip; console.dir(this.dataGrip); + console.dir(this.fileGrip); if (!applicationHasCustom.title) { - document.title = getTitle(this.dataGrip, this.commits); + document.title = getTitle(this.dataGrip, this.fileGrip, this.commits); } } @@ -101,7 +91,7 @@ class DataGripStore implements IDataGripStore { console.log('need update data TODO'); dataGrip.updateByFilters(); if (!dataGrip.author.list.length) return; - achievements.updateByDataGrip(dataGrip); + achievements.updateByGrip(dataGrip, fileGrip); this.dataGrip = null; this.dataGrip = dataGrip; } diff --git a/src/ts/translations/ru/achievements.ts b/src/ts/translations/ru/achievements.ts index 5e0a03c..b85a0b8 100644 --- a/src/ts/translations/ru/achievements.ts +++ b/src/ts/translations/ru/achievements.ts @@ -45,24 +45,24 @@ export default ` § achievements.moreDaysForTask.description: работа по задачам идёт медленнее чем у остальных § achievements.more2DaysForTask.title: Cо слоу § achievements.more2DaysForTask.description: больше двух дней на задачу -§ achievements.moreDaysInProject.title: Часть команды, часть коробля -§ achievements.moreDaysInProject.description: больше всего дней на проекте § achievements.more3YearsInProject.title: Старожил § achievements.more3YearsInProject.description: больше трех лет на проекте § achievements.lessDaysInProject.title: А это кто? § achievements.lessDaysInProject.description: меньше всего дней на проекте -§ achievements.more90DaysInProject.title: Добро пожаловать -§ achievements.more90DaysInProject.description: не уволили на испытательном § achievements.lessDaysForTask.title: Скорострел § achievements.lessDaysForTask.description: одна задача занимает меньше дня § achievements.adam.title: Адам § achievements.adam.description: первый стабильный сотрудник на проекте +§ achievements.more90DaysInProject.title: Добро пожаловать +§ achievements.more90DaysInProject.description: не уволили на испытательном § achievements.more365DaysInProject.title: Годовасик § achievements.more365DaysInProject.description: отработал год на проекте § achievements.more666DaysInProject.title: Чёрт § achievements.more666DaysInProject.description: отработал 666 дней на проекте § achievements.more777DaysInProject.title: Азино 3 топора § achievements.more777DaysInProject.description: отработал 777 дней на проекте +§ achievements.moreDaysInProject.title: Часть команды, часть коробля +§ achievements.moreDaysInProject.description: больше всего дней на проекте § achievements.moreRefactoring.title: Выпускающий редактор § achievements.moreRefactoring.description: сделал больше всех меток «рефакторинг» § achievements.longestMessage.title: А разговоров то было...