-
-
Notifications
You must be signed in to change notification settings - Fork 102
Clean PR: Slack member sync implementation #1629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary by CodeRabbit
Summary by CodeRabbit
WalkthroughThe Slack sync management command was refactored to modularize API interactions, add robust error handling with retries and exponential backoff, and implement token fallback logic. The channel sync process now explicitly fetches and stores each channel's member count using the Slack API. A test case for the command was added, and a migration removes an obsolete workspace field. Changes
Assessment against linked issues
Assessment against linked issues: Out-of-scope changesNo out-of-scope changes found. Suggested labels
Suggested reviewers
✨ Finishing Touches
🧪 Generate Unit Tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (1)
backend/apps/slack/management/commands/slack_sync_data.py (1)
34-35
: Break long line for better readability.The token fallback logic is correct, but the line exceeds the character limit.
- # Updated logic: fallback to env var if missing or empty - bot_token = (getattr(workspace, "bot_token", "") or "").strip() or os.environ.get("DJANGO_SLACK_BOT_TOKEN") + # Updated logic: fallback to env var if missing or empty + bot_token = ( + (getattr(workspace, "bot_token", "") or "").strip() + or os.environ.get("DJANGO_SLACK_BOT_TOKEN") + )
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/apps/slack/management/commands/slack_sync_data.py
(2 hunks)backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
(1 hunks)backend/apps/slack/tests/test_slack_sync_data.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/apps/slack/management/commands/slack_sync_data.py (3)
backend/apps/slack/models/conversation.py (3)
Conversation
(11-90)update_data
(68-90)bulk_save
(63-65)backend/apps/slack/models/member.py (3)
Member
(10-78)update_data
(66-78)bulk_save
(61-63)backend/apps/slack/models/workspace.py (2)
Workspace
(10-43)bot_token
(36-43)
backend/apps/slack/tests/test_slack_sync_data.py (2)
backend/apps/slack/models/workspace.py (1)
Workspace
(10-43)backend/apps/slack/models/event.py (1)
create
(62-79)
🪛 Ruff (0.11.9)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/slack/management/commands/slack_sync_data.py
3-3: random
imported but unused
Remove unused import: random
(F401)
35-35: Line too long (119 > 99)
(E501)
57-57: Abstract raise
to an inner function
(TRY301)
57-57: Avoid specifying long messages outside the exception class
(TRY003)
57-57: Exception must not use an f-string literal, assign to variable first
Assign to variable; remove f-string literal
(EM102)
58-58: Consider moving this statement to an else
block
(TRY300)
60-60: Magic value used in comparison, consider replacing 429
with a constant variable
(PLR2004)
62-62: Line too long (102 > 99)
(E501)
66-66: Use raise
without specifying exception name
Remove exception name
(TRY201)
70-70: Avoid specifying long messages outside the exception class
(TRY003)
70-70: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
98-98: Line too long (116 > 99)
(E501)
backend/apps/slack/tests/test_slack_sync_data.py
1-1: File backend/apps/slack/tests/test_slack_sync_data.py
is part of an implicit namespace package. Add an __init__.py
.
(INP001)
57-57: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
57-57: Boolean positional value in function call
(FBT003)
🪛 Pylint (3.3.7)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
[refactor] 6-6: Too few public methods (0/2)
(R0903)
🔇 Additional comments (3)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py (1)
1-18
: LGTM! Migration correctly removes the obsolete field.The migration appropriately removes the
total_members_count
field from the workspace model, aligning with the new approach of dynamically fetching member counts through the Slack API.backend/apps/slack/management/commands/slack_sync_data.py (2)
71-116
: Well-implemented conversation fetching with proper member count handling.The method correctly handles pagination, fetches missing member counts using
conversations.info
, and efficiently bulk saves the data. This aligns perfectly with the PR objective of complete synchronization.
117-147
: Clean implementation of member synchronization.The method efficiently handles pagination and uses an elegant list comprehension pattern for member creation. The implementation is consistent with the conversation fetching approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
backend/apps/slack/management/commands/slack_sync_data.py (1)
59-66
:retries
is still incremented twice ⇒ premature exhaustion of retry budget
The earlier review comment is still valid.- retries += 1 ... - retries += 1 + retries += 1 # keep only this incrementLeaving both lines inflates
retries
by 2 for every 429 response, cutting the effective retry budget in half and skewing the back-off calculation.
🧹 Nitpick comments (1)
backend/apps/slack/management/commands/slack_sync_data.py (1)
86-101
: High per-channelconversations.info
fan-out may hit rate limitsCalling
conversations_info
individually for every channel withoutnum_members
results in N additional API requests (848 in your test case). Even with back-off, this dominates the rate-limit budget.Consider batching the enrichment step:
- First pass: collect channel IDs missing
num_members
.- Sleep the recommended
delay
.- Iterate over the collected IDs in small chunks, respecting rate limits.
This reduces the call rate from N per page to N / chunk_size and keeps the logic isolated from the pagination loop.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
backend/apps/slack/management/commands/slack_sync_data.py
(2 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/apps/slack/management/commands/slack_sync_data.py
34-34: Line too long (119 > 99)
(E501)
56-56: Abstract raise
to an inner function
(TRY301)
56-56: Avoid specifying long messages outside the exception class
(TRY003)
56-56: Exception must not use an f-string literal, assign to variable first
Assign to variable; remove f-string literal
(EM102)
57-57: Consider moving this statement to an else
block
(TRY300)
59-59: Magic value used in comparison, consider replacing 429
with a constant variable
(PLR2004)
61-61: Line too long (102 > 99)
(E501)
65-65: Use raise
without specifying exception name
Remove exception name
(TRY201)
69-69: Avoid specifying long messages outside the exception class
(TRY003)
69-69: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
97-97: Line too long (116 > 99)
(E501)
🔇 Additional comments (1)
backend/apps/slack/management/commands/slack_sync_data.py (1)
33-37
: One-token fallback can mis-sync multi-workspace installsUsing a single environment variable as a universal fallback means every workspace without a DB-stored token will be synchronised with the same bot token.
If more than one workspace exists, data leakage or API failures are possible.Consider either:
- aborting when a workspace token is missing, or
- accepting an env-var suffixed with the workspace ID / slug (
DJANGO_SLACK_BOT_TOKEN_<workspace_id>
).
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
backend/apps/slack/management/commands/slack_sync_data.py (2)
69-69
: RaisingSlackApiError
with{}
breaks downstream code
SlackApiError
expects a validSlackResponse
instance; passing an empty dict
will explode when any handler inspectse.response.status_code
.- raise SlackApiError("Max retries exceeded", {}) + raise RuntimeError("Max retries exceeded while calling Slack API")Consider defining a tiny
SlackSyncError
for clarity.
59-67
: Retry counter is incremented twice → premature “max-retries” and double sleep
retries
is bumped once inside the 429 branch (line 63) and again immediately after theexcept
block (line 66).
Result:
• The loop exits after ⌈MAX_RETRIES/2⌉ real attempts.
• A 429 response sleeps twice (rate-limit delay + exponential back-off) wasting time.- time.sleep(retry_after) - retries += 1 + time.sleep(retry_after) @@ - retries += 1 + retries += 1 # single, centralised increment
🧹 Nitpick comments (3)
backend/apps/slack/management/commands/slack_sync_data.py (1)
87-100
: Potential N + 1 API calls whennum_members
missing
conversations.info
is invoked per channel ifnum_members
is absent, causing
up to 848 extra calls in large workspaces. This will hit rate limits hard.Possible remedies:
- Cache member counts in memory and fetch only for new channels.
- Batch the calls via
conversations.info
in parallel workers with
concurrency control.- Persist
num_members
once and skip refresh unless thelast_updated
timestamp is stale.Not blocking, but worth revisiting.
backend/apps/slack/tests/test_slack_sync_data.py (2)
15-20
: Patch the exact symbol used in the command moduleThe command imports
WebClient
viafrom slack_sdk import WebClient
.
Patchingslack_sdk.web.client.WebClient
works today, but is brittle. Patch the
object inside the target module to guarantee isolation.- self.web_client_patcher = patch.object(slack_web_client, "WebClient", autospec=True) + self.web_client_patcher = patch( + "backend.apps.slack.management.commands.slack_sync_data.WebClient", + autospec=True, + )
70-78
: Prefer plainassert
over unittest’sassertEqual
for countsUsing
assert … == …
makes failures shorter and removes a dependency on
unittest
helpers when not needed.- self.assertEqual(Member.objects.count(), 3) - self.assertEqual(Conversation.objects.count(), 2) + assert Member.objects.count() == 3 + assert Conversation.objects.count() == 2
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/apps/slack/management/commands/slack_sync_data.py
(2 hunks)backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
(1 hunks)backend/apps/slack/tests/test_slack_sync_data.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/apps/slack/tests/test_slack_sync_data.py
1-1: File backend/apps/slack/tests/test_slack_sync_data.py
is part of an implicit namespace package. Add an __init__.py
.
(INP001)
31-31: Possible hardcoded password assigned to: "DJANGO_SLACK_BOT_TOKEN"
(S105)
57-57: Blank line contains whitespace
Remove whitespace from blank line
(W293)
72-72: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
73-73: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
74-74: Blank line contains whitespace
Remove whitespace from blank line
(W293)
76-76: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
77-77: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
78-78: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
backend/apps/slack/management/commands/slack_sync_data.py
34-34: Line too long (119 > 99)
(E501)
56-56: Abstract raise
to an inner function
(TRY301)
56-56: Avoid specifying long messages outside the exception class
(TRY003)
56-56: Exception must not use an f-string literal, assign to variable first
Assign to variable; remove f-string literal
(EM102)
57-57: Consider moving this statement to an else
block
(TRY300)
59-59: Magic value used in comparison, consider replacing 429
with a constant variable
(PLR2004)
61-61: Line too long (102 > 99)
(E501)
65-65: Use raise
without specifying exception name
Remove exception name
(TRY201)
69-69: Avoid specifying long messages outside the exception class
(TRY003)
69-69: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
97-97: Line too long (116 > 99)
(E501)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
🪛 Pylint (3.3.7)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py
[refactor] 6-6: Too few public methods (0/2)
(R0903)
🔇 Additional comments (1)
backend/apps/slack/migrations/0014_remove_workspace_total_members_count.py (1)
12-17
: Schema migration looks goodStraight-forward removal of an unused column; no further action required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (6)
backend/apps/slack/migrations/0017_workspace_total_members_count.py (1)
14-17
: ConsiderPositiveIntegerField
& DB index for faster aggregations
total_members_count
can never be negative. Usingmodels.PositiveIntegerField
communicates the constraint to both Django and the DB and may give you cheaper range checks.
If you expect to query / sort by this column (e.g. dashboards that show “largest workspace first”) adddb_index=True
now – adding it later requires another migration.- field=models.IntegerField(blank=True, null=True), + field=models.PositiveIntegerField(blank=True, null=True, db_index=True),backend/apps/slack/tests/test_slack_sync_data.py (3)
24-37
: Redundantimport os
shadows module – just reuse the top-level importThe second
import os
(line 36) creates a local symbol and triggers Ruff’s F823.
Delete it and keep using the module imported at the top of the file.- import os - os.environ["DJANGO_SLACK_BOT_TOKEN"] = "xoxb-test-token" + os.environ["DJANGO_SLACK_BOT_TOKEN"] = "xoxb-test-token"
85-95
: Duplicateconversations_list
side-effect – second assignment winsLines 73-77 already stub
conversations_list
.
The block at 85-94 overwrites it with slightly different data. Remove one of the two to avoid accidental test flakiness.
1-2
: Add__init__.py
to maketests
a packageRuff’s INP001 is correct – Django’s test discovery works, but tooling and IDEs treat implicit namespace packages poorly. Create an empty
__init__.py
next to this file.backend/apps/slack/management/commands/slack_sync_data.py (2)
5-8
: Remove unused / duplicate imports
random
is never used andtime
is imported twice. Clean-up keeps the file lint-free.-import random -import time +import time
65-71
: Use constant for HTTP 429 & preserve tracebackMinor polish:
- if e.response.status_code == 429: + if e.response.status_code == HTTPStatus.TOO_MANY_REQUESTS: # from http import HTTPStatusand use bare
raise
instead ofraise e
to keep the original traceback (line 72).
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/apps/slack/management/commands/slack_sync_data.py
(3 hunks)backend/apps/slack/migrations/0017_workspace_total_members_count.py
(1 hunks)backend/apps/slack/tests/test_slack_sync_data.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/apps/slack/tests/test_slack_sync_data.py
1-1: File backend/apps/slack/tests/test_slack_sync_data.py
is part of an implicit namespace package. Add an __init__.py
.
(INP001)
2-2: django.test.override_settings
imported but unused
Remove unused import: django.test.override_settings
(F401)
24-24: Local variable os
referenced before assignment
(F823)
37-37: Possible hardcoded password assigned to: "DJANGO_SLACK_BOT_TOKEN"
(S105)
43-43: Line too long (110 > 99)
(E501)
45-45: Line too long (106 > 99)
(E501)
78-78: Blank line contains whitespace
Remove whitespace from blank line
(W293)
97-97: Redefinition of unused tearDown
from line 38
(F811)
115-115: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
116-116: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
117-117: Blank line contains whitespace
Remove whitespace from blank line
(W293)
119-119: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
120-120: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
121-121: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
124-124: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
125-125: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
126-126: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
127-127: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
backend/apps/slack/management/commands/slack_sync_data.py
6-6: random
imported but unused
Remove unused import: random
(F401)
8-8: Redefinition of unused time
from line 4
Remove definition: time
(F811)
55-55: Avoid specifying long messages outside the exception class
(TRY003)
55-55: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
62-62: Abstract raise
to an inner function
(TRY301)
62-62: Avoid specifying long messages outside the exception class
(TRY003)
62-62: Exception must not use an f-string literal, assign to variable first
Assign to variable; remove f-string literal
(EM102)
63-63: Consider moving this statement to an else
block
(TRY300)
65-65: Magic value used in comparison, consider replacing 429
with a constant variable
(PLR2004)
77-77: Missing explicit return
at the end of function able to return non-None
value
Add explicit return
statement
(RET503)
107-107: Line too long (100 > 99)
(E501)
backend/apps/slack/migrations/0017_workspace_total_members_count.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
🪛 Pylint (3.3.7)
backend/apps/slack/tests/test_slack_sync_data.py
[refactor] 11-11: Too many instance attributes (14/7)
(R0902)
[error] 24-24: Using variable 'os' before assignment
(E0601)
[error] 97-97: method already defined line 38
(E0102)
backend/apps/slack/migrations/0017_workspace_total_members_count.py
[refactor] 6-6: Too few public methods (0/2)
(R0903)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
backend/apps/slack/tests/test_slack_sync_data.py (2)
96-104
: DuplicatetearDown
silently overrides the first definitionPython keeps only the last method definition; the earlier
tearDown
(lines 37-55) is ignored. After removing the misplaced logic (see previous comment) you must delete this duplicate to avoid confusion.- def tearDown(self): - self.app_patcher.stop() - self.web_client_patcher.stop() - self.env_patcher.stop() - self.conv_update_patch.stop() - self.conv_bulk_patch.stop() - self.member_update_patch.stop() - self.member_bulk_patch.stop()
37-55
:tearDown
is doing setup work and creates side-effects – move this logic tosetUp
Inside the first
tearDown
you:
- stop two patches (good) then immediately start four new patches and create another
Workspace
.- stub Slack API side-effects.
This reverses the normal test-lifecycle, leaves mocks active across tests, and silently hides the newly-started patches because a second
tearDown
later (lines 96-104) will shadow this one, so none of the cleanup actually runs.
Result: resource leaks & non-deterministic tests.Refactor by:
• Moving everypatch(...).start()
and the extraWorkspace
/API stubbing intosetUp
.
• Keeping a singletearDown
that only calls.stop()
on the patches started insetUp
.Example minimal fix:
@@ class SlackSyncDataCommandTests(TestCase): - def tearDown(self): - self.app_patcher.stop() - self.web_client_patcher.stop() - - # Patch model logic - self.conv_update_patch = patch("apps.slack.models.Conversation.update_data", return_value=MagicMock()) - self.conv_bulk_patch = patch("apps.slack.models.Conversation.bulk_save") - self.member_update_patch = patch("apps.slack.models.Member.update_data", return_value=MagicMock()) - self.member_bulk_patch = patch("apps.slack.models.Member.bulk_save") - - self.mock_conv_update = self.conv_update_patch.start() - self.mock_conv_bulk = self.conv_bulk_patch.start() - self.mock_member_update = self.member_update_patch.start() - self.mock_member_bulk = self.member_bulk_patch.start() - - # Create Workspace (bot_token will be fetched from env, not passed) - self.workspace = Workspace.objects.create(name="CI Test Workspace") + # move the 4 patches, Workspace creation and Slack side-effects to setUp() + def tearDown(self): + self.app_patcher.stop() + self.web_client_patcher.stop() + self.env_patcher.stop() + self.conv_update_patch.stop() + self.conv_bulk_patch.stop() + self.member_update_patch.stop() + self.member_bulk_patch.stop()
🧹 Nitpick comments (3)
backend/apps/slack/tests/test_slack_sync_data.py (3)
72-93
:conversations_list
is assigned then replaced by aside_effect
– pick oneLines 72-76 set a fixed return value, but lines 84-93 overwrite it with a
side_effect
. The first assignment is never used, which is misleading.Delete the dead code or merge both cases into a single
side_effect
list that covers all pages.
1-3
: Unused import & missing__init__.py
file
override_settings
is imported but never used. Also, the test directory lacks an__init__.py
, triggering Ruff INP001.-from django.test import TestCase, override_settings +from django.test import TestCaseAdd an empty
__init__.py
inbackend/apps/slack/tests/
so tooling recognises this as a package.
112-126
: Prefer plainassert
overunittest
helpers for simplicityRuff flags all
self.assertEqual/True
usages (PT009). Django’sSimpleTestCase
supports pytest-style asserts; switching shortens code and improves diff readability.- self.assertEqual(Member.objects.count(), 3) - self.assertEqual(Conversation.objects.count(), 2) + assert Member.objects.count() == 3 + assert Conversation.objects.count() == 2 ... - self.assertEqual(self.mock_web_client.users_list.call_count, 2) + assert self.mock_web_client.users_list.call_count == 2
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
backend/apps/slack/tests/test_slack_sync_data.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/apps/slack/tests/test_slack_sync_data.py
1-1: File backend/apps/slack/tests/test_slack_sync_data.py
is part of an implicit namespace package. Add an __init__.py
.
(INP001)
2-2: django.test.override_settings
imported but unused
Remove unused import: django.test.override_settings
(F401)
36-36: Possible hardcoded password assigned to: "DJANGO_SLACK_BOT_TOKEN"
(S105)
42-42: Line too long (110 > 99)
(E501)
44-44: Line too long (106 > 99)
(E501)
77-77: Blank line contains whitespace
Remove whitespace from blank line
(W293)
96-96: Redefinition of unused tearDown
from line 37
(F811)
114-114: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
115-115: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
116-116: Blank line contains whitespace
Remove whitespace from blank line
(W293)
118-118: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
119-119: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
120-120: Use a regular assert
instead of unittest-style assertEqual
Replace assertEqual(...)
with assert ...
(PT009)
123-123: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
124-124: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
125-125: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
126-126: Use a regular assert
instead of unittest-style assertTrue
Replace assertTrue(...)
with assert ...
(PT009)
🪛 Pylint (3.3.7)
backend/apps/slack/tests/test_slack_sync_data.py
[refactor] 11-11: Too many instance attributes (14/7)
(R0902)
[error] 96-96: method already defined line 37
(E0102)
🔇 Additional comments (1)
backend/apps/slack/tests/test_slack_sync_data.py (1)
24-36
: Environment variable names are inconsistentYou patch
SLACK_BOT_TOKEN
(line 24) but later rely onDJANGO_SLACK_BOT_TOKEN
(line 36).
If the management command looks for one specific key, the other is dead code; if it checks both, mocking twice is unnecessary. Pick one canonical env-var name and use it consistently.- self.env_patcher = patch.dict(os.environ, {"SLACK_BOT_TOKEN": "fake-token"}) + self.env_patcher = patch.dict(os.environ, {"DJANGO_SLACK_BOT_TOKEN": "fake-token"}) ... - os.environ["DJANGO_SLACK_BOT_TOKEN"] = "xoxb-test-token"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
backend/apps/slack/management/commands/slack_sync_data.py (1)
5-9
: Remove dead imports and the duplicatetime
binding
random
is never referenced andtime
is imported twice. Cleaning these up avoids lint noise and the accidental shadowing of the firsttime
reference.-import os -import random -import secrets -import time +import os +import secrets
🧹 Nitpick comments (2)
backend/apps/slack/management/commands/slack_sync_data.py (2)
78-110
: High API-call volume whennum_members
is absent
conversations.list
often omitsnum_members
, leading to an extra
conversations.info
per channel (≈ 848 extra requests in your test run).
Consider batching to respect rate limits:
- First, call
conversations.list
withinclude_num_members=True
(supported for public channels).- For the remainder, gather channel IDs and issue info calls in chunks, sleeping between batches.
This reduces overall latency and the likelihood of 429s.
140-145
: Minor readability nit – nested comprehensionThe assignment expression inside the generator is handy but hard to scan.
A straightforward loop is clearer and avoids the temporarymember
leak:for m in response["members"]: member_obj = Member.update_data(m, workspace) if member_obj: members.append(member_obj)
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
backend/apps/slack/management/commands/slack_sync_data.py
(3 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/apps/slack/management/commands/slack_sync_data.py
6-6: random
imported but unused
Remove unused import: random
(F401)
8-8: Redefinition of unused time
from line 4
Remove definition: time
(F811)
55-55: Avoid specifying long messages outside the exception class
(TRY003)
55-55: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
61-61: Avoid specifying long messages outside the exception class
(TRY003)
61-61: Exception must not use an f-string literal, assign to variable first
Assign to variable; remove f-string literal
(EM102)
62-62: Consider moving this statement to an else
block
(TRY300)
64-64: Magic value used in comparison, consider replacing 429
with a constant variable
(PLR2004)
76-76: Do not implicitly return None
in function able to return non-None
value
Add explicit None
return value
(RET502)
106-106: Line too long (100 > 99)
(E501)
🪛 Pylint (3.3.7)
backend/apps/slack/management/commands/slack_sync_data.py
[refactor] 53-53: Either all return statements in a function should return an expression, or none of them should.
(R1710)
|
Hi! Just a quick check-in — the PR has been updated and is ready for review. Thanks for your time and guidance! |
Resolves #1518
Hey team 👋
This PR fully implements syncing Slack channel member counts via conversations.info and integrates that data cleanly into our system.
I’ve tested it on a workspace with 848 channels and over 38,000+ members, and the sync now runs completely end-to-end — no missing data, no partial syncs, no crashes.
✅ What’s Done
✅ Integrated logic to fetch and store num_members for every Slack channel
✅ Slack rate limits handled cleanly via retry + exponential backoff
✅ Verified data is accurate and fully populated in the DB
✅ Unit test (test_slack_sync_data.py) using unittest.mock to fully simulate Slack responses — CI-ready
🧠 Notes
Handles workspaces with large user bases (~35K+) and deep pagination
No more rate-limit crashes — retry system is battle-tested
Test passed locally (took time, but flawless execution)
Ready for merge — CI/CD should pass
Thanks for the support!
— shdwcodrResolves #1518
Hey team 👋
This PR fully implements syncing Slack channel member counts via conversations.info and integrates that data cleanly into our system.
I’ve tested it on a workspace with 848 channels and over 38,000+ members, and the sync now runs completely end-to-end — no missing data, no partial syncs, no crashes.
✅ What’s Done
✅ Integrated logic to fetch and store num_members for every Slack channel
✅ Slack rate limits handled cleanly via retry + exponential backoff
✅ Verified data is accurate and fully populated in the DB
✅ Unit test (test_slack_sync_data.py) using unittest.mock to fully simulate Slack responses — CI-ready
🧠 Notes
Handles workspaces with large user bases (~35K+) and deep pagination
No more rate-limit crashes — retry system is battle-tested
Test passed locally (took time, but flawless execution)
Ready for merge — CI/CD should pass
Thanks for the support!
— shdwcodr