feat: enhance Jira filters and dashboard functionality
- Added new test scripts in `package.json` for story points and Jira fields validation. - Updated `JiraDashboardPageClient` to utilize raw analytics for filtering, improving data handling with active filters. - Introduced a loading state in `FilterBar` with visual feedback for filter application, enhancing user experience. - Refactored `useJiraFilters` to support local filtering based on initial analytics, streamlining filter management. - Enhanced `JiraAnalyticsService` to calculate story points based on issue types, improving accuracy in analytics.
This commit is contained in:
@@ -3,7 +3,7 @@ import { getAvailableJiraFilters, getFilteredJiraAnalytics } from '@/actions/jir
|
||||
import { AvailableFilters, JiraAnalyticsFilters, JiraAnalytics } from '@/lib/types';
|
||||
import { JiraAdvancedFiltersService } from '@/services/integrations/jira/advanced-filters';
|
||||
|
||||
export function useJiraFilters() {
|
||||
export function useJiraFilters(initialAnalytics?: JiraAnalytics | null) {
|
||||
const [availableFilters, setAvailableFilters] = useState<AvailableFilters>({
|
||||
components: [],
|
||||
fixVersions: [],
|
||||
@@ -20,11 +20,120 @@ export function useJiraFilters() {
|
||||
|
||||
const [filteredAnalytics, setFilteredAnalytics] = useState<JiraAnalytics | null>(null);
|
||||
const [isLoadingFilters, setIsLoadingFilters] = useState(false);
|
||||
const [isLoadingAnalytics, setIsLoadingAnalytics] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Extraire les filtres depuis les analytics existantes
|
||||
const extractFiltersFromAnalytics = useCallback((analytics: JiraAnalytics) => {
|
||||
// On peut extraire les filtres directement des métriques existantes
|
||||
const filters: AvailableFilters = {
|
||||
components: [],
|
||||
fixVersions: [],
|
||||
issueTypes: [],
|
||||
statuses: [],
|
||||
assignees: [],
|
||||
labels: [],
|
||||
priorities: []
|
||||
};
|
||||
|
||||
// Extraire les assignees depuis la distribution
|
||||
if (analytics.teamMetrics.issuesDistribution) {
|
||||
filters.assignees = analytics.teamMetrics.issuesDistribution.map(item => ({
|
||||
value: item.displayName,
|
||||
label: item.displayName,
|
||||
count: item.totalIssues
|
||||
}));
|
||||
}
|
||||
|
||||
// Extraire les statuts depuis workInProgress
|
||||
if (analytics.workInProgress.byStatus) {
|
||||
filters.statuses = analytics.workInProgress.byStatus.map(item => ({
|
||||
value: item.status,
|
||||
label: item.status,
|
||||
count: item.count
|
||||
}));
|
||||
}
|
||||
|
||||
// Extraire les types d'issues depuis cycleTimeByType
|
||||
if (analytics.cycleTimeMetrics.cycleTimeByType) {
|
||||
filters.issueTypes = analytics.cycleTimeMetrics.cycleTimeByType.map(item => ({
|
||||
value: item.issueType,
|
||||
label: item.issueType,
|
||||
count: item.samples
|
||||
}));
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, []);
|
||||
|
||||
// Filtrer les analytics localement
|
||||
const filterAnalyticsLocally = useCallback((analytics: JiraAnalytics, filters: Partial<JiraAnalyticsFilters>): JiraAnalytics => {
|
||||
// Filtrage simplifié basé sur les métriques disponibles
|
||||
let filteredAnalytics = { ...analytics };
|
||||
|
||||
// Filtrer par assignees
|
||||
if (filters.assignees && filters.assignees.length > 0) {
|
||||
const filteredDistribution = analytics.teamMetrics.issuesDistribution.filter(item =>
|
||||
filters.assignees!.includes(item.displayName)
|
||||
);
|
||||
|
||||
filteredAnalytics = {
|
||||
...filteredAnalytics,
|
||||
teamMetrics: {
|
||||
...filteredAnalytics.teamMetrics,
|
||||
issuesDistribution: filteredDistribution,
|
||||
totalAssignees: filteredDistribution.length,
|
||||
activeAssignees: filteredDistribution.filter(a => a.totalIssues > 0).length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Filtrer par statuts
|
||||
if (filters.statuses && filters.statuses.length > 0) {
|
||||
const filteredStatusDistribution = analytics.workInProgress.byStatus.filter(item =>
|
||||
filters.statuses!.includes(item.status)
|
||||
);
|
||||
|
||||
filteredAnalytics = {
|
||||
...filteredAnalytics,
|
||||
workInProgress: {
|
||||
...filteredAnalytics.workInProgress,
|
||||
byStatus: filteredStatusDistribution
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Filtrer par types d'issues
|
||||
if (filters.issueTypes && filters.issueTypes.length > 0) {
|
||||
const filteredCycleTime = analytics.cycleTimeMetrics.cycleTimeByType.filter(item =>
|
||||
filters.issueTypes!.includes(item.issueType)
|
||||
);
|
||||
|
||||
filteredAnalytics = {
|
||||
...filteredAnalytics,
|
||||
cycleTimeMetrics: {
|
||||
...filteredAnalytics.cycleTimeMetrics,
|
||||
cycleTimeByType: filteredCycleTime
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Recalculer le total des issues
|
||||
const totalIssues = filteredAnalytics.teamMetrics.issuesDistribution.reduce((sum, item) => sum + item.totalIssues, 0);
|
||||
filteredAnalytics.project.totalIssues = totalIssues;
|
||||
|
||||
return filteredAnalytics;
|
||||
}, []);
|
||||
|
||||
// Charger les filtres disponibles
|
||||
const loadAvailableFilters = useCallback(async () => {
|
||||
// Si on a déjà des analytics, extraire les filtres directement
|
||||
if (initialAnalytics) {
|
||||
const filters = extractFiltersFromAnalytics(initialAnalytics);
|
||||
setAvailableFilters(filters);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon, faire l'appel API
|
||||
setIsLoadingFilters(true);
|
||||
setError(null);
|
||||
|
||||
@@ -41,28 +150,28 @@ export function useJiraFilters() {
|
||||
} finally {
|
||||
setIsLoadingFilters(false);
|
||||
}
|
||||
}, []);
|
||||
}, [initialAnalytics, extractFiltersFromAnalytics]);
|
||||
|
||||
// Appliquer les filtres et récupérer les analytics filtrées
|
||||
const applyFilters = useCallback(async (filters: Partial<JiraAnalyticsFilters>) => {
|
||||
setIsLoadingAnalytics(true);
|
||||
setError(null);
|
||||
// Appliquer les filtres localement sur les analytics existantes
|
||||
const applyFilters = useCallback((filters: Partial<JiraAnalyticsFilters>) => {
|
||||
// Mettre à jour les filtres actifs immédiatement
|
||||
setActiveFilters(filters);
|
||||
|
||||
try {
|
||||
const result = await getFilteredJiraAnalytics(filters);
|
||||
|
||||
if (result.success && result.data) {
|
||||
setFilteredAnalytics(result.data);
|
||||
setActiveFilters(filters);
|
||||
} else {
|
||||
setError(result.error || 'Erreur lors du filtrage');
|
||||
}
|
||||
} catch {
|
||||
setError('Erreur de connexion');
|
||||
} finally {
|
||||
setIsLoadingAnalytics(false);
|
||||
// Si aucun filtre actif, effacer les analytics filtrées
|
||||
if (!JiraAdvancedFiltersService.hasActiveFilters(filters)) {
|
||||
setFilteredAnalytics(null);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Si on a des analytics initiales, les filtrer localement
|
||||
if (initialAnalytics) {
|
||||
// Pour le filtrage local, on simule le filtrage en modifiant les métriques
|
||||
// En réalité, on devrait avoir accès aux issues individuelles pour un vrai filtrage
|
||||
// Pour l'instant, on fait un filtrage simplifié sur les métriques disponibles
|
||||
const filteredAnalytics = filterAnalyticsLocally(initialAnalytics, filters);
|
||||
setFilteredAnalytics(filteredAnalytics);
|
||||
}
|
||||
}, [initialAnalytics]);
|
||||
|
||||
// Effacer tous les filtres
|
||||
const clearFilters = useCallback(() => {
|
||||
@@ -76,13 +185,20 @@ export function useJiraFilters() {
|
||||
loadAvailableFilters();
|
||||
}, [loadAvailableFilters]);
|
||||
|
||||
// Mettre à jour les filtres quand les analytics changent
|
||||
useEffect(() => {
|
||||
if (initialAnalytics) {
|
||||
const filters = extractFiltersFromAnalytics(initialAnalytics);
|
||||
setAvailableFilters(filters);
|
||||
}
|
||||
}, [initialAnalytics, extractFiltersFromAnalytics]);
|
||||
|
||||
return {
|
||||
// État
|
||||
availableFilters,
|
||||
activeFilters,
|
||||
filteredAnalytics,
|
||||
isLoadingFilters,
|
||||
isLoadingAnalytics,
|
||||
error,
|
||||
|
||||
// Actions
|
||||
|
||||
Reference in New Issue
Block a user