Skip to content

Commit 852eabd

Browse files
committed
feature #35828 [Notifier][Slack] Send messages using Incoming Webhooks (birkof, fabpot)
This PR was merged into the 5.1-dev branch. Discussion ---------- [Notifier][Slack] Send messages using Incoming Webhooks | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | <!-- prefix each issue number with "Fix #", if any --> | License | MIT | Doc PR | <!-- required for new features --> > Legacy tokens are a deprecated method of generating tokens for testing and development. As tokens will became deprecated on May 5th, 2020 we should use Incoming Webhooks app for using this bundle. **Usage:** _`slack://hooks.slack.com/services/xxx/xxx/xxx`_ Commits ------- 4b0807b [Notifier] Tweak Slack support e242cc3 Git rebase form master
2 parents ee3caba + 4b0807b commit 852eabd

File tree

6 files changed

+71
-71
lines changed

6 files changed

+71
-71
lines changed

src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.1.0
5+
-----
6+
7+
* [BC BREAK] Change API endpoit to use the Slack Incoming Webhooks API
8+
49
5.0.0
510
-----
611

src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ public static function fromNotification(Notification $notification): self
5050
public function toArray(): array
5151
{
5252
$options = $this->options;
53-
if (isset($options['blocks'])) {
54-
$options['blocks'] = json_encode($options['blocks']);
55-
}
5653
unset($options['recipient_id']);
5754

5855
return $options;
@@ -65,11 +62,24 @@ public function getRecipientId(): ?string
6562

6663
/**
6764
* @return $this
65+
*
66+
* @deprecated since Symfony 5.1, use recipient() instead.
6867
*/
6968
public function channel(string $channel): self
7069
{
71-
$this->options['channel'] = $channel;
72-
$this->options['recipient_id'] = $channel;
70+
trigger_deprecation('symfony/slack-notifier', '5.1', 'The "%s()" method is deprecated, use "recipient()" instead.', __METHOD__);
71+
72+
return $this;
73+
}
74+
75+
/**
76+
* @param string $id The hook id (anything after https://hooks.slack.com/services/)
77+
*
78+
* @return $this
79+
*/
80+
public function recipient(string $id): self
81+
{
82+
$this->options['recipient_id'] = $id;
7383

7484
return $this;
7585
}

src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,41 +20,44 @@
2020
use Symfony\Contracts\HttpClient\HttpClientInterface;
2121

2222
/**
23+
* Send messages via Slack using Slack Incoming Webhooks.
24+
*
2325
* @author Fabien Potencier <fabien@symfony.com>
26+
* @author Daniel Stancu <birkof@birkof.ro>
2427
*
2528
* @internal
2629
*
30+
* @see https://api.slack.com/messaging/webhooks
31+
*
2732
* @experimental in 5.0
2833
*/
2934
final class SlackTransport extends AbstractTransport
3035
{
31-
protected const HOST = 'slack.com';
36+
protected const HOST = 'hooks.slack.com';
3237

33-
private $accessToken;
34-
private $chatChannel;
38+
private $id;
3539

36-
public function __construct(string $accessToken, string $channel = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
40+
/**
41+
* @param string $id The hook id (anything after https://hooks.slack.com/services/)
42+
*/
43+
public function __construct(string $id, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
3744
{
38-
$this->accessToken = $accessToken;
39-
$this->chatChannel = $channel;
45+
$this->id = $id;
4046
$this->client = $client;
4147

4248
parent::__construct($client, $dispatcher);
4349
}
4450

4551
public function __toString(): string
4652
{
47-
return sprintf('slack://%s?channel=%s', $this->getEndpoint(), $this->chatChannel);
53+
return sprintf('slack://%s/%s', $this->getEndpoint(), $this->id);
4854
}
4955

5056
public function supports(MessageInterface $message): bool
5157
{
5258
return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof SlackOptions);
5359
}
5460

55-
/**
56-
* @see https://api.slack.com/methods/chat.postMessage
57-
*/
5861
protected function doSend(MessageInterface $message): void
5962
{
6063
if (!$message instanceof ChatMessage) {
@@ -69,22 +72,19 @@ protected function doSend(MessageInterface $message): void
6972
}
7073

7174
$options = $opts ? $opts->toArray() : [];
72-
$options['token'] = $this->accessToken;
73-
if (!isset($options['channel'])) {
74-
$options['channel'] = $message->getRecipientId() ?: $this->chatChannel;
75-
}
75+
$id = $message->getRecipientId() ?: $this->id;
7676
$options['text'] = $message->getSubject();
77-
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/chat.postMessage', [
78-
'body' => array_filter($options),
77+
$response = $this->client->request('POST', sprintf('https://%s/services/%s', $this->getEndpoint(), $id), [
78+
'json' => array_filter($options),
7979
]);
8080

8181
if (200 !== $response->getStatusCode()) {
82-
throw new TransportException(sprintf('Unable to post the Slack message: %s.', $response->getContent(false)), $response);
82+
throw new TransportException(sprintf('Unable to post the Slack message: '.$response->getContent(false)), $response);
8383
}
8484

85-
$result = $response->toArray(false);
86-
if (!$result['ok']) {
87-
throw new TransportException(sprintf('Unable to post the Slack message: %s.', $result['error']), $response);
85+
$result = $response->getContent(false);
86+
if ('ok' !== $result) {
87+
throw new TransportException(sprintf('Unable to post the Slack message: '.$result), $response);
8888
}
8989
}
9090
}

src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ final class SlackTransportFactory extends AbstractTransportFactory
2929
public function create(Dsn $dsn): TransportInterface
3030
{
3131
$scheme = $dsn->getScheme();
32-
$accessToken = $this->getUser($dsn);
33-
$channel = $dsn->getOption('channel');
32+
$id = ltrim($dsn->getPath(), '/');
3433
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
3534
$port = $dsn->getPort();
3635

3736
if ('slack' === $scheme) {
38-
return (new SlackTransport($accessToken, $channel, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
37+
return (new SlackTransport($id, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
3938
}
4039

4140
throw new UnsupportedSchemeException($dsn, 'slack', $this->getSupportedSchemes());

src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportFactoryTest.php

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
16-
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
1716
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
1817
use Symfony\Component\Notifier\Transport\Dsn;
1918

@@ -24,26 +23,18 @@ public function testCreateWithDsn(): void
2423
$factory = new SlackTransportFactory();
2524

2625
$host = 'testHost';
27-
$channel = 'testChannel';
28-
$transport = $factory->create(Dsn::fromString(sprintf('slack://testUser@%s/?channel=%s', $host, $channel)));
26+
$path = 'testPath';
27+
$transport = $factory->create(Dsn::fromString(sprintf('slack://%s/%s', $host, $path)));
2928

30-
$this->assertSame(sprintf('slack://%s?channel=%s', $host, $channel), (string) $transport);
31-
}
32-
33-
public function testCreateWithNoTokenThrowsMalformed(): void
34-
{
35-
$factory = new SlackTransportFactory();
36-
37-
$this->expectException(IncompleteDsnException::class);
38-
$factory->create(Dsn::fromString(sprintf('slack://%s/?channel=%s', 'testHost', 'testChannel')));
29+
$this->assertSame(sprintf('slack://%s/%s', $host, $path), (string) $transport);
3930
}
4031

4132
public function testSupportsSlackScheme(): void
4233
{
4334
$factory = new SlackTransportFactory();
4435

45-
$this->assertTrue($factory->supports(Dsn::fromString('slack://host/?channel=testChannel')));
46-
$this->assertFalse($factory->supports(Dsn::fromString('somethingElse://host/?channel=testChannel')));
36+
$this->assertTrue($factory->supports(Dsn::fromString('slack://host/path')));
37+
$this->assertFalse($factory->supports(Dsn::fromString('somethingElse://host/path')));
4738
}
4839

4940
public function testNonSlackSchemeThrows(): void
@@ -52,6 +43,6 @@ public function testNonSlackSchemeThrows(): void
5243

5344
$this->expectException(UnsupportedSchemeException::class);
5445

55-
$factory->create(Dsn::fromString('somethingElse://user:pwd@host/?channel=testChannel'));
46+
$factory->create(Dsn::fromString('somethingElse://host/path'));
5647
}
5748
}

src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ final class SlackTransportTest extends TestCase
2929
public function testToStringContainsProperties(): void
3030
{
3131
$host = 'testHost';
32-
$channel = 'testChannel';
32+
$path = 'testPath';
3333

34-
$transport = new SlackTransport('testToken', $channel, $this->createMock(HttpClientInterface::class));
34+
$transport = new SlackTransport($path, $this->createMock(HttpClientInterface::class));
3535
$transport->setHost('testHost');
3636

37-
$this->assertSame(sprintf('slack://%s?channel=%s', $host, $channel), (string) $transport);
37+
$this->assertSame(sprintf('slack://%s/%s', $host, $path), (string) $transport);
3838
}
3939

4040
public function testSupportsChatMessage(): void
4141
{
42-
$transport = new SlackTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class));
42+
$transport = new SlackTransport('testPath', $this->createMock(HttpClientInterface::class));
4343

4444
$this->assertTrue($transport->supports(new ChatMessage('testChatMessage')));
4545
$this->assertFalse($transport->supports($this->createMock(MessageInterface::class)));
@@ -49,7 +49,7 @@ public function testSendNonChatMessageThrows(): void
4949
{
5050
$this->expectException(LogicException::class);
5151

52-
$transport = new SlackTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class));
52+
$transport = new SlackTransport('testPath', $this->createMock(HttpClientInterface::class));
5353

5454
$transport->send($this->createMock(MessageInterface::class));
5555
}
@@ -70,15 +70,15 @@ public function testSendWithEmptyArrayResponseThrows(): void
7070
return $response;
7171
});
7272

73-
$transport = new SlackTransport('testToken', 'testChannel', $client);
73+
$transport = new SlackTransport('testPath', $client);
7474

7575
$transport->send(new ChatMessage('testMessage'));
7676
}
7777

7878
public function testSendWithErrorResponseThrows(): void
7979
{
8080
$this->expectException(TransportException::class);
81-
$this->expectExceptionMessageRegExp('/testErrorCode/');
81+
$this->expectExceptionMessage('testErrorCode');
8282

8383
$response = $this->createMock(ResponseInterface::class);
8484
$response->expects($this->exactly(2))
@@ -87,21 +87,20 @@ public function testSendWithErrorResponseThrows(): void
8787

8888
$response->expects($this->once())
8989
->method('getContent')
90-
->willReturn(json_encode(['error' => 'testErrorCode']));
90+
->willReturn('testErrorCode');
9191

9292
$client = new MockHttpClient(static function () use ($response): ResponseInterface {
9393
return $response;
9494
});
9595

96-
$transport = new SlackTransport('testToken', 'testChannel', $client);
96+
$transport = new SlackTransport('testPath', $client);
9797

9898
$transport->send(new ChatMessage('testMessage'));
9999
}
100100

101101
public function testSendWithOptions(): void
102102
{
103-
$token = 'testToken';
104-
$channel = 'testChannel';
103+
$path = 'testPath';
105104
$message = 'testMessage';
106105

107106
$response = $this->createMock(ResponseInterface::class);
@@ -112,25 +111,24 @@ public function testSendWithOptions(): void
112111

113112
$response->expects($this->once())
114113
->method('getContent')
115-
->willReturn(json_encode(['ok' => true]));
114+
->willReturn('ok');
116115

117-
$expectedBody = sprintf('token=%s&channel=%s&text=%s', $token, $channel, $message);
116+
$expectedBody = json_encode(['text' => $message]);
118117

119118
$client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response, $expectedBody): ResponseInterface {
120119
$this->assertSame($expectedBody, $options['body']);
121120

122121
return $response;
123122
});
124123

125-
$transport = new SlackTransport($token, $channel, $client);
124+
$transport = new SlackTransport($path, $client);
126125

127126
$transport->send(new ChatMessage('testMessage'));
128127
}
129128

130129
public function testSendWithNotification(): void
131130
{
132-
$token = 'testToken';
133-
$channel = 'testChannel';
131+
$host = 'testHost';
134132
$message = 'testMessage';
135133

136134
$response = $this->createMock(ResponseInterface::class);
@@ -141,16 +139,14 @@ public function testSendWithNotification(): void
141139

142140
$response->expects($this->once())
143141
->method('getContent')
144-
->willReturn(json_encode(['ok' => true]));
142+
->willReturn('ok');
145143

146144
$notification = new Notification($message);
147145
$chatMessage = ChatMessage::fromNotification($notification);
148146
$options = SlackOptions::fromNotification($notification);
149147

150-
$expectedBody = http_build_query([
151-
'blocks' => $options->toArray()['blocks'],
152-
'token' => $token,
153-
'channel' => $channel,
148+
$expectedBody = json_encode([
149+
'blocks' => json_decode($options->toArray()['blocks'], true),
154150
'text' => $message,
155151
]);
156152

@@ -160,7 +156,7 @@ public function testSendWithNotification(): void
160156
return $response;
161157
});
162158

163-
$transport = new SlackTransport($token, $channel, $client);
159+
$transport = new SlackTransport($host, $client);
164160

165161
$transport->send($chatMessage);
166162
}
@@ -173,15 +169,14 @@ public function testSendWithInvalidOptions(): void
173169
return $this->createMock(ResponseInterface::class);
174170
});
175171

176-
$transport = new SlackTransport('testToken', 'testChannel', $client);
172+
$transport = new SlackTransport('testHost', $client);
177173

178174
$transport->send(new ChatMessage('testMessage', $this->createMock(MessageOptionsInterface::class)));
179175
}
180176

181177
public function testSendWith200ResponseButNotOk(): void
182178
{
183-
$token = 'testToken';
184-
$channel = 'testChannel';
179+
$host = 'testChannel';
185180
$message = 'testMessage';
186181

187182
$this->expectException(TransportException::class);
@@ -194,17 +189,17 @@ public function testSendWith200ResponseButNotOk(): void
194189

195190
$response->expects($this->once())
196191
->method('getContent')
197-
->willReturn(json_encode(['ok' => false, 'error' => 'testErrorCode']));
192+
->willReturn('testErrorCode');
198193

199-
$expectedBody = sprintf('token=%s&channel=%s&text=%s', $token, $channel, $message);
194+
$expectedBody = json_encode(['text' => $message]);
200195

201196
$client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response, $expectedBody): ResponseInterface {
202197
$this->assertSame($expectedBody, $options['body']);
203198

204199
return $response;
205200
});
206201

207-
$transport = new SlackTransport($token, $channel, $client);
202+
$transport = new SlackTransport($host, $client);
208203

209204
$transport->send(new ChatMessage('testMessage'));
210205
}

0 commit comments

Comments
 (0)