Skip to content

Commit f885447

Browse files
ruberVulpesseratch
andauthored
Fix 794: Add admin.conversations.* API methods (#795)
Co-authored-by: Kazuhiro Sera <seratch@gmail.com>
1 parent f5cafdb commit f885447

6 files changed

Lines changed: 535 additions & 8 deletions

File tree

integration_tests/env_variable_names.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
SLACK_SDK_TEST_GRID_WORKSPACE_BOT_TOKEN = "SLACK_SDK_TEST_GRID_WORKSPACE_BOT_TOKEN"
2424
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID = "SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID"
2525
SLACK_SDK_TEST_GRID_TEAM_ID = "SLACK_SDK_TEST_GRID_TEAM_ID"
26+
SLACK_SDK_TEST_GRID_USER_ID = "SLACK_SDK_TEST_GRID_USER_ID"
2627

2728
# Webhook
2829
SLACK_SDK_TEST_INCOMING_WEBHOOK_URL = "SLACK_SDK_TEST_INCOMING_WEBHOOK_URL"
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import asyncio
2+
import logging
3+
import os
4+
import time
5+
import unittest
6+
7+
from integration_tests.env_variable_names import \
8+
SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN, \
9+
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID, SLACK_SDK_TEST_GRID_TEAM_ID, \
10+
SLACK_SDK_TEST_GRID_USER_ID
11+
from integration_tests.helpers import async_test
12+
from slack import WebClient
13+
14+
15+
class TestWebClient(unittest.TestCase):
16+
"""Runs integration tests with real Slack API"""
17+
18+
# TODO: admin_conversations_disconnectShared - not_allowed_token_type
19+
# TODO: admin_conversations_ekm_listOriginalConnectedChannelInfo - enable the feature
20+
21+
def setUp(self):
22+
self.logger = logging.getLogger(__name__)
23+
self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN]
24+
self.sync_client: WebClient = WebClient(
25+
token=self.org_admin_token,
26+
run_async=False,
27+
loop=asyncio.new_event_loop()
28+
)
29+
self.async_client: WebClient = WebClient(token=self.org_admin_token, run_async=True)
30+
31+
self.team_id = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID]
32+
self.idp_group_id = os.environ[SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID]
33+
self.user_id = os.environ[SLACK_SDK_TEST_GRID_USER_ID]
34+
self.channel_name = f'test-channel-{int(round(time.time() * 1000))}'
35+
self.channel_rename = f'test-channel-renamed-{int(round(time.time() * 1000))}'
36+
37+
def tearDown(self):
38+
pass
39+
40+
def test_sync(self):
41+
client = self.sync_client
42+
43+
conv_creation = client.admin_conversations_create(
44+
is_private=False,
45+
name=self.channel_name,
46+
team_id=self.team_id,
47+
)
48+
self.assertIsNotNone(conv_creation)
49+
created_channel_id = conv_creation.data["channel_id"]
50+
51+
self.assertIsNotNone(client.admin_conversations_invite(
52+
channel_id=created_channel_id,
53+
user_ids=[self.user_id],
54+
))
55+
self.assertIsNotNone(client.admin_conversations_archive(
56+
channel_id=created_channel_id,
57+
))
58+
self.assertIsNotNone(client.admin_conversations_unarchive(
59+
channel_id=created_channel_id,
60+
))
61+
self.assertIsNotNone(client.admin_conversations_rename(
62+
channel_id=created_channel_id,
63+
name=self.channel_rename,
64+
))
65+
self.assertIsNotNone(client.admin_conversations_search())
66+
67+
self.assertIsNotNone(client.admin_conversations_getConversationPrefs(
68+
channel_id=created_channel_id,
69+
))
70+
self.assertIsNotNone(client.admin_conversations_setConversationPrefs(
71+
channel_id=created_channel_id,
72+
prefs={},
73+
))
74+
75+
self.assertIsNotNone(client.admin_conversations_getTeams(
76+
channel_id=created_channel_id,
77+
))
78+
self.assertIsNotNone(client.admin_conversations_setTeams(
79+
team_id=self.team_id,
80+
channel_id=created_channel_id,
81+
org_channel=True,
82+
))
83+
time.sleep(2) # To avoid channel_not_found
84+
self.assertIsNotNone(client.admin_conversations_convertToPrivate(
85+
channel_id=created_channel_id,
86+
))
87+
time.sleep(2) # To avoid internal_error
88+
self.assertIsNotNone(client.admin_conversations_archive(
89+
channel_id=created_channel_id,
90+
))
91+
time.sleep(2) # To avoid internal_error
92+
self.assertIsNotNone(client.admin_conversations_delete(
93+
channel_id=created_channel_id,
94+
))
95+
96+
@async_test
97+
async def test_async(self):
98+
# await asyncio.sleep(seconds) are included to avoid rate limiting errors
99+
100+
client = self.async_client
101+
102+
conv_creation = await client.admin_conversations_create(
103+
is_private=False,
104+
name=self.channel_name,
105+
team_id=self.team_id,
106+
)
107+
self.assertIsNotNone(conv_creation)
108+
created_channel_id = conv_creation.data["channel_id"]
109+
110+
self.assertIsNotNone(await client.admin_conversations_invite(
111+
channel_id=created_channel_id,
112+
user_ids=[self.user_id],
113+
))
114+
self.assertIsNotNone(await client.admin_conversations_archive(
115+
channel_id=created_channel_id,
116+
))
117+
self.assertIsNotNone(await client.admin_conversations_unarchive(
118+
channel_id=created_channel_id,
119+
))
120+
self.assertIsNotNone(await client.admin_conversations_rename(
121+
channel_id=created_channel_id,
122+
name=self.channel_rename,
123+
))
124+
self.assertIsNotNone(await client.admin_conversations_search())
125+
126+
self.assertIsNotNone(await client.admin_conversations_getConversationPrefs(
127+
channel_id=created_channel_id,
128+
))
129+
self.assertIsNotNone(await client.admin_conversations_setConversationPrefs(
130+
channel_id=created_channel_id,
131+
prefs={},
132+
))
133+
134+
self.assertIsNotNone(await client.admin_conversations_getTeams(
135+
channel_id=created_channel_id,
136+
))
137+
self.assertIsNotNone(await client.admin_conversations_setTeams(
138+
team_id=self.team_id,
139+
channel_id=created_channel_id,
140+
org_channel=True,
141+
))
142+
await asyncio.sleep(2) # To avoid channel_not_found
143+
self.assertIsNotNone(await client.admin_conversations_convertToPrivate(
144+
channel_id=created_channel_id,
145+
))
146+
await asyncio.sleep(2) # To avoid internal_error
147+
self.assertIsNotNone(await client.admin_conversations_archive(
148+
channel_id=created_channel_id,
149+
))
150+
await asyncio.sleep(2) # To avoid internal_error
151+
self.assertIsNotNone(await client.admin_conversations_delete(
152+
channel_id=created_channel_id,
153+
))

integration_tests/web/test_admin_conversations_restrictAccess.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from integration_tests.env_variable_names import SLACK_SDK_TEST_GRID_WORKSPACE_ADMIN_USER_TOKEN, \
88
SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN, \
9-
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID, SLACK_SDK_TEST_GRID_TEAM_ID
9+
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID, SLACK_SDK_TEST_GRID_TEAM_ID, SLACK_SDK_TEST_WEB_TEST_USER_ID
1010
from integration_tests.helpers import async_test
1111
from slack import WebClient
1212

@@ -45,6 +45,7 @@ def tearDown(self):
4545
pass
4646

4747
def test_sync(self):
48+
# time.sleep(seconds) are included to avoid rate limiting errors
4849
client = self.sync_client
4950

5051
add_group = client.admin_conversations_restrictAccess_addGroup(
@@ -75,6 +76,8 @@ def test_sync(self):
7576

7677
@async_test
7778
async def test_async(self):
79+
# await asyncio.sleep(seconds) are included to avoid rate limiting errors
80+
7881
client = self.async_client
7982

8083
add_group = await client.admin_conversations_restrictAccess_addGroup(
@@ -101,4 +104,4 @@ async def test_async(self):
101104
)
102105
self.assertIsNotNone(remove_group)
103106
# To avoid rate limiting errors
104-
await asyncio.sleep(20)
107+
await asyncio.sleep(20)

slack/web/async_client.py

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,30 @@ async def admin_conversations_restrictAccess_removeGroup(
183183
params=kwargs,
184184
)
185185

186-
async def admin_conversations_setTeams(self, **kwargs) -> AsyncSlackResponse:
187-
"""Set the workspaces in an Enterprise grid org that connect to a channel."""
186+
async def admin_conversations_setTeams(
187+
self, *, channel_id: str, **kwargs
188+
) -> AsyncSlackResponse:
189+
"""Set the workspaces in an Enterprise grid org that connect to a channel.
190+
191+
Args:
192+
channel_id (str): The encoded channel_id to add or remove to workspaces.
193+
194+
"""
195+
kwargs.update({"channel_id": channel_id})
188196
return await self.api_call("admin.conversations.setTeams", json=kwargs)
189197

198+
async def admin_conversations_getTeams(
199+
self, *, channel_id: str, **kwargs
200+
) -> AsyncSlackResponse:
201+
"""Set the workspaces in an Enterprise grid org that connect to a channel.
202+
203+
Args:
204+
channel_id (str): The channel to determine connected workspaces within the organization for.
205+
206+
"""
207+
kwargs.update({"channel_id": channel_id})
208+
return await self.api_call("admin.conversations.getTeams", json=kwargs)
209+
190210
async def admin_emoji_add(self, **kwargs) -> AsyncSlackResponse:
191211
"""Add an emoji."""
192212
return await self.api_call("admin.emoji.add", http_verb="GET", params=kwargs)
@@ -2156,3 +2176,147 @@ async def views_publish(
21562176
else:
21572177
kwargs.update({"view": view})
21582178
return await self.api_call("views.publish", json=kwargs)
2179+
2180+
async def admin_conversations_create(
2181+
self, *, is_private: bool, name: str, **kwargs
2182+
) -> AsyncSlackResponse:
2183+
"""Create a public or private channel-based conversation.
2184+
2185+
Args:
2186+
is_private (bool): When true, creates a private channel instead of a public channel
2187+
name (str): Name of the public or private channel to create.
2188+
org_wide (bool): When true, the channel will be available org-wide.
2189+
Note: if the channel is not org_wide=true, you must specify a team_id for this channel
2190+
team_id (str): The workspace to create the channel in.
2191+
Note: this argument is required unless you set org_wide=true.
2192+
2193+
"""
2194+
kwargs.update({"is_private": is_private, "name": name})
2195+
return await self.api_call("admin.conversations.create", json=kwargs)
2196+
2197+
async def admin_conversations_delete(
2198+
self, *, channel_id: str, **kwargs
2199+
) -> AsyncSlackResponse:
2200+
"""Delete a public or private channel.
2201+
2202+
Args:
2203+
channel_id (str): The channel to delete.
2204+
2205+
"""
2206+
kwargs.update({"channel_id": channel_id})
2207+
return await self.api_call("admin.conversations.delete", json=kwargs)
2208+
2209+
async def admin_conversations_invite(
2210+
self, *, channel_id: str, user_ids: Union[str, List[str]], **kwargs
2211+
) -> AsyncSlackResponse:
2212+
"""Invite a user to a public or private channel.
2213+
2214+
Args:
2215+
channel_id (str): The channel that the users will be invited to.
2216+
user_ids (str or list): The users to invite.
2217+
"""
2218+
kwargs.update({"channel_id": channel_id})
2219+
if isinstance(user_ids, list):
2220+
kwargs.update({"user_ids": ",".join(user_ids)})
2221+
else:
2222+
kwargs.update({"user_ids": user_ids})
2223+
# NOTE: the endpoint is unable to handle Content-Type: application/json as of Sep 3, 2020.
2224+
return await self.api_call("admin.conversations.invite", params=kwargs)
2225+
2226+
async def admin_conversations_archive(
2227+
self, *, channel_id: str, **kwargs
2228+
) -> AsyncSlackResponse:
2229+
"""Archive a public or private channel.
2230+
2231+
Args:
2232+
channel_id (str): The channel to archive.
2233+
"""
2234+
kwargs.update({"channel_id": channel_id})
2235+
return await self.api_call("admin.conversations.archive", json=kwargs)
2236+
2237+
async def admin_conversations_unarchive(
2238+
self, *, channel_id: str, **kwargs
2239+
) -> AsyncSlackResponse:
2240+
"""Unarchive a public or private channel.
2241+
2242+
Args:
2243+
channel_id (str): The channel to unarchive.
2244+
"""
2245+
kwargs.update({"channel_id": channel_id})
2246+
return await self.api_call("admin.conversations.unarchive", json=kwargs)
2247+
2248+
async def admin_conversations_rename(
2249+
self, *, channel_id: str, name: str, **kwargs
2250+
) -> AsyncSlackResponse:
2251+
"""Rename a public or private channel.
2252+
2253+
Args:
2254+
channel_id (str): The channel to rename.
2255+
name (str): The name to rename the channel to.
2256+
"""
2257+
kwargs.update({"channel_id": channel_id, "name": name})
2258+
return await self.api_call("admin.conversations.rename", json=kwargs)
2259+
2260+
async def admin_conversations_search(self, **kwargs) -> AsyncSlackResponse:
2261+
"""Search for public or private channels in an Enterprise organization."""
2262+
return await self.api_call("admin.conversations.search", json=kwargs)
2263+
2264+
async def admin_conversations_convertToPrivate(
2265+
self, *, channel_id: str, **kwargs
2266+
) -> AsyncSlackResponse:
2267+
"""Convert a public channel to a private channel.
2268+
2269+
Args:
2270+
channel_id (str): The channel to convert to private.
2271+
"""
2272+
kwargs.update({"channel_id": channel_id})
2273+
return await self.api_call("admin.conversations.convertToPrivate", json=kwargs)
2274+
2275+
async def admin_conversations_setConversationPrefs(
2276+
self, *, channel_id: str, prefs: Union[str, dict], **kwargs
2277+
) -> AsyncSlackResponse:
2278+
"""Set the posting permissions for a public or private channel.
2279+
2280+
Args:
2281+
channel_id (str): The channel to set the prefs for
2282+
prefs (str or dict): The prefs for this channel in a stringified JSON format.
2283+
"""
2284+
kwargs.update({"channel_id": channel_id, "prefs": prefs})
2285+
return await self.api_call(
2286+
"admin.conversations.setConversationPrefs", json=kwargs
2287+
)
2288+
2289+
async def admin_conversations_getConversationPrefs(
2290+
self, *, channel_id: str, **kwargs
2291+
) -> AsyncSlackResponse:
2292+
"""Get conversation preferences for a public or private channel.
2293+
2294+
Args:
2295+
channel_id (str): The channel to get the preferences for.
2296+
"""
2297+
kwargs.update({"channel_id": channel_id})
2298+
return await self.api_call(
2299+
"admin.conversations.getConversationPrefs", json=kwargs
2300+
)
2301+
2302+
async def admin_conversations_disconnectShared(
2303+
self, *, channel_id: str, **kwargs
2304+
) -> AsyncSlackResponse:
2305+
"""Disconnect a connected channel from one or more workspaces.
2306+
2307+
Args:
2308+
channel_id (str): The channel to be disconnected from some workspaces.
2309+
"""
2310+
kwargs.update({"channel_id": channel_id})
2311+
return await self.api_call("admin.conversations.disconnectShared", json=kwargs)
2312+
2313+
async def admin_conversations_ekm_listOriginalConnectedChannelInfo(
2314+
self, **kwargs
2315+
) -> AsyncSlackResponse:
2316+
"""List all disconnected channels—i.e.,
2317+
channels that were once connected to other workspaces and then disconnected—and
2318+
the corresponding original channel IDs for key revocation with EKM.
2319+
"""
2320+
return await self.api_call(
2321+
"admin.conversations.ekm.listOriginalConnectedChannelInfo", params=kwargs
2322+
)

0 commit comments

Comments
 (0)