Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • Jw_sonar_backup
  • add_route_assignments
  • add_route_user
  • assignment_filter
  • bedran_exercise-list
  • exercise_list_filter
  • interactive-mode-preference
  • jw_sonar
  • main
  • move-to-esm-only
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.2.0
  • 3.2.2
  • 3.2.3
  • 3.3.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 4.0.0
  • 4.0.1
  • 4.1.0
  • 4.1.1
  • 4.2.0
  • 5.0.0
  • 6.0.0-dev
  • Latest
  • Pre-alpha
  • v1.0.1
36 results

Target

Select target project
  • Dojo_Project_Nguyen/ui/dojocli
  • dojo_project/projects/ui/dojocli
  • tom.andrivet/dojocli
  • orestis.malaspin/dojocli
4 results
Select Git revision
  • Jw_sonar_backup
  • add_route_assignments
  • add_route_user
  • assignment_filter
  • bedran_exercise-list
  • exercise_list_filter
  • interactive-mode-preference
  • jw_sonar
  • main
  • move-to-esm-only
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.2.0
  • 3.2.2
  • 3.2.3
  • 3.3.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 4.0.0
  • 4.0.1
  • 4.1.0
  • 4.1.1
  • 4.2.0
  • 5.0.0
  • 6.0.0-dev
  • Latest
  • Pre-alpha
  • v1.0.1
36 results
Show changes
Commits on Source (9)
Showing
with 617 additions and 368 deletions
......@@ -2,6 +2,7 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/DojoExercise_c_hello_world" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" />
</component>
......
......@@ -22,6 +22,7 @@
"commander": "^13.1.0",
"form-data": "^4.0.2",
"fs-extra": "^11.3.0",
"fuse.js": "^7.1.0",
"http-status-codes": "^2.3.0",
"inquirer": "^8.2.6",
"json5": "^2.2.3",
......@@ -2725,14 +2726,6 @@
"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",
......@@ -4008,6 +4001,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuse.js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/genversion": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/genversion/-/genversion-3.2.0.tgz",
......
......@@ -46,6 +46,7 @@
"commander" : "^13.1.0",
"form-data" : "^4.0.2",
"fs-extra" : "^11.3.0",
"fuse.js" : "^7.1.0",
"http-status-codes" : "^2.3.0",
"inquirer" : "^8.2.6",
"json5" : "^2.2.3",
......
......@@ -3,9 +3,8 @@ 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 ExerciseResultCommand from "./subcommands/ExerciseResultCommand";
import ExerciseSummaryCommand from "./subcommands/ExerciseSummaryCommand";
import ExerciseSearchCommand from './subcommands/ExerciseSearchCommand';
import ExerciseInfoCommand from './subcommands/ExerciseInfoCommand';
class ExerciseCommand extends CommanderCommand {
......@@ -21,10 +20,10 @@ class ExerciseCommand extends CommanderCommand {
ExerciseRunCommand.registerOnCommand(this.command);
ExerciseDeleteCommand.registerOnCommand(this.command);
ExerciseCorrectionCommand.registerOnCommand(this.command);
ExerciseListCommand.registerOnCommand(this.command);
ExerciseResultCommand.registerOnCommand(this.command);
ExerciseSummaryCommand.registerOnCommand(this.command);
ExerciseSearchCommand.registerOnCommand(this.command);
ExerciseInfoCommand.registerOnCommand(this.command);
// ExerciseResultCommand.registerOnCommand(this.command);
// ExerciseSummaryCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> {
......
......@@ -10,6 +10,7 @@ import inquirer from 'inquirer';
import Config from '../../../config/Config';
import ClientsSharedConfig from '../../../sharedByClients/config/ClientsSharedConfig';
import { Option } from 'commander';
import ExerciseHelper from '../../../helpers/Dojo/ExerciseHelper';
type CommandOptions = { assignment: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean, force?: boolean };
......@@ -161,26 +162,7 @@ class ExerciseCreateCommand extends CommanderCommand {
this.exercise = await DojoBackendManager.createExercise(this.assignment!.name, this.members!);
const oraInfo = (message: string) => {
ora({
text : message,
indent: 4
}).start().info();
};
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Id:') } ${ this.exercise.id }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.exercise.name }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.exercise.gitlabCreationInfo.web_url }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.exercise.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.exercise.gitlabCreationInfo.ssh_url_to_repo }`);
}
private async cloneRepository(options: CommandOptions) {
if ( options.clone ) {
console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
await Config.gitlabManager.cloneRepository(options.clone, this.exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise_${ this.exercise.assignmentName }`, true, 0);
}
await ExerciseHelper.displayDetails(this.exercise);
}
......@@ -188,7 +170,7 @@ class ExerciseCreateCommand extends CommanderCommand {
try {
await this.dataRetrieval(options);
await this.createExercise();
await this.cloneRepository(options);
await ExerciseHelper.clone(this.exercise, options.clone ?? false);
} catch ( e ) { /* Do nothing */ }
}
}
......
import CommanderCommand from '../../CommanderCommand';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import AccessesHelper from '../../../helpers/AccessesHelper';
import TextStyle from '../../../types/TextStyle';
import ExerciseHelper from '../../../helpers/Dojo/ExerciseHelper';
class ExerciseDeleteCommand extends CommanderCommand {
......@@ -20,16 +20,10 @@ class ExerciseDeleteCommand extends CommanderCommand {
await AccessesHelper.checkStudent();
}
private async deleteExercise(exerciseIdOrUrl: string) {
console.log(TextStyle.BLOCK('Please wait while we are deleting the exercise...'));
await DojoBackendManager.deleteExercise(exerciseIdOrUrl);
}
protected async commandAction(exerciseIdOrUrl: string): Promise<void> {
try {
await this.dataRetrieval();
await this.deleteExercise(exerciseIdOrUrl);
await ExerciseHelper.delete(exerciseIdOrUrl);
} catch ( e ) { /* Do nothing */ }
}
}
......
import CommanderCommand from '../../CommanderCommand';
import AccessesHelper from '../../../helpers/AccessesHelper';
import TextStyle from '../../../types/TextStyle';
import ExerciseHelper from '../../../helpers/Dojo/ExerciseHelper';
import Exercise from '../../../sharedByClients/models/Exercise';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import ora from 'ora';
import Config from '../../../config/Config';
class ExerciseInfoCommand extends CommanderCommand {
protected commandName: string = 'info';
protected defineCommand(): void {
this.command
.description('delete an exercise')
.argument('id or url', 'id or url of the exercise')
.action(this.commandAction.bind(this));
}
private async dataRetrieval(exerciseIdOrUrl: string): Promise<Exercise> {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkStudent();
// Fetch exercise
const exercisesGetSpinner: ora.Ora = ora({
text : `Checking exercise`,
indent: 4
}).start();
const exercise = await DojoBackendManager.getExercise(exerciseIdOrUrl);
if ( !exercise ) {
exercisesGetSpinner.fail(`Exercise not found`);
throw new Error();
}
exercisesGetSpinner.succeed(`Exercise fetched successfully`);
return exercise;
}
protected async commandAction(exerciseIdOrUrl: string): Promise<void> {
try {
const exercise = await this.dataRetrieval(exerciseIdOrUrl);
return ExerciseHelper.displayDetails(exercise, Config.interactiveMode);
} catch ( e ) { /* Do nothing */ }
}
}
export default new ExerciseInfoCommand();
// 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
......@@ -6,6 +6,8 @@ import Result from '../../../sharedByClients/models/Result';
import inquirer from 'inquirer';
// THIS COMMAND IS NOT WORKING YET - NEEDS TO BE REWRITTEN
class ExerciseResultCommand extends CommanderCommand {
protected commandName: string = 'result';
......@@ -34,18 +36,16 @@ class ExerciseResultCommand extends CommanderCommand {
if ( results.length === 0 ) {
spinner.info('No results found for this exercise.');
} else {
const answer = await inquirer.prompt([
{
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'],
}
]);
choices: [ 'Tests réussis', 'Tests échoués', 'Les deux' ]
} ]);
const { testType } = answer;
this.displayResults(results, testType);
this.displayResults(results, testType as string);
spinner.succeed('Exercise results fetched successfully.');
}
} catch ( error ) {
......@@ -119,4 +119,6 @@ class ExerciseResultCommand extends CommanderCommand {
});
}
}
export default new ExerciseResultCommand();
......@@ -7,7 +7,7 @@ class ExerciseRunCommand extends CommanderCommand {
protected commandName: string = 'run';
protected defineCommand() {
GlobalHelper.runCommandDefinition(this.command)
GlobalHelper.runCommandDefinition(this.command, false)
.description('locally run an exercise')
.action(this.commandAction.bind(this));
}
......
// 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 ExerciseSearchCommand extends CommanderCommand {
protected commandName: string = 'search';
protected aliasNames: 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'
}, new inquirer.Separator() ]
} ])).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 ExerciseSearchCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import ora from 'ora';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import Exercise from "../../../sharedByClients/models/Exercise";
import Exercise from '../../../sharedByClients/models/Exercise';
import Result from '../../../sharedByClients/models/Result';
import Table from 'cli-table3';
// THIS COMMAND IS NOT WORKING - NEEDS TO BE REWRITTEN AND THINK OF HIS INTEREST
class ExerciseSummaryCommand extends CommanderCommand {
protected commandName: string = 'summary';
......@@ -41,15 +44,18 @@ class ExerciseSummaryCommand extends CommanderCommand {
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) {
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 || '' });
results.push({
exercise,
successfulTests,
dateTime: exerciseResults[0]?.dateTime || ''
});
}
} catch ( error ) {
console.error(`Error fetching results for exercise ${ exercise.id }:`, error);
......@@ -60,7 +66,7 @@ class ExerciseSummaryCommand extends CommanderCommand {
}
private countSuccessfulTests(results: Result[]): number {
return results.reduce((count, result) => count + (result.success ? result.results.successfulTestsList.length : 0), 0);
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 }[] {
......@@ -80,24 +86,21 @@ class ExerciseSummaryCommand extends CommanderCommand {
});
// Define colWidths based on maxWidths
const colWidths = maxWidths.map(width => ({ width }));
// const colWidths = maxWidths.map(width => ({ width }));
// Create the table
const table = new Table({
head: headers,
head: headers
});
// Populate the table with data
sortedExercises.forEach((exercise, index) => {
table.push([
index + 1,
exercise.exercise.name,
exercise.successfulTests,
exercise.dateTime
]);
table.push([ index + 1, exercise.exercise.name, exercise.successfulTests, exercise.dateTime ]);
});
console.log(table.toString(), '\n');
}
}
export default new ExerciseSummaryCommand();
import Exercise from '../../sharedByClients/models/Exercise';
import ora from 'ora';
import TextStyle from '../../types/TextStyle';
import inquirer from 'inquirer';
import Config from '../../config/Config';
import DojoBackendManager from '../../managers/DojoBackendManager';
class ExerciseHelper {
/**
* Clone the exercise repository
* @param exercise
* @param providedPath If a string is provided, the repository will be cloned in the specified directory. If true, the repository will be cloned in the current directory. If false, the repository will not be cloned, and if undefined, the user will be prompted for the path.
*/
async clone(exercise: Exercise, providedPath: string | boolean | undefined) {
if ( providedPath === false ) {
return;
}
let path: string | boolean = './';
if ( providedPath === undefined ) {
path = (await inquirer.prompt([ {
type : 'input',
name : 'path',
message: `Please enter the path (blank, '.' or './' for current directory):`
} ])).path;
} else {
path = providedPath;
}
console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
await Config.gitlabManager.cloneRepository(path === '' ? true : path, exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise_${ exercise.assignmentName }`, true, 0);
}
async delete(exerciseIdOrUrl: string) {
console.log(TextStyle.BLOCK('Please wait while we are deleting the exercise...'));
await DojoBackendManager.deleteExercise(exerciseIdOrUrl);
}
async actionMenu(exercise: Exercise): Promise<void> {
// eslint-disable-next-line no-constant-condition
while ( true ) {
const action: string = (await inquirer.prompt([ {
type : 'list',
name : 'action',
message: 'What action do you want to do on the exercise ?',
choices: [ {
name : 'Display details of the exercise',
value: 'info'
}, new inquirer.Separator(), {
name : 'Clone (SSH required) in current directory (will create a subdirectory)',
value: 'cloneInCurrentDirectory'
}, {
name : 'Clone (SSH required) in the specified directory (will create a subdirectory)',
value: 'clone'
}, new inquirer.Separator(), {
name : 'Delete the exercise',
value: 'delete'
}, new inquirer.Separator(), {
name : 'Exit',
value: 'exit'
}, new inquirer.Separator() ]
} ])).action;
switch ( action ) {
case 'info':
await this.displayDetails(exercise, false);
break;
case 'cloneInCurrentDirectory':
await this.clone(exercise, true);
break;
case 'clone':
await this.clone(exercise, undefined);
break;
case 'delete':
await this.delete(exercise.id);
return;
case 'exit':
return;
default:
ora().fail('Invalid option.');
return;
}
}
}
async displayDetails(exercise: Exercise, showActionMenu: boolean = false): Promise<void> {
ora().info(`Details of the exercise:`);
const oraInfo = (message: string, indent: number = 4) => {
ora({
text : message,
indent: indent
}).start().info();
};
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Id:') } ${ exercise.id }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ exercise.name }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Assignment:') } ${ exercise.assignmentName }`);
// Display exercise teachers
if ( exercise.assignment?.staff && exercise.assignment?.staff.length > 0 ) {
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Teachers:') }`);
exercise.assignment?.staff.forEach(staff => {
console.log(` - ${ staff.gitlabUsername }`);
});
} else {
ora({
text : `${ TextStyle.LIST_ITEM_NAME('Teachers:') } No teachers found for this exercise.`,
indent: 4
}).start().warn();
}
// Display exercise members
if ( exercise.members && exercise.members.length > 0 ) {
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Members:') }`);
exercise.members.forEach(member => {
console.log(` - ${ member.gitlabUsername }`);
});
} else {
ora({
text : `${ TextStyle.LIST_ITEM_NAME('Members:') } No members found for this exercise.`,
indent: 4
}).start().warn();
}
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Gitlab URL:') } ${ exercise.gitlabCreationInfo.web_url }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ exercise.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ exercise.gitlabCreationInfo.ssh_url_to_repo }`);
if ( showActionMenu ) {
await this.actionMenu(exercise);
}
}
}
export default new ExerciseHelper();
\ No newline at end of file
......@@ -8,9 +8,9 @@ import Config from '../config/Config';
class GlobalHelper {
public runCommandDefinition(command: Command) {
public runCommandDefinition(command: Command, isAssignment: boolean = true): Command {
command
.option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise)
.option('-p, --path <value>', `${ isAssignment ? 'assignment' : 'exercise' } path`, Config.folders.defaultLocalExercise)
.option('-v, --verbose', 'verbose mode - display principal container output in live')
.addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
.addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }));
......
......@@ -17,6 +17,8 @@ import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig
import inquirer from 'inquirer';
import SharedConfig from '../shared/config/SharedConfig';
import ConfigFiles from '../config/ConfigFiles';
import SessionManager from './SessionManager';
import UserRole from '../sharedByClients/models/UserRole';
class DojoBackendManager {
......@@ -478,7 +480,7 @@ class DojoBackendManager {
public async getUserExercises(): Promise<Array<Exercise> | undefined> {
try {
const response = await axios.get<DojoBackendResponse<Array<Exercise>>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_LIST));
const response = await axios.get<DojoBackendResponse<Array<Exercise>>>(DojoBackendHelper.getApiUrl(ApiRoute.USER_EXERCISES_LIST, { userId: SessionManager.profile?.id }));
return response.data.data;
} catch ( error ) {
console.error('Error fetching user exercises:', error);
......@@ -486,13 +488,13 @@ class DojoBackendManager {
}
}
public async getExerciseDetails(exerciseIdOrUrl: string): Promise<Exercise | undefined> {
public async getExercise(exerciseIdOrUrl: string): Promise<Exercise | undefined> {
try {
const response = await axios.get<Exercise>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_DETAILS_GET, {
const response = await axios.get<DojoBackendResponse<Exercise>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_GET_DELETE, {
exerciseIdOrUrl: exerciseIdOrUrl
}));
return response.data;
return response.data.data;
} catch ( error ) {
console.error('Error fetching exercise details:', error);
return undefined;
......@@ -540,30 +542,19 @@ class DojoBackendManager {
}
}
public async getUsers(roleFilter?: string): Promise<Array<User> | undefined> {
public async getUsers(role?: string): Promise<Array<User> | undefined> {
try {
const response = await axios.get<DojoBackendResponse<Array<User>>>(DojoBackendHelper.getApiUrl(ApiRoute.USER_LIST), { params: roleFilter ? { roleFilter: roleFilter } : {} });
const response = await axios.get<DojoBackendResponse<Array<User>>>(DojoBackendHelper.getApiUrl(ApiRoute.USER_LIST), { params: role ? { role: role } : {} });
return response.data.data;
} catch ( error ) {
console.error('Error fetching professors:', error);
console.error('Error fetching users:', error);
return undefined;
}
}
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;
}
return this.getUsers(UserRole.TEACHING_STAFF);
}
}
......
Subproject commit 530fe7459023f7fa11b14a9edf7a99629304cf8b
Subproject commit 81c1c69cdc9ed0b381c60fe4fd2e4668abe00625
......@@ -5,7 +5,9 @@ class TextStyle {
public readonly BLOCK = chalk.cyan;
public readonly CODE = chalk.bgHex('F7F7F7').grey.italic;
public readonly LIST_ITEM_NAME = chalk.magenta;
public readonly LIST_SUBITEM_NAME = chalk.green;
public readonly QUESTION = chalk.greenBright;
public readonly TIPS = chalk.blueBright;
public readonly URL = chalk.blue.underline;
public readonly WARNING = chalk.red;
}
......