From e5ca2c88306c7968a1cada42d941ddc78907f29b Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 07:41:46 +0000 Subject: [PATCH 01/44] Merge migrations --- .../core/migrations/0029_merge_20250127_0741.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/gurubase-backend/backend/core/migrations/0029_merge_20250127_0741.py diff --git a/src/gurubase-backend/backend/core/migrations/0029_merge_20250127_0741.py b/src/gurubase-backend/backend/core/migrations/0029_merge_20250127_0741.py new file mode 100644 index 0000000..93d0746 --- /dev/null +++ b/src/gurubase-backend/backend/core/migrations/0029_merge_20250127_0741.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.13 on 2025-01-27 07:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0022_merge_0021_apikey_name_0021_merge_20250120_0950'), + ('core', '0028_alter_integration_unique_together'), + ] + + operations = [ + ] From 04ecc075d5755d9396262d25057e19ef3e463572 Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 13:18:27 +0300 Subject: [PATCH 02/44] Add tooltip to integration list --- .../Integrations/IntegrationTypesList.jsx | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/gurubase-frontend/src/components/Integrations/IntegrationTypesList.jsx b/src/gurubase-frontend/src/components/Integrations/IntegrationTypesList.jsx index f6e70f2..b4e7bff 100644 --- a/src/gurubase-frontend/src/components/Integrations/IntegrationTypesList.jsx +++ b/src/gurubase-frontend/src/components/Integrations/IntegrationTypesList.jsx @@ -15,6 +15,7 @@ import { getIntegrationsList } from "@/app/actions"; const IntegrationTypesList = ({ customGuru }) => { const [connectedIntegrations, setConnectedIntegrations] = useState([]); const [loading, setLoading] = useState(true); + const [hoveredIntegration, setHoveredIntegration] = useState(null); useEffect(() => { const fetchIntegrations = async () => { @@ -79,6 +80,10 @@ const IntegrationTypesList = ({ customGuru }) => { {integrationTypes.map((integration) => { const Icon = integration.icon; const isConnected = isIntegrationConnected(integration.type); + const workspaceName = connectedIntegrations.find( + (connectedIntegration) => + connectedIntegration.type === integration.type + )?.workspace_name; return ( { "bg-[#FDFDFD] hover:bg-gray-50 transition-colors" )}> {isConnected && ( -
+
+ setHoveredIntegration(integration.name) + } + onMouseLeave={() => setHoveredIntegration(null)}> + {hoveredIntegration === integration.name && ( +
+
+

+ Connected to {workspaceName} +

+
+ )}
)}
From fd0e3025d3bd1deee1b795701c6167f5c669d18b Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 13:23:57 +0300 Subject: [PATCH 03/44] Add modal for integration disconnect --- .../Integrations/IntegrationContent.jsx | 101 +++++++++++------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx index af35b30..e01449c 100644 --- a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx +++ b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx @@ -36,6 +36,13 @@ import { IntegrationInfo, IntegrationError } from "./IntegrationShared"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from "@/components/ui/modal-dialog.jsx"; const IntegrationContent = ({ type, customGuru, error }) => { const [integrationData, setIntegrationData] = useState(null); @@ -46,6 +53,7 @@ const IntegrationContent = ({ type, customGuru, error }) => { const [hasChanges, setHasChanges] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isDisconnecting, setIsDisconnecting] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); const integrationConfig = { slack: { @@ -165,46 +173,10 @@ const IntegrationContent = ({ type, customGuru, error }) => { />
- @@ -377,6 +349,59 @@ const IntegrationContent = ({ type, customGuru, error }) => { )}
+ + + +
+ + + Disconnect {name} + + + Are you sure you want to disconnect this integration? + + +
+ + +
+
+
+
); } From 5c9f2b7d8afb587ce78b74a3190e035e1a408c38 Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 11:08:49 +0000 Subject: [PATCH 04/44] Move access token revoke to signal --- src/gurubase-backend/backend/core/signals.py | 16 +++++++++++++++- src/gurubase-backend/backend/core/views.py | 13 +------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/gurubase-backend/backend/core/signals.py b/src/gurubase-backend/backend/core/signals.py index 84867e9..6edf6dd 100644 --- a/src/gurubase-backend/backend/core/signals.py +++ b/src/gurubase-backend/backend/core/signals.py @@ -818,4 +818,18 @@ def create_api_key_for_integration(sender, instance, **kwargs): @receiver(pre_delete, sender=Integration) def delete_api_key_for_integration(sender, instance, **kwargs): if instance.api_key: - instance.api_key.delete() \ No newline at end of file + instance.api_key.delete() + +@receiver(pre_delete, sender=Integration) +def revoke_integration_access_token(sender, instance, **kwargs): + """Revoke the OAuth access token when an integration is deleted.""" + try: + # Get the appropriate strategy for the integration type + from .integrations import IntegrationFactory + strategy = IntegrationFactory.get_strategy(instance.type, instance) + + # Attempt to revoke the access token + strategy.revoke_access_token() + except Exception as e: + logger.warning(f"Failed to revoke access token for integration {instance.id}: {e}", exc_info=True) + # Continue with deletion even if token revocation fails diff --git a/src/gurubase-backend/backend/core/views.py b/src/gurubase-backend/backend/core/views.py index 5f08dc8..8e29659 100644 --- a/src/gurubase-backend/backend/core/views.py +++ b/src/gurubase-backend/backend/core/views.py @@ -1803,19 +1803,8 @@ def manage_integration(request, guru_type, integration_type): 'date_updated': integration.date_updated, }) elif request.method == 'DELETE': - # Get the appropriate strategy for the integration type - strategy = IntegrationFactory.get_strategy(integration.type, integration) - - # Invalidate the OAuth token - try: - strategy.revoke_access_token() - except Exception as e: - logger.warning(f"Failed to revoke access token: {e}", exc_info=True) - # Continue with deletion even if token revocation fails - - # Delete the integration + # Delete the integration - token revocation is handled by signal integration.delete() - return Response({"encoded_guru_slug": encode_guru_slug(guru_type_object.slug)}, status=status.HTTP_202_ACCEPTED) except Integration.DoesNotExist: From 74d5856ee0f9e2cfc2acaa5cf5705e3f7d4943e0 Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 11:08:58 +0000 Subject: [PATCH 05/44] Add signal to manage channels on slack bot --- src/gurubase-backend/backend/core/signals.py | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/gurubase-backend/backend/core/signals.py b/src/gurubase-backend/backend/core/signals.py index 6edf6dd..5e73d7a 100644 --- a/src/gurubase-backend/backend/core/signals.py +++ b/src/gurubase-backend/backend/core/signals.py @@ -833,3 +833,69 @@ def revoke_integration_access_token(sender, instance, **kwargs): except Exception as e: logger.warning(f"Failed to revoke access token for integration {instance.id}: {e}", exc_info=True) # Continue with deletion even if token revocation fails + +@receiver(pre_save, sender=Integration) +def manage_slack_channels(sender, instance, **kwargs): + """Manage Slack channel membership when channels are updated.""" + if instance.type != Integration.Type.SLACK: + return + + try: + # Get the old instance if it exists + if instance.id: + old_instance = Integration.objects.get(id=instance.id) + old_channels = { + channel['id']: channel.get('allowed', False) + for channel in old_instance.channels + } + else: + old_channels = {} + + new_channels = { + channel['id']: channel.get('allowed', False) + for channel in instance.channels + } + + # Skip if no changes to channels + if old_channels == new_channels: + return + + from slack_sdk import WebClient + client = WebClient(token=instance.access_token) + + # Leave channels that are no longer allowed + channels_to_leave = [ + channel_id for channel_id, was_allowed in old_channels.items() + if was_allowed and ( + channel_id not in new_channels or # Channel removed + not new_channels[channel_id] # Channel no longer allowed + ) + ] + + # Join newly allowed channels + channels_to_join = [ + channel_id for channel_id, is_allowed in new_channels.items() + if is_allowed and ( + channel_id not in old_channels or # New channel + not old_channels[channel_id] # Previously not allowed + ) + ] + + # Leave channels + for channel_id in channels_to_leave: + try: + client.conversations_leave(channel=channel_id) + except Exception as e: + logger.warning(f"Failed to leave Slack channel {channel_id}: {e}", exc_info=True) + + # Join channels + for channel_id in channels_to_join: + try: + client.conversations_join(channel=channel_id) + except Exception as e: + logger.warning(f"Failed to join Slack channel {channel_id}: {e}", exc_info=True) + + except Integration.DoesNotExist: + pass # This is a new integration + except Exception as e: + logger.warning(f"Failed to manage Slack channels for integration {instance.id}: {e}", exc_info=True) \ No newline at end of file From 85cb77385f166e5ab399145139c1000dc351309b Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 14:12:48 +0300 Subject: [PATCH 06/44] Prevent test button clickability until save --- .../Integrations/IntegrationContent.jsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx index e01449c..ba8c7be 100644 --- a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx +++ b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx @@ -47,6 +47,7 @@ import { const IntegrationContent = ({ type, customGuru, error }) => { const [integrationData, setIntegrationData] = useState(null); const [channels, setChannels] = useState([]); + const [originalChannels, setOriginalChannels] = useState([]); const [internalError, setInternalError] = useState(null); const [loading, setLoading] = useState(true); const [channelsLoading, setChannelsLoading] = useState(true); @@ -61,7 +62,7 @@ const IntegrationContent = ({ type, customGuru, error }) => { description: "By connecting your account, you can easily share all your posts and invite your friends.", iconSize: "w-5 h-5", - url: `https://slack.com/oauth/v2/authorize?client_id=8327841447732.8318709976774&scope=channels:history,channels:join,channels:read,chat:write,groups:history,im:history,groups:read,mpim:read,im:read&user_scope=channels:history,chat:write,channels:read,groups:read,groups:history,im:history`, + url: `https://slack.com/oauth/v2/authorize?client_id=8327841447732.8318709976774&scope=app_mentions:read,channels:history,channels:join,channels:read,chat:write,groups:history,groups:read,groups:write,im:history,im:read,im:write,mpim:read,mpim:write,channels:manage&user_scope=channels:history,channels:read,chat:write,groups:history,groups:read,im:history,channels:write,groups:write,mpim:write,im:write`, icon: SlackIcon }, discord: { @@ -123,6 +124,7 @@ const IntegrationContent = ({ type, customGuru, error }) => { console.error("Failed to fetch channels:", channelsData.message); } else { setChannels(channelsData?.channels || []); + setOriginalChannels(channelsData?.channels || []); } } catch (err) { console.error("Failed to fetch channels:", err); @@ -216,7 +218,17 @@ const IntegrationContent = ({ type, customGuru, error }) => {
-
- + + Send Test Message + +
+ +
))} {/* Add New Channel */} {channels.filter((c) => !c.allowed).length > 0 && ( -
+
Date: Tue, 28 Jan 2025 00:06:35 +0300 Subject: [PATCH 23/44] Fix loading skeletons --- .../Integrations/IntegrationContent.jsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx index bddaf4a..3c14c91 100644 --- a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx +++ b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx @@ -144,8 +144,12 @@ const IntegrationContent = ({ type, customGuru, error }) => {
-
- +
+
); @@ -197,8 +201,12 @@ const IntegrationContent = ({ type, customGuru, error }) => {
{/* Allowed Channels */} {channelsLoading ? ( -
- +
+
) : ( <> From 242d6340dcff1677891d5a2f83965359cc0edf7f Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Mon, 27 Jan 2025 21:09:06 +0000 Subject: [PATCH 24/44] Order channel list --- src/gurubase-backend/backend/core/integrations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gurubase-backend/backend/core/integrations.py b/src/gurubase-backend/backend/core/integrations.py index d36d6d6..28b1f62 100644 --- a/src/gurubase-backend/backend/core/integrations.py +++ b/src/gurubase-backend/backend/core/integrations.py @@ -164,7 +164,7 @@ def _list_channels() -> list: for c in channels if c['type'] == 0 # 0 is text channel ] - return text_channels + return sorted(text_channels, key=lambda x: x['name']) return self.handle_api_call(_list_channels) @@ -277,7 +277,7 @@ def _list_channels() -> list: if not cursor: break - return channels + return sorted(channels, key=lambda x: x['name']) return self.handle_api_call(_list_channels) From 8e5e34109f7735e1dae6aa6620c31a86af5879fa Mon Sep 17 00:00:00 2001 From: Aral Yekta Date: Tue, 28 Jan 2025 09:59:47 +0300 Subject: [PATCH 25/44] Fix dropdowns --- .../src/components/Integrations/IntegrationContent.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx index 3c14c91..fae775e 100644 --- a/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx +++ b/src/gurubase-frontend/src/components/Integrations/IntegrationContent.jsx @@ -217,7 +217,7 @@ const IntegrationContent = ({ type, customGuru, error }) => {
-
+
Channel @@ -289,7 +289,7 @@ const IntegrationContent = ({ type, customGuru, error }) => { {channels.filter((c) => !c.allowed).length > 0 && (
-
+