diff --git a/src/app/components/signal-board/signal-board.css b/src/app/components/signal-board/signal-board.css
index b0d2bd2..9441de0 100644
--- a/src/app/components/signal-board/signal-board.css
+++ b/src/app/components/signal-board/signal-board.css
@@ -167,14 +167,16 @@
pointer-events: none;
}
-.jira-comment-section {
+.jira-comment-section,
+.github-comment-section {
display: flex;
gap: 0.5rem;
width: 100%;
margin-bottom: 0.75rem;
}
-.jira-comment-section input {
+.jira-comment-section input,
+.github-comment-section input {
flex: 1;
border: 1px solid #cbd5e1;
border-radius: 999px;
@@ -184,7 +186,8 @@
transition: border-color 0.2s;
}
-.jira-comment-section input:focus {
+.jira-comment-section input:focus,
+.github-comment-section input:focus {
border-color: #6366f1;
}
@@ -194,6 +197,17 @@
flex-wrap: wrap;
}
+/* Badge variants */
+.badge.success {
+ background: #dcfce7;
+ color: #15803d;
+}
+
+.badge.danger {
+ background: #fee2e2;
+ color: #b91c1c;
+}
+
/* Toast */
.toast-container {
position: fixed;
diff --git a/src/app/components/signal-board/signal-board.html b/src/app/components/signal-board/signal-board.html
index 37fff06..c041f58 100644
--- a/src/app/components/signal-board/signal-board.html
+++ b/src/app/components/signal-board/signal-board.html
@@ -26,7 +26,11 @@
{{ signal.title }}
{{ signal.content }}
- {{ signal.metadata.state }}
+
+ {{ signal.metadata.state }}
+
#{{ getSlackChannel(signal) }}
{{ signal.metadata.issueType }}
{{ signal.metadata.priority }}
@@ -46,8 +50,16 @@
{{ signal.title }}
+
+
diff --git a/src/app/components/signal-board/signal-board.ts b/src/app/components/signal-board/signal-board.ts
index 1dfa39d..7dc7441 100644
--- a/src/app/components/signal-board/signal-board.ts
+++ b/src/app/components/signal-board/signal-board.ts
@@ -23,6 +23,8 @@ export class SignalBoardComponent {
loadingTransitions: { [signalId: string]: boolean } = {};
jiraComments: { [signalId: string]: string } = {};
submittingComment: { [signalId: string]: boolean } = {};
+ githubComments: { [signalId: string]: string } = {};
+ submittingGitHubComment: { [signalId: string]: boolean } = {};
toastMessage = '';
trackBySignal(_: number, signal: Signal): string {
@@ -148,4 +150,52 @@ export class SignalBoardComponent {
this.cdr.detectChanges();
}, 4000);
}
+
+ addGitHubComment(signal: Signal) {
+ const workspaceId = String(signal.workspaceId ?? '');
+ const repo = signal.metadata.repository;
+ const number = signal.metadata.number;
+ const comment = this.githubComments[signal.id];
+
+ if (!comment || !comment.trim() || !repo || !number) return;
+
+ this.submittingGitHubComment[signal.id] = true;
+ this.integrationService.addGitHubComment(workspaceId, repo, number, comment).subscribe({
+ next: () => {
+ this.showToast('Comment added to GitHub');
+ this.githubComments[signal.id] = '';
+ this.submittingGitHubComment[signal.id] = false;
+ this.cdr.detectChanges();
+ },
+ error: () => {
+ this.showToast('Failed to add GitHub comment');
+ this.submittingGitHubComment[signal.id] = false;
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ toggleGitHubState(signal: Signal) {
+ const workspaceId = String(signal.workspaceId ?? '');
+ const repo = signal.metadata.repository;
+ const number = signal.metadata.number;
+ const currentState = signal.metadata.state;
+
+ if (!repo || !number) return;
+
+ const newState = currentState === 'open' ? 'closed' : 'open';
+
+ this.integrationService.updateGitHubIssueState(workspaceId, repo, number, newState).subscribe({
+ next: () => {
+ this.showToast(`Issue ${newState === 'closed' ? 'closed' : 'reopened'}`);
+ if (signal.metadata) {
+ signal.metadata.state = newState;
+ }
+ this.cdr.detectChanges();
+ },
+ error: () => {
+ this.showToast('Failed to update GitHub status');
+ }
+ });
+ }
}
diff --git a/src/app/components/workspace-integrations/workspace-integrations.ts b/src/app/components/workspace-integrations/workspace-integrations.ts
index 5c2b98d..1cd37ab 100644
--- a/src/app/components/workspace-integrations/workspace-integrations.ts
+++ b/src/app/components/workspace-integrations/workspace-integrations.ts
@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
-import { ActivatedRoute, RouterLink } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
import { IntegrationService } from '../../services/integration.service';
import { GitHubRepo, SyncStatus } from '../../models/github-integration.model';
import { SlackChannel } from '../../models/slack-integration.model';
@@ -9,7 +9,7 @@ import { AtlassianResource, JiraSyncStatus } from '../../models/jira-integration
@Component({
selector: 'app-workspace-integrations',
standalone: true,
- imports: [CommonModule, RouterLink],
+ imports: [CommonModule],
templateUrl: './workspace-integrations.html',
styleUrl: './workspace-integrations.css'
})
@@ -108,7 +108,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
connectGitHub(): void {
this.githubErrorMessage = '';
this.githubFeedbackMessage = 'Starting GitHub connection...';
- this.integrationService.connectGitHub().subscribe({
+ this.integrationService.connectGitHub(this.workspaceId).subscribe({
error: (error: Error) => {
this.githubErrorMessage = error.message;
this.githubFeedbackMessage = '';
@@ -117,7 +117,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
}
disconnectGitHub(): void {
- this.integrationService.disconnectGitHub().subscribe(() => {
+ this.integrationService.disconnectGitHub(this.workspaceId).subscribe(() => {
this.githubSyncStatus = undefined;
this.githubFeedbackMessage = 'GitHub integration disconnected.';
this.loadRepos();
@@ -189,7 +189,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
saveRepoSelection(): void {
this.isGitHubSaving = true;
this.githubErrorMessage = '';
- this.integrationService.updateGitHubRepos(this.selectedRepoIds).subscribe({
+ this.integrationService.updateGitHubRepos(this.workspaceId, this.selectedRepoIds).subscribe({
next: () => {
this.isGitHubSaving = false;
this.githubFeedbackMessage = 'Repository selection saved.';
@@ -205,7 +205,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
syncNow(): void {
this.isSyncing = true;
this.githubErrorMessage = '';
- this.integrationService.syncGitHub().subscribe({
+ this.integrationService.syncGitHub(this.workspaceId).subscribe({
next: (status) => {
this.githubSyncStatus = status;
this.isSyncing = false;
@@ -260,7 +260,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
}
private loadRepos(): void {
- this.integrationService.getGitHubRepos().subscribe(response => {
+ this.integrationService.getGitHubRepos(this.workspaceId).subscribe(response => {
this.isGitHubConnected = response.connected;
this.repos = response.repos;
this.selectedRepoIds = response.repos.filter(repo => repo.isConnected).map(repo => repo.id);
diff --git a/src/app/components/workspace-members/workspace-members.ts b/src/app/components/workspace-members/workspace-members.ts
index 9d758ba..fb1ec57 100644
--- a/src/app/components/workspace-members/workspace-members.ts
+++ b/src/app/components/workspace-members/workspace-members.ts
@@ -1,14 +1,14 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { ActivatedRoute, RouterLink } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
import { Invitation, InvitationRole, WorkspaceMember, WorkspaceRole } from '../../models/workspace-member.model';
import { WorkspaceMemberService } from '../../services/workspace-member.service';
@Component({
selector: 'app-workspace-members',
standalone: true,
- imports: [CommonModule, FormsModule, RouterLink],
+ imports: [CommonModule, FormsModule],
templateUrl: './workspace-members.html',
styleUrl: './workspace-members.css'
})
diff --git a/src/app/services/integration.service.ts b/src/app/services/integration.service.ts
index 17cbd8a..923ce6d 100644
--- a/src/app/services/integration.service.ts
+++ b/src/app/services/integration.service.ts
@@ -114,23 +114,24 @@ export class IntegrationService {
);
}
- getGitHubAuthUrl(): Observable<{ authUrl: string }> {
- return this.http.get(`${this.apiUrl}/github/auth`).pipe(
+ getGitHubAuthUrl(workspaceId: string): Observable<{ authUrl: string }> {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.get(`${this.apiUrl}/github/auth`, { params }).pipe(
map((response) => ({ authUrl: response.auth_url })),
catchError((error) => throwError(() => toError(error, 'Unable to start GitHub connection.'))),
);
}
- connectGitHub(): Observable {
- return this.getGitHubAuthUrl().pipe(
+ connectGitHub(workspaceId: string): Observable {
+ return this.getGitHubAuthUrl(workspaceId).pipe(
map(({ authUrl }) => {
window.open(authUrl, '_blank');
}),
);
}
- getGitHubRepos(): Observable {
- return this.getIntegrations().pipe(
+ getGitHubRepos(workspaceId: string): Observable {
+ return this.getIntegrations(workspaceId).pipe(
switchMap((integrations) => {
const githubIntegration = integrations.find((integration) => integration.provider === 'github');
if (!githubIntegration) {
@@ -142,8 +143,9 @@ export class IntegrationService {
const metadata = this.parseMetadata(githubIntegration.metadata);
const selectedRepoIds = this.readNumberArray(metadata['selected_repo_ids']);
+ const params = new HttpParams().set('workspace_id', workspaceId);
- return this.http.get(`${this.apiUrl}/github/repos`).pipe(
+ return this.http.get(`${this.apiUrl}/github/repos`, { params }).pipe(
map((repos) => ({
connected: true,
repos: repos.map((repo) => this.mapGitHubRepo(repo, selectedRepoIds)),
@@ -157,20 +159,23 @@ export class IntegrationService {
);
}
- updateGitHubRepos(repoIds: number[]): Observable {
- return this.http.patch(`${this.apiUrl}/github/repos`, { repo_ids: repoIds }).pipe(
+ updateGitHubRepos(workspaceId: string, repoIds: number[]): Observable {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.patch(`${this.apiUrl}/github/repos`, { repo_ids: repoIds }, { params }).pipe(
catchError((error) => throwError(() => toError(error, 'Unable to save repository selection.'))),
);
}
- disconnectGitHub(): Observable {
- return this.http.delete(`${this.apiUrl}/github`).pipe(
+ disconnectGitHub(workspaceId: string): Observable {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.delete(`${this.apiUrl}/github`, { params }).pipe(
catchError((error) => throwError(() => toError(error, 'Unable to disconnect GitHub.'))),
);
}
- syncGitHub(): Observable {
- return this.http.post(`${this.apiUrl}/github/sync`, {}).pipe(
+ syncGitHub(workspaceId: string): Observable {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.post(`${this.apiUrl}/github/sync`, {}, { params }).pipe(
map((response) => {
const status: SyncStatus['status'] = response.status === 'sync_started' ? 'in_progress' : 'failed';
return {
@@ -182,6 +187,20 @@ export class IntegrationService {
);
}
+ addGitHubComment(workspaceId: string, repo: string, number: number, comment: string): Observable {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.post(`${this.apiUrl}/github/issues/${number}/comments`, { repo, body: comment }, { params }).pipe(
+ catchError((error) => throwError(() => toError(error, 'Unable to add GitHub comment.'))),
+ );
+ }
+
+ updateGitHubIssueState(workspaceId: string, repo: string, number: number, state: string): Observable {
+ const params = new HttpParams().set('workspace_id', workspaceId);
+ return this.http.patch(`${this.apiUrl}/github/issues/${number}/state`, { repo, state }, { params }).pipe(
+ catchError((error) => throwError(() => toError(error, 'Unable to update GitHub issue status.'))),
+ );
+ }
+
getJiraAuthUrl(workspaceId: string): Observable<{ authUrl: string }> {
const params = new HttpParams().set('workspace_id', workspaceId);
return this.http.get(`${this.apiUrl}/jira/auth`, { params }).pipe(