Skip to content

Added Slack conversation list sync #1032

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

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4c68447
fixed
Yashgupta9330 Mar 6, 2025
5aebd16
Merge branch 'main' into sync
Yashgupta9330 Mar 7, 2025
1abba39
Merge branch 'OWASP:main' into sync
Yashgupta9330 Mar 7, 2025
cc0e09e
fixed linting error
Yashgupta9330 Mar 7, 2025
d1b1da7
Merge branch 'main' into sync
Yashgupta9330 Mar 7, 2025
1d78412
Merge branch 'main' into sync
Yashgupta9330 Mar 8, 2025
7cf6528
did some minor changes
Yashgupta9330 Mar 8, 2025
67e4ad0
Merge branch 'main' into sync
Yashgupta9330 Mar 8, 2025
7fb112f
pulled
Yashgupta9330 Mar 8, 2025
342b359
pulled
Yashgupta9330 Mar 8, 2025
28ab7dc
Merge branch 'main' into sync
Yashgupta9330 Mar 9, 2025
92a1ca4
deleted some migrations
Yashgupta9330 Mar 9, 2025
f9bbf17
pulled
Yashgupta9330 Mar 9, 2025
c27f386
Merge branch 'main' into sync
arkid15r Mar 11, 2025
3782aaf
Merge branch 'main' into sync
Yashgupta9330 Mar 12, 2025
c649a50
Refactor Slack conversation model and add sync command for Slack chan…
Yashgupta9330 Mar 12, 2025
4097bbd
Merge branch 'main' into sync
Yashgupta9330 Mar 12, 2025
a93b748
Merge branch 'main' into sync
Yashgupta9330 Mar 12, 2025
1424246
Merge branch 'main' into sync
Yashgupta9330 Mar 13, 2025
88963a9
did minor changes
Yashgupta9330 Mar 13, 2025
56d840b
Merge branch 'main' into sync
Yashgupta9330 Mar 13, 2025
aeb6f7d
Merge branch 'main' into sync
Yashgupta9330 Mar 13, 2025
55c04e8
Merge branch 'main' into sync
Yashgupta9330 Mar 13, 2025
cda007b
Merge branch 'main' into sync
Yashgupta9330 Mar 13, 2025
5dab8eb
Merge branch 'main' into sync
Yashgupta9330 Mar 14, 2025
3b96cfa
Merge branch 'main' into sync
Yashgupta9330 Mar 15, 2025
90ae352
Merge branch 'OWASP:main' into sync
Yashgupta9330 Mar 16, 2025
c5ee59d
Merge branch 'main' into sync
Yashgupta9330 Mar 17, 2025
8176ba7
Merge branch 'main' into pr/Yashgupta9330/1032
arkid15r Mar 17, 2025
bc0857d
Update code
arkid15r Mar 17, 2025
0d65b9e
Merge branch 'main' into sync
Yashgupta9330 Mar 18, 2025
6bd79a0
Merge branch 'main' into sync
Yashgupta9330 Mar 18, 2025
62cbe49
added test
Yashgupta9330 Mar 18, 2025
421aa7f
Merge branch 'main' into sync
Yashgupta9330 Mar 19, 2025
292a32f
Merge branch 'main' into sync
Yashgupta9330 Mar 26, 2025
b9bcfe2
Merge branch 'main' into sync
Yashgupta9330 Mar 26, 2025
016ecfe
Merge branch 'main' into sync
Yashgupta9330 Mar 30, 2025
be5576f
Merge branch 'main' into sync
Yashgupta9330 Mar 31, 2025
5079694
Merge branch 'main' into sync
Yashgupta9330 Apr 1, 2025
c0112e9
Merge branch 'main' into sync
Yashgupta9330 Apr 8, 2025
c347926
Merge branch 'main' into sync
Yashgupta9330 Apr 18, 2025
703acc3
added
Yashgupta9330 Apr 18, 2025
c7eae97
Merge branch 'main' into sync
Yashgupta9330 Apr 20, 2025
c2936c8
Merge branch 'main' into sync
Yashgupta9330 Apr 20, 2025
77e96cc
Merge branch 'main' into sync
Yashgupta9330 Apr 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ setup:
shell-backend:
@CMD="/bin/sh" $(MAKE) exec-backend-command-it

slack-sync-conversation-list:
@echo "Syncing Slack conversation list"
@CMD="python manage.py slack_sync_conversation_list" $(MAKE) exec-backend-command

sync-data: \
update-data \
enrich-data \
Expand Down
65 changes: 65 additions & 0 deletions backend/apps/slack/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,73 @@

from django.contrib import admin

from apps.slack.models.conversation import Conversation
from apps.slack.models.event import Event


class ConversationAdmin(admin.ModelAdmin):
"""Admin configuration for Conversation model."""

list_display = (
"name",
"entity_id",
"created_at",
"is_private",
"is_archived",
"is_general",
)
search_fields = (
"name",
"topic",
"purpose",
"entity_id",
"creator_id",
)
list_filter = (
"created_at",
"is_private",
"is_archived",
"is_general",
)
readonly_fields = (
"entity_id",
"created_at",
"creator_id",
)
fieldsets = (
(
"Conversation Information",
{
"fields": (
"entity_id",
"name",
"created_at",
"creator_id",
)
},
),
(
"Properties",
{
"fields": (
"is_private",
"is_archived",
"is_general",
)
},
),
(
"Content",
{
"fields": (
"topic",
"purpose",
)
},
),
)


class EventAdmin(admin.ModelAdmin):
search_fields = (
"channel_id",
Expand All @@ -16,4 +80,5 @@ class EventAdmin(admin.ModelAdmin):
list_filter = ("trigger",)


admin.site.register(Conversation, ConversationAdmin)
admin.site.register(Event, EventAdmin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""Command to sync Slack channels and groups from the OWASP Slack workspace."""

import logging
import time

from django.core.management.base import BaseCommand
from slack_sdk.errors import SlackApiError

from apps.slack.apps import SlackConfig
from apps.slack.models.conversation import Conversation

logger = logging.getLogger(__name__)


class Command(BaseCommand):
"""Django command to synchronize Slack conversations."""

help = "Sync Slack channels and groups from the OWASP Slack workspace."

def add_arguments(self, parser):
"""Define command line arguments."""
parser.add_argument(
"--batch-size",
type=int,
default=200,
help="Number of conversations to retrieve per request",
)
parser.add_argument(
"--delay",
type=float,
default=0.1,
help="Delay between API requests in seconds",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Fetch conversations but don't update the database",
)

def handle(self, *args, **options):
"""Execute the command."""
# Parse command arguments
batch_size = options["batch_size"]
delay = options["delay"]
dry_run = options.get("dry_run", False)

if dry_run:
self.stdout.write(
self.style.WARNING("Running in dry-run mode - no database changes will be made")
)

try:
app = SlackConfig.app
if not app:
logger.error("Slack app is not configured properly")
self.stdout.write(self.style.ERROR("Slack app is not configured properly"))
return

all_conversations = self._fetch_all_conversations(app, batch_size, delay)

if not dry_run and all_conversations:
self.stdout.write(f"Saving {len(all_conversations)} conversations to database...")
conversations = []
cnt = 0
for conversation_data in all_conversations:
conversation = Conversation.update_data(conversation_data, save=False)
if conversation:
conversations.append(conversation)
cnt += 1
else:
self.stdout.write(
self.style.WARNING(
f"Failed to process conversation: {conversation_data.get('id')}"
)
)

if conversations:
Conversation.bulk_save(conversations)

self.stdout.write(
self.style.SUCCESS(f"Successfully synced {cnt} Slack conversations")
)
else:
self.stdout.write(
self.style.SUCCESS(
f"Processed {len(all_conversations)} conversations (dry run)"
)
)

except SlackApiError as e:
error_msg = f"Error calling Slack API: {e}"
logger.exception(error_msg)
self.stdout.write(self.style.ERROR(error_msg))
except Exception as e:
error_msg = f"Failed to sync Slack conversations: {e}"
logger.exception(error_msg)
self.stdout.write(self.style.ERROR(error_msg))

def _fetch_all_conversations(self, app, batch_size, delay):
"""Fetch all conversations from Slack API.

Args:
----
app: Slack app instance
batch_size: Number of conversations to retrieve per request
delay: Delay between API requests in seconds

Returns:
-------
List of conversation data

"""
all_conversations = []
next_cursor = None
total_processed = 0

self.stdout.write("Fetching conversations from Slack API...")

while True:
# Fetch batch of conversations
result = app.client.conversations_list(
limit=batch_size,
exclude_archived=False,
types="public_channel,private_channel",
cursor=next_cursor or None,
timeout=30,
)

if not result.get("ok"):
error = result.get("error", "Unknown error")
logger.exception("Slack API error: %s", error)
self.stdout.write(self.style.ERROR(f"Slack API error: {error}"))
break

batch_conversations = result.get("channels", [])
total_processed += len(batch_conversations)

# Display progress
self.stdout.write(f"Processed {total_processed} conversations...")

# Collect conversations
if batch_conversations:
all_conversations.extend(batch_conversations)

# Handle pagination
next_cursor = result.get("response_metadata", {}).get("next_cursor")
if not next_cursor:
break

# Rate limiting protection
if delay > 0:
time.sleep(delay)

self.stdout.write(f"Total conversations retrieved: {len(all_conversations)}")
return all_conversations
38 changes: 38 additions & 0 deletions backend/apps/slack/migrations/0004_conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 5.1.7 on 2025-03-08 19:24

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("slack", "0003_remove_event_command"),
]

operations = [
migrations.CreateModel(
name="Conversation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("nest_created_at", models.DateTimeField(auto_now_add=True)),
("nest_updated_at", models.DateTimeField(auto_now=True)),
("entity_id", models.CharField(max_length=255, unique=True)),
("name", models.CharField(max_length=255)),
("created_at", models.DateTimeField(blank=True, null=True)),
("is_private", models.BooleanField(default=False)),
("is_archived", models.BooleanField(default=False)),
("is_general", models.BooleanField(default=False)),
("topic", models.TextField(blank=True, default="")),
("purpose", models.TextField(blank=True, default="")),
("creator_id", models.CharField(max_length=255)),
],
options={
"verbose_name_plural": "Conversations",
"db_table": "slack_conversations",
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 5.1.7 on 2025-03-12 14:33

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("slack", "0004_conversation"),
]

operations = [
migrations.AlterField(
model_name="conversation",
name="created_at",
field=models.DateTimeField(blank=True, null=True, verbose_name="Created At"),
),
migrations.AlterField(
model_name="conversation",
name="creator_id",
field=models.CharField(max_length=255, verbose_name="Creator ID"),
),
migrations.AlterField(
model_name="conversation",
name="entity_id",
field=models.CharField(max_length=255, unique=True, verbose_name="Entity ID"),
),
migrations.AlterField(
model_name="conversation",
name="is_archived",
field=models.BooleanField(default=False, verbose_name="Is Archived"),
),
migrations.AlterField(
model_name="conversation",
name="is_general",
field=models.BooleanField(default=False, verbose_name="Is General"),
),
migrations.AlterField(
model_name="conversation",
name="is_private",
field=models.BooleanField(default=False, verbose_name="Is Private"),
),
migrations.AlterField(
model_name="conversation",
name="name",
field=models.CharField(max_length=255, verbose_name="Name"),
),
migrations.AlterField(
model_name="conversation",
name="purpose",
field=models.TextField(blank=True, default="", verbose_name="Purpose"),
),
migrations.AlterField(
model_name="conversation",
name="topic",
field=models.TextField(blank=True, default="", verbose_name="Topic"),
),
]
Loading