Skip to content

Commit 82d3b09

Browse files
authored
[Feature] Add notifications (#33)
* fix lang * add telegram notifications * add custom event dispatcher * Linter returns LintResult * template as multiline * fix tests * fix stat-analyse * add doc * remove notes collection * add conditions * up * up * up * up * Move metrics to Shared * Move metrics to Shared * Add TelegramBotTest * add contract to tg bot * up * add twig renderer test * Add RenderingNotifierTest * up config * up workflow * Add name to LintStartedEvent * Add ConditionListenerTest * add test * move tests * move tests * add test * add test * Add test * add date * upd message * upd message * add gitlab mr url * fix tests * upd template * upd template * upd template * fix hydrator * Add test
1 parent bee22b0 commit 82d3b09

File tree

120 files changed

+3999
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+3999
-274
lines changed

.github/workflows/testing.yml

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ jobs:
4242
- name: MR Lint
4343
env:
4444
MR_LINTER_GITHUB_HTTP_TOKEN: ${{ secrets.MR_LINTER_GITHUB_HTTP_TOKEN }}
45+
MR_LINTER_TELEGRAM_BOT_TOKEN: ${{ secrets.MR_LINTER_TELEGRAM_BOT_TOKEN }}
46+
MR_LINTER_TELEGRAM_CHAT_ID: ${{ secrets.MR_LINTER_TELEGRAM_CHAT_ID }}
4547
run: composer mr-lint
4648

4749
- name: Lint

.mr-linter.yml

+22
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,27 @@ rules:
6363
labels:
6464
has: "Feature"
6565

66+
notifications:
67+
channels:
68+
dev:
69+
type: 'telegram_bot'
70+
chat_id: 'env(MR_LINTER_TELEGRAM_CHAT_ID)'
71+
bot_token: 'env(MR_LINTER_TELEGRAM_BOT_TOKEN)'
72+
on:
73+
lint_finished:
74+
channel: 'dev'
75+
template: |
76+
👀 Review on PR "{{ request.title }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
77+
78+
🌲 {{ request.sourceBranch }} ➡ {{ request.targetBranch }}
79+
80+
🌐 {{ request.uri }}
81+
82+
📉 Notes: {{ result.notes.count }}
83+
84+
{% for note in result.notes %}
85+
- {{ note.description }}
86+
{% endfor %}
87+
6688
credentials:
6789
github_actions: "env(MR_LINTER_GITHUB_HTTP_TOKEN)"

CHANGELOG.MD

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
This file contains changelogs.
44

5+
v0.10.0
6+
--------------------
7+
#### Added
8+
* Notifications for telegram on events: **lint_finished**, **lint_started**
9+
510
v0.9.0
611
--------------------
712
#### Added

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"guzzlehttp/guzzle": "^7.5",
2424
"guzzlehttp/promises": "^1.5",
2525
"league/commonmark": "^2.3",
26-
"psr/clock": "^1.0"
26+
"psr/clock": "^1.0",
27+
"twig/twig": "^3.5"
2728
},
2829
"require-dev": {
2930
"phpunit/phpunit": "^9.5",

composer.lock

+77-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/Builder/ConfigJsonSchema/Generator.php

+76-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace ArtARTs36\MergeRequestLinter\DocBuilder\ConfigJsonSchema;
44

55
use ArtARTs36\MergeRequestLinter\DocBuilder\ConfigJsonSchema\Schema\JsonSchema;
6+
use ArtARTs36\MergeRequestLinter\DocBuilder\EventFinder;
7+
use ArtARTs36\MergeRequestLinter\Domain\Notifications\ChannelType;
8+
use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest;
69

710
class Generator
811
{
@@ -17,7 +20,7 @@ public function generate(): JsonSchema
1720
{
1821
$schema = new JsonSchema();
1922

20-
$schema->addDefinition('rule_conditions', $this->operatorSchemaArrayGenerator->generate());
23+
$schema->addDefinition('rule_conditions', $this->operatorSchemaArrayGenerator->generate(MergeRequest::class));
2124

2225
$schema->addProperty('rules', [
2326
'type' => 'object',
@@ -40,6 +43,78 @@ public function generate(): JsonSchema
4043
'additionalProperties' => false,
4144
]);
4245

46+
$notificationsChannelTgBotRef = $schema->addDefinition('notifications_channel_telegram_bot', [
47+
'properties' => [
48+
'type' => [
49+
'type' => 'string',
50+
'const' => ChannelType::TelegramBot->value,
51+
],
52+
'bot_token' => [
53+
'type' => 'string',
54+
],
55+
'chat_id' => [
56+
'type' => 'string',
57+
],
58+
],
59+
]);
60+
61+
$notificationsChannelRef = $schema->addDefinition('notifications_channel', [
62+
'type' => 'object',
63+
'oneOf' => [
64+
[
65+
'$ref' => $notificationsChannelTgBotRef,
66+
],
67+
],
68+
]);
69+
70+
$schema->addProperty('notifications', [
71+
'type' => 'object',
72+
'properties' => [
73+
'channels' => [
74+
'type' => 'object',
75+
'additionalProperties' => [
76+
'$ref' => $notificationsChannelRef,
77+
],
78+
],
79+
'on' => [
80+
'type' => 'object',
81+
'properties' => $this->mapEvents(),
82+
],
83+
],
84+
], false);
85+
4386
return $schema;
4487
}
88+
89+
private function mapEvents(): array
90+
{
91+
$eventFinder = new EventFinder();
92+
93+
$eventClasses = $eventFinder->find();
94+
95+
$map = [];
96+
97+
foreach ($eventClasses as $class) {
98+
if (! defined("$class::NAME")) {
99+
continue;
100+
}
101+
102+
$eventName = $class::NAME;
103+
104+
$map[$eventName] = [
105+
'type' => 'object',
106+
'properties' => [
107+
'template' => [
108+
'type' => 'string',
109+
],
110+
'channel' => [
111+
'type' => 'string',
112+
],
113+
'when' => $this->operatorSchemaArrayGenerator->generate($class),
114+
],
115+
];
116+
}
117+
118+
return $map;
119+
}
45120
}

docs/Builder/ConfigJsonSchema/OperatorSchemaArrayGenerator.php

+26-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Generic\EqualsAnyEvaluator;
1414
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Generic\EqualsEvaluator;
1515
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Generic\IsEmptyEvaluator;
16+
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\GteEvaluator;
1617
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\HasAnyEvaluator;
1718
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\HasEvaluator;
1819
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\LinesMaxEvaluator;
20+
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\LteEvaluator;
1921
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\NotEqualsEvaluator;
2022
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\NotHasEvaluator;
2123
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Strings\Cases\IsCamelCaseEvaluator;
@@ -35,8 +37,10 @@
3537
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Strings\NotStartsEvaluator;
3638
use ArtARTs36\MergeRequestLinter\Application\Condition\Evaluators\Strings\StartsEvaluator;
3739
use ArtARTs36\MergeRequestLinter\Domain\Condition\ConditionOperator;
40+
use ArtARTs36\MergeRequestLinter\Domain\Note\Notes;
3841
use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest;
3942
use ArtARTs36\MergeRequestLinter\Shared\Contracts\DataStructure\Map;
43+
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee;
4044
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap;
4145
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Set;
4246
use ArtARTs36\MergeRequestLinter\Shared\Reflector\Property;
@@ -117,10 +121,29 @@ class OperatorSchemaArrayGenerator
117121
AllEvaluator::class,
118122
AnyEvaluator::class,
119123
],
124+
Arrayee::class => [
125+
CountMinEvaluator::class,
126+
CountMaxEvaluator::class,
127+
CountEqualsEvaluator::class,
128+
CountNotEqualsEvaluator::class,
129+
CountEqualsAnyEvaluator::class,
130+
HasEvaluator::class,
131+
NotHasEvaluator::class,
132+
HasAnyEvaluator::class,
133+
IsEmptyEvaluator::class,
134+
AllEvaluator::class,
135+
AnyEvaluator::class,
136+
],
120137
'bool' => [
121138
EqualsEvaluator::class,
122139
NotEqualsEvaluator::class,
123140
],
141+
'float' => [
142+
EqualsEvaluator::class,
143+
NotEqualsEvaluator::class,
144+
LteEvaluator::class,
145+
GteEvaluator::class,
146+
],
124147
Markdown::class => [
125148
ContainsHeadingEvaluator::class,
126149
],
@@ -132,7 +155,7 @@ public function __construct(
132155
//
133156
}
134157

135-
public function generate(): array
158+
public function generate(string $forClass): array
136159
{
137160
$opArray = [
138161
'properties' => [],
@@ -141,7 +164,7 @@ public function generate(): array
141164

142165
$operatorMetadata = $this->operatorMetadataLoader->load();
143166

144-
return $this->doGenerate(Reflector::mapProperties(MergeRequest::class), $operatorMetadata, $opArray, '');
167+
return $this->doGenerate(Reflector::mapProperties($forClass), $operatorMetadata, $opArray, '');
145168
}
146169

147170
/**
@@ -313,6 +336,6 @@ private function mapEvaluatorData(OperatorMetadata $operatorMeta): array
313336

314337
private function allowObjectScan(string $type): bool
315338
{
316-
return $type !== ArrayMap::class && $type !== Set::class && $type !== Str::class && $type !== Markdown::class;
339+
return $type !== ArrayMap::class && $type !== Set::class && $type !== Str::class && $type !== Markdown::class && $type !== Arrayee::class;
317340
}
318341
}

docs/Builder/ConfigJsonSchema/Schema/JsonSchema.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ public function addDefinition(string $name, array $definition): string
2121
return '#/definitions/' . $name;
2222
}
2323

24-
public function addProperty(string $name, array $prop): void
24+
public function addProperty(string $name, array $prop, bool $required = true): void
2525
{
2626
$this->schema['properties'][$name] = $prop;
27-
$this->schema['required'][] = $name;
27+
28+
if ($required) {
29+
$this->schema['required'][] = $name;
30+
}
2831
}
2932

3033
public function toJson(): string

docs/Builder/EventFinder.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\DocBuilder;
4+
5+
class EventFinder
6+
{
7+
public function find(): array
8+
{
9+
$files = $this->search(__DIR__ . '/../../src/', '/.*Event\.php/');
10+
$classes = [];
11+
12+
foreach ($files as [$file]) {
13+
$tokens = token_get_all(file_get_contents($file));
14+
15+
foreach ($tokens as $index => [$id, $value]) {
16+
if ($id !== T_NAMESPACE) {
17+
continue;
18+
}
19+
20+
$needToken = $tokens[$index + 2];
21+
22+
if ($needToken[0] !== T_NAME_QUALIFIED) {
23+
throw new \Exception();
24+
}
25+
26+
$namespace = $needToken[1];
27+
28+
$classes[] = $namespace . '\\' . pathinfo($file, PATHINFO_FILENAME);
29+
}
30+
}
31+
32+
return $classes;
33+
}
34+
35+
private function search(string $folder, string $regPattern): array
36+
{
37+
$dir = new \RecursiveDirectoryIterator($folder);
38+
$ite = new \RecursiveIteratorIterator($dir);
39+
$files = new \RegexIterator($ite, $regPattern, \RegexIterator::GET_MATCH);
40+
$fileList = [];
41+
42+
foreach($files as $file) {
43+
$fileList[] = $file;
44+
}
45+
46+
return $fileList;
47+
}
48+
}

0 commit comments

Comments
 (0)