feat: add project key support for Jira analytics

- Introduced `projectKey` and `ignoredProjects` fields in Jira configuration to enhance analytics capabilities.
- Implemented project validation logic in `JiraConfigClient` and integrated it into the `JiraConfigForm` for user input.
- Updated `IntegrationsSettingsPageClient` to display analytics dashboard link based on the configured project key.
- Enhanced API routes to handle project key in Jira sync and user preferences.
- Marked related tasks as complete in `TODO.md`.
This commit is contained in:
Julien Froidefond
2025-09-18 22:08:29 +02:00
parent 4f9cff94f3
commit 78a96b9c92
22 changed files with 2240 additions and 35 deletions

View File

@@ -10,6 +10,7 @@ export interface JiraConfig {
baseUrl: string;
email: string;
apiToken: string;
projectKey?: string; // Clé du projet à surveiller pour les analytics d'équipe (ex: "MYTEAM")
ignoredProjects?: string[]; // Liste des clés de projets à ignorer (ex: ["DEMO", "TEST"])
}
@@ -39,12 +40,42 @@ export class JiraService {
this.config = config;
}
/**
* Valide l'existence d'un projet Jira
*/
async validateProject(projectKey: string): Promise<{ exists: boolean; name?: string; error?: string }> {
try {
const response = await this.makeJiraRequestPrivate(`/rest/api/3/project/${projectKey}`);
if (response.status === 404) {
return { exists: false, error: `Projet "${projectKey}" introuvable` };
}
if (!response.ok) {
const errorText = await response.text();
return { exists: false, error: `Erreur API: ${response.status} - ${errorText}` };
}
const project = await response.json();
return {
exists: true,
name: project.name
};
} catch (error) {
console.error('Erreur lors de la validation du projet:', error);
return {
exists: false,
error: error instanceof Error ? error.message : 'Erreur de connexion'
};
}
}
/**
* Teste la connexion à Jira
*/
async testConnection(): Promise<boolean> {
try {
const response = await this.makeJiraRequest('/rest/api/3/myself');
const response = await this.makeJiraRequestPrivate('/rest/api/3/myself');
if (!response.ok) {
console.error(`Test connexion Jira échoué: ${response.status} ${response.statusText}`);
const errorText = await response.text();
@@ -80,11 +111,10 @@ export class JiraService {
}
/**
* Récupère les tickets assignés à l'utilisateur connecté avec pagination
* Récupère les tickets avec une requête JQL personnalisée avec pagination
*/
async getAssignedIssues(): Promise<JiraTask[]> {
async searchIssues(jql: string): Promise<JiraTask[]> {
try {
const jql = 'assignee = currentUser() AND resolution = Unresolved AND issuetype != Epic ORDER BY updated DESC';
const fields = ['id', 'key', 'summary', 'description', 'status', 'priority', 'assignee', 'project', 'issuetype', 'duedate', 'created', 'updated', 'labels'];
const allIssues: unknown[] = [];
@@ -114,7 +144,7 @@ export class JiraService {
console.log(`🌐 POST /rest/api/3/search/jql avec ${nextPageToken ? 'nextPageToken' : 'première page'}`);
const response = await this.makeJiraRequest('/rest/api/3/search/jql', 'POST', requestBody);
const response = await this.makeJiraRequestPrivate('/rest/api/3/search/jql', 'POST', requestBody);
console.log(`📡 Status réponse: ${response.status}`);
@@ -174,6 +204,14 @@ export class JiraService {
}
}
/**
* Récupère les tickets assignés à l'utilisateur connecté
*/
async getAssignedIssues(): Promise<JiraTask[]> {
const jql = 'assignee = currentUser() AND resolution = Unresolved AND issuetype != Epic ORDER BY updated DESC';
return this.searchIssues(jql);
}
/**
* S'assure que le tag "🔗 From Jira" existe dans la base
*/
@@ -649,10 +687,17 @@ export class JiraService {
return priorityMapping[jiraPriority] || 'medium';
}
/**
* Effectue une requête à l'API Jira avec authentification (méthode publique pour analytics)
*/
async makeJiraRequest(endpoint: string, method: string = 'GET', body?: unknown): Promise<Response> {
return this.makeJiraRequestPrivate(endpoint, method, body);
}
/**
* Effectue une requête à l'API Jira avec authentification
*/
private async makeJiraRequest(endpoint: string, method: string = 'GET', body?: unknown): Promise<Response> {
private async makeJiraRequestPrivate(endpoint: string, method: string = 'GET', body?: unknown): Promise<Response> {
const url = `${this.config.baseUrl}${endpoint}`;
const auth = Buffer.from(`${this.config.email}:${this.config.apiToken}`).toString('base64');