feat: enhance Jira analytics with caching and force refresh

- Updated `getJiraAnalytics` to accept a `forceRefresh` parameter for optional cache bypass.
- Modified `getProjectAnalytics` to check the cache and return cached data unless forced to refresh.
- Adjusted `loadAnalytics` in `useJiraAnalytics` to trigger a forced refresh on manual updates.
- Improved UI in `JiraDashboardPageClient` to indicate when data is served from cache.
This commit is contained in:
Julien Froidefond
2025-09-18 22:28:34 +02:00
parent 5d73a6c279
commit 4c03ae946f
6 changed files with 309 additions and 26 deletions

View File

@@ -13,7 +13,7 @@ export type JiraAnalyticsResult = {
/**
* Server Action pour récupérer les analytics Jira du projet configuré
*/
export async function getJiraAnalytics(): Promise<JiraAnalyticsResult> {
export async function getJiraAnalytics(forceRefresh = false): Promise<JiraAnalyticsResult> {
try {
// Récupérer la config Jira depuis la base de données
const jiraConfig = await userPreferencesService.getJiraConfig();
@@ -40,8 +40,8 @@ export async function getJiraAnalytics(): Promise<JiraAnalyticsResult> {
projectKey: jiraConfig.projectKey
});
// Récupérer les analytics
const analytics = await analyticsService.getProjectAnalytics();
// Récupérer les analytics (avec cache ou actualisation forcée)
const analytics = await analyticsService.getProjectAnalytics(forceRefresh);
return {
success: true,

98
src/actions/jira-cache.ts Normal file
View File

@@ -0,0 +1,98 @@
'use server';
import { jiraAnalyticsCache } from '@/services/jira-analytics-cache';
import { userPreferencesService } from '@/services/user-preferences';
export type CacheStatsResult = {
success: boolean;
data?: {
totalEntries: number;
projects: Array<{ projectKey: string; age: string; size: number }>;
};
error?: string;
};
export type CacheActionResult = {
success: boolean;
message?: string;
error?: string;
};
/**
* Server Action pour récupérer les statistiques du cache
*/
export async function getJiraCacheStats(): Promise<CacheStatsResult> {
try {
const stats = jiraAnalyticsCache.getStats();
return {
success: true,
data: stats
};
} catch (error) {
console.error('❌ Erreur lors de la récupération des stats du cache:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
};
}
}
/**
* Server Action pour invalider le cache du projet configuré
*/
export async function invalidateJiraCache(): Promise<CacheActionResult> {
try {
// Récupérer la config Jira actuelle
const jiraConfig = await userPreferencesService.getJiraConfig();
if (!jiraConfig.enabled || !jiraConfig.baseUrl || !jiraConfig.email || !jiraConfig.apiToken || !jiraConfig.projectKey) {
return {
success: false,
error: 'Configuration Jira incomplète'
};
}
// Invalider le cache pour ce projet
jiraAnalyticsCache.invalidate({
baseUrl: jiraConfig.baseUrl,
email: jiraConfig.email,
apiToken: jiraConfig.apiToken,
projectKey: jiraConfig.projectKey
});
return {
success: true,
message: `Cache invalidé pour le projet ${jiraConfig.projectKey}`
};
} catch (error) {
console.error('❌ Erreur lors de l\'invalidation du cache:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
};
}
}
/**
* Server Action pour invalider tout le cache analytics
*/
export async function invalidateAllJiraCache(): Promise<CacheActionResult> {
try {
jiraAnalyticsCache.invalidateAll();
return {
success: true,
message: 'Tout le cache analytics a été invalidé'
};
} catch (error) {
console.error('❌ Erreur lors de l\'invalidation totale du cache:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
};
}
}

View File

@@ -154,13 +154,20 @@ export function JiraDashboardPageClient({ initialJiraConfig }: JiraDashboardPage
))}
</div>
<Button
onClick={refreshAnalytics}
disabled={isLoading}
variant="secondary"
>
{isLoading ? '🔄 Actualisation...' : '🔄 Actualiser'}
</Button>
<div className="flex items-center gap-2">
{analytics && (
<div className="text-xs text-[var(--muted-foreground)] px-2 py-1 bg-[var(--card)] border border-[var(--border)] rounded">
💾 Données en cache
</div>
)}
<Button
onClick={refreshAnalytics}
disabled={isLoading}
variant="secondary"
>
{isLoading ? '🔄 Actualisation...' : '🔄 Actualiser'}
</Button>
</div>
</div>
</div>