Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions src/app/components/signal-board/signal-board.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,17 @@
}

.jira-comment-section,
.github-comment-section {
.github-comment-section,
.slack-comment-section {
display: flex;
gap: 0.5rem;
width: 100%;
margin-bottom: 0.75rem;
}

.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;
Expand All @@ -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;
}

Expand Down
5 changes: 5 additions & 0 deletions src/app/components/signal-board/signal-board.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ <h3>{{ signal.title }}</h3>
<button type="button" class="ghost-btn" [disabled]="submittingGitHubComment[signal.id] || !githubComments[signal.id]" (click)="addGitHubComment(signal)">Comment</button>
</div>

<div class="slack-comment-section" *ngIf="signal.sourceType === 'slack'">
<input type="text" placeholder="Reply to thread..." [(ngModel)]="slackReplies[signal.id]" [disabled]="submittingSlackReply[signal.id]">
<button type="button" class="ghost-btn" [disabled]="submittingSlackReply[signal.id] || !slackReplies[signal.id]" (click)="sendSlackReply(signal)">Reply</button>
</div>

<div class="action-buttons-group">
<a class="link-btn" *ngIf="signal.url" [href]="signal.url" target="_blank" rel="noreferrer">{{ getOpenLabel(signal) }}</a>
<button type="button" class="ghost-btn" *ngIf="signal.sourceType === 'github'" (click)="toggleGitHubState(signal)">
Expand Down
27 changes: 27 additions & 0 deletions src/app/components/signal-board/signal-board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,31 @@ export class SignalBoardComponent {
}
});
}

slackReplies: { [signalId: string]: string } = {};
submittingSlackReply: { [signalId: string]: boolean } = {};

sendSlackReply(signal: Signal) {
const workspaceId = String(signal.workspaceId ?? '');
const channelId = String(signal.metadata['channel_id'] || signal.sourceId.split(':')[0]);
const threadTs = String(signal.metadata['ts'] || signal.externalId);
const text = this.slackReplies[signal.id];

if (!text || !text.trim() || !channelId || !threadTs) return;

this.submittingSlackReply[signal.id] = true;
this.integrationService.replyToSlack(workspaceId, channelId, threadTs, text).subscribe({
next: () => {
this.showToast('Reply sent to Slack thread');
this.slackReplies[signal.id] = '';
this.submittingSlackReply[signal.id] = false;
this.cdr.detectChanges();
},
error: () => {
this.showToast('Failed to send Slack reply');
this.submittingSlackReply[signal.id] = false;
this.cdr.detectChanges();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ <h3>Slack connection</h3>
<div class="card-actions">
<button *ngIf="!isSlackConnected" type="button" class="primary-btn slack-btn" (click)="connectSlack()">Connect Slack</button>
<button *ngIf="isSlackConnected" type="button" class="secondary-btn" (click)="disconnectSlack()">Disconnect</button>
<button *ngIf="isSlackConnected" type="button" class="primary-btn slack-btn" [disabled]="isSlackSyncing" (click)="syncSlackNow()">
{{ isSlackSyncing ? 'Syncing...' : 'Sync Now' }}
</button>
</div>
</article>

Expand Down Expand Up @@ -60,8 +63,12 @@ <h3>Channel selection</h3>

<section class="sync-card" *ngIf="isSlackConnected">
<h3>Slack sync status</h3>
<p *ngIf="slackLastSyncAt">Last channel update: {{ slackLastSyncAt | date: 'medium' }}</p>
<p *ngIf="slackLastSyncAt">Last sync: {{ slackLastSyncAt | date: 'medium' }}</p>
<p *ngIf="!slackLastSyncAt">No Slack sync has run yet.</p>
<p *ngIf="slackSyncStatus">
Current status:
<strong>{{ slackSyncStatus.status }}</strong>
</p>
</section>

<article class="integration-card">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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;
Expand All @@ -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 = '';
Expand Down
25 changes: 25 additions & 0 deletions src/app/services/integration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ export class IntegrationService {
);
}

syncSlack(workspaceId: string): Observable<SyncStatus> {
const params = new HttpParams().set('workspace_id', workspaceId);
return this.http.post<GitHubSyncResponse>(`${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<void> {
return this.getIntegrations(workspaceId).pipe(
switchMap((integrations) => {
Expand Down Expand Up @@ -365,4 +379,15 @@ export class IntegrationService {
}),
);
}

replyToSlack(workspaceId: string, channelId: string, threadTs: string, text: string): Observable<any> {
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.'))),
);
}
}
Loading