Skip to content

Commit e7641ba

Browse files
authored
Slack Notification Channel (#10)
* Added slack notification channel. * Updated readme. *Added screenshots.
1 parent c5ff893 commit e7641ba

File tree

8 files changed

+287
-14
lines changed

8 files changed

+287
-14
lines changed

README.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ Adjust the configuration file to suite your application.
8282
'enabled' => false, // Do you want discord webhook notifications?
8383
'hook' => env('DISCORD_HOOK', 'please_fill_me_in'),
8484
],
85+
'slack' => [
86+
'enabled' => false, // Do you want Slack webhook notifications?
87+
'hook' => env('SLACK_HOOK', 'please_fill_me_in'),
88+
],
8589
]
8690
]
8791
```
@@ -112,6 +116,17 @@ applications [broadcast configuration](https://laravel.com/docs/9.x/broadcasting
112116
}
113117
```
114118

119+
### Slack Notification Channel
120+
121+
To utilize Slack Notifications, you will need to [create a webhook](https://api.slack.com/messaging/webhooks#create_a_webhook) for one of your Slack Channels. Once you have your
122+
webhook url, add the following variable to your .env file.
123+
124+
```dotenv
125+
SLACK_HOOK=<hook>
126+
```
127+
128+
Once you have done this, you can enable Slack Notifications in the configuration file.
129+
115130
### Discord Notification Channel
116131

117132
Get a webhook URL from discord in the channel you want to receive your notifications in by
@@ -124,20 +139,10 @@ DISCORD_HOOK=<hook>
124139

125140
Once you have done this, you can enable Discord Notifications in the configuration file.
126141

127-
### Screenshots
128-
129-
<div align="center">
130-
<a href="https://github.com/YorCreative">
131-
<img src="content/discord_notification_console.png" alt="Logo" width="223" height="300">
132-
</a>
133-
</div>
134-
<br>
135-
<div align="center">
136-
<a href="https://github.com/YorCreative">
137-
<img src="content/discord_notification_request.png" alt="Logo" width="286" height="343">
138-
</a>
139-
</div>
142+
### Wiki Documentation
140143

144+
- [Notification Channels Wiki](https://github.com/YorCreative/Laravel-Query-Watcher/wiki/Notification-Channels)
145+
- [Screenshots](https://github.com/YorCreative/Laravel-Query-Watcher/wiki/Screenshots)
141146
## Testing
142147

143148
```bash

config/querywatcher.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@
3434
'enabled' => false,
3535
'hook' => env('DISCORD_HOOK', 'placeholder'),
3636
],
37+
'slack' => [
38+
'enabled' => false,
39+
'hook' => env('SLACK_HOOK', 'placeholder'),
40+
],
3741
],
3842
];
13.3 KB
Loading
24.8 KB
Loading

src/Services/ChannelService.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Support\Collection;
66
use YorCreative\QueryWatcher\Models\QueryModel;
77
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels\Discord;
8+
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels\Slack;
89
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\NotificationStrategy;
910

1011
class ChannelService
@@ -32,6 +33,7 @@ protected static function getChannels(): Collection
3233
{
3334
return new Collection([
3435
new Discord(),
36+
new Slack(),
3537
]);
3638
}
3739
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
namespace YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels;
4+
5+
use Illuminate\Support\Facades\Http;
6+
use YorCreative\QueryWatcher\Models\QueryModel;
7+
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\NotificationChannelInterface;
8+
9+
class Slack implements NotificationChannelInterface
10+
{
11+
/**
12+
* @return bool
13+
*/
14+
public function isEnabled(): bool
15+
{
16+
return config('querywatcher.channels.slack.enabled');
17+
}
18+
19+
/**
20+
* @param QueryModel $model
21+
*/
22+
public function notify(QueryModel $model): void
23+
{
24+
// Core Base Information
25+
$payload = $this->buildCoreBaseEnrichment($model);
26+
27+
// Contextual Information
28+
if ($model->trigger['action'] == 'Console') {
29+
$payload['blocks'] = $this->buildConsoleEnrichment($payload['blocks']);
30+
} else {
31+
$payload['blocks'] = $this->buildRequestEnrichment($payload['blocks'], $model);
32+
}
33+
34+
// Fire off hook
35+
Http::retry(3)->contentType('application/json')->post(config('querywatcher.channels.slack.hook'), $payload);
36+
}
37+
38+
/**
39+
* @note Slack Block Builder Reference
40+
* https://app.slack.com/block-kit-builder
41+
*
42+
* If you would like to improve your Slack message, see the block kit builder.
43+
*/
44+
45+
/**
46+
* @param QueryModel $model
47+
* @return \array[][]
48+
*/
49+
protected function buildCoreBaseEnrichment(QueryModel $model): array
50+
{
51+
return [
52+
'blocks' => [
53+
[
54+
'type' => 'divider',
55+
],
56+
[
57+
'type' => 'header',
58+
'text' => [
59+
'type' => 'plain_text',
60+
'text' => 'Captured Query',
61+
],
62+
],
63+
[
64+
'type' => 'section',
65+
'text' => [
66+
'type' => 'plain_text',
67+
'text' => $model->sql,
68+
],
69+
],
70+
[
71+
'type' => 'header',
72+
'text' => [
73+
'type' => 'plain_text',
74+
'text' => 'Query Bindings',
75+
],
76+
],
77+
[
78+
'type' => 'section',
79+
'text' => [
80+
'type' => 'plain_text',
81+
'text' => json_encode($model->getBindings()),
82+
],
83+
],
84+
[
85+
'type' => 'header',
86+
'text' => [
87+
'type' => 'plain_text',
88+
'text' => 'Execution Time',
89+
],
90+
],
91+
[
92+
'type' => 'section',
93+
'text' => [
94+
'type' => 'plain_text',
95+
'text' => json_encode($model->time).' ms',
96+
],
97+
],
98+
[
99+
'type' => 'header',
100+
'text' => [
101+
'type' => 'plain_text',
102+
'text' => 'Connection',
103+
],
104+
],
105+
[
106+
'type' => 'section',
107+
'text' => [
108+
'type' => 'plain_text',
109+
'text' => $model->connection,
110+
],
111+
],
112+
],
113+
];
114+
}
115+
116+
/**
117+
* @param array $payload
118+
* @return array
119+
*/
120+
protected function buildConsoleEnrichment(array $payload): array
121+
{
122+
return array_merge($payload, [
123+
[
124+
'type' => 'header',
125+
'text' => [
126+
'type' => 'plain_text',
127+
'text' => 'Contextual Information',
128+
],
129+
],
130+
[
131+
'type' => 'context',
132+
'elements' => [
133+
[
134+
'type' => 'mrkdwn',
135+
'text' => '*Trigger:* Console',
136+
],
137+
],
138+
],
139+
]);
140+
}
141+
142+
/**
143+
* @param array $payload
144+
* @param QueryModel $model
145+
* @return array
146+
*/
147+
protected function buildRequestEnrichment(array $payload, QueryModel $model): array
148+
{
149+
return array_merge($payload, [
150+
[
151+
'type' => 'header',
152+
'text' => [
153+
'type' => 'plain_text',
154+
'text' => 'Contextual Information',
155+
],
156+
],
157+
[
158+
'type' => 'context',
159+
'elements' => [
160+
[
161+
'type' => 'mrkdwn',
162+
'text' => '*Trigger:* '.$model->trigger['action'],
163+
],
164+
],
165+
],
166+
[
167+
'type' => 'context',
168+
'elements' => [
169+
[
170+
'type' => 'mrkdwn',
171+
'text' => '*Method:* '.$model->trigger['context']['method'],
172+
],
173+
],
174+
],
175+
[
176+
'type' => 'context',
177+
'elements' => [
178+
[
179+
'type' => 'mrkdwn',
180+
'text' => '*URL:* '.$model->trigger['context']['url'],
181+
],
182+
],
183+
],
184+
[
185+
'type' => 'context',
186+
'elements' => [
187+
0 => [
188+
'type' => 'mrkdwn',
189+
'text' => '*Request Input:*',
190+
],
191+
],
192+
],
193+
[
194+
'type' => 'section',
195+
'text' => [
196+
'type' => 'mrkdwn',
197+
'text' => json_encode($model->trigger['context']['input']),
198+
],
199+
],
200+
]);
201+
}
202+
}

tests/Integration/Services/ChannelServiceTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ReflectionClass;
66
use YorCreative\QueryWatcher\Services\ChannelService;
77
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels\Discord;
8+
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels\Slack;
89
use YorCreative\QueryWatcher\Tests\TestCase;
910

1011
class ChannelServiceTest extends TestCase
@@ -22,11 +23,12 @@ public function it_has_available_channels()
2223

2324
$channels = $protectedMethod->invokeArgs(new ChannelService(), []);
2425

25-
$this->assertCount(1, $channels);
26+
$this->assertCount(2, $channels);
2627

2728
$channels->each(function ($channel) {
2829
$this->assertTrue(in_array($channel, [
2930
new Discord(),
31+
new Slack(),
3032
]));
3133
});
3234
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace YorCreative\QueryWatcher\Tests\Unit\Strategies\Channels;
4+
5+
use Illuminate\Support\Facades\Http;
6+
use YorCreative\QueryWatcher\Strategies\NotificationStrategy\Channels\Slack;
7+
use YorCreative\QueryWatcher\Tests\TestCase;
8+
9+
class SlackTest extends TestCase
10+
{
11+
public Slack $slackChannel;
12+
13+
public function setUp(): void
14+
{
15+
parent::setUp(); // TODO: Change the autogenerated stub
16+
17+
$this->slackChannel = new Slack();
18+
}
19+
20+
/**
21+
* @test
22+
* @group Unit
23+
* @group Channels
24+
* @group Strategies
25+
*/
26+
public function it_can_send_http_request_to_slack()
27+
{
28+
Http::fake();
29+
30+
$this->slackChannel->notify($this->queryModel);
31+
32+
Http::assertSentCount(1);
33+
}
34+
35+
/**
36+
* @test
37+
* @group Unit
38+
* @group Channels
39+
* @group Strategies
40+
*/
41+
public function it_can_determine_that_slack_channel_is_enabled()
42+
{
43+
app()['config']->set('querywatcher.channels.slack.enabled', true);
44+
$this->assertTrue($this->slackChannel->isEnabled());
45+
}
46+
47+
/**
48+
* @test
49+
* @group Unit
50+
* @group Channels
51+
* @group Strategies
52+
*/
53+
public function it_can_determine_that_slack_channel_is_not_enabled()
54+
{
55+
app()['config']->set('querywatcher.channels.slack.enabled', false);
56+
$this->assertFalse($this->slackChannel->isEnabled());
57+
}
58+
}

0 commit comments

Comments
 (0)