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