diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json index 50f206cf82c6e9959e972830450524d4c9828ec7..b308861b79d9985a1ce5bada43bafcb6d98f04b1 100644 --- a/NodeApp/package-lock.json +++ b/NodeApp/package-lock.json @@ -2725,6 +2725,14 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", diff --git a/NodeApp/package.json b/NodeApp/package.json index 0370c080636c251eefc826fe5bd58941a24a16f3..8c264ef8534c494511aeef313e5696a36e119d8a 100644 --- a/NodeApp/package.json +++ b/NodeApp/package.json @@ -8,9 +8,9 @@ "bin" : { "dojo": "./dist/app.js" }, - "pkg" : { + "pkg": { "scripts": [], - "assets" : [ + "assets": [ "node_modules/axios/dist/node/axios.cjs", ".env", "assets/**/*" @@ -24,7 +24,7 @@ "node18-win-x86" ] }, - "scripts" : { + "scripts": { "dotenv:build": "npx dotenvx encrypt", "lint" : "npx eslint .", "genversion" : "npx genversion -s -e src/config/Version.ts", diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts index 7d80e5f9894757332588ed31b84b9cdf5bd75553..bdc5fa4a473926c10887f046b9a29535e10a9977 100644 --- a/NodeApp/src/commander/exercise/ExerciseCommand.ts +++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts @@ -3,7 +3,9 @@ import ExerciseCreateCommand from './subcommands/ExerciseCreateCommand.js'; import ExerciseRunCommand from './subcommands/ExerciseRunCommand.js'; import ExerciseCorrectionCommand from './subcommands/ExerciseCorrectionCommand.js'; import ExerciseDeleteCommand from './subcommands/ExerciseDeleteCommand'; -import ExerciseListCommand from "./subcommands/ExerciseListCommand"; +import ExerciseListCommand from "./subcommands/ExerciseListCommand"; +import ExerciseResultCommand from "./subcommands/ExerciseResultCommand"; +import ExerciseSummaryCommand from "./subcommands/ExerciseSummaryCommand"; class ExerciseCommand extends CommanderCommand { @@ -20,6 +22,9 @@ class ExerciseCommand extends CommanderCommand { ExerciseDeleteCommand.registerOnCommand(this.command); ExerciseCorrectionCommand.registerOnCommand(this.command); ExerciseListCommand.registerOnCommand(this.command); + ExerciseResultCommand.registerOnCommand(this.command); + ExerciseSummaryCommand.registerOnCommand(this.command); + } protected async commandAction(): Promise<void> { diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts index 14ec7d36e75fde800708092f469a6b5578fa36c4..319e3bc3350282e6dce6af7496f8fe49e89cb999 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts @@ -101,7 +101,7 @@ class ExerciseListCommand extends CommanderCommand { message: 'Comment souhaitez-vous filtrer les exercices ?', choices: [ { name: 'Par saisie texte', value: 'fuzzy' }, - { name: 'Par professeurs', value: 'professor' }, + //{ name: 'Par professeurs', value: 'professor' }, { name: 'Exit', value: 'exit' }, ], }, diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseResultCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseResultCommand.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e46b7fd2652ffb9376118931a140657278a50235 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseResultCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseResultCommand.ts @@ -0,0 +1,122 @@ +import CommanderCommand from '../../CommanderCommand'; +import chalk from 'chalk'; +import ora from 'ora'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import Result from '../../../sharedByClients/models/Result'; +import inquirer from 'inquirer'; + + +class ExerciseResultCommand extends CommanderCommand { + protected commandName: string = 'result'; + + protected defineCommand(): void { + this.command + .description('results of an exercise') + .argument('<idOrLink>', 'display results of a specific exercise by Id or Gitlab Link') + .action(this.commandAction.bind(this)); + } + + protected async commandAction(idOrLink: string): Promise<void> { + const spinner = ora('Fetching exercise results...').start(); + + try { + const exerciseId = this.extractExerciseId(idOrLink); + // console.log('Exercise ID:', exerciseId); + spinner.info(`Fetching results for exercise with ID: ${exerciseId}`); + const results = await DojoBackendManager.getExerciseResults(exerciseId); + + if (!results) { + spinner.info('No results found for this exercise.'); + spinner.succeed('Exercise results fetched successfully.'); + return; + } + + if (results.length === 0) { + spinner.info('No results found for this exercise.'); + } else { + const answer = await inquirer.prompt([ + { + type: 'list', + name: 'testType', + message: 'Choisissez le type de tests à afficher:', + choices: ['Tests réussis', 'Tests échoués', 'Les deux'], + } + ]); + + const { testType } = answer; + + this.displayResults(results, testType); + spinner.succeed('Exercise results fetched successfully.'); + } + } catch (error) { + spinner.fail('Error fetching exercise results.'); + console.error(error); + } + } + + private extractExerciseId(idOrLink: string): string { + if (idOrLink.length <= 36) { + return idOrLink; + } else { + const lastUnderscoreIndex = idOrLink.lastIndexOf('_'); + // console.log('Last underscore index:', lastUnderscoreIndex); + if (lastUnderscoreIndex !== -1) { // -1 = pas de underscore trouvé + return idOrLink.substring(lastUnderscoreIndex + 1); // Extrait la sous-chaîne après le dernier underscore dans idOrLink + } else { + return ''; + } + } + } + + private displayResults(results: Result[], testType: string): void { + if (!results || results.length === 0) { + console.log('No results to display.'); + return; + } + + // Filtrer les résultats en fonction du type de test choisi + const filteredResults = results.filter(result => { + if (testType === 'Tests réussis') { + return result.success; + } else if (testType === 'Tests échoués') { + return !result.success; + } + return true; // 'Les deux' ou autre + }); + + if (filteredResults.length === 0) { + console.log('No results to display for the selected test type.'); + return; + } + + filteredResults.forEach(result => { + console.log(chalk.magenta(`Résultats de l\`exercice : ${result.exerciseId}`)); + console.log(` - Date et heure : ${result.dateTime}`); + console.log(` - Succès : ${result.success ? chalk.green('Oui') : chalk.red('Non')}`); + console.log(' - Détails des résultats :'); + console.log(` - Tests réussis : ${result.results.successfulTests}`); + console.log(` - Tests échoués : ${result.results.failedTests}`); + + if (testType === 'Tests réussis' || testType === 'Les deux') { + console.log(' - Liste des tests réussis :'); + if (Array.isArray(result.results.successfulTestsList)) { + result.results.successfulTestsList.forEach((test: string) => { + console.log(` - ${test} ${chalk.green('\u2713')}`); + }); + } + } + + if (testType === 'Tests échoués' || testType === 'Les deux') { + console.log(' - Liste des tests échoués :'); + if (Array.isArray(result.results.failedTestsList)) { + result.results.failedTestsList.forEach((test: string) => { + console.log(` - ${test} ${chalk.red('\u2717')}`); + }); + } + } + + console.log('-----------------------------------'); + }); + } +} +export default new ExerciseResultCommand(); diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseSummaryCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseSummaryCommand.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2e316fbe767c7332de795aeb53f273302b9c87d8 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseSummaryCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseSummaryCommand.ts @@ -0,0 +1,103 @@ +import CommanderCommand from '../../CommanderCommand'; +import ora from 'ora'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import Exercise from "../../../sharedByClients/models/Exercise"; +import Result from '../../../sharedByClients/models/Result'; +import Table from 'cli-table3'; + +class ExerciseSummaryCommand extends CommanderCommand { + protected commandName: string = 'summary'; + + protected defineCommand(): void { + this.command + .description('Display top exercises based on successful tests') + .action(this.commandAction.bind(this)); + } + + protected async commandAction(): Promise<void> { + const spinner = ora('Classsement... \n').start(); + + try { + const exercises = await DojoBackendManager.getUserExercises(); + const exerciseResults = await this.fetchExerciseResults(exercises); + + if (exerciseResults.length === 0) { + spinner.info('No exercise results found.'); + spinner.succeed('Exercise summary fetched successfully.'); + return; + } + + const sortedExercises = this.sortExercisesBySuccessfulTests(exerciseResults); + + this.displayExerciseSummary(sortedExercises); + + spinner.succeed('Exercise summary fetched successfully.'); + } catch (error) { + spinner.fail('Error fetching exercise summary.'); + console.error(error); + } + } + + private async fetchExerciseResults(exercises: Exercise[] | undefined): Promise<{ exercise: Exercise; successfulTests: number; dateTime: string }[]> { + const results: { exercise: Exercise, successfulTests: number, dateTime: string }[] = []; + + // @ts-ignore + for (const exercise of exercises) { + try { + const exerciseId = exercise.id; + const exerciseResults = await DojoBackendManager.getExerciseResults(exerciseId); + + if (exerciseResults) { + const successfulTests = this.countSuccessfulTests(exerciseResults); + results.push({ exercise, successfulTests, dateTime: exerciseResults[0]?.dateTime || '' }); + } + } catch (error) { + console.error(`Error fetching results for exercise ${exercise.id}:`, error); + } + } + + return results; + } + + private countSuccessfulTests(results: Result[]): number { + return results.reduce((count, result) => count + (result.success ? result.results.successfulTestsList.length : 0), 0); + } + + private sortExercisesBySuccessfulTests(exerciseResults: { exercise: Exercise, successfulTests: number, dateTime: string }[]): { exercise: Exercise, successfulTests: number, dateTime: string }[] { + return exerciseResults.sort((a, b) => b.successfulTests - a.successfulTests); + } + + private displayExerciseSummary(sortedExercises: { exercise: Exercise, successfulTests: number, dateTime: string }[]): void { + // Calculate the maximum width for each column + const headers = ['#', 'Exercise', 'Nb de tests réussis', 'Date']; + const maxWidths = headers.map(header => header.length); + + sortedExercises.forEach((exercise, index) => { + maxWidths[0] = Math.max(maxWidths[0], (index + 1).toString().length); + maxWidths[1] = Math.max(maxWidths[1], exercise.exercise.name?.length); + maxWidths[2] = Math.max(maxWidths[2], exercise.successfulTests.toString().length); + maxWidths[3] = Math.max(maxWidths[3], exercise.dateTime.length); + }); + + // Define colWidths based on maxWidths + const colWidths = maxWidths.map(width => ({ width })); + + // Create the table + const table = new Table({ + head: headers, + }); + + // Populate the table with data + sortedExercises.forEach((exercise, index) => { + table.push([ + index + 1, + exercise.exercise.name, + exercise.successfulTests, + exercise.dateTime + ]); + }); + + console.log(table.toString(),'\n'); + } +} +export default new ExerciseSummaryCommand();