Skip to content

Commit c14cf4e

Browse files
committed
Updating channel creation to remove Slack admin if wanted (WIP)
1 parent 7a0db05 commit c14cf4e

File tree

9 files changed

+210
-126
lines changed

9 files changed

+210
-126
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.1.13 on 2023-01-23 16:25
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('accounts', '0020_auto_20230104_1655'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='slacksitesettings',
15+
name='remove_admin_from_channel',
16+
field=models.BooleanField(default=True, help_text='The user linked to the ADMIN_BOT_TOKEN will automatically be added to any new channels. If this is ticked, the user will be removed from private team channels if they are not part of the team, facilitator or the slack admins'),
17+
),
18+
]

accounts/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ class SlackSiteSettings(SingletonModel):
203203
communication_channel_type = models.CharField(
204204
max_length=50, choices=COMMUNICATION_CHANNEL_TYPES,
205205
default='slack_private_channel')
206+
remove_admin_from_channel = models.BooleanField(
207+
default=True,
208+
help_text=("The user linked to the ADMIN_BOT_TOKEN will automatically "
209+
"be added to any new channels. If this is ticked, the user "
210+
"will be removed from private team channels if they are not "
211+
"part of the team, facilitator or the slack admins"))
206212

207213
def __str__(self):
208214
return "Slack Settings"

custom_slack_provider/slack.py

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import logging
12
import re
23
import requests
34
from requests.auth import AuthBase
45

5-
from django.conf import settings
6+
7+
logger = logging.getLogger(__name__)
8+
logger.setLevel(logging.INFO)
69

710

811
class AuthBearer(AuthBase):
@@ -25,12 +28,16 @@ class CustomSlackClient():
2528
user_detail_url = 'https://slack.com/api/users.info'
2629
create_conversation_url = 'https://slack.com/api/conversations.create'
2730
invite_conversation_url = 'https://slack.com/api/conversations.invite'
31+
leave_conversation_url = 'https://slack.com/api/conversations.leave'
2832

2933
def __init__(self, token):
3034
self.token = token
3135

3236
def _make_slack_get_request(self, url, params=None):
3337
resp = requests.get(url, auth=AuthBearer(self.token), params=params)
38+
if resp.status_code != 200:
39+
raise SlackException(resp.get("error"))
40+
3441
resp = resp.json()
3542

3643
if not resp.get('ok'):
@@ -40,31 +47,60 @@ def _make_slack_get_request(self, url, params=None):
4047

4148
def _make_slack_post_request(self, url, data):
4249
resp = requests.post(url, auth=AuthBearer(self.token), data=data)
43-
resp = resp.json()
44-
45-
if not resp.get('ok'):
46-
raise SlackException(resp.get("error"))
47-
48-
return resp
50+
if resp.status_code != 200:
51+
return {
52+
'ok': False,
53+
'error': resp.get("error")
54+
}
55+
return resp.json()
4956

5057
def get_identity(self):
5158
return self._make_slack_get_request(self.identity_url)
5259

60+
def leave_channel(self, channel):
61+
data = {
62+
'channel': channel
63+
}
64+
leave_channel = self._make_slack_post_request(
65+
self.leave_conversation_url, data=data)
66+
67+
if not leave_channel.get('ok'):
68+
print(('An error occurred adding users to the Private Slack Channel. '
69+
f'Error code: {leave_channel.get("error")}'))
70+
71+
return leave_channel
72+
5373
def get_user_info(self, userid):
5474
params = {"user": userid}
5575
response = self._make_slack_get_request(self.user_detail_url,
5676
params=params)
5777
return response.get('user', {})
5878

59-
def create_slack_channel(self, channel_name, is_private=True):
79+
def create_slack_channel(self, channel_name, team_id, is_private=True):
6080
data = {
81+
"team_id": team_id,
6182
"name": channel_name,
6283
"is_private": is_private,
63-
"team_id": settings.SLACK_TEAM_ID,
6484
}
6585
new_channel = self._make_slack_post_request(
6686
self.create_conversation_url, data=data)
67-
return new_channel.get('channel', {})
87+
88+
if not new_channel.get('ok'):
89+
if new_channel.get('error') == 'name_taken':
90+
error_msg = (f'An error occurred creating the Private Slack Channel. '
91+
f'A channel with the name "{channel_name}" already '
92+
f'exists. Please change your team name and try again '
93+
f'or contact an administrator')
94+
else:
95+
error_msg = (f'An error occurred creating the Private Slack Channel. '
96+
f'Error code: {new_channel.get("error")}')
97+
return {
98+
'ok': False,
99+
'error': error_msg
100+
}
101+
102+
logger.info(f"Successfully created {channel_name} ({new_channel.get('channel', {}).get('id')}).")
103+
return new_channel
68104

69105
def _extract_userid_from_username(self, username):
70106
""" Extracts the Slack userid from a hackathon platform userid
@@ -74,11 +110,36 @@ def _extract_userid_from_username(self, username):
74110
raise SlackException('Error adding user to channel')
75111
return username.split('_')[0]
76112

77-
def add_user_to_slack_channel(self, username, channel_id):
113+
def invite_users_to_slack_channel(self, users, channel):
114+
data = {
115+
"users": users,
116+
"channel": channel,
117+
}
118+
user_added = self._make_slack_post_request(
119+
self.invite_conversation_url, data=data)
120+
121+
if not user_added.get('ok'):
122+
return {
123+
'ok': False,
124+
'error': ('An error occurred adding users to Private Slack Channel {channel}. '
125+
f'Error code: {user_added.get("error")}')
126+
}
127+
128+
return user_added
129+
130+
def kick_user_from_slack_channel(self, user, channel):
78131
data = {
79-
"user": self._extract_userid_from_username(username),
80-
"channel": channel_id,
132+
"user": user,
133+
"channel": channel,
81134
}
82135
user_added = self._make_slack_post_request(
83136
self.invite_conversation_url, data=data)
137+
138+
if not user_added.get('ok'):
139+
return {
140+
'ok': False,
141+
'error': (f'An error occurred kicking user {user} from Private Slack Channel {channel}. '
142+
f'Error code: {user_added.get("error")}')
143+
}
144+
84145
return user_added

custom_slack_provider/tests.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from .provider import SlackProvider
88
from custom_slack_provider.slack import CustomSlackClient, SlackException
99
from django.core.management import call_command
10-
from
1110

1211

1312
class SlackOAuth2Tests(OAuth2TestsMixin, TestCase):
@@ -36,6 +35,7 @@ def setUp(self):
3635
def test__make_slack_get_request(self, get):
3736
mock_response = Mock()
3837
mock_response.json.return_value = {'ok': True}
38+
mock_response.status_code = 200
3939
get.return_value = mock_response
4040
client = CustomSlackClient(self.token)
4141
response = client._make_slack_get_request(url='test')
@@ -53,6 +53,7 @@ def test__make_slack_get_request(self, get):
5353
def test__make_slack_post_request(self, post):
5454
mock_response = Mock()
5555
mock_response.json.return_value = {'ok': True}
56+
mock_response.status_code = 200
5657
post.return_value = mock_response
5758
client = CustomSlackClient(self.token)
5859
response = client._make_slack_post_request(url='test', data={})
@@ -86,12 +87,12 @@ def test__extract_userid_from_username(self):
8687
self.assertEquals(e.message, 'Error adding user to channel')
8788

8889
@patch('custom_slack_provider.slack.CustomSlackClient._make_slack_post_request') # noqa: 501
89-
def test_add_user_to_slack_channel(self, _make_slack_post_request):
90+
def test_invite_users_to_slack_channel(self, _make_slack_post_request):
9091
_make_slack_post_request.return_value = {
9192
'ok': True,
9293
'channel': {'id': 'CH123123'}
9394
}
9495
client = CustomSlackClient(self.token)
95-
response = client.add_user_to_slack_channel(username='UA123123_T15666',
96-
channel_id='CH123123')
96+
response = client.invite_users_to_slack_channel(users='UA123123_T15666',
97+
channel='CH123123')
9798
self.assertEqual(response['channel']['id'], 'CH123123')

hackathon/tasks.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
import os
2+
import re
33

44
from celery import shared_task
55
from django.conf import settings
@@ -12,9 +12,10 @@
1212
from celery import shared_task
1313
from django.conf import settings
1414

15-
from accounts.models import CustomUser as User
15+
from custom_slack_provider.slack import CustomSlackClient
1616
from hackathon.models import Hackathon
17-
from custom_slack_provider.slack import CustomSlackClient, SlackException
17+
from teams.tasks import remove_admin_from_channel
18+
1819

1920
logger = logging.getLogger(__name__)
2021
logger.setLevel(logging.INFO)
@@ -45,37 +46,69 @@ def send_email_from_template(user_email, user_name, hackathon_display_name, temp
4546

4647

4748
@shared_task
48-
def create_new_slack_channel(hackathon_id, channel_name):
49+
def create_new_hackathon_slack_channel(hackathon_id, channel_name):
4950
""" Create a new Slack Channel/Conversation in an existing Workspace """
50-
if not settings.SLACK_ENABLED:
51-
logger.info("Slack not enabled.")
51+
slack_site_settings = SlackSiteSettings.objects.first()
52+
if (not (settings.SLACK_ENABLED or settings.SLACK_BOT_TOKEN or settings.SLACK_ADMIN_TOKEN
53+
or settings.SLACK_WORKSPACE or not slack_site_settings)):
54+
logger.info("This feature is not enabeled.")
5255
return
5356

5457
hackathon = Hackathon.objects.get(id=hackathon_id)
5558
logger.info(
5659
(f"Creating new Slack channel {channel_name} for hackathon "
5760
f"{hackathon.display_name} in Slack Workspace "
5861
f"{settings.SLACK_WORKSPACE}({settings.SLACK_TEAM_ID})"))
59-
slack_client = CustomSlackClient(settings.SLACK_BOT_TOKEN)
60-
channel = slack_client.create_slack_channel(
61-
channel_name, is_private=True)
6262

63-
channel_id = channel.get('id')
64-
logger.info(f"Channel with id {channel_id} created.")
63+
admin_client = CustomSlackClient(settings.SLACK_ADMIN_TOKEN)
64+
channel_response = admin_client.create_slack_channel(
65+
team_id=settings.SLACK_TEAM_ID,
66+
channel_name=channel_name,
67+
is_private=True,
68+
)
6569

66-
if not channel_id:
67-
logger.error("No Channel Id found.")
68-
return
70+
if not channel_response['ok']:
71+
logger.error(channel_response['error'])
6972

70-
channel_url = f'https://{settings.SLACK_WORKSPACE}.slack.com/archives/{channel_id}' # noqa: E501
73+
channel = channel_response.get('channel', {}).get('id')
74+
channel_url = f'https://{settings.SLACK_WORKSPACE}.slack.com/archives/{channel}'
7175
hackathon.channel_url = channel_url
7276
hackathon.save()
73-
logger.info(f"Hackathon {hackathon.display_name} updated successfully.")
74-
75-
logger.info("Adding channel admins")
76-
for admin in hackathon.channel_admins.all():
77-
try:
78-
slack_client.add_user_to_slack_channel(admin.username, channel_id)
79-
except SlackException:
80-
logger.exception((f"Could not add user with id {admin.id} "
81-
f"to channel {channel_id}."))
77+
logger.info(f"Channel with id {channel} created.")
78+
79+
# Add admins to channel for administration purposes
80+
users = [admin.username for admin in slack_site_settings.slack_admins.all()]
81+
# First need to add Slack Bot to then add users to channel
82+
response = admin_client.invite_users_to_slack_channel(
83+
users=settings.SLACK_BOT_ID,
84+
channel=channel,
85+
)
86+
if not response['ok']:
87+
logger.error(response['error'])
88+
return
89+
90+
bot_client = CustomSlackClient(settings.SLACK_BOT_TOKEN)
91+
pattern = re.compile(r'^U[a-zA-Z0-9]*[_]T[a-zA-Z0-9]*$')
92+
users_to_invite = ','.join([user.split('_')[0]
93+
for user in users if pattern.match(user)])
94+
bot_client.invite_users_to_slack_channel(
95+
users=users_to_invite,
96+
channel=channel,
97+
)
98+
99+
if not response['ok']:
100+
logger.error(response['error'])
101+
return
102+
103+
if slack_site_settings.remove_admin_from_channel:
104+
remove_admin_from_channel(users_to_invite, channel)
105+
106+
107+
@shared_task
108+
def invite_user_to_hackathon_slack_channel(hackathon_id, user_id):
109+
bot_client = CustomSlackClient(settings.SLACK_BOT_TOKEN)
110+
111+
112+
@shared_task
113+
def kick_user_to_hackathon_slack_channel(user, channel):
114+
pass

hackathon/tests/task_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from accounts.models import Organisation, CustomUser as User
99
from hackathon.models import Hackathon
10-
from hackathon.tasks import create_new_slack_channel
10+
from hackathon.tasks import create_new_hackathon_slack_channel
1111

1212

1313
class TaskTests(TestCase):
@@ -29,15 +29,15 @@ def setUp(self):
2929
@override_settings(CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
3030
CELERY_ALWAYS_EAGER=True,
3131
BROKER_BACKEND='memory')
32-
def test_create_new_slack_channel(self):
32+
def test_create_new_hackathon_slack_channel(self):
3333
channel_id = 'CH123123'
3434
responses.add(
3535
responses.POST, 'https://slack.com/api/conversations.create',
3636
json={'ok': True, 'channel': {'id': channel_id}}, status=200)
3737
responses.add(
3838
responses.POST, 'https://slack.com/api/conversations.invite',
3939
json={'ok': True}, status=200)
40-
create_new_slack_channel.apply_async(args=[
40+
create_new_hackathon_slack_channel.apply_async(args=[
4141
self.hackathon.id, self.user.username])
4242

4343
import time; time.sleep(3)

0 commit comments

Comments
 (0)