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
Showing
with 1069 additions and 67 deletions
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';
// THIS COMMAND IS NOT WORKING YET - NEEDS TO BE REWRITTEN
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 as string);
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 for this 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();
import CommanderCommand from '../../CommanderCommand';
import Config from '../../../config/Config';
import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper';
import { Option } from 'commander';
import CommanderCommand from '../../CommanderCommand.js';
import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper.js';
import GlobalHelper from '../../../helpers/GlobalHelper.js';
class ExerciseRunCommand extends CommanderCommand {
protected commandName: string = 'run';
protected defineCommand() {
// This command is synced with the "assignment run" command
this.command
.description('locally run an exercise')
.option('-p, --path <value>', '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 }))
.action(this.commandAction.bind(this));
GlobalHelper.runCommandDefinition(this.command, false)
.description('locally run an exercise')
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options);
await (new ExerciseRunHelper(options)).run();
}
}
......
// 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 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';
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 }[] = [];
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();
import CommanderCommand from '../CommanderCommand.js';
import SettingsApiCommand from './subcommands/SettingsApiCommand';
import SettingsInteractiveModeCommand from './subcommands/SettingsInteractiveModeCommand';
class SettingsCommand extends CommanderCommand {
protected commandName: string = 'settings';
protected defineCommand() {
this.command
.description('manage Dojo settings');
}
protected defineSubCommands() {
SettingsApiCommand.registerOnCommand(this.command);
SettingsInteractiveModeCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> {
// No action
}
}
export default new SettingsCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand.js';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import { Option } from 'commander';
import Config from '../../../config/Config';
class SettingsApiCommand extends CommanderCommand {
protected commandName: string = 'api';
protected defineCommand() {
this.command
.description('change Dojo API URL')
.option('-u, --url <string>', 'specify the url of the Dojo API')
.addOption(new Option('--clean', 'clean the Dojo API settings').conflicts('url'))
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { url: string, clean: boolean }): Promise<void> {
if ( options.clean ) {
await DojoBackendManager.cleanApiUrl();
} else if ( options.url ) {
await DojoBackendManager.setApiUrl(options.url);
} else if ( Config.interactiveMode ) {
await DojoBackendManager.askApiUrl(true);
} else {
this.command.help();
}
}
}
export default new SettingsApiCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand.js';
import { Option } from 'commander';
import Config from '../../../config/Config';
class SettingsInteractiveModeCommand extends CommanderCommand {
protected commandName: string = 'interactive-mode';
protected defineCommand() {
this.command
.description('enable / disable interactive mode by default')
.addOption(new Option('-i, --interactive', 'ENABLE interactive mode by default'))
.addOption(new Option('-n, --no_interactive', 'DISABLE interactive mode by default').conflicts('interactive')) // WARNING: Due to a bug in commander.js, the conflicts method doesn't work as expected if the command is named no-interactive
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { interactive: boolean, no_interactive: boolean }): Promise<void> {
if ( options.interactive ) {
Config.setInteractiveMode(true);
} else if ( options.no_interactive ) {
Config.setInteractiveMode(false);
} else if ( Config.interactiveMode ) {
await Config.askInteractiveMode();
} else {
this.command.help();
}
}
}
export default new SettingsInteractiveModeCommand();
\ No newline at end of file
import CommanderCommand from '../CommanderCommand';
import TagCreateCommand from './subcommands/TagCreateCommand';
import TagDelete from './subcommands/TagDeleteCommand';
import TagProposalCommand from './subcommands/proposal/TagProposalCommand';
class TagCommand extends CommanderCommand {
protected commandName: string = 'tag';
protected defineCommand() {
this.command
.description('manage tags');
}
protected defineSubCommands() {
TagCreateCommand.registerOnCommand(this.command);
TagDelete.registerOnCommand(this.command);
TagProposalCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> {
// No action
}
}
export default new TagCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import { Option } from 'commander';
import TextStyle from '../../../types/TextStyle';
import SessionManager from '../../../managers/SessionManager';
import ora from 'ora';
type CommandOptions = { name: string, type: 'Language' | 'Framework' | 'Theme' | 'UserDefined' }
class TagCreateCommand extends CommanderCommand {
protected commandName: string = 'create';
protected defineCommand() {
this.command
.description('create a new tag')
.requiredOption('-n, --name <name>', 'name of the tag')
.addOption(new Option('-t, --type <type>', 'type of the tag').choices([ 'Language', 'Framework', 'Theme', 'UserDefined' ]).makeOptionMandatory(true))
.action(this.commandAction.bind(this));
}
private async dataRetrieval(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
const sessionResult = await SessionManager.testSession(true, [ 'admin' ]);
if ( !sessionResult ) {
throw new Error();
}
if ( options.type !== 'UserDefined' && !sessionResult.admin ) {
ora({
text : `Only admins can create non UserDefined tags`,
indent: 4
}).start().fail();
throw new Error();
}
}
private async createTag(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are creating the tag...'));
const tag = await DojoBackendManager.createTag(options.name, options.type);
if ( !tag ) {
throw new Error();
}
}
protected async commandAction(options: CommandOptions): Promise<void> {
try {
await this.dataRetrieval(options);
await this.createTag(options);
} catch ( e ) { /* Do nothing */ }
}
}
export default new TagCreateCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import TextStyle from '../../../types/TextStyle';
import AccessesHelper from '../../../helpers/AccessesHelper';
class TagDeleteCommand extends CommanderCommand {
protected commandName: string = 'delete';
protected defineCommand() {
this.command
.description('Delete a tag')
.argument('<name>', 'name of the tag')
.action(this.commandAction.bind(this));
}
private async dataRetrieval() {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkAdmin();
}
private async deleteTag(name: string) {
console.log(TextStyle.BLOCK('Please wait while we are deleting the tag...'));
if ( !await DojoBackendManager.deleteTag(name) ) {
throw new Error();
}
}
protected async commandAction(name: string): Promise<void> {
try {
await this.dataRetrieval();
await this.deleteTag(name);
} catch ( e ) { /* Do nothing */ }
}
}
export default new TagDeleteCommand();
\ No newline at end of file
import CommanderCommand from '../../../CommanderCommand';
import TagProposalListCommand from './subcommands/TagProposalListCommand';
import TagProposalCreateCommand from './subcommands/TagProposalCreateCommand';
import TagProposalApproveCommand from './subcommands/TagProposalApproveCommand';
import TagProposalDeclineCommand from './subcommands/TagProposalDeclineCommand';
class TagProposalCommand extends CommanderCommand {
protected commandName: string = 'proposal';
protected defineCommand() {
this.command.description('manage tag proposals');
}
protected defineSubCommands() {
TagProposalListCommand.registerOnCommand(this.command);
TagProposalCreateCommand.registerOnCommand(this.command);
TagProposalApproveCommand.registerOnCommand(this.command);
TagProposalDeclineCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> {
// No action
}
}
export default new TagProposalCommand();
\ No newline at end of file
import CommanderCommand from '../../../../CommanderCommand';
import DojoBackendManager from '../../../../../managers/DojoBackendManager';
import TextStyle from '../../../../../types/TextStyle';
import AccessesHelper from '../../../../../helpers/AccessesHelper';
type CommandOptions = { commentary?: string }
abstract class TagProposalAnswerCommand extends CommanderCommand {
protected abstract state: 'Approved' | 'Declined';
protected defineCommand() {
this.command
.description(`${ this.state === 'Approved' ? 'Approve' : 'Decline' } a tag proposition`)
.argument('<name>', 'name of the tag proposition')
.option('-c, --commentary <comment>', 'add a commentary to the answer')
.action(this.commandAction.bind(this));
}
private async dataRetrieval() {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkAdmin();
}
private async answerTag(name: string, options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are answering to the tag proposal...'));
if ( !await DojoBackendManager.answerTagProposal(name, this.state, options.commentary ?? '') ) {
throw new Error();
}
}
protected async commandAction(name: string, options: CommandOptions): Promise<void> {
try {
await this.dataRetrieval();
await this.answerTag(name, options);
} catch ( e ) { /* Do nothing */ }
}
}
export default TagProposalAnswerCommand;
\ No newline at end of file
import TagProposalAnswerCommand from './TagProposalAnswerCommand';
class TagProposalApproveCommand extends TagProposalAnswerCommand {
protected commandName: string = 'approve';
protected state: 'Approved' | 'Declined' = 'Approved';
}
export default new TagProposalApproveCommand();
\ No newline at end of file
import CommanderCommand from '../../../../CommanderCommand';
import DojoBackendManager from '../../../../../managers/DojoBackendManager';
import { Option } from 'commander';
import TextStyle from '../../../../../types/TextStyle';
import AccessesHelper from '../../../../../helpers/AccessesHelper';
type CommandOptions = { name: string, type: 'Language' | 'Framework' | 'Theme' | 'UserDefined' }
class TagProposalCreateCommand extends CommanderCommand {
protected commandName: string = 'create';
protected defineCommand() {
this.command
.description('Propose a new tag')
.requiredOption('-n, --name <name>', 'name of the tag')
.addOption(new Option('-t, --type <type>', 'type of the tag').choices([ 'Language', 'Framework', 'Theme', 'UserDefined' ]).makeOptionMandatory(true))
.action(this.commandAction.bind(this));
}
private async dataRetrieval() {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkTeachingStaff();
}
private async createTagProposal(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are creating the tag proposal...'));
const tag = await DojoBackendManager.createTagProposal(options.name, options.type);
if ( !tag ) {
throw new Error();
}
}
protected async commandAction(options: CommandOptions): Promise<void> {
try {
await this.dataRetrieval();
await this.createTagProposal(options);
} catch ( e ) { /* Do nothing */ }
}
}
export default new TagProposalCreateCommand();
\ No newline at end of file
import TagProposalAnswerCommand from './TagProposalAnswerCommand';
class TagProposalDeclineCommand extends TagProposalAnswerCommand {
protected commandName: string = 'decline';
protected state: 'Approved' | 'Declined' = 'Declined';
}
export default new TagProposalDeclineCommand();
\ No newline at end of file
import CommanderCommand from '../../../../CommanderCommand';
import TagProposal from '../../../../../sharedByClients/models/TagProposal';
import DojoBackendManager from '../../../../../managers/DojoBackendManager';
import TextStyle from '../../../../../types/TextStyle';
import AccessesHelper from '../../../../../helpers/AccessesHelper';
import { Option } from 'commander';
import ora from 'ora';
import Table from 'cli-table3';
type CommandOptions = { state: 'PendingApproval' | 'Approved' | 'Declined' }
class TagProposalListCommand extends CommanderCommand {
protected commandName: string = 'list';
protected defineCommand() {
this.command
.description('Get a tag proposition')
.addOption(new Option('-s, --state <state>', 'state of the tag proposal').choices([ 'PendingApproval', 'Approved', 'Declined' ]).default('PendingApproval'))
.action(this.commandAction.bind(this));
}
private async dataRetrieval() {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkAdmin();
}
private async listTagProposals(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are creating the tag...'));
const spinner: ora.Ora = ora('Retrieving tag proposals...');
const tags = await DojoBackendManager.getTagProposals(options.state);
if ( tags && tags.length > 0 ) {
spinner.succeed(`Tag proposals retrieved. Here is the list of tag proposal with '${ options.state }' state:`);
const table = new Table({
head: [ 'Name', 'Type', 'Details' ]
});
tags.forEach((tag: TagProposal) => {
table.push([ tag.name, tag.type, tag.details ]);
});
console.log(table.toString());
} else if ( tags ) {
spinner.fail(`There is no tag proposal with '${ options.state }' state.`);
throw new Error();
} else {
spinner.fail('Failed to retrieve tag proposals.');
throw new Error();
}
}
protected async commandAction(options: CommandOptions): Promise<void> {
try {
await this.dataRetrieval();
await this.listTagProposals(options);
} catch ( e ) { /* Do nothing */ }
}
}
export default new TagProposalListCommand();
\ No newline at end of file
import getAppDataPath from 'appdata-path';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import GitlabManager from '../managers/GitlabManager';
import ConfigFiles from './ConfigFiles';
import inquirer from 'inquirer';
class Config {
public readonly localConfig: {
folder: string; sessionFile: string; stateFile: string;
};
readonly INTERACTIVE_MODE_CONFIG_KEY = 'interactiveMode';
public gitlabManager!: GitlabManager;
public readonly versionUpdateInformationPeriodHours: number;
public versionUpdateInformationPeriodHours!: number;
public readonly gitlab: {
public gitlab!: {
cliReleasePage: string
cliPreAlphaReleasePage: string
};
public readonly login: {
public login!: {
server: {
port: number, route: string
}, gitlab: {
......@@ -22,50 +26,70 @@ class Config {
}
};
public readonly folders: {
public folders!: {
defaultLocalExercise: string
};
public readonly exercise: {
public exercise!: {
neededFiles: Array<string>
};
public interactiveMode: boolean;
public interactiveMode!: boolean;
constructor() {
this.localConfig = {
folder : getAppDataPath('DojoCLI'),
sessionFile: process.env.LOCAL_CONFIG_FILE_SESSION || '',
stateFile : process.env.LOCAL_CONFIG_STATE || ''
};
constructor() { }
this.versionUpdateInformationPeriodHours = Number(process.env.VERSION_UPDATE_INFORMATION_PERIOD_HOURS || 24);
async init(apiUrl: string) {
await ClientsSharedConfig.init(apiUrl);
const getEnvVar = ClientsSharedConfig.envVarGetter();
this.gitlabManager = new GitlabManager(ClientsSharedConfig.gitlab.URL, ClientsSharedConfig.login.gitlab.client.id, ClientsSharedConfig.login.gitlab.url.redirect, ClientsSharedConfig.login.gitlab.url.token);
this.versionUpdateInformationPeriodHours = Number(getEnvVar('VERSION_UPDATE_INFORMATION_PERIOD_HOURS', '24'));
this.gitlab = {
cliReleasePage: process.env.GITLAB_CLI_RELEASE_PAGE || ''
cliReleasePage : getEnvVar('GITLAB_CLI_RELEASE_PAGE', ''),
cliPreAlphaReleasePage: getEnvVar('GITLAB_CLI_PREALPHA_RELEASE_PAGE', '')
};
this.login = {
server: {
port : Number(process.env.LOGIN_SERVER_PORT || 30992),
route: process.env.LOGIN_SERVER_ROUTE || ''
port : Number(getEnvVar('LOGIN_SERVER_PORT', '30992')),
route: getEnvVar('LOGIN_SERVER_ROUTE', '')
},
gitlab: {
url: {
code: process.env.LOGIN_GITLAB_URL_CODE || ''
code: getEnvVar('LOGIN_GITLAB_URL_CODE', '')
}
}
};
this.folders = {
defaultLocalExercise: process.env.LOCAL_EXERCISE_DEFAULT_FOLDER || './'
defaultLocalExercise: getEnvVar('LOCAL_EXERCISE_DEFAULT_FOLDER', './')
};
this.exercise = {
neededFiles: JSON.parse(process.env.EXERCISE_NEEDED_FILES || '[]')
neededFiles: JSON.parse(getEnvVar('EXERCISE_NEEDED_FILES', '[]'))
};
this.interactiveMode = process.env.INTERACTIVE_MODE === 'true';
const interactiveMode: boolean | null = ConfigFiles.stateConfigFile.getParam(this.INTERACTIVE_MODE_CONFIG_KEY) as boolean | null;
this.interactiveMode = interactiveMode == null ? await this.askInteractiveMode() : interactiveMode;
}
setInteractiveMode(state: boolean) {
ConfigFiles.stateConfigFile.setParam(this.INTERACTIVE_MODE_CONFIG_KEY, state);
}
async askInteractiveMode(): Promise<boolean> {
const state: boolean = (await inquirer.prompt({
name : 'state',
message: 'An interactive mode is available. Do you want to enable it by default ?',
type : 'confirm',
default: true
})).state;
this.setInteractiveMode(state);
return state;
}
}
......
import LocalConfigFile from './LocalConfigFile';
import Config from './Config';
import LocalConfigFile from './LocalConfigFile.js';
import getAppDataPath from 'appdata-path';
const sessionConfigFile = new LocalConfigFile(Config.localConfig.sessionFile);
const stateConfigFile = new LocalConfigFile(Config.localConfig.stateFile);
class ConfigFiles {
private localConfig: {
folder: string; sessionFile: string; stateFile: string;
};
public sessionConfigFile!: LocalConfigFile;
public stateConfigFile!: LocalConfigFile;
export { sessionConfigFile, stateConfigFile };
\ No newline at end of file
constructor() {
this.localConfig = {
folder : getAppDataPath('DojoCLI'),
sessionFile: process.env.LOCAL_CONFIG_FILE_SESSION ?? '',
stateFile : process.env.LOCAL_CONFIG_FILE_STATE ?? ''
};
}
init() {
this.sessionConfigFile = new LocalConfigFile(this.localConfig.folder, this.localConfig.sessionFile);
this.stateConfigFile = new LocalConfigFile(this.localConfig.folder, this.localConfig.stateFile);
}
}
export default new ConfigFiles();
\ No newline at end of file
import * as fs from 'fs';
import Config from './Config';
import JSON5 from 'json5';
class LocalConfigFile {
constructor(private filename: string) {
private readonly folder: string;
private readonly filename: string;
constructor(folder: string, filename: string) {
this.folder = folder;
this.filename = filename;
this.loadConfig();
}
private get configPath(): string {
return `${ Config.localConfig.folder }/${ this.filename }`;
return `${ this.folder }/${ this.filename }`;
}
private _config: { [key: string]: unknown } = {};
loadConfig() {
if ( !fs.existsSync(this.configPath) ) {
fs.mkdirSync(Config.localConfig.folder, { recursive: true });
fs.mkdirSync(this.folder, { recursive: true });
fs.writeFileSync(this.configPath, JSON5.stringify({}));
}
......@@ -28,7 +33,7 @@ class LocalConfigFile {
}
}
getParam(key: string): unknown | null {
getParam(key: string): unknown {
const value = key in this._config ? this._config[key] : null;
if ( value === null ) {
return null;
......
import SessionManager from '../managers/SessionManager';
import GitlabManager from '../managers/GitlabManager';
import SessionManager from '../managers/SessionManager.js';
import Config from '../config/Config';
class AccessesHelper {
async checkStudent(testGitlab: boolean = false): Promise<boolean> {
const sessionResult = await SessionManager.testSession(true, [ 'student' ]);
private async checkAccess(accessName: string, testGitlab: boolean = false) {
const sessionResult = await SessionManager.testSession(true, [ accessName ]);
if ( !sessionResult ) {
return false;
if ( !sessionResult || !(sessionResult as unknown as { [key: string]: boolean })[accessName] ) {
throw new Error();
}
if ( testGitlab ) {
return (await GitlabManager.testToken(true)).every(result => result);
} else {
return true;
if ( testGitlab && !(await Config.gitlabManager.testToken(true)).every(result => result) ) {
throw new Error();
}
}
async checkTeachingStaff(testGitlab: boolean = false): Promise<boolean> {
const sessionResult = await SessionManager.testSession(true, [ 'teachingStaff' ]);
async checkStudent(testGitlab: boolean = false) {
await this.checkAccess('student', testGitlab);
}
if ( !sessionResult || !sessionResult.teachingStaff ) {
return false;
}
async checkTeachingStaff(testGitlab: boolean = false) {
await this.checkAccess('teachingStaff', testGitlab);
}
if ( testGitlab ) {
return (await GitlabManager.testToken(true)).every(result => result);
} else {
return true;
}
async checkAdmin(testGitlab: boolean = false) {
await this.checkAccess('admin', testGitlab);
}
}
......