feat: add maxSyncPeriod configuration to TFS settings

- Introduced maxSyncPeriod option in TfsConfigForm for user-defined synchronization duration.
- Updated TfsService to filter pull requests based on the configured maxSyncPeriod.
- Enhanced TfsPullRequest type to include 'rejected' status for better PR management.
- Set default maxSyncPeriod to '90d' in user preferences and TFS configuration.
This commit is contained in:
Julien Froidefond
2025-10-03 09:06:24 +02:00
parent 7900ba3b73
commit c84ee86ed4
4 changed files with 92 additions and 15 deletions

View File

@@ -15,6 +15,7 @@ export function TfsConfigForm() {
personalAccessToken: '', personalAccessToken: '',
repositories: [], repositories: [],
ignoredRepositories: [], ignoredRepositories: [],
maxSyncPeriod: '90d',
}); });
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const [message, setMessage] = useState<{ const [message, setMessage] = useState<{
@@ -94,13 +95,14 @@ export function TfsConfigForm() {
startTransition(async () => { startTransition(async () => {
setMessage(null); setMessage(null);
// Réinitialiser la config // Réinitialiser la config
const resetConfig = { const resetConfig: TfsConfig = {
enabled: false, enabled: false,
organizationUrl: '', organizationUrl: '',
projectName: '', projectName: '',
personalAccessToken: '', personalAccessToken: '',
repositories: [], repositories: [],
ignoredRepositories: [], ignoredRepositories: [],
maxSyncPeriod: '90d',
}; };
const result = await saveTfsConfig(resetConfig); const result = await saveTfsConfig(resetConfig);
@@ -275,6 +277,14 @@ export function TfsConfigForm() {
<span className="text-xs">Aucun</span> <span className="text-xs">Aucun</span>
)} )}
</div> </div>
<div>
<span className="text-[var(--muted-foreground)]">
Période max de sync:
</span>{' '}
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
{config?.maxSyncPeriod || '90d'}
</code>
</div>
</div> </div>
</div> </div>
)} )}
@@ -475,6 +485,29 @@ export function TfsConfigForm() {
</div> </div>
)} )}
</div> </div>
<div>
<label className="block text-sm font-medium mb-2">
Période maximale de synchronisation
</label>
<select
value={config.maxSyncPeriod || '90d'}
onChange={(e) => updateConfig('maxSyncPeriod', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent"
>
<option value="7d">7 jours</option>
<option value="30d">30 jours</option>
<option value="90d">90 jours (recommandé)</option>
<option value="180d">180 jours</option>
<option value="1y">1 an</option>
<option value="2y">2 ans</option>
<option value="3y">3 ans</option>
</select>
<p className="text-xs text-[var(--muted-foreground)] mt-1">
Définit la période maximale pour récupérer les Pull Requests.
Les PRs plus anciennes seront ignorées lors de la synchronisation.
</p>
</div>
</> </>
)} )}

View File

@@ -221,7 +221,7 @@ export interface TfsPullRequest {
pullRequestId: number; pullRequestId: number;
title: string; title: string;
description?: string; description?: string;
status: 'active' | 'completed' | 'abandoned'; status: 'active' | 'completed' | 'abandoned' | 'rejected';
createdBy: { createdBy: {
displayName: string; displayName: string;
uniqueName: string; // email uniqueName: string; // email

View File

@@ -53,6 +53,7 @@ const DEFAULT_PREFERENCES: UserPreferences = {
personalAccessToken: '', personalAccessToken: '',
repositories: [], repositories: [],
ignoredRepositories: [], ignoredRepositories: [],
maxSyncPeriod: '90d', // Par défaut 90 jours
}, },
tfsAutoSync: false, tfsAutoSync: false,
tfsSyncInterval: 'daily', tfsSyncInterval: 'daily',

View File

@@ -16,6 +16,7 @@ export interface TfsConfig {
personalAccessToken?: string; personalAccessToken?: string;
repositories?: string[]; // Liste des repos à surveiller repositories?: string[]; // Liste des repos à surveiller
ignoredRepositories?: string[]; // Liste des repos à ignorer ignoredRepositories?: string[]; // Liste des repos à ignorer
maxSyncPeriod?: '7d' | '30d' | '90d' | '180d' | '1y' | '2y' | '3y'; // Période maximale de synchronisation
} }
export interface TfsSyncAction { export interface TfsSyncAction {
@@ -375,10 +376,10 @@ export class TfsService {
/** /**
* Filtre les PRs par statut pertinent * Filtre les PRs par statut pertinent
* - Garde toutes les PRs actives créées dans les 90 derniers jours * - Garde toutes les PRs actives créées dans la période configurée
* - Garde les PRs completed récentes (moins de 30 jours) * - Garde les PRs completed récentes (moins de 30 jours)
* - Exclut les PRs abandoned * - Exclut les PRs abandoned et rejected
* - Exclut les PRs trop anciennes * - Exclut les PRs trop anciennes selon la configuration
* - Exclut les PRs automatiques (Renovate, etc.) * - Exclut les PRs automatiques (Renovate, etc.)
*/ */
private filterByRelevantStatus( private filterByRelevantStatus(
@@ -387,6 +388,11 @@ export class TfsService {
const now = new Date(); const now = new Date();
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
// Calculer la période maximale selon la configuration
const maxSyncPeriod = this.config.maxSyncPeriod || '90d';
const maxSyncPeriodMs = this.getMaxSyncPeriodMs(maxSyncPeriod);
const maxSyncDate = new Date(now.getTime() - maxSyncPeriodMs);
return pullRequests.filter((pr) => { return pullRequests.filter((pr) => {
// Exclure les PRs automatiques (Renovate, Dependabot, etc.) // Exclure les PRs automatiques (Renovate, Dependabot, etc.)
if (this.isAutomaticPR(pr)) { if (this.isAutomaticPR(pr)) {
@@ -396,20 +402,20 @@ export class TfsService {
return false; return false;
} }
// Filtrer d'abord par âge - exclure les PRs trop anciennes // Filtrer par âge selon la configuration
// const createdDate = parseDate(pr.creationDate); const createdDate = parseDate(pr.creationDate);
// if (createdDate < ninetyDaysAgo) { if (createdDate < maxSyncDate) {
// console.log( console.log(
// `🗺 PR ${pr.pullRequestId} (${pr.title}): Trop ancienne (${formatDateForDisplay(createdDate)}) - EXCLUE` `🗺 PR ${pr.pullRequestId} (${pr.title}): Trop ancienne (${formatDateForDisplay(createdDate)}, limite: ${maxSyncPeriod}) - EXCLUE`
// ); );
// return false; return false;
// } }
switch (pr.status.toLowerCase()) { switch (pr.status.toLowerCase()) {
case 'active': case 'active':
// PRs actives récentes // PRs actives dans la période configurée
console.log( console.log(
`✅ PR ${pr.pullRequestId} (${pr.title}): Active récente - INCLUSE` `✅ PR ${pr.pullRequestId} (${pr.title}): Active récente (${maxSyncPeriod}) - INCLUSE`
); );
return true; return true;
@@ -426,15 +432,52 @@ export class TfsService {
case 'abandoned': case 'abandoned':
// PRs abandonnées ne sont pas pertinentes // PRs abandonnées ne sont pas pertinentes
console.log(
`❌ PR ${pr.pullRequestId} (${pr.title}): Abandonnée - EXCLUE`
);
return false;
case 'rejected':
// PRs rejetées ne sont pas pertinentes
console.log(
`❌ PR ${pr.pullRequestId} (${pr.title}): Rejetée - EXCLUE`
);
return false; return false;
default: default:
// Statut inconnu, on l'inclut par précaution // Statut inconnu, on l'inclut par précaution
console.log(
`⚠️ PR ${pr.pullRequestId} (${pr.title}): Statut inconnu "${pr.status}" - INCLUSE par précaution`
);
return true; return true;
} }
}); });
} }
/**
* Convertit une période de synchronisation en millisecondes
*/
private getMaxSyncPeriodMs(period: string): number {
switch (period) {
case '7d':
return 7 * 24 * 60 * 60 * 1000; // 7 jours
case '30d':
return 30 * 24 * 60 * 60 * 1000; // 30 jours
case '90d':
return 90 * 24 * 60 * 60 * 1000; // 90 jours
case '180d':
return 180 * 24 * 60 * 60 * 1000; // 180 jours
case '1y':
return 365 * 24 * 60 * 60 * 1000; // 1 an
case '2y':
return 2 * 365 * 24 * 60 * 60 * 1000; // 2 ans
case '3y':
return 3 * 365 * 24 * 60 * 60 * 1000; // 3 ans
default:
return 90 * 24 * 60 * 60 * 1000; // Par défaut 90 jours
}
}
/** /**
* Détermine si une PR est automatique (bot, renovate, dependabot, etc.) * Détermine si une PR est automatique (bot, renovate, dependabot, etc.)
*/ */