|
| 1 | +import * as XLSX from "xlsx"; |
| 2 | +import type { Specification } from "@/shared/types/specification"; |
| 3 | +import type { TestSuite } from "@/shared/types/test-suite"; |
| 4 | +import type { TraceabilityMatrix } from "@/shared/types/traceability"; |
| 5 | + |
| 6 | +function formatComplianceProfile( |
| 7 | + profile: Specification["compliance_profile"], |
| 8 | +): string { |
| 9 | + if (!profile) return ""; |
| 10 | + if (typeof profile === "string") return profile; |
| 11 | + const [key, value] = Object.entries(profile)[0] ?? []; |
| 12 | + return key ? `${key} (${value})` : ""; |
| 13 | +} |
| 14 | + |
| 15 | +/** Construit un workbook XLSX complet avec toutes les donnees du pipeline */ |
| 16 | +export function buildFullExportWorkbook( |
| 17 | + spec: Specification, |
| 18 | + suite: TestSuite, |
| 19 | + traceability: TraceabilityMatrix, |
| 20 | +): XLSX.WorkBook { |
| 21 | + const workbook = XLSX.utils.book_new(); |
| 22 | + |
| 23 | + // 1. Metadonnees |
| 24 | + const metaRows = [ |
| 25 | + { |
| 26 | + Champ: "ID", |
| 27 | + Valeur: spec.id, |
| 28 | + }, |
| 29 | + { Champ: "Titre", Valeur: spec.title }, |
| 30 | + { Champ: "Version", Valeur: spec.version }, |
| 31 | + { Champ: "Statut", Valeur: spec.status }, |
| 32 | + { Champ: "Auteur", Valeur: spec.author ?? "" }, |
| 33 | + { Champ: "Date de creation", Valeur: spec.created_at }, |
| 34 | + { Champ: "Baseline", Valeur: spec.baseline ?? "" }, |
| 35 | + { Champ: "Version outil", Valeur: spec.tool_version }, |
| 36 | + { |
| 37 | + Champ: "Profil conformite", |
| 38 | + Valeur: formatComplianceProfile(spec.compliance_profile), |
| 39 | + }, |
| 40 | + { |
| 41 | + Champ: "Stories source", |
| 42 | + Valeur: spec.source_stories.join(", "), |
| 43 | + }, |
| 44 | + ]; |
| 45 | + XLSX.utils.book_append_sheet( |
| 46 | + workbook, |
| 47 | + XLSX.utils.json_to_sheet(metaRows), |
| 48 | + "Metadonnees", |
| 49 | + ); |
| 50 | + |
| 51 | + // 2. Scenarios utilisateur |
| 52 | + const scenarioRows = spec.user_scenarios.map((us) => ({ |
| 53 | + ID: us.id, |
| 54 | + Titre: us.title, |
| 55 | + Priorite: us.priority, |
| 56 | + Description: us.description, |
| 57 | + "Justification priorite": us.why_priority, |
| 58 | + "Test independant": us.independent_test, |
| 59 | + "Story source": us.source_story_id, |
| 60 | + "Scenarios acceptation": us.acceptance_scenarios |
| 61 | + .map((a) => `Etant donne: ${a.given} | Quand: ${a.when} | Alors: ${a.then}`) |
| 62 | + .join("\n"), |
| 63 | + })); |
| 64 | + XLSX.utils.book_append_sheet( |
| 65 | + workbook, |
| 66 | + XLSX.utils.json_to_sheet(scenarioRows), |
| 67 | + "Scenarios", |
| 68 | + ); |
| 69 | + |
| 70 | + // 3. Exigences fonctionnelles |
| 71 | + const reqRows = spec.functional_requirements.map((fr) => ({ |
| 72 | + ID: fr.id, |
| 73 | + Enonce: fr.statement, |
| 74 | + Priorite: fr.priority, |
| 75 | + Categorie: fr.category, |
| 76 | + Testable: fr.testable ? "Oui" : "Non", |
| 77 | + "Methode verification": fr.verification_method, |
| 78 | + Risque: fr.risk_level ?? "", |
| 79 | + Justification: fr.rationale ?? "", |
| 80 | + Source: fr.source ?? "", |
| 81 | + "Exigence parente": fr.parent_requirement ?? "", |
| 82 | + "Alloue a": fr.allocated_to.join(", "), |
| 83 | + "Caracteristique qualite": fr.quality_characteristic ?? "", |
| 84 | + })); |
| 85 | + XLSX.utils.book_append_sheet( |
| 86 | + workbook, |
| 87 | + XLSX.utils.json_to_sheet(reqRows), |
| 88 | + "Exigences", |
| 89 | + ); |
| 90 | + |
| 91 | + // 4. Entites |
| 92 | + const entityRows = spec.key_entities.map((e) => ({ |
| 93 | + Nom: e.name, |
| 94 | + Description: e.description, |
| 95 | + Attributs: e.attributes.join(", "), |
| 96 | + Relations: e.relationships.join(", "), |
| 97 | + })); |
| 98 | + XLSX.utils.book_append_sheet( |
| 99 | + workbook, |
| 100 | + XLSX.utils.json_to_sheet(entityRows), |
| 101 | + "Entites", |
| 102 | + ); |
| 103 | + |
| 104 | + // 5. Cas limites |
| 105 | + const edgeRows = spec.edge_cases.map((ec) => ({ |
| 106 | + Description: ec.description, |
| 107 | + "Scenario lie": ec.related_scenario ?? "", |
| 108 | + Severite: ec.severity, |
| 109 | + })); |
| 110 | + XLSX.utils.book_append_sheet( |
| 111 | + workbook, |
| 112 | + XLSX.utils.json_to_sheet(edgeRows), |
| 113 | + "Cas limites", |
| 114 | + ); |
| 115 | + |
| 116 | + // 6. Criteres de succes |
| 117 | + const criteriaRows = spec.success_criteria.map((sc) => ({ |
| 118 | + ID: sc.id, |
| 119 | + Description: sc.description, |
| 120 | + "Metrique mesurable": sc.measurable_metric, |
| 121 | + })); |
| 122 | + XLSX.utils.book_append_sheet( |
| 123 | + workbook, |
| 124 | + XLSX.utils.json_to_sheet(criteriaRows), |
| 125 | + "Criteres succes", |
| 126 | + ); |
| 127 | + |
| 128 | + // 7. Clarifications |
| 129 | + const clarRows = spec.clarifications_needed.map((c) => ({ |
| 130 | + Question: c.question, |
| 131 | + Contexte: c.context, |
| 132 | + "Options suggerees": c.suggested_options.join(", "), |
| 133 | + Impact: c.impact, |
| 134 | + Resolue: c.resolved ? "Oui" : "Non", |
| 135 | + Reponse: c.answer ?? "", |
| 136 | + })); |
| 137 | + XLSX.utils.book_append_sheet( |
| 138 | + workbook, |
| 139 | + XLSX.utils.json_to_sheet(clarRows), |
| 140 | + "Clarifications", |
| 141 | + ); |
| 142 | + |
| 143 | + // 8. Validation |
| 144 | + const validationRows: Record<string, string | number>[] = []; |
| 145 | + if (spec.validation) { |
| 146 | + validationRows.push({ |
| 147 | + Critere: "Score completude", |
| 148 | + Valeur: spec.validation.completeness_score, |
| 149 | + }); |
| 150 | + validationRows.push({ |
| 151 | + Critere: "Score clarte", |
| 152 | + Valeur: spec.validation.clarity_score, |
| 153 | + }); |
| 154 | + validationRows.push({ |
| 155 | + Critere: "Score testabilite", |
| 156 | + Valeur: spec.validation.testability_score, |
| 157 | + }); |
| 158 | + for (const item of spec.validation.checklist_items) { |
| 159 | + validationRows.push({ |
| 160 | + Critere: `[${item.category}] ${item.description}`, |
| 161 | + Valeur: item.passed ? "OK" : "NOK", |
| 162 | + }); |
| 163 | + } |
| 164 | + } |
| 165 | + XLSX.utils.book_append_sheet( |
| 166 | + workbook, |
| 167 | + XLSX.utils.json_to_sheet( |
| 168 | + validationRows.length > 0 |
| 169 | + ? validationRows |
| 170 | + : [{ Critere: "Aucune validation", Valeur: "" }], |
| 171 | + ), |
| 172 | + "Validation", |
| 173 | + ); |
| 174 | + |
| 175 | + // 9. Features & Scenarios de test |
| 176 | + const testRows = suite.features.flatMap((f) => |
| 177 | + f.scenarios.map((s) => ({ |
| 178 | + Feature: f.name, |
| 179 | + "Description feature": f.description, |
| 180 | + "Niveau test": f.test_level, |
| 181 | + "Tags feature": f.tags.join(", "), |
| 182 | + "Exigences couvertes": f.covered_requirements.join(", "), |
| 183 | + "Scenarios source": f.source_scenario_ids.join(", "), |
| 184 | + Scenario: s.name, |
| 185 | + Type: s.scenario_type, |
| 186 | + "Tags scenario": s.tags.join(", "), |
| 187 | + "Technique couverture": s.coverage_technique ?? "", |
| 188 | + "Exigences verifiees": s.verification_of.join(", "), |
| 189 | + Etapes: s.steps |
| 190 | + .map((st) => { |
| 191 | + let line = `${st.keyword} ${st.text}`; |
| 192 | + if (st.doc_string) line += `\n """\n ${st.doc_string}\n """`; |
| 193 | + if (st.data_table) |
| 194 | + line += `\n | ${st.data_table.map((r) => r.join(" | ")).join(" |\n | ")} |`; |
| 195 | + return line; |
| 196 | + }) |
| 197 | + .join("\n"), |
| 198 | + Exemples: s.examples |
| 199 | + ? `| ${s.examples.headers.join(" | ")} |\n${s.examples.rows.map((r) => `| ${r.join(" | ")} |`).join("\n")}` |
| 200 | + : "", |
| 201 | + "Suggestions donnees test": s.test_data_suggestions.join(", "), |
| 202 | + })), |
| 203 | + ); |
| 204 | + XLSX.utils.book_append_sheet( |
| 205 | + workbook, |
| 206 | + XLSX.utils.json_to_sheet(testRows), |
| 207 | + "Tests", |
| 208 | + ); |
| 209 | + |
| 210 | + // 10. Couverture des tests (resume) |
| 211 | + const coverageRows = [ |
| 212 | + { |
| 213 | + "Exigences couvertes": suite.coverage.requirements_covered.join(", "), |
| 214 | + "Total exigences": suite.coverage.requirements_total, |
| 215 | + "Couverture (%)": suite.coverage.coverage_percentage, |
| 216 | + "Happy path": suite.coverage.scenarios_by_type.happy_path, |
| 217 | + "Cas limites": suite.coverage.scenarios_by_type.edge_case, |
| 218 | + "Scenarios erreur": suite.coverage.scenarios_by_type.error_scenario, |
| 219 | + "Conditions limites": suite.coverage.scenarios_by_type.boundary, |
| 220 | + }, |
| 221 | + ]; |
| 222 | + XLSX.utils.book_append_sheet( |
| 223 | + workbook, |
| 224 | + XLSX.utils.json_to_sheet(coverageRows), |
| 225 | + "Couverture tests", |
| 226 | + ); |
| 227 | + |
| 228 | + // 11. Tracabilite |
| 229 | + const traceRows = traceability.entries.map((e) => ({ |
| 230 | + "ID Exigence": e.requirement_id, |
| 231 | + Enonce: e.statement, |
| 232 | + Priorite: e.priority, |
| 233 | + Risque: e.risk_level ?? "", |
| 234 | + "Stories source": e.source_stories.join(", "), |
| 235 | + Verification: e.verification_method, |
| 236 | + Features: e.covering_features.join(", "), |
| 237 | + Scenarios: e.covering_scenarios.join(", "), |
| 238 | + Techniques: e.coverage_techniques.join(", "), |
| 239 | + Statut: e.status, |
| 240 | + })); |
| 241 | + XLSX.utils.book_append_sheet( |
| 242 | + workbook, |
| 243 | + XLSX.utils.json_to_sheet(traceRows), |
| 244 | + "Tracabilite", |
| 245 | + ); |
| 246 | + |
| 247 | + // 12. Resume tracabilite |
| 248 | + const summaryRows = [ |
| 249 | + { |
| 250 | + "Total exigences": traceability.summary.total_requirements, |
| 251 | + Couvertes: traceability.summary.covered, |
| 252 | + "Partiellement couvertes": traceability.summary.partially_covered, |
| 253 | + "Non couvertes": traceability.summary.not_covered, |
| 254 | + "Verifiees autrement": traceability.summary.verified_other, |
| 255 | + "Couverture (%)": traceability.summary.forward_coverage_pct, |
| 256 | + "Tests orphelins": traceability.summary.orphan_tests.join(", "), |
| 257 | + }, |
| 258 | + ]; |
| 259 | + XLSX.utils.book_append_sheet( |
| 260 | + workbook, |
| 261 | + XLSX.utils.json_to_sheet(summaryRows), |
| 262 | + "Resume tracabilite", |
| 263 | + ); |
| 264 | + |
| 265 | + // 13. Notes de conformite |
| 266 | + const complianceRows = |
| 267 | + traceability.compliance_notes.length > 0 |
| 268 | + ? traceability.compliance_notes.map((n) => ({ |
| 269 | + Standard: n.standard, |
| 270 | + Section: n.section, |
| 271 | + Statut: n.status, |
| 272 | + Details: n.details, |
| 273 | + })) |
| 274 | + : [{ Standard: "Aucune note", Section: "", Statut: "", Details: "" }]; |
| 275 | + XLSX.utils.book_append_sheet( |
| 276 | + workbook, |
| 277 | + XLSX.utils.json_to_sheet(complianceRows), |
| 278 | + "Conformite", |
| 279 | + ); |
| 280 | + |
| 281 | + return workbook; |
| 282 | +} |
| 283 | + |
| 284 | +/** Exporte toutes les donnees du pipeline en un seul fichier XLSX */ |
| 285 | +export function exportAll( |
| 286 | + spec: Specification, |
| 287 | + suite: TestSuite, |
| 288 | + traceability: TraceabilityMatrix, |
| 289 | +): Blob { |
| 290 | + const workbook = buildFullExportWorkbook(spec, suite, traceability); |
| 291 | + const buffer = XLSX.write(workbook, { type: "array", bookType: "xlsx" }); |
| 292 | + return new Blob([buffer], { |
| 293 | + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| 294 | + }); |
| 295 | +} |
0 commit comments