feat(status): ajouter statut NO_YAML pour les entrées sans fichier YAML
- shared: ajoute 'NO_YAML' à ApiStatus - back/apis: status initial dérivé de yamlContent (PENDING si YAML, NO_YAML sinon) - back/uploads: supprime la contrainte "fichier requis", gère le cas sans fichier - front/api-detail: badgeClass/statusLabel gèrent NO_YAML (badge warning "Sans fichier") - front/catalog: badgeClass ajoute le case NO_YAML → badge warning - front/upload: fichier optionnel (canProceedFromStep1 retourne true si 0 fichier) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -41,7 +41,7 @@ export class ApisService {
|
|||||||
description: dto.description ?? null,
|
description: dto.description ?? null,
|
||||||
type: dto.type,
|
type: dto.type,
|
||||||
yamlContent: dto.yamlContent,
|
yamlContent: dto.yamlContent,
|
||||||
status: 'PENDING',
|
status: dto.yamlContent ? 'PENDING' : 'NO_YAML',
|
||||||
categoryId: dto.categoryId ?? null,
|
categoryId: dto.categoryId ?? null,
|
||||||
isCurrent,
|
isCurrent,
|
||||||
contactFunctionalName: dto.contactFunctionalName,
|
contactFunctionalName: dto.contactFunctionalName,
|
||||||
|
|||||||
@@ -205,7 +205,6 @@ export class UploadsController {
|
|||||||
contactTechnicalEmail: string;
|
contactTechnicalEmail: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (!files || files.length === 0) throw new BadRequestException('YAML file is required');
|
|
||||||
if (!body.name) throw new BadRequestException('name is required');
|
if (!body.name) throw new BadRequestException('name is required');
|
||||||
if (!body.convention) throw new BadRequestException('convention is required');
|
if (!body.convention) throw new BadRequestException('convention is required');
|
||||||
|
|
||||||
@@ -218,7 +217,10 @@ export class UploadsController {
|
|||||||
let yamlContent: string;
|
let yamlContent: string;
|
||||||
let detectedType: ApiType | null = null;
|
let detectedType: ApiType | null = null;
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (!files || files.length === 0) {
|
||||||
|
/* Pas de fichier : entrée créée sans YAML (YAML pourra être ajouté plus tard) */
|
||||||
|
yamlContent = '';
|
||||||
|
} else if (files.length === 1) {
|
||||||
/* Cas simple : un seul fichier */
|
/* Cas simple : un seul fichier */
|
||||||
yamlContent = fs.readFileSync(files[0].path, 'utf-8');
|
yamlContent = fs.readFileSync(files[0].path, 'utf-8');
|
||||||
|
|
||||||
@@ -260,8 +262,10 @@ export class UploadsController {
|
|||||||
contactTechnicalEmail: body.contactTechnicalEmail,
|
contactTechnicalEmail: body.contactTechnicalEmail,
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Fire-and-forget : génération asynchrone */
|
/* Fire-and-forget : génération asynchrone (seulement si YAML fourni) */
|
||||||
this.docsService.generate(entry.id).catch(console.error);
|
if (yamlContent) {
|
||||||
|
this.docsService.generate(entry.id).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -596,8 +596,17 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
|
|||||||
onDelete() {
|
onDelete() {
|
||||||
if (!confirm('Supprimer cette API ?')) return;
|
if (!confirm('Supprimer cette API ?')) return;
|
||||||
this.deleting.set(true);
|
this.deleting.set(true);
|
||||||
|
/* Calculer la cible de redirection avant la suppression */
|
||||||
|
const otherVersions = this.versions().filter((v) => v.id !== this.id);
|
||||||
|
const redirectTarget = otherVersions.find((v) => v.isCurrent) ?? otherVersions[0];
|
||||||
this.apiService.delete(this.id).subscribe({
|
this.apiService.delete(this.id).subscribe({
|
||||||
next: () => this.router.navigate(['/catalog']),
|
next: () => {
|
||||||
|
if (redirectTarget) {
|
||||||
|
this.router.navigate(['/catalog', redirectTarget.id]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(this.backUrl());
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.deleting.set(false),
|
error: () => this.deleting.set(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -605,12 +614,14 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
|
|||||||
badgeClass(status: string): string {
|
badgeClass(status: string): string {
|
||||||
if (status === 'GENERATED') return 'fr-badge fr-badge--success';
|
if (status === 'GENERATED') return 'fr-badge fr-badge--success';
|
||||||
if (status === 'ERROR') return 'fr-badge fr-badge--error';
|
if (status === 'ERROR') return 'fr-badge fr-badge--error';
|
||||||
|
if (status === 'NO_YAML') return 'fr-badge fr-badge--warning';
|
||||||
return 'fr-badge fr-badge--info';
|
return 'fr-badge fr-badge--info';
|
||||||
}
|
}
|
||||||
|
|
||||||
statusLabel(status: string): string {
|
statusLabel(status: string): string {
|
||||||
if (status === 'GENERATED') return 'Générée';
|
if (status === 'GENERATED') return 'Générée';
|
||||||
if (status === 'ERROR') return 'Erreur';
|
if (status === 'ERROR') return 'Erreur';
|
||||||
|
if (status === 'NO_YAML') return 'Sans fichier';
|
||||||
return 'En attente';
|
return 'En attente';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ export class CatalogComponent implements OnInit {
|
|||||||
const base = 'fr-badge';
|
const base = 'fr-badge';
|
||||||
if (status === 'GENERATED') return `${base} fr-badge--success`;
|
if (status === 'GENERATED') return `${base} fr-badge--success`;
|
||||||
if (status === 'ERROR') return `${base} fr-badge--error`;
|
if (status === 'ERROR') return `${base} fr-badge--error`;
|
||||||
|
if (status === 'NO_YAML') return `${base} fr-badge--warning`;
|
||||||
return `${base} fr-badge--info`;
|
return `${base} fr-badge--info`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -393,7 +393,9 @@ import { ApiEntry, Category, API_CONVENTION_LABELS, ApiConvention } from '@datac
|
|||||||
<div class="fr-col-12 fr-col-md-6">
|
<div class="fr-col-12 fr-col-md-6">
|
||||||
<dt class="fr-text--bold">Fichier{{ selectedFiles().length > 1 ? 's' : '' }}</dt>
|
<dt class="fr-text--bold">Fichier{{ selectedFiles().length > 1 ? 's' : '' }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
@if (selectedFiles().length === 1) {
|
@if (selectedFiles().length === 0) {
|
||||||
|
<span class="fr-badge fr-badge--warning fr-badge--sm">Aucun fichier</span>
|
||||||
|
} @else if (selectedFiles().length === 1) {
|
||||||
{{ selectedFiles()[0].name }}
|
{{ selectedFiles()[0].name }}
|
||||||
} @else {
|
} @else {
|
||||||
@for (f of selectedFiles(); track f.name) {
|
@for (f of selectedFiles(); track f.name) {
|
||||||
@@ -568,7 +570,7 @@ export class UploadComponent implements OnInit {
|
|||||||
return !!(this.metaForm.get('versionPatch')?.invalid && this.metaForm.get('versionPatch')?.touched);
|
return !!(this.metaForm.get('versionPatch')?.invalid && this.metaForm.get('versionPatch')?.touched);
|
||||||
}
|
}
|
||||||
canProceedFromStep1 = computed(() => {
|
canProceedFromStep1 = computed(() => {
|
||||||
if (this.selectedFiles().length === 0) return false;
|
if (this.selectedFiles().length === 0) return true; // fichier optionnel
|
||||||
if (this.validating()) return false;
|
if (this.validating()) return false;
|
||||||
return this.validationDone() && this.validationErrors().length === 0;
|
return this.validationDone() && this.validationErrors().length === 0;
|
||||||
});
|
});
|
||||||
@@ -627,6 +629,7 @@ export class UploadComponent implements OnInit {
|
|||||||
|
|
||||||
onFileChange(event: Event) {
|
onFileChange(event: Event) {
|
||||||
this.fileError.set(null);
|
this.fileError.set(null);
|
||||||
|
this.uploadError.set(null);
|
||||||
this.detectedType.set(null);
|
this.detectedType.set(null);
|
||||||
this.detectedMainFilename.set(null);
|
this.detectedMainFilename.set(null);
|
||||||
this.validationErrors.set([]);
|
this.validationErrors.set([]);
|
||||||
@@ -725,7 +728,12 @@ export class UploadComponent implements OnInit {
|
|||||||
onSubmit() {
|
onSubmit() {
|
||||||
const files = this.selectedFiles();
|
const files = this.selectedFiles();
|
||||||
this.metaForm.markAllAsTouched();
|
this.metaForm.markAllAsTouched();
|
||||||
if (files.length === 0 || this.metaForm.invalid) return;
|
if (this.metaForm.invalid) {
|
||||||
|
/* Naviguer vers l'étape avec les erreurs pour les rendre visibles */
|
||||||
|
const infoInvalid = this.INFO_FIELDS.some((f) => this.metaForm.get(f)?.invalid);
|
||||||
|
this.step.set(infoInvalid ? 2 : 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.uploading.set(true);
|
this.uploading.set(true);
|
||||||
this.uploadError.set(null);
|
this.uploadError.set(null);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type ApiType = 'ASYNCAPI' | 'OPENAPI';
|
export type ApiType = 'ASYNCAPI' | 'OPENAPI';
|
||||||
|
|
||||||
export type ApiStatus = 'PENDING' | 'GENERATED' | 'ERROR';
|
export type ApiStatus = 'PENDING' | 'GENERATED' | 'ERROR' | 'NO_YAML';
|
||||||
|
|
||||||
export type ApiConvention = 'CONSULTER' | 'ENREGISTRER' | 'ETRE_NOTIFIE';
|
export type ApiConvention = 'CONSULTER' | 'ENREGISTRER' | 'ETRE_NOTIFIE';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user