Repository: lightdash/lightdash
Pull request #14980: feat: delete AI agent slack integrations when removing Slack installation
Author: login: notgiorgi
Labels: name: released
Created: 2025-05-23T12:01:33Z | Merged: 2025-05-23T14:46:41Z

<!-- Thanks so much for your PR, your contribution is appreciated! ❤️ -->

Closes: #14934

### Description:

Added functionality to delete AI agent Slack integrations when a Slack installation is removed from an organization. This ensures that when a user disconnects Slack from their organization, all related AI agent Slack integrations are properly cleaned up as well.



Diff:
diff --git a/packages/backend/src/ee/index.ts b/packages/backend/src/ee/index.ts
index c6db915977fcc..b4aa11b484262 100644
--- a/packages/backend/src/ee/index.ts
+++ b/packages/backend/src/ee/index.ts
@@ -130,6 +130,7 @@ export async function getEnterpriseAppArguments(): Promise<EnterpriseAppArgument
                     slackAuthenticationModel:
                         models.getSlackAuthenticationModel() as CommercialSlackAuthenticationModel,
                     analytics: context.lightdashAnalytics,
+                    aiAgentModel: models.getAiAgentModel(),
                 }),
             supportService: ({ models, context, repository, clients }) =>
                 new SupportService({
diff --git a/packages/backend/src/ee/models/AiAgentModel.ts b/packages/backend/src/ee/models/AiAgentModel.ts
index 7895d87f14353..c75a5275cba71 100644
--- a/packages/backend/src/ee/models/AiAgentModel.ts
+++ b/packages/backend/src/ee/models/AiAgentModel.ts
@@ -507,4 +507,27 @@ export class AiAgentModel {
             return messages;
         });
     }
+
+    async deleteSlackIntegrations({
+        organizationUuid,
+    }: {
+        organizationUuid: string;
+    }): Promise<void> {
+        await this.database
+            .from(AiAgentIntegrationTableName)
+            .delete()
+            .where(
+                `${AiAgentIntegrationTableName}.ai_agent_integration_uuid`,
+                'IN',
+                this.database
+                    .from(AiAgentSlackIntegrationTableName)
+                    .select(
+                        `${AiAgentSlackIntegrationTableName}.ai_agent_integration_uuid`,
+                    )
+                    .where(
+                        `${AiAgentSlackIntegrationTableName}.organization_uuid`,
+                        organizationUuid,
+                    ),
+            );
+    }
 }
diff --git a/packages/backend/src/ee/services/CommercialSlackIntegrationService.ts b/packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
index 55511dcd698c5..7b9f1d615e755 100644
--- a/packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
+++ b/packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
@@ -1,8 +1,25 @@
 import { ForbiddenError, SessionUser, SlackSettings } from '@lightdash/common';
-import { SlackIntegrationService } from '../../services/SlackIntegrationService/SlackIntegrationService';
+import {
+    SlackIntegrationService,
+    SlackIntegrationServiceArguments,
+} from '../../services/SlackIntegrationService/SlackIntegrationService';
+import { AiAgentModel } from '../models/AiAgentModel';
 import { CommercialSlackAuthenticationModel } from '../models/CommercialSlackAuthenticationModel';
 
 export class CommercialSlackIntegrationService extends SlackIntegrationService<CommercialSlackAuthenticationModel> {
+    private readonly aiAgentModel: AiAgentModel;
+
+    constructor({
+        aiAgentModel,
+        ...rest
+    }: SlackIntegrationServiceArguments<CommercialSlackAuthenticationModel> & {
+        aiAgentModel: AiAgentModel;
+    }) {
+        super(rest);
+
+        this.aiAgentModel = aiAgentModel;
+    }
+
     async getInstallationFromOrganizationUuid(user: SessionUser) {
         const organizationUuid = user?.organizationUuid;
         if (!organizationUuid) throw new ForbiddenError();
@@ -26,4 +43,12 @@ export class CommercialSlackIntegrationService extends SlackIntegrationService<C
 
         return response;
     }
+
+    async deleteInstallationFromOrganizationUuid(user: SessionUser) {
+        await super.deleteInstallationFromOrganizationUuid(user);
+
+        await this.aiAgentModel.deleteSlackIntegrations({
+            organizationUuid: user.organizationUuid!,
+        });
+    }
 }
diff --git a/packages/backend/src/services/SlackIntegrationService/SlackIntegrationService.ts b/packages/backend/src/services/SlackIntegrationService/SlackIntegrationService.ts
index 7aaca7d404938..8a405cb45a49a 100644
--- a/packages/backend/src/services/SlackIntegrationService/SlackIntegrationService.ts
+++ b/packages/backend/src/services/SlackIntegrationService/SlackIntegrationService.ts
@@ -8,7 +8,7 @@ import { LightdashAnalytics } from '../../analytics/LightdashAnalytics';
 import { SlackAuthenticationModel } from '../../models/SlackAuthenticationModel';
 import { BaseService } from '../BaseService';
 
-type SlackIntegrationServiceArguments<
+export type SlackIntegrationServiceArguments<
     T extends SlackAuthenticationModel = SlackAuthenticationModel,
 > = {
     analytics: LightdashAnalytics;


Reviews:
user: login: joaoviana
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T12:54:33Z
user: login: notgiorgi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T12:55:37Z
user: login: joaoviana
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T12:56:10Z

Review comments:
diff_hunk: @@ -26,4 +43,12 @@ export class CommercialSlackIntegrationService extends SlackIntegrationService<C
 
         return response;
     }
+
+    async deleteInstallationFromOrganizationUuid(user: SessionUser) {
+        await super.deleteInstallationFromOrganizationUuid(user);
+
+        await this.aiAgentModel.deleteSlackIntegrations({
+            organizationUuid: user.organizationUuid!,
path: packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
user: login: joaoviana
body: I think this is safe, but maybe let's do a check if the org uuid doesn't exist?
created_at: 2025-05-23T12:54:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,4 +43,12 @@ export class CommercialSlackIntegrationService extends SlackIntegrationService<C
 
         return response;
     }
+
+    async deleteInstallationFromOrganizationUuid(user: SessionUser) {
+        await super.deleteInstallationFromOrganizationUuid(user);
+
+        await this.aiAgentModel.deleteSlackIntegrations({
+            organizationUuid: user.organizationUuid!,
path: packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
user: login: notgiorgi
body: went with the bang as `super.deleteInstallationFromOrganizationUuid` performs that check
created_at: 2025-05-23T12:55:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,4 +43,12 @@ export class CommercialSlackIntegrationService extends SlackIntegrationService<C
 
         return response;
     }
+
+    async deleteInstallationFromOrganizationUuid(user: SessionUser) {
+        await super.deleteInstallationFromOrganizationUuid(user);
+
+        await this.aiAgentModel.deleteSlackIntegrations({
+            organizationUuid: user.organizationUuid!,
path: packages/backend/src/ee/services/CommercialSlackIntegrationService.ts
user: login: joaoviana
body: ah nice!
created_at: 2025-05-23T12:56:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: notgiorgi
created_at: 2025-05-23T12:01:47Z
body: * **#14980** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14980?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14980?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#14958** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14958?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: CONTRIBUTOR
user: login: notgiorgi
created_at: 2025-05-23T14:46:29Z
body: ### Merge activity

* **May 23, 2:46 PM UTC**: A user started a stack merge that includes this pull request via [Graphite](https://app.graphite.dev/github/pr/lightdash/lightdash/14980).
* **May 23, 2:46 PM UTC**: @notgiorgi merged this pull request with [Graphite](https://app.graphite.dev/github/pr/lightdash/lightdash/14980).

author_association: CONTRIBUTOR
user: login: lightdash-bot
created_at: 2025-05-23T14:49:20Z
body: :tada: This PR is included in version 0.1647.0 :tada:

The release is available on:
- `0.1647.0`
- [GitHub release](https://github.com/lightdash/lightdash/releases/tag/0.1647.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: COLLABORATOR