Skip to content
Snippets Groups Projects
Commit c7c2a19c authored by michael.minelli's avatar michael.minelli
Browse files

ExerciseSearchCommand => Refactor and fix the command

parent 044202d4
No related branches found
No related tags found
No related merge requests found
// 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
// ExerciseListCommand.ts
import CommanderCommand from '../../CommanderCommand';
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';
import TextStyle from '../../../types/TextStyle';
import ExerciseHelper from '../../../helpers/Dojo/ExerciseHelper';
import { Option } from 'commander';
import Config from '../../../config/Config';
type CommandOptions = { all: boolean, name: string, teacher: string };
class ExerciseListCommand extends CommanderCommand {
protected commandName: string = 'list';
protected teachers: User[] = [];
protected allExercises: Exercise[] = [];
protected filteredExercises: Exercise[] = [];
protected currentSearchFilter = '';
protected defineCommand(): void {
this.command
.description('list your exercises')
.addOption(new Option('-a, --all', 'list all exercises').conflicts([ 'name', 'teacher' ]))
.addOption(new Option('-n, --name <pattern_to_search>', 'search exercises by name').conflicts([ 'all', 'teacher' ]))
.addOption(new Option('-t, --teacher <pattern_to_search>', 'search exercises by teacher').conflicts([ 'all', 'name' ]))
.action(this.commandAction.bind(this));
}
private async dataRetrieval(getTeachers: boolean) {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
// Check access
await AccessesHelper.checkStudent();
// Fetch teachers
if ( getTeachers ) {
const teachersGetSpinner: ora.Ora = ora(`Fetching teachers`).start();
try {
const teachers: Array<User> | undefined = await DojoBackendManager.getTeachers();
if ( teachers ) {
this.teachers = teachers;
} else {
throw new Error();
}
} catch ( error ) {
teachersGetSpinner.fail(`Error while fetching teachers.`);
throw new Error();
}
teachersGetSpinner.succeed(`Teachers fetched successfully.`);
}
// Fetch user's exercises
const exercisesGetSpinner: ora.Ora = ora(`Checking user's exercises`).start();
this.allExercises = await DojoBackendManager.getUserExercises() ?? [];
this.filteredExercises = this.allExercises;
if ( this.allExercises.length === 0 ) {
exercisesGetSpinner.fail(`You do not have any exercises yet.`);
throw new Error();
}
exercisesGetSpinner.succeed(`User's exercises fetched successfully.`);
}
private clear(): void {
this.currentSearchFilter = '';
this.filteredExercises = this.allExercises;
}
private async displayMenu(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while ( true ) {
console.log('');
ora(`${ '='.repeat(25) } Current filter: ${ this.currentSearchFilter == '' ? 'no filter' : this.currentSearchFilter } ${ '='.repeat(25) }`).info();
const action: string = (await inquirer.prompt([ {
type : 'list',
name : 'action',
message: 'What do you want ?',
choices: [ {
name : 'Display current filtered exercises list',
value: 'list'
}, new inquirer.Separator(), {
name : 'Get details of an exercise',
value: 'details'
}, new inquirer.Separator(), {
name : 'Filter by name',
value: 'fuzzy'
}, {
name : 'Filter by teacher',
value: 'teacher'
}, new inquirer.Separator(), {
name : 'Clear filters',
value: 'clear'
}, new inquirer.Separator(), {
name : 'Exit',
value: 'exit'
} ]
} ])).action;
switch ( action ) {
case 'list':
await this.displayExerciseList();
break;
case 'details':
await this.selectExerciseForDetails();
return;
case 'fuzzy':
await this.filterByExerciseName();
break;
case 'teacher':
await this.filterByTeacherInteractive();
break;
case 'clear':
this.clear();
break;
case 'exit':
throw new Error();
default:
ora().info('Invalid filter type.');
return;
}
}
}
private async selectExerciseForDetails(): Promise<void> {
const { selectedExercise } = await inquirer.prompt([ {
type : 'list',
name : 'selectedExercise',
message: 'Please select an exercise :',
choices: [ ...this.filteredExercises.map(exercise => ({
name : exercise.name,
value: exercise.id
})), new inquirer.Separator(), {
name : 'Cancel',
value: 'cancel'
} ]
} ]);
if ( selectedExercise === 'cancel' ) {
return;
}
const selected = this.filteredExercises.find(ex => ex.id === selectedExercise);
if ( selected ) {
return ExerciseHelper.displayDetails(selected, true);
} else {
ora().info('Invalid selection. No exercise details to show.');
}
}
private async filterByExerciseName(searchQuery: string | undefined = undefined): Promise<void> {
if ( searchQuery === undefined ) {
searchQuery = (await inquirer.prompt([ {
type : 'input',
name : 'searchQuery',
message: 'Please enter the searched string (leave blank if you want all exercises list):'
} ])).searchQuery;
}
this.currentSearchFilter = `[Name] ${ searchQuery }`;
if ( !searchQuery ) {
this.filteredExercises = this.allExercises;
} else {
const fuse = new Fuse(this.allExercises, {
keys : [ 'name' ],
threshold: 0.5,
distance : 150
});
this.filteredExercises = fuse.search(searchQuery).map(result => result.item);
}
await this.displayExerciseList();
}
private async filterByTeacher(searchQuery: string): Promise<void> {
if ( this.teachers.length === 0 ) {
ora().info('No teachers found.');
return;
}
this.currentSearchFilter = `[Teacher] ${ searchQuery }`;
const exercises: Array<Exercise & { teachers: string }> = this.allExercises.map(exercise => ({
...exercise,
teachers: (exercise.assignment?.staff ?? []).map(staff => staff.gitlabUsername).join(' ')
})) as Array<Exercise & { teachers: string }>;
const fuse = new Fuse(exercises, {
keys : [ 'teachers' ],
threshold: 0.5,
distance : 150
});
this.filteredExercises = fuse.search(searchQuery).map(result => result.item);
await this.displayExerciseList();
}
private async filterByTeacherInteractive(): Promise<void> {
if ( this.teachers.length === 0 ) {
ora().info('No teachers found.');
return;
}
const teacherChoices = this.teachers.map(teacher => ({
name : `${ teacher.gitlabUsername }`,
value: teacher
}));
teacherChoices.sort((a, b) => a.name.split('.')[1].localeCompare(b.name.split('.')[1]));
const selectedTeacher: User = (await inquirer.prompt([ {
type : 'list',
name : 'selectedTeacher',
message: 'Please select a teacher:',
choices: teacherChoices
} ])).selectedTeacher;
this.currentSearchFilter = `[Teacher] ${ selectedTeacher.gitlabUsername }`;
this.filteredExercises = this.allExercises.filter(exercise => (exercise.assignment?.staff ?? []).find(staff => staff.id === selectedTeacher.id) !== undefined);
await this.displayExerciseList();
}
private async displayExerciseList(): Promise<void> {
ora(`Search results for filter: ${ this.currentSearchFilter == '' ? 'no filter' : this.currentSearchFilter }`).info();
if ( this.filteredExercises.length === 0 ) {
ora().info('No exercises found.');
return;
}
this.filteredExercises.forEach(exercise => {
console.log(TextStyle.LIST_ITEM_NAME(`➡ ${ exercise.name }`));
console.log(` ${ TextStyle.LIST_SUBITEM_NAME('- Id:') } ${ exercise.id }`);
console.log(` ${ TextStyle.LIST_SUBITEM_NAME('- Gitlab URL:') } ${ exercise.gitlabCreationInfo.web_url }`);
});
}
private async displayExerciseTable(): Promise<void> {
ora(`Search results for filter: ${ this.currentSearchFilter == '' ? 'no filter' : this.currentSearchFilter }`).info();
if ( this.filteredExercises.length === 0 ) {
ora().info('No exercises found.');
return;
}
const headers = [ 'Exercise Name', 'GitLab Link' ];
// Calculate the maximum width for each column
const maxWidths = headers.map(header => header.length);
this.filteredExercises.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
});
this.filteredExercises.forEach((exercise) => {
table.push([ exercise.name, exercise.gitlabLink ]);
});
console.log(table.toString());
}
protected async commandAction(options: CommandOptions): Promise<void> {
try {
if ( !options.all && !options.name && !options.teacher && !Config.interactiveMode ) {
ora().fail('At least one filter or interactive mode is required.');
this.command.help();
return;
}
await this.dataRetrieval(!(options.all || options.name));
if ( Config.interactiveMode ) {
await this.displayMenu();
} else {
if ( options.all ) {
await this.displayExerciseList();
} else if ( options.name ) {
await this.filterByExerciseName(options.name);
} else if ( options.teacher ) {
await this.filterByTeacher(options.teacher);
}
ora().info(`${ TextStyle.TIPS('[Tips]') } If you want to see more details about an exercise, use the command ${ TextStyle.CODE('dojo exercise info <id or url>') }.`);
}
} catch ( e ) { /* Do nothing */ }
}
}
export default new ExerciseListCommand();
\ No newline at end of file
Subproject commit 530fe7459023f7fa11b14a9edf7a99629304cf8b Subproject commit 81c1c69cdc9ed0b381c60fe4fd2e4668abe00625
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment