Repository: getsentry/sentry

Issue:
Slack Integration: 
Hi, I'm having trouble setting up the new Slack integration to work with private channels.
- With the legacy integration we had Sentry sending messages into private channels.
- When authorizing the Slack app I selected "Specific channels" and entered the names of the private channels, as well as "slackbot, which is private to you"..
- The authorization appeared to succeed.  After authorizing I saw a message `Sentry APP [2:39 PM]
was added to this conversation by Luke Moore.` in those channels.
- When editing the alert rule I've set the "take these actions" to send a notification to the private workspace but I see the error: 'The slack resource "website-private" does not exist or has not been granted access in the SideFX Slack workspace.'  I've tried with and without a leading "#".

Am I doing something wrong?
![image](https://user-images.githubusercontent.com/6863550/38211023-2492766c-3687-11e8-9a6f-2715dd542580.png)
![image](https://user-images.githubusercontent.com/6863550/38210995-0cdbf4da-3687-11e8-9504-6c66a44feb04.png)


Discussion:

Resolving patch:
diff --git a/src/sentry/integrations/slack/integration.py b/src/sentry/integrations/slack/integration.py
--- a/src/sentry/integrations/slack/integration.py
+++ b/src/sentry/integrations/slack/integration.py
@@ -39,7 +39,8 @@ class SlackIntegration(Integration):
 
     identity_oauth_scopes = frozenset([
         'channels:read',
-        'users:read'
+        'groups:read',
+        'users:read',
         'chat:write',
         'links:read',
         'links:write',
diff --git a/src/sentry/integrations/slack/notify_action.py b/src/sentry/integrations/slack/notify_action.py
--- a/src/sentry/integrations/slack/notify_action.py
+++ b/src/sentry/integrations/slack/notify_action.py
@@ -201,6 +201,17 @@ def get_channel_id(self, integration_id, name):
 
         channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)
 
+        # Channel may be private, use the groups.list endpoint to find them
+        if not channel_id:
+            resp = session.get('https://slack.com/api/groups.list', params=channels_payload)
+            resp = resp.json()
+            if not resp.get('ok'):
+                self.logger.info('rule.slack.group_list_failed', extra={'error': resp.get('error')})
+                return None
+
+            channel_id = {c['name']: c['id'] for c in resp['groups']}.get(name)
+
+        # We should sufficiently have been able to find the channel by now
         if channel_id:
             if channel_id in channel_perms['excluded_ids']:
                 return None


Test patch:
diff --git a/tests/sentry/integrations/slack/test_notify_action.py b/tests/sentry/integrations/slack/test_notify_action.py
--- a/tests/sentry/integrations/slack/test_notify_action.py
+++ b/tests/sentry/integrations/slack/test_notify_action.py
@@ -135,6 +135,48 @@ def test_valid_channel_selected(self):
         form = rule.get_form_instance()
         assert form.is_valid()
 
+    @responses.activate
+    def test_valid_private_channel_selected(self):
+        rule = self.get_rule(data={
+            'workspace': self.integration.id,
+            'channel': '#my-private-channel',
+            'tags': '',
+        })
+
+        channels = {
+            'ok': 'true',
+            'channels': [
+                {'name': 'my-channel', 'id': 'chan-id'},
+                {'name': 'other-chann', 'id': 'chan-id'},
+            ],
+        }
+
+        responses.add(
+            method=responses.GET,
+            url='https://slack.com/api/channels.list',
+            status=200,
+            content_type='application/json',
+            body=json.dumps(channels),
+        )
+
+        groups = {
+            'ok': 'true',
+            'groups': [
+                {'name': 'my-private-channel', 'id': 'chan-id'},
+            ],
+        }
+
+        responses.add(
+            method=responses.GET,
+            url='https://slack.com/api/groups.list',
+            status=200,
+            content_type='application/json',
+            body=json.dumps(groups),
+        )
+
+        form = rule.get_form_instance()
+        assert form.is_valid()
+
     @responses.activate
     def test_valid_member_selected(self):
         rule = self.get_rule(data={
@@ -159,6 +201,21 @@ def test_valid_member_selected(self):
             body=json.dumps(channels),
         )
 
+        groups = {
+            'ok': 'true',
+            'groups': [
+                {'name': 'my-private-channel', 'id': 'chan-id'},
+            ],
+        }
+
+        responses.add(
+            method=responses.GET,
+            url='https://slack.com/api/groups.list',
+            status=200,
+            content_type='application/json',
+            body=json.dumps(groups),
+        )
+
         members = {
             'ok': 'true',
             'members': [
@@ -199,12 +256,19 @@ def test_invalid_channel_selected(self):
             body=json.dumps(channels),
         )
 
+        groups = {
+            'ok': 'true',
+            'groups': [
+                {'name': 'my-private-channel', 'id': 'chan-id'},
+            ],
+        }
+
         responses.add(
             method=responses.GET,
-            url='https://slack.com/api/channels.list',
+            url='https://slack.com/api/groups.list',
             status=200,
             content_type='application/json',
-            body=json.dumps(channels),
+            body=json.dumps(groups),
         )
 
         members = {