-
+
- {{ resource.name }}
- {{ resource.url }}
+ {{ project.key }} - {{ project.name }}
+ {{ project.siteName || project.siteUrl }}
diff --git a/src/app/components/workspace-integrations/workspace-integrations.spec.ts b/src/app/components/workspace-integrations/workspace-integrations.spec.ts
index 440a8b8..ff63e6c 100644
--- a/src/app/components/workspace-integrations/workspace-integrations.spec.ts
+++ b/src/app/components/workspace-integrations/workspace-integrations.spec.ts
@@ -55,7 +55,7 @@ describe('WorkspaceIntegrationsComponent', () => {
mockIntegrationService.getJiraProjects.and.returnValue(of({
connected: false,
- resources: [],
+ projects: [],
lastSyncAt: undefined
}));
mockIntegrationService.connectJira.and.returnValue(of(void 0));
@@ -70,7 +70,8 @@ describe('WorkspaceIntegrationsComponent', () => {
provide: ActivatedRoute,
useValue: {
snapshot: {
- paramMap: convertToParamMap({ id: 'workspace-1' })
+ paramMap: convertToParamMap({ id: 'workspace-1' }),
+ queryParamMap: convertToParamMap({})
}
}
}
diff --git a/src/app/components/workspace-integrations/workspace-integrations.ts b/src/app/components/workspace-integrations/workspace-integrations.ts
index 7762db3..08564ec 100644
--- a/src/app/components/workspace-integrations/workspace-integrations.ts
+++ b/src/app/components/workspace-integrations/workspace-integrations.ts
@@ -4,7 +4,7 @@ import { ActivatedRoute, RouterLink } from '@angular/router';
import { IntegrationService } from '../../services/integration.service';
import { GitHubRepo, SyncStatus } from '../../models/github-integration.model';
import { SlackChannel } from '../../models/slack-integration.model';
-import { AtlassianResource, JiraSyncStatus } from '../../models/jira-integration.model';
+import { JiraProject, JiraSyncStatus } from '../../models/jira-integration.model';
@Component({
selector: 'app-workspace-integrations',
@@ -41,7 +41,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
isSyncing = false;
isJiraConnected = false;
- jiraResources: AtlassianResource[] = [];
+ jiraProjects: JiraProject[] = [];
jiraLastSyncAt?: Date;
jiraSyncStatus?: JiraSyncStatus;
jiraFeedbackMessage = '';
@@ -49,7 +49,25 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
isJiraSyncing = false;
ngOnInit(): void {
- this.workspaceId = this.route.snapshot.paramMap.get('id') ?? '';
+ this.workspaceId = this.route.snapshot.paramMap.get('id') ||
+ this.route.parent?.snapshot.paramMap.get('id') || '';
+
+ // Check for OAuth redirect results
+ const queryParams = this.route.snapshot.queryParamMap;
+ const jiraStatus = queryParams.get('jira');
+ if (jiraStatus === 'connected') {
+ this.jiraFeedbackMessage = 'Jira connected successfully!';
+ } else if (jiraStatus === 'failed') {
+ this.jiraErrorMessage = 'Failed to connect Jira.';
+ }
+
+ const githubStatus = queryParams.get('github');
+ if (githubStatus === 'connected') {
+ this.githubFeedbackMessage = 'GitHub connected successfully!';
+ } else if (githubStatus === 'failed') {
+ this.githubErrorMessage = 'Failed to connect GitHub.';
+ }
+
this.loadAllIntegrations();
window.addEventListener('focus', this.onWindowFocus);
}
@@ -80,6 +98,9 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
this.slackErrorMessage = '';
this.slackFeedbackMessage = 'Starting Slack connection...';
this.integrationService.connectSlack(this.workspaceId).subscribe({
+ next: () => {
+ this.slackFeedbackMessage = '';
+ },
error: (error: Error) => {
this.slackErrorMessage = error.message;
this.slackFeedbackMessage = '';
@@ -98,6 +119,9 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
this.githubErrorMessage = '';
this.githubFeedbackMessage = 'Starting GitHub connection...';
this.integrationService.connectGitHub().subscribe({
+ next: () => {
+ this.githubFeedbackMessage = '';
+ },
error: (error: Error) => {
this.githubErrorMessage = error.message;
this.githubFeedbackMessage = '';
@@ -117,6 +141,9 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
this.jiraErrorMessage = '';
this.jiraFeedbackMessage = 'Starting Jira connection...';
this.integrationService.connectJira(this.workspaceId).subscribe({
+ next: () => {
+ this.jiraFeedbackMessage = '';
+ },
error: (error: Error) => {
this.jiraErrorMessage = error.message;
this.jiraFeedbackMessage = '';
@@ -262,7 +289,7 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy {
private loadJira(): void {
this.integrationService.getJiraProjects(this.workspaceId).subscribe(response => {
this.isJiraConnected = response.connected;
- this.jiraResources = response.resources;
+ this.jiraProjects = response.projects;
this.jiraLastSyncAt = response.lastSyncAt;
});
}
diff --git a/src/app/models/jira-integration.model.ts b/src/app/models/jira-integration.model.ts
index 7f398bf..2172f58 100644
--- a/src/app/models/jira-integration.model.ts
+++ b/src/app/models/jira-integration.model.ts
@@ -2,15 +2,10 @@ export interface JiraProject {
id: string;
key: string;
name: string;
- avatarUrl: string;
-}
-
-export interface AtlassianResource {
- id: string;
- url: string;
- name: string;
- scopes: string[];
- avatarUrl: string;
+ avatarUrl?: string;
+ siteId: string;
+ siteName: string;
+ siteUrl: string;
}
export interface JiraSyncStatus {
@@ -21,6 +16,6 @@ export interface JiraSyncStatus {
export interface JiraIntegrationResponse {
connected: boolean;
- resources: AtlassianResource[];
- lastSyncAt?: string;
+ projects: JiraProject[];
+ lastSyncAt?: Date;
}
diff --git a/src/app/services/integration.service.ts b/src/app/services/integration.service.ts
index f6aa728..9ebaf5b 100644
--- a/src/app/services/integration.service.ts
+++ b/src/app/services/integration.service.ts
@@ -1,7 +1,8 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
-import { catchError, map, Observable, of, switchMap, throwError } from 'rxjs';
+import { catchError, map, Observable, of, switchMap, tap, throwError } from 'rxjs';
import { GitHubConnectionState, GitHubRepo, SyncStatus } from '../models/github-integration.model';
+import { JiraIntegrationResponse, JiraProject, JiraSyncStatus } from '../models/jira-integration.model';
import { SlackConnectionState } from '../models/slack-integration.model';
import { toError } from './http-error';
@@ -37,6 +38,10 @@ interface GitHubSyncResponse {
status: string;
}
+interface JiraSyncResponse {
+ status: string;
+}
+
@Injectable({
providedIn: 'root',
})
@@ -56,6 +61,7 @@ export class IntegrationService {
return this.getSlackAuthUrl(workspaceId).pipe(
map(({ authUrl }) => {
window.open(authUrl, '_blank');
+ return;
}),
);
}
@@ -125,6 +131,7 @@ export class IntegrationService {
return this.getGitHubAuthUrl().pipe(
map(({ authUrl }) => {
window.open(authUrl, '_blank');
+ return;
}),
);
}
@@ -182,38 +189,48 @@ export class IntegrationService {
);
}
- getJiraAuthUrl(workspaceId: string): Observable<{ authUrl: string }> {
- const params = new HttpParams().set('workspace_id', workspaceId);
+ getJiraAuthUrl(workspaceId: string, redirectUrl?: string): Observable<{ authUrl: string }> {
+ console.log('Fetching Jira Auth URL for workspace:', workspaceId);
+ let params = new HttpParams().set('workspace_id', workspaceId);
+ if (redirectUrl) {
+ params = params.set('redirect_url', redirectUrl);
+ }
return this.http.get
(`${this.apiUrl}/jira/auth`, { params }).pipe(
+ tap(resp => console.log('Jira Auth URL response:', resp)),
map((response) => ({ authUrl: response.auth_url })),
- catchError((error) => throwError(() => toError(error, 'Unable to start Jira connection.'))),
+ catchError((error) => {
+ console.error('Jira Auth URL error:', error);
+ return throwError(() => toError(error, 'Unable to start Jira connection.'));
+ }),
);
}
connectJira(workspaceId: string): Observable {
- return this.getJiraAuthUrl(workspaceId).pipe(
+ const redirectUrl = window.location.origin + window.location.pathname;
+ return this.getJiraAuthUrl(workspaceId, redirectUrl).pipe(
map(({ authUrl }) => {
- window.open(authUrl, '_blank');
+ window.location.href = authUrl;
+ return;
}),
);
}
- getJiraProjects(workspaceId: string): Observable {
+ getJiraProjects(workspaceId: string): Observable {
return this.getIntegrations(workspaceId).pipe(
switchMap((integrations) => {
const jiraIntegration = integrations.find((integration) => integration.provider === 'jira');
if (!jiraIntegration) {
return of({
connected: false,
- resources: [],
+ projects: [],
});
}
const params = new HttpParams().set('workspace_id', workspaceId);
- return this.http.get(`${this.apiUrl}/jira/projects`, { params }).pipe(
- map((resources) => ({
+ return this.http.get(`${this.apiUrl}/jira/projects`, { params }).pipe(
+ map((projects) => ({
connected: true,
- resources: resources,
+ projects,
lastSyncAt: jiraIntegration.updated_at ? new Date(jiraIntegration.updated_at) : undefined,
})),
);
@@ -229,13 +246,13 @@ export class IntegrationService {
);
}
- syncJira(workspaceId: string): Observable {
+ syncJira(workspaceId: string): Observable {
const params = new HttpParams().set('workspace_id', workspaceId);
- return this.http.post(`${this.apiUrl}/jira/sync`, {}, { params }).pipe(
+ return this.http.post(`${this.apiUrl}/jira/sync`, {}, { params }).pipe(
map((response) => {
return {
status: response.status === 'sync_started' ? 'in_progress' : 'failed',
- };
+ } as JiraSyncStatus;
}),
catchError((error) => throwError(() => toError(error, 'Unable to sync Jira.'))),
);
diff --git a/src/app/services/signal.service.ts b/src/app/services/signal.service.ts
index cf221a7..4d8a94d 100644
--- a/src/app/services/signal.service.ts
+++ b/src/app/services/signal.service.ts
@@ -22,9 +22,16 @@ interface SignalResponse {
state?: 'open' | 'closed';
labels?: string[];
issueType?: string;
+ issue_type?: string;
priority?: string;
projectKey?: string;
+ project_key?: string;
+ issueKey?: string;
+ issue_key?: string;
+ assigneeName?: string;
+ assignee_name?: string;
assignees?: string[];
+ status?: string;
};
received_at?: string;
}
@@ -91,6 +98,19 @@ export class SignalService {
};
}
+ if (signal.source_type === 'jira') {
+ const metadata = signal.source_metadata ?? {};
+ return {
+ ...metadata,
+ issueType: metadata.issueType ?? metadata.issue_type,
+ projectKey: metadata.projectKey ?? metadata.project_key,
+ issueKey: metadata.issueKey ?? metadata.issue_key ?? signal.external_id,
+ assignees: metadata.assigneeName || metadata.assignee_name
+ ? [String(metadata.assigneeName ?? metadata.assignee_name)]
+ : [],
+ };
+ }
+
return {
...(signal.source_metadata ?? {}),
};