From a383bcfa024814f4ca0ee71c8b2ea8828d29a1e9 Mon Sep 17 00:00:00 2001
From: "bedran.sezer" <bedran.sezer@etu.hesge.ch>
Date: Fri, 21 Jun 2024 02:19:42 +0200
Subject: [PATCH] add summary

---
 NodeApp/package-lock.json                     |  20 +++
 NodeApp/package.json                          | 111 ++++++++--------
 .../src/commander/exercise/ExerciseCommand.ts |   7 +-
 .../subcommands/ExerciseListCommand.ts        |   2 +-
 .../subcommands/ExerciseResultCommand.ts      | 122 ++++++++++++++++++
 .../subcommands/ExerciseSummaryCommand.ts     | 103 +++++++++++++++
 6 files changed, 308 insertions(+), 57 deletions(-)

diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json
index 6bb6f37..91b8632 100644
--- a/NodeApp/package-lock.json
+++ b/NodeApp/package-lock.json
@@ -18,6 +18,7 @@
                 "axios": "^1.7.2",
                 "boxen": "^5.1.2",
                 "chalk": "^4.1.2",
+                "cli-table": "^0.3.11",
                 "commander": "^12.1.0",
                 "form-data": "^4.0.0",
                 "fs-extra": "^11.2.0",
@@ -2349,6 +2350,17 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/cli-table": {
+            "version": "0.3.11",
+            "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz",
+            "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==",
+            "dependencies": {
+                "colors": "1.0.3"
+            },
+            "engines": {
+                "node": ">= 0.2.0"
+            }
+        },
         "node_modules/cli-width": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
@@ -2440,6 +2452,14 @@
             "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
             "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
         },
+        "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 b6155d9..c091bd7 100644
--- a/NodeApp/package.json
+++ b/NodeApp/package.json
@@ -1,16 +1,16 @@
 {
-    "name"           : "dojo_cli",
-    "description"    : "CLI of the Dojo project",
-    "version"        : "4.1.1",
-    "license"        : "AGPLv3",
-    "author"         : "Michaël Minelli <dojo@minelli.me>",
-    "main"           : "dist/app.js",
-    "bin"            : {
+    "name": "dojo_cli",
+    "description": "CLI of the Dojo project",
+    "version": "4.1.1",
+    "license": "AGPLv3",
+    "author": "Michaël Minelli <dojo@minelli.me>",
+    "main": "dist/app.js",
+    "bin": {
         "dojo": "./dist/app.js"
     },
-    "pkg"            : {
+    "pkg": {
         "scripts": [],
-        "assets" : [
+        "assets": [
             "node_modules/axios/dist/node/axios.cjs",
             ".env",
             "assets/**/*"
@@ -24,57 +24,58 @@
             "node18-win-x86"
         ]
     },
-    "scripts"        : {
+    "scripts": {
         "dotenv:build": "npx dotenvx encrypt",
-        "lint"        : "npx eslint .",
-        "genversion"  : "npx genversion -s -e src/config/Version.ts",
-        "build"       : "npm run genversion; npx tsc",
-        "start:dev"   : "npm run genversion; npm run lint; tsc --noEmit && npx tsx src/app.ts",
-        "test"        : "echo \"Error: no test specified\" && exit 1"
+        "lint": "npx eslint .",
+        "genversion": "npx genversion -s -e src/config/Version.ts",
+        "build": "npm run genversion; npx tsc",
+        "start:dev": "npm run genversion; npm run lint; tsc --noEmit && npx tsx src/app.ts",
+        "test": "echo \"Error: no test specified\" && exit 1"
     },
-    "dependencies"   : {
-        "@dotenvx/dotenvx"          : "^0.44.1",
-        "@eslint/js"                : "^9.3.0",
-        "@gitbeaker/core"           : "^40.0.3",
+    "dependencies": {
+        "@dotenvx/dotenvx": "^0.44.1",
+        "@eslint/js": "^9.3.0",
+        "@gitbeaker/core": "^40.0.3",
         "@gitbeaker/requester-utils": "^40.0.3",
-        "@gitbeaker/rest"           : "^40.0.3",
-        "appdata-path"              : "^1.0.0",
-        "axios"                     : "^1.7.2",
-        "boxen"                     : "^5.1.2",
-        "chalk"                     : "^4.1.2",
-        "commander"                 : "^12.1.0",
-        "form-data"                 : "^4.0.0",
-        "fs-extra"                  : "^11.2.0",
-        "http-status-codes"         : "^2.3.0",
-        "inquirer"                  : "^8.2.6",
-        "json5"                     : "^2.2.3",
-        "jsonwebtoken"              : "^8.5.1",
-        "open"                      : "^8.4.2",
-        "ora"                       : "^5.4.1",
-        "semver"                    : "^7.6.2",
-        "tar-stream"                : "^3.1.7",
-        "winston"                   : "^3.13.0",
-        "winston-transport"         : "^4.7.0",
-        "yaml"                      : "^2.4.2",
-        "zod"                       : "^3.23.8",
-        "zod-validation-error"      : "^3.3.0"
+        "@gitbeaker/rest": "^40.0.3",
+        "appdata-path": "^1.0.0",
+        "axios": "^1.7.2",
+        "boxen": "^5.1.2",
+        "chalk": "^4.1.2",
+        "cli-table": "^0.3.11",
+        "commander": "^12.1.0",
+        "form-data": "^4.0.0",
+        "fs-extra": "^11.2.0",
+        "http-status-codes": "^2.3.0",
+        "inquirer": "^8.2.6",
+        "json5": "^2.2.3",
+        "jsonwebtoken": "^8.5.1",
+        "open": "^8.4.2",
+        "ora": "^5.4.1",
+        "semver": "^7.6.2",
+        "tar-stream": "^3.1.7",
+        "winston": "^3.13.0",
+        "winston-transport": "^4.7.0",
+        "yaml": "^2.4.2",
+        "zod": "^3.23.8",
+        "zod-validation-error": "^3.3.0"
     },
     "devDependencies": {
-        "@types/fs-extra"                 : "^11.0.4",
-        "@types/inquirer"                 : "^8.2.10",
-        "@types/jsonwebtoken"             : "^8.5.9",
-        "@types/node"                     : "^18.19.33",
-        "@types/semver"                   : "^7.5.8",
-        "@types/tar-stream"               : "^3.1.3",
+        "@types/fs-extra": "^11.0.4",
+        "@types/inquirer": "^8.2.10",
+        "@types/jsonwebtoken": "^8.5.9",
+        "@types/node": "^18.19.33",
+        "@types/semver": "^7.5.8",
+        "@types/tar-stream": "^3.1.3",
         "@typescript-eslint/eslint-plugin": "^7.11.0",
-        "@typescript-eslint/parser"       : "^7.11.0",
-        "dotenv-vault"                    : "^1.26.1",
-        "eslint"                          : "^8.57.0",
-        "genversion"                      : "^3.2.0",
-        "pkg"                             : "^5.8.1",
-        "tiny-typed-emitter"              : "^2.1.0",
-        "tsx"                             : "^4.11.0",
-        "typescript"                      : "^5.4.5",
-        "typescript-eslint"               : "^7.11.0"
+        "@typescript-eslint/parser": "^7.11.0",
+        "dotenv-vault": "^1.26.1",
+        "eslint": "^8.57.0",
+        "genversion": "^3.2.0",
+        "pkg": "^5.8.1",
+        "tiny-typed-emitter": "^2.1.0",
+        "tsx": "^4.11.0",
+        "typescript": "^5.4.5",
+        "typescript-eslint": "^7.11.0"
     }
 }
diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts
index 1138f67..690e6a2 100644
--- a/NodeApp/src/commander/exercise/ExerciseCommand.ts
+++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts
@@ -2,7 +2,9 @@ import CommanderCommand          from '../CommanderCommand.js';
 import ExerciseCreateCommand     from './subcommands/ExerciseCreateCommand.js';
 import ExerciseRunCommand        from './subcommands/ExerciseRunCommand.js';
 import ExerciseCorrectionCommand from './subcommands/ExerciseCorrectionCommand.js';
-import ExerciseListCommand from "./subcommands/ExerciseListCommand";
+import ExerciseListCommand       from "./subcommands/ExerciseListCommand";
+import ExerciseResultCommand     from "./subcommands/ExerciseResultCommand";
+import ExerciseSummaryCommand   from "./subcommands/ExerciseSummaryCommand";
 
 
 class ExerciseCommand extends CommanderCommand {
@@ -18,6 +20,9 @@ class ExerciseCommand extends CommanderCommand {
         ExerciseRunCommand.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 14ec7d3..319e3bc 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 e69de29..e46b7fd 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 e69de29..2e316fb 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();
-- 
GitLab