From 70b2e14b86c74a711c4126b795ba40103b1b5ee6 Mon Sep 17 00:00:00 2001
From: "bedran.sezer" <bedran.sezer@etu.hesge.ch>
Date: Thu, 20 Jun 2024 23:30:32 +0200
Subject: [PATCH] update fuzzy matching

---
 .idea/inspectionProfiles/Project_Default.xml  |   6 +
 NodeApp/.env.vault                            |   2 +-
 NodeApp/.idea/vcs.xml                         |   1 -
 .../src/commander/exercise/ExerciseCommand.ts |   2 +
 .../subcommands/ExerciseListCommand.ts        | 235 ++++++++++++++++++
 NodeApp/src/managers/DojoBackendManager.ts    |  11 +
 6 files changed, 255 insertions(+), 2 deletions(-)
 create mode 100644 .idea/inspectionProfiles/Project_Default.xml
 create mode 100644 NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts

diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/NodeApp/.env.vault b/NodeApp/.env.vault
index dfdcf92..1b27b44 100644
--- a/NodeApp/.env.vault
+++ b/NodeApp/.env.vault
@@ -1,6 +1,6 @@
 #/-------------------.env.vault---------------------/
 #/         cloud-agnostic vaulting standard         /
-#/   [how it works](https://dotenvx.com/env-vault)  /
+#/   [how it works](https://dotenv.org/env-vault)   /
 #/--------------------------------------------------/
 
 # development
diff --git a/NodeApp/.idea/vcs.xml b/NodeApp/.idea/vcs.xml
index 5e5bcd0..d86e73b 100644
--- a/NodeApp/.idea/vcs.xml
+++ b/NodeApp/.idea/vcs.xml
@@ -2,7 +2,6 @@
 <project version="4">
   <component name="VcsDirectoryMappings">
     <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
-    <mapping directory="$PROJECT_DIR$/DojoExercise_Technique_de compilation - TP" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" />
   </component>
diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts
index 02e40e3..7d80e5f 100644
--- a/NodeApp/src/commander/exercise/ExerciseCommand.ts
+++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts
@@ -3,6 +3,7 @@ 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";
 
 
 class ExerciseCommand extends CommanderCommand {
@@ -18,6 +19,7 @@ class ExerciseCommand extends CommanderCommand {
         ExerciseRunCommand.registerOnCommand(this.command);
         ExerciseDeleteCommand.registerOnCommand(this.command);
         ExerciseCorrectionCommand.registerOnCommand(this.command);
+        ExerciseListCommand.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
new file mode 100644
index 0000000..14ec7d3
--- /dev/null
+++ b/NodeApp/src/commander/exercise/subcommands/ExerciseListCommand.ts
@@ -0,0 +1,235 @@
+// ExerciseListCommand.ts
+import CommanderCommand from '../../CommanderCommand';
+import chalk from 'chalk';
+import ora from 'ora';
+import DojoBackendManager from '../../../managers/DojoBackendManager';
+import AccessesHelper from '../../../helpers/AccessesHelper';
+import Exercise from '../../../sharedByClients/models/Exercise';
+import inquirer from 'inquirer';
+import Table from 'cli-table3';
+
+import Fuse from 'fuse.js';
+import User from '../../../sharedByClients/models/User';
+
+class ExerciseListCommand extends CommanderCommand {
+    protected commandName: string = 'list';
+
+    protected defineCommand(): void {
+        this.command
+            .description('list your exercises')
+            .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(): Promise<void> {
+        console.log(chalk.cyan('Please wait while we retrieve your exercises...'));
+
+        // Check access
+        if (!await AccessesHelper.checkStudent()) {
+            return;
+        }
+
+        // Fetch user's exercises
+        const userExercises: Exercise[] | undefined = await DojoBackendManager.getUserExercises();
+
+        if (!userExercises || userExercises.length === 0) {
+            ora().info('You have no exercises yet.');
+            return;
+        }
+
+        // Display the list of exercises
+        this.displayExerciseList(userExercises);
+
+        // Ask the user for further actions
+        await this.askUserForActions(userExercises);
+    }
+
+    private async askUserForActions(exercises: Exercise[]): Promise<void> {
+        const { action } = await inquirer.prompt([
+            {
+                type: 'list',
+                name: 'action',
+                message: 'Que souhaitez-vous faire ?',
+                choices: [
+                    { name: 'Voir les détails d\'exercice', value: 'details'},
+                    { name: 'Filter les exercises', value: 'filter' },
+                    { name: 'Exit', value: 'exit' },
+                ],
+            },
+        ]);
+
+        if (action === 'details') {
+            await this.selectExerciseForDetails(exercises);
+        } else if (action === 'filter') {
+            await this.filterExercises(exercises);
+        } else {
+            ora().info('No further actions selected.');
+        }
+    }
+
+    private async selectExerciseForDetails(exercises: Exercise[]): Promise<void> {
+        const { selectedExercise } = await inquirer.prompt([{
+            type: 'list',
+            name: 'selectedExercise',
+            message: 'Selectionner un exercice :',
+            choices: [
+                ...exercises.map(exercise => ({
+                    name: exercise.name,
+                    value: exercise.id,
+                })),
+                { name: 'Exit', value: 'exit' },
+            ],
+        }]);
+
+        if (selectedExercise === 'exit') {
+            ora().info('Pas de détails requis: détails dispo  avec la commande `dojo exercise info <id>`.');
+            return;
+        }
+
+        const selected = exercises.find(ex => ex.id === selectedExercise);
+        if (selected) {
+            await this.displayExerciseDetails(selected);
+        } else {
+            ora().info('Invalid selection. No exercise details to show.');
+        }
+    }
+
+    private async filterExercises(exercises: Exercise[]): Promise<void> {
+        const { filterType } = await inquirer.prompt([
+            {
+                type: 'list',
+                name: 'filterType',
+                message: 'Comment souhaitez-vous filtrer les exercices ?',
+                choices: [
+                    { name: 'Par saisie texte', value: 'fuzzy' },
+                    { name: 'Par professeurs', value: 'professor' },
+                    { name: 'Exit', value: 'exit' },
+                ],
+            },
+        ]);
+
+        if (filterType === 'fuzzy') {
+            await this.fuzzySearchExercises(exercises);
+        } else if (filterType === 'professor') {
+            await this.filterByProfessor(exercises);
+        } else {
+            ora().info('No filtering selected.');
+        }
+    }
+
+    private async fuzzySearchExercises(exercises: Exercise[]): Promise<void> {
+        const { searchQuery } = await inquirer.prompt([
+            {
+                type: 'input',
+                name: 'searchQuery',
+                message: 'Entrez le nom de l\'exercice (laisser vide pour la liste complète) :',
+            },
+        ]);
+
+        if (!searchQuery) {
+            this.displayExerciseList(exercises);
+            return;
+        }
+
+        const fuse = new Fuse(exercises, {
+            keys: ['name'],
+            threshold: 0.5,
+            distance: 150,
+        });
+
+        const searchResults = fuse.search(searchQuery).map(result => result.item);
+
+        if (searchResults.length === 0) {
+            ora().info('Aucun exercice trouvé correspondant à votre recherche.');
+            return;
+        }
+
+        if (searchResults.length === 1) {
+            // Display details and members for the single matching exercise
+            const singleExercise = searchResults[0];
+            this.displayExerciseDetails(singleExercise);
+        } else {
+            // Display only exercise names and info about viewing details
+            ora().info(' Plusieurs exercices trouvés correspondant à votre recherche :');
+            const exerciseNames = searchResults.map(exercise => exercise.name);
+            console.log('  ', exerciseNames.join('\n   '));
+
+            ora().info('Les détails sont disponibles avec la commande : `dojo exercise info <id>`.');
+        }
+    }
+
+    private async filterByProfessor(exercises: Exercise[]): Promise<void> {
+
+        const professors: User[] | undefined = await DojoBackendManager.getProfessors();
+
+        if (!professors || professors.length === 0) {
+            ora().info('No professors found.');
+            return;
+        }
+
+        const professorChoices = professors.map(professor => ({
+            name: `${professor.gitlabUsername}`,
+            value: professor // Use the professor object as the value
+        }));
+
+        const { selectedProfessor } = await inquirer.prompt([
+            {
+                type: 'list',
+                name: 'selectedProfessor',
+                message: 'Selectionnez un professeur:',
+                choices: professorChoices
+            }
+        ]);
+
+        console.log(`Selected professor: ${selectedProfessor.gitlabUsername}`);
+        ora().info('Filter by professor is not yet implemented.');
+    }
+
+    private displayExerciseList(exercises: Exercise[]): void {
+        const headers = ['Exercise Name', 'GitLab Link'];
+
+        // Calculate the maximum width for each column
+        const maxWidths = headers.map(header => header.length);
+
+        exercises.forEach(exercise => {
+            maxWidths[0] = Math.max(maxWidths[0], exercise.name.length);
+            maxWidths[1] = Math.max(maxWidths[1], exercise.gitlabLink.length);
+        });
+
+        const table = new Table({
+            head: headers,
+        });
+
+        exercises.forEach((exercise) => {
+            table.push([
+                exercise.name,
+                exercise.gitlabLink,
+            ]);
+        });
+
+        ora().info('Your exercises:');
+        console.log(table.toString());
+    }
+
+    private async displayExerciseDetails(exercise: Exercise): Promise<void> {
+        ora().info(`Detail of Exercise with ID: ${exercise.id}`);
+        console.log(chalk.magenta('  - Exercise Name:'), exercise.name);
+        console.log(chalk.magenta('  - Assignment Name:'), exercise.assignmentName);
+        console.log(chalk.magenta('  - GitLab ID:'), exercise.gitlabId);
+        console.log(chalk.magenta('  - GitLab Link:'), chalk.blue.underline(exercise.gitlabLink));
+        console.log(chalk.magenta('  - GitLab Last Info Date:'), exercise.gitlabLastInfoDate);
+
+        // Fetch exercise members
+        const exerciseMembers = await DojoBackendManager.getExerciseMembers(exercise.id);
+
+        if (exerciseMembers && exerciseMembers.length > 0) {
+            ora().info('Exercise Members:');
+            exerciseMembers.forEach(member => {
+                console.log(chalk.magenta(`  - ${member.id} ${member.name}`));
+            });
+        } else {
+            ora().info('No members found for this exercise.');
+        }
+    }
+}
+
+export default new ExerciseListCommand();
\ No newline at end of file
diff --git a/NodeApp/src/managers/DojoBackendManager.ts b/NodeApp/src/managers/DojoBackendManager.ts
index 50c2c0e..3d7d094 100644
--- a/NodeApp/src/managers/DojoBackendManager.ts
+++ b/NodeApp/src/managers/DojoBackendManager.ts
@@ -554,6 +554,17 @@ class DojoBackendManager {
     public async getTeachers(): Promise<Array<User> | undefined> {
         return this.getUsers('teacher');
     }
+
+    public async getExerciseDetail(exerciseId: string): Promise<Exercise | undefined> {
+        try {
+
+            const response = await axios.get<Exercise>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_DETAIL).replace('{{exerciseId}}', String(exerciseId)));
+            return response.data;
+        } catch ( error ) {
+            console.error('Error fetching exercise details:', error);
+            return undefined;
+        }
+    }
 }
 
 
-- 
GitLab