From 513363df7918abda2293cb3bacff49f21ea737e3 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 28 Apr 2026 20:12:52 -0400 Subject: [PATCH] feat(slack): real-time signal UI and threaded replies (ref Sentinent-AI/Sentinent#30) --- .../components/signal-board/signal-board.css | 9 ++++--- .../components/signal-board/signal-board.html | 5 ++++ .../components/signal-board/signal-board.ts | 27 +++++++++++++++++++ .../workspace-integrations.html | 9 ++++++- .../workspace-integrations.ts | 22 +++++++++++++++ src/app/services/integration.service.ts | 25 +++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/app/components/signal-board/signal-board.css b/src/app/components/signal-board/signal-board.css index 9441de0..13e20b4 100644 --- a/src/app/components/signal-board/signal-board.css +++ b/src/app/components/signal-board/signal-board.css @@ -168,7 +168,8 @@ } .jira-comment-section, -.github-comment-section { +.github-comment-section, +.slack-comment-section { display: flex; gap: 0.5rem; width: 100%; @@ -176,7 +177,8 @@ } .jira-comment-section input, -.github-comment-section input { +.github-comment-section input, +.slack-comment-section input { flex: 1; border: 1px solid #cbd5e1; border-radius: 999px; @@ -187,7 +189,8 @@ } .jira-comment-section input:focus, -.github-comment-section input:focus { +.github-comment-section input:focus, +.slack-comment-section input:focus { border-color: #6366f1; } diff --git a/src/app/components/signal-board/signal-board.html b/src/app/components/signal-board/signal-board.html index c041f58..72ba03c 100644 --- a/src/app/components/signal-board/signal-board.html +++ b/src/app/components/signal-board/signal-board.html @@ -55,6 +55,11 @@

{{ signal.title }}

+
+ + +
+
{{ getOpenLabel(signal) }} +
@@ -60,8 +63,12 @@

Channel selection

Slack sync status

-

Last channel update: {{ slackLastSyncAt | date: 'medium' }}

+

Last sync: {{ slackLastSyncAt | date: 'medium' }}

No Slack sync has run yet.

+

+ Current status: + {{ slackSyncStatus.status }} +

diff --git a/src/app/components/workspace-integrations/workspace-integrations.ts b/src/app/components/workspace-integrations/workspace-integrations.ts index 1cd37ab..37ffdad 100644 --- a/src/app/components/workspace-integrations/workspace-integrations.ts +++ b/src/app/components/workspace-integrations/workspace-integrations.ts @@ -24,9 +24,11 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy { slackWorkspaceName = ''; slackWorkspaceUrl = ''; slackLastSyncAt?: Date; + slackSyncStatus?: SyncStatus; slackFeedbackMessage = ''; slackErrorMessage = ''; isSlackSaving = false; + isSlackSyncing = false; repos: GitHubRepo[] = []; selectedRepoIds: number[] = []; @@ -178,6 +180,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy { this.isSlackSaving = false; this.slackFeedbackMessage = 'Slack channel selection saved.'; this.loadSlackChannels(); + this.syncSlackNow(); // Trigger sync after saving selection }, error: () => { this.isSlackSaving = false; @@ -186,6 +189,25 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy { }); } + syncSlackNow(): void { + this.isSlackSyncing = true; + this.slackErrorMessage = ''; + this.integrationService.syncSlack(this.workspaceId).subscribe({ + next: (status) => { + this.slackSyncStatus = status; + this.isSlackSyncing = false; + this.slackFeedbackMessage = status.status === 'in_progress' + ? 'Slack sync started. Messages will refresh shortly.' + : 'Slack sync could not be started.'; + this.loadSlackChannels(); + }, + error: (error: Error) => { + this.isSlackSyncing = false; + this.slackErrorMessage = error.message; + } + }); + } + saveRepoSelection(): void { this.isGitHubSaving = true; this.githubErrorMessage = ''; diff --git a/src/app/services/integration.service.ts b/src/app/services/integration.service.ts index 923ce6d..10b6698 100644 --- a/src/app/services/integration.service.ts +++ b/src/app/services/integration.service.ts @@ -101,6 +101,20 @@ export class IntegrationService { ); } + syncSlack(workspaceId: string): Observable { + const params = new HttpParams().set('workspace_id', workspaceId); + return this.http.post(`${this.apiUrl}/slack/sync`, {}, { params }).pipe( + map((response) => { + const status: SyncStatus['status'] = response.status === 'sync_started' ? 'in_progress' : 'failed'; + return { + syncId: `sync-${Date.now()}`, + status, + }; + }), + catchError((error) => throwError(() => toError(error, 'Unable to sync Slack.'))), + ); + } + disconnectSlack(workspaceId: string): Observable { return this.getIntegrations(workspaceId).pipe( switchMap((integrations) => { @@ -365,4 +379,15 @@ export class IntegrationService { }), ); } + + replyToSlack(workspaceId: string, channelId: string, threadTs: string, text: string): Observable { + const params = new HttpParams().set('workspace_id', workspaceId); + return this.http.post(`${this.apiUrl}/integrations/slack/reply`, { + channel_id: channelId, + thread_ts: threadTs, + text: text + }, { params }).pipe( + catchError((error) => throwError(() => toError(error, 'Unable to send Slack reply.'))), + ); + } }