Skip to content

Commit

Permalink
Fix #1087 Add admin.conversations.bulk{Archive|Delete|Move} API metho…
Browse files Browse the repository at this point in the history
…d support (#1311)
  • Loading branch information
WilliamBergamin authored Dec 9, 2022
1 parent 0fedf4f commit 9217bcb
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 27 deletions.
55 changes: 32 additions & 23 deletions .github/maintainers_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,35 @@ work for this project again in the future, `cd` into this project directory and
run `source env_3.9.6/bin/activate` again.

The last step is to install this project's dependencies and run all unit tests; to do so, you can run

```bash
$ ./scripts/run_validation.sh
```

Also check out [how
we configure GitHub Actions to install dependencies for this project for use in
our continuous integration](https://github.com/slackapi/python-slack-sdk/blob/v3.17.0/.github/workflows/ci-build.yml#L28-L32).

## Tasks

### Testing (Unit Tests)

When you make changes to this SDK, please write unit tests verifying if the changes work as you expected. You can easily run all the tests and formatting/linter with the below scripts.

Run all the unit tests, code formatter, and code analyzer:

```bash
$ ./scripts/run_validation.sh
```

Run all the unit tests (no formatter nor code analyzer):

```bash
$ ./scripts/run_unit_tests.sh
```

Run a specific unit test:

```bash
$ ./scripts/run_unit_tests.sh tests/web/test_web_client.py
```
Expand All @@ -89,11 +94,13 @@ You can rely on GitHub Actions builds for running the tests on a variety of Pyth
This project also has integration tests that verify the SDK works with the Slack API platform. As a preparation, you need to set [the required env variables](https://github.com/slackapi/python-slack-sdk/blob/main/integration_tests/env_variable_names.py) properly. You don't need to setup all of them if you just want to run some of the tests. Commonly, `SLACK_SDK_TEST_BOT_TOKEN` and `SLACK_SDK_TEST_USER_TOKEN` are used for running `WebClient` tests.

Run all integration tests:

```bash
$ ./scripts/run_integration_tests.sh
```

Run a specific integration test:

```bash
$ ./scripts/run_integration_tests.sh integration_tests/web/test_async_web_client.py
```
Expand All @@ -104,12 +111,14 @@ The documentation is generated from the source and templates in the `docs-src` d
gets committed to the repo in `docs` and also published to a GitHub Pages website.

You can generate and preview the **SDK document pages** by running

```bash
$ ./scripts/docs.sh
$ open docs/index.html
```

Similarly you can generate and preview the **API documents for `slack_sdk` package modules** by running

```bash
$ ./scripts/generate_api_docs.sh
```
Expand All @@ -118,46 +127,46 @@ $ ./scripts/generate_api_docs.sh

1. Create the commit for the release:

- Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slack_sdk/version.py`.
- Build the docs with `./scripts/docs.sh` and then `./scripts/generate_api_docs.sh`.
- Create a branch for the release with `git checkout -b v2.5.0`
- Make a commit that includes the new version number: `git commit -m 'version 2.5.0'`.
- Open a PR and merge after receiving at least one approval from other maintainers.
- Create a git tag for the release. For example `git tag v2.5.0`.
- Push the tag up to github with `git push origin --tags`
- Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slack_sdk/version.py`.
- Build the docs with `./scripts/docs.sh` and then `./scripts/generate_api_docs.sh`.
- Create a branch for the release with `git checkout -b v2.5.0`
- Make a commit that includes the new version number: `git commit -m 'version 2.5.0'`.
- Open a PR and merge after receiving at least one approval from other maintainers.
- Create a git tag for the release. For example `git tag v2.5.0`.
- Push the tag up to github with `git push origin --tags`

2. Distribute the release

- Use the latest stable Python runtime
- `python -m venv env`
- `python setup.py upload`
- Create a GitHub Release. You will select the commit with updated version number (e.g. `version 2.5.0`) to associate with the tag, and name the tag after this version (e.g. `v2.5.0`). This will also serve as a Changelog for the project. Add a description of changes to the Release. Mention Issue and PR #'s and @-mention contributors.
- Use the latest stable Python runtime
- `python -m venv env`
- `python setup.py upload`
- Create a GitHub Release. You will select the commit with updated version number (e.g. `version 2.5.0`) to associate with the tag, and name the tag after this version (e.g. `v2.5.0`). This will also serve as a Changelog for the project. Add a description of changes to the Release. Mention Issue and PR #'s and @-mention contributors.

```markdown
Refer to [v{version} milestone](https://github.com/slackapi/python-slack-sdk/milestone/{TODO}?closed=1) to know the complete list of the issues resolved by this release.
```markdown
Refer to [v{version} milestone](https://github.com/slackapi/python-slack-sdk/milestone/{TODO}?closed=1) to know the complete list of the issues resolved by this release.

**Updates**
**Updates**

1. [WebClient] #111 Make an awesome change - Thanks @SlackHQ
1. [RTMClient] #222 Make an awesome change - Thanks @SlackAPI
1. [WebClient] #111 Make an awesome change - Thanks @SlackHQ
2. [RTMClient] #222 Make an awesome change - Thanks @SlackAPI

**All Changes**
**All Changes**

https://github.com/slackapi/python-slack-sdk/compare/{the previous release version tag}...{the release version tag}
```
https://github.com/slackapi/python-slack-sdk/compare/{the previous release version tag}...{the release version tag}
```

3. (Slack Internal) Communicate the release internally

- Include a link to the GitHub release
- Include a link to the GitHub release

4. Make announcements

- #slack-api in dev4slack.slack.com
- #lang-python in community.slack.com
- #slack-api in dev4slack.slack.com
- #lang-python in community.slack.com

5. (Slack Internal) Tweet by @SlackAPI

- Not necessary for patch updates, might be needed for minor updates, definitely needed for major updates. Include a link to the GitHub release
- Not necessary for patch updates, might be needed for minor updates, definitely needed for major updates. Include a link to the GitHub release

## Workflow

Expand Down
1 change: 1 addition & 0 deletions integration_tests/env_variable_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID = "SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID"
SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID_2 = "SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID_2"
SLACK_SDK_TEST_GRID_TEAM_ID = "SLACK_SDK_TEST_GRID_TEAM_ID"
SLACK_SDK_TEST_GRID_TEAM_ID_2 = "SLACK_SDK_TEST_GRID_TEAM_ID_2"
SLACK_SDK_TEST_GRID_USER_ID = "SLACK_SDK_TEST_GRID_USER_ID"
# The following user must be a full member, who is not a primary owner
SLACK_SDK_TEST_GRID_USER_ID_ADMIN_AUTH = "SLACK_SDK_TEST_GRID_USER_ID_ADMIN_AUTH"
Expand Down
150 changes: 150 additions & 0 deletions integration_tests/web/test_admin_conversations_bulk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import asyncio
import logging
import os
import time
import unittest

from integration_tests.env_variable_names import (
SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN,
SLACK_SDK_TEST_GRID_TEAM_ID_2,
SLACK_SDK_TEST_GRID_TEAM_ID,
)
from integration_tests.helpers import async_test
from slack_sdk.web import WebClient, SlackResponse
from slack_sdk.errors import SlackApiError
from slack_sdk.web.async_client import AsyncWebClient


class TestWebClient(unittest.TestCase):
"""Runs integration tests with real Slack API"""

def setUp(self):
self.logger = logging.getLogger(__name__)
self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN]
self.sync_client: WebClient = WebClient(token=self.org_admin_token)
self.async_client: AsyncWebClient = AsyncWebClient(token=self.org_admin_token)

self.team_id = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID]
self.team_id_2 = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID_2]
self.channel_name = f"test-channel-{int(round(time.time() * 1000))}"

def tearDown(self):
pass

def test_sync_move(self):
client = self.sync_client

conv_creation = client.admin_conversations_create(
is_private=False,
name=self.channel_name,
team_id=self.team_id,
)
self.assertIsNotNone(conv_creation)
created_channel_id = conv_creation.data["channel_id"]

self.assertIsNotNone(
_get_bulk_response(
client.admin_conversations_bulkMove,
channel_ids=[created_channel_id],
target_team_id=self.team_id_2,
)
)

def test_sync(self):
client = self.sync_client

conv_creation = client.admin_conversations_create(
is_private=False,
name=self.channel_name,
team_id=self.team_id,
)
self.assertIsNotNone(conv_creation)
created_channel_id = conv_creation.data["channel_id"]

self.assertIsNotNone(
_get_bulk_response(
client.admin_conversations_bulkArchive,
channel_ids=[created_channel_id],
)
)

self.assertIsNotNone(
_get_bulk_response(
client.admin_conversations_bulkDelete,
channel_ids=[created_channel_id],
)
)

@async_test
async def test_async_move(self):

client = self.async_client

conv_creation = await client.admin_conversations_create(
is_private=False,
name=self.channel_name,
team_id=self.team_id,
)
self.assertIsNotNone(conv_creation)
created_channel_id = conv_creation.data["channel_id"]

self.assertIsNotNone(
await _get_async_bulk_response(
client.admin_conversations_bulkMove,
channel_ids=[created_channel_id],
target_team_id=self.team_id_2,
)
)

@async_test
async def test_async(self):

client = self.async_client

conv_creation = await client.admin_conversations_create(
is_private=False,
name=self.channel_name,
team_id=self.team_id,
)
self.assertIsNotNone(conv_creation)
created_channel_id = conv_creation.data["channel_id"]

self.assertIsNotNone(
await _get_async_bulk_response(
client.admin_conversations_bulkArchive,
channel_ids=[created_channel_id],
)
)

self.assertIsNotNone(
await _get_async_bulk_response(
client.admin_conversations_bulkDelete,
channel_ids=[created_channel_id],
)
)


async def _get_async_bulk_response(method, **kwargs) -> SlackResponse:
while True:
try:
return await method(**kwargs)
except SlackApiError as e:
if not _action_in_progress(e.response):
raise e
await asyncio.sleep(3)


def _get_bulk_response(method, **kwargs) -> SlackResponse:
while True:
try:
return method(**kwargs)
except SlackApiError as e:
if not _action_in_progress(e.response):
raise e
time.sleep(3)


def _action_in_progress(response: SlackResponse) -> bool:
if response.data.get("error", "") == "action_already_in_progress":
return True
return False
42 changes: 42 additions & 0 deletions slack_sdk/web/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,48 @@ async def admin_conversations_setCustomRetention(
kwargs.update({"channel_id": channel_id, "duration_days": duration_days})
return await self.api_call("admin.conversations.setCustomRetention", params=kwargs)

async def admin_conversations_bulkArchive(
self,
*,
channel_ids: Union[Sequence[str], str],
**kwargs,
) -> AsyncSlackResponse:
"""Archive public or private channels in bulk.
https://api.slack.com/methods/admin.conversations.bulkArchive
"""
kwargs.update({"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids})
return await self.api_call("admin.conversations.bulkArchive", params=kwargs)

async def admin_conversations_bulkDelete(
self,
*,
channel_ids: Union[Sequence[str], str],
**kwargs,
) -> AsyncSlackResponse:
"""Delete public or private channels in bulk.
https://slack.com/api/admin.conversations.bulkDelete
"""
kwargs.update({"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids})
return await self.api_call("admin.conversations.bulkDelete", params=kwargs)

async def admin_conversations_bulkMove(
self,
*,
channel_ids: Union[Sequence[str], str],
target_team_id: str,
**kwargs,
) -> AsyncSlackResponse:
"""Move public or private channels in bulk.
https://api.slack.com/methods/admin.conversations.bulkMove
"""
kwargs.update(
{
"target_team_id": target_team_id,
"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids,
}
)
return await self.api_call("admin.conversations.bulkMove", params=kwargs)

async def admin_emoji_add(
self,
*,
Expand Down
42 changes: 42 additions & 0 deletions slack_sdk/web/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,48 @@ def admin_conversations_setCustomRetention(
kwargs.update({"channel_id": channel_id, "duration_days": duration_days})
return self.api_call("admin.conversations.setCustomRetention", params=kwargs)

def admin_conversations_bulkArchive(
self,
*,
channel_ids: Union[Sequence[str], str],
**kwargs,
) -> SlackResponse:
"""Archive public or private channels in bulk.
https://api.slack.com/methods/admin.conversations.bulkArchive
"""
kwargs.update({"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids})
return self.api_call("admin.conversations.bulkArchive", params=kwargs)

def admin_conversations_bulkDelete(
self,
*,
channel_ids: Union[Sequence[str], str],
**kwargs,
) -> SlackResponse:
"""Delete public or private channels in bulk.
https://slack.com/api/admin.conversations.bulkDelete
"""
kwargs.update({"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids})
return self.api_call("admin.conversations.bulkDelete", params=kwargs)

def admin_conversations_bulkMove(
self,
*,
channel_ids: Union[Sequence[str], str],
target_team_id: str,
**kwargs,
) -> SlackResponse:
"""Move public or private channels in bulk.
https://api.slack.com/methods/admin.conversations.bulkMove
"""
kwargs.update(
{
"target_team_id": target_team_id,
"channel_ids": ",".join(channel_ids) if isinstance(channel_ids, (list, tuple)) else channel_ids,
}
)
return self.api_call("admin.conversations.bulkMove", params=kwargs)

def admin_emoji_add(
self,
*,
Expand Down
Loading

0 comments on commit 9217bcb

Please sign in to comment.