Skip to content

Commit

Permalink
Added the feature "Delete problems in batch" (omegaup#7672)
Browse files Browse the repository at this point in the history
# Description

Se agrega la funcionalidad de "Eliminar problemas" desde el listado de
"Mis problemas".

Esto sirve para poder eliminar los problemas que se crearon con algún
error al cargar el archivo .zip, y esto ocasiona que no se pueda
ingresar a la vista de detalles del problema.

También se agrega la opción de "Eliminar problemas en batch", que
funciona al seleccionar varios problemas. Esta opción sólo se habilita
si los problemas seleccionados pueden ser eliminados. Recordar que no es
posible eliminar problemas que ya tienen envíos realizados.


![DeleteProblemsInBatch](https://github.com/omegaup/omegaup/assets/3230352/40d86fbd-28da-4a2e-b687-855971f41a13)

Fixes: omegaup#7452 

# Checklist:

- [x] The code follows the [coding
guidelines](https://github.com/omegaup/omegaup/wiki/Coding-guidelines)
of omegaUp.
- [x] The tests were executed and all of them passed.
- [x] If you are creating a feature, the new tests were added.
- [x] If the change is large (> 200 lines), this PR was split into
various Pull Requests. It's preferred to create one PR for changes in
controllers + unit tests in PHPUnit, and then another Pull Request for
UI + tests in Jest, Cypress or both.

---------

Co-authored-by: Anas Iqbal <[email protected]>
Co-authored-by: iqbal <[email protected]>
Co-authored-by: Hugo Dueñas <[email protected]>
  • Loading branch information
4 people authored Jul 5, 2024
1 parent 55a7a99 commit 2bc420a
Show file tree
Hide file tree
Showing 20 changed files with 306 additions and 79 deletions.
5 changes: 3 additions & 2 deletions cypress/e2e/basic_commands.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LoginOptions, RunOptions } from '../support/types';
import { loginPage } from '../support/pageObjects/loginPage';
import { coursePage } from '../support/pageObjects/coursePage';
import { contestPage } from '../support/pageObjects/contestPage';
import { problemPage } from '../support/pageObjects/problemPage';

describe('Basic Commands Test', () => {
beforeEach(() => {
Expand Down Expand Up @@ -74,15 +75,15 @@ describe('Basic Commands Test', () => {

it('Should create a problem', () => {
const loginOptions = loginPage.registerMultipleUsers(1);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
cy.login(loginOptions[0]);
cy.createProblem(problemOptions[0]);
coursePage.verifyProblem(problemOptions[0]);
cy.logout();
});

it('Should make a run of a problem', () => {
const problemOptions = contestPage.generateProblemOptions(1)[0];
const problemOptions = problemPage.generateProblemOptions(1)[0];

const runOptions: RunOptions = {
problemAlias: problemOptions.problemAlias,
Expand Down
23 changes: 12 additions & 11 deletions cypress/e2e/course.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { loginPage } from '../support/pageObjects/loginPage';
import { contestPage } from '../support/pageObjects/contestPage';
import { profilePage } from '../support/pageObjects/profilePage';
import getEditorIframeBody from '../support/pageObjects/util';
import { problemPage } from '../support/pageObjects/problemPage';

describe('Course Test', () => {
let loginOptions: LoginOptions[] = [];
Expand All @@ -27,7 +28,7 @@ describe('Course Test', () => {
const users = [loginOptions[0].username];
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
problemOptions = contestPage.generateProblemOptions(1);
problemOptions = problemPage.generateProblemOptions(1);

// We are creating an course with assignment that end after two minutes
// so that it can be used as a past assignment which will be tested later
Expand Down Expand Up @@ -56,7 +57,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
cy.login(loginOptions[1]);
cy.createProblem(problemOptions[0]);
coursePage.createCourse(courseOptions);
Expand Down Expand Up @@ -130,7 +131,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
const runOptions: RunOptions = {
problemAlias: problemOptions[0].problemAlias,
fixturePath: 'main.cpp',
Expand Down Expand Up @@ -212,7 +213,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);

cy.login(loginOptions[1]);
cy.createProblem(problemOptions[0]);
Expand Down Expand Up @@ -250,7 +251,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
const runOptions: RunOptions = {
problemAlias: problemOptions[0].problemAlias,
fixturePath: 'main.cpp',
Expand Down Expand Up @@ -292,7 +293,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);

cy.login(loginOptions[0]);
cy.createProblem(problemOptions[0]);
Expand Down Expand Up @@ -339,7 +340,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
const runOptions: RunOptions = {
problemAlias: problemOptions[0].problemAlias,
fixturePath: 'main.cpp',
Expand Down Expand Up @@ -408,7 +409,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_1' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(2);
const problemOptions = problemPage.generateProblemOptions(2);
const problems = [
problemOptions[0].problemAlias,
problemOptions[1].problemAlias,
Expand Down Expand Up @@ -477,7 +478,7 @@ describe('Course Test', () => {
const courseOptions = coursePage.generateCourseOptions();
const assignmentAlias = 'ut_rank_hw_' + uuid();
const shortAlias = assignmentAlias.slice(0, 12);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);

cy.login(loginOptions[0]);
cy.createProblem(problemOptions[0]);
Expand Down Expand Up @@ -515,8 +516,8 @@ describe('Course Test', () => {
const shortAlias1 = assignmentAlias1.slice(0, 12);
const assignmentAlias2 = 'ut_rank_hw_2' + uuid();
const shortAlias2 = assignmentAlias2.slice(0, 12);
const problemOptions1 = contestPage.generateProblemOptions(1);
const problemOptions2 = contestPage.generateProblemOptions(1);
const problemOptions1 = problemPage.generateProblemOptions(1);
const problemOptions2 = problemPage.generateProblemOptions(1);
const courseUrl = '/course/' + courseOptions.courseAlias;
const studentsProgressUrl =
'/course/' + courseOptions.courseAlias + '/students/';
Expand Down
86 changes: 64 additions & 22 deletions cypress/e2e/problem_collection.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { contestPage } from '../support/pageObjects/contestPage';
import { loginPage } from '../support/pageObjects/loginPage';
import { problemPage } from '../support/pageObjects/problemPage';
import { profilePage } from '../support/pageObjects/profilePage';
import { RunOptions } from '../support/types';

describe('Problem Collection Test', () => {
Expand All @@ -14,7 +15,7 @@ describe('Problem Collection Test', () => {
const loginOptions = loginPage.registerMultipleUsers(1);
const languages = ['all', 'en', 'es', 'pt'];

const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);

cy.login(loginOptions[0]);
cy.createProblem(problemOptions[0]);
Expand All @@ -29,7 +30,7 @@ describe('Problem Collection Test', () => {

it('Should search a problem and solve it', () => {
const loginOptions = loginPage.registerMultipleUsers(1);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);

const runOptions: RunOptions = {
problemAlias: problemOptions[0].problemAlias,
Expand All @@ -49,34 +50,34 @@ describe('Problem Collection Test', () => {
cy.logout();
});

// Uncomment this test when the following issue is fixed
// Unskip this test when the following issue is fixed
// https://github.com/omegaup/omegaup/issues/7218

// it('Should add additional tags to a problem as admin', () => {
// const loginOptions = loginPage.registerMultipleUsers(1);
// const problemOptions = contestPage.generateProblemOptions(1);
// problemOptions[0].problemLevelIndex = 5;
it.skip('Should add additional tags to a problem as admin', () => {
const loginOptions = loginPage.registerMultipleUsers(1);
const problemOptions = problemPage.generateProblemOptions(1);
problemOptions[0].problemLevelIndex = 5;

// cy.login(loginOptions[0]);
// cy.createProblem(problemOptions[0]);
// cy.logout();
cy.login(loginOptions[0]);
cy.createProblem(problemOptions[0]);
cy.logout();

// cy.loginAdmin();
// problemPage.navigateToAllProblemsTab();
// problemPage.verifyFilterByAlias(problemOptions[0].problemAlias);
// problemPage.openProblem(problemOptions[0].problemAlias);
// problemPage.qualifyProblem(['Dynamic programming', 'Backtracking']);
// cy.logout();
cy.loginAdmin();
problemPage.navigateToAllProblemsTab();
problemPage.verifyFilterByAlias(problemOptions[0].problemAlias);
problemPage.openProblem(problemOptions[0].problemAlias);
problemPage.qualifyProblem(['Dynamic programming', 'Backtracking']);
cy.logout();

// cy.login(loginOptions[0]);
// problemPage.navigateToAllProblemsTab();
// problemPage.verifyFilterByAlias(problemOptions[0].problemAlias);
// cy.logout();
// });
cy.login(loginOptions[0]);
problemPage.navigateToAllProblemsTab();
problemPage.verifyFilterByAlias(problemOptions[0].problemAlias);
cy.logout();
});

it('Should report a problem', () => {
const loginOptions = loginPage.registerMultipleUsers(2);
const problemOptions = contestPage.generateProblemOptions(1);
const problemOptions = problemPage.generateProblemOptions(1);
problemOptions[0].publicAccess = true;

cy.login(loginOptions[0]);
Expand All @@ -99,4 +100,45 @@ describe('Problem Collection Test', () => {
problemPage.verifyBan(problemOptions[0].problemAlias);
cy.logout();
});


it('Should be able to remove problems with no submissions', () => {
const numberOfProblems = 3;
const numberOfUsers = 1;
const loginOptions = loginPage.registerMultipleUsers(numberOfUsers);

const problemOptions = problemPage.generateProblemOptions(numberOfProblems);

cy.login(loginOptions[0]);
// Create 3 problems
cy.createProblem(problemOptions[0]);
cy.createProblem({ ...problemOptions[1], firstTimeVisited: false });
cy.createProblem({ ...problemOptions[2], firstTimeVisited: false });

profilePage.navigateToMyProblemsPage();

// Verify that the problems are displayed
problemOptions.forEach((problem) => {
profilePage.verifyProblemVisibility(problem.problemAlias);
});

// Delete single problem
profilePage.deleteProblem(problemOptions[0].problemAlias);

// Problem 0 should not be visible
profilePage.verifyProblemVisibility(problemOptions[0].problemAlias, false);

// Delete problems in batch
const problemAliases = problemOptions
.filter((problem) => problem.problemAlias !== problemOptions[0].problemAlias)
.map((problem) => problem.problemAlias);
profilePage.deleteProblemsInBatch(problemAliases);

// None of the problems should be visible
problemOptions.forEach((problem) => {
profilePage.verifyProblemVisibility(problem.problemAlias, false);
});

cy.logout();
});
});
22 changes: 3 additions & 19 deletions cypress/support/pageObjects/contestPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'cypress-file-upload';
import 'cypress-wait-until';
import { v4 as uuid } from 'uuid';
import { problemPage } from './problemPage';

import {
ContestOptions,
Expand Down Expand Up @@ -160,11 +161,11 @@ export class ContestPage {
firstTimeVisited: boolean = true,
numberOfProblems: number = 1,
): ContestOptions {
const problems = this.generateProblemOptions(numberOfProblems);
const problems = problemPage.generateProblemOptions(numberOfProblems);
const contestProblems: ProblemOptions[] = [];
const contestRuns: RunOptions[] = [];

problems.forEach( (problem) => {
problems.forEach( (problem: ProblemOptions) => {
problem.firstTimeVisited = firstTimeVisited;

cy.login(loginOption);
Expand Down Expand Up @@ -204,23 +205,6 @@ export class ContestPage {
return contestOptions;
}

generateProblemOptions(noOfProblems: number): ProblemOptions[] {
const problems: ProblemOptions[] = [];

for (let i = 0; i < noOfProblems; i++) {
const problemOptions: ProblemOptions = {
problemAlias: uuid().slice(0, 10),
tag: 'Recursion',
autoCompleteTextTag: 'recur',
problemLevelIndex: 0,
};

problems.push(problemOptions);
}

return problems;
}

setPasswordForIdentity(identityName: string, password: string): void {
cy.get('[data-identity-change-password]').first().click();
cy.get('input[type=password]').first().type(password);
Expand Down
20 changes: 19 additions & 1 deletion cypress/support/pageObjects/problemPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RunOptions } from '../types';
import { ProblemOptions, RunOptions } from '../types';
import { v4 as uuid } from 'uuid';

export class ProblemPage {
navigateToAllProblemsTab(): void {
Expand Down Expand Up @@ -86,6 +87,23 @@ export class ProblemPage {
cy.get('[data-filter-submit-button]').click();
cy.get('[data-problem-title-list]').should('not.exist');
}

generateProblemOptions(noOfProblems: number): ProblemOptions[] {
const problems: ProblemOptions[] = [];

for (let i = 0; i < noOfProblems; i++) {
const problemOptions: ProblemOptions = {
problemAlias: uuid().slice(0, 10),
tag: 'Recursion',
autoCompleteTextTag: 'recur',
problemLevelIndex: 0,
};

problems.push(problemOptions);
}

return problems;
}
}

export const problemPage = new ProblemPage();
29 changes: 29 additions & 0 deletions cypress/support/pageObjects/profilePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,35 @@ export class ProfilePage {
cy.get('[data-preference-save-button]').click();
cy.get('#alert-close').click();
}

navigateToMyProblemsPage(): void {
cy.get('[data-nav-user]').click();
cy.get('a[href="/profile/#created-content"]').click();
cy.get('a[href="/problem/mine/"]').click();
}

verifyProblemVisibility(problemAlias: string, visible: boolean = true): void {
if (!visible) {
cy.get(`a[href="/arena/problem/${problemAlias}/"]`).should('not.exist');
return;
}
cy.get(`a[href="/arena/problem/${problemAlias}/"]`).should('be.visible');
}

deleteProblem(problemAlias: string): void {
cy.get(`[data-delete-problem="${problemAlias}"]`).click();
cy.get('.modal-footer>button.btn-danger').click();
}

deleteProblemsInBatch(problemAliases: string[]): void {
problemAliases.forEach((problemAlias) => {
cy.get(`input[type="checkbox"][data-selected-problem="${problemAlias}"]`).click();
});
cy.get('select[data-selected-problems]').select("2");
cy.get("[data-visibility-action]").click();
cy.get('.modal-footer>button.btn-danger').click();
}

createTeamGroup(teamGroupOptions: TeamGroupOptions): void {
cy.get('[data-nav-user]').click();
cy.get('[data-nav-user-teams-groups]').click();
Expand Down
4 changes: 3 additions & 1 deletion frontend/templates/en.lang
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,9 @@ problemEditCommitMessage = "Update description"
problemEditDeleteCancel = "Cancel"
problemEditDeleteConfirmationMessage = "Are you sure you want to delete the problem?"
problemEditDeleteOk = "Delete"
problemEditDeleteRequireConfirmation = "Confirmation Required"
problemEditDeleteRequireConfirmation = "Delete problem %(problemAlias)"
problemEditDeleteSelectedProblemsConfirmationMessage = "Are you sure you want to delete the selected problems?"
problemEditDeleteSelectedProblemsRequireConfirmation = "Delete selected problems"
problemEditEditMarkdown = "Edit statement"
problemEditEditProblem = "Edit problem"
problemEditEmailClarifications = "Receive questions about the problem by email"
Expand Down
4 changes: 3 additions & 1 deletion frontend/templates/es.lang
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,9 @@ problemEditCommitMessage = "Descripción del cambio"
problemEditDeleteCancel = "Cancelar"
problemEditDeleteConfirmationMessage = "¿Realmente deseas eliminar el problema?"
problemEditDeleteOk = "Eliminar"
problemEditDeleteRequireConfirmation = "confirmación requerida"
problemEditDeleteRequireConfirmation = "Eliminar problema %(problemAlias)"
problemEditDeleteSelectedProblemsConfirmationMessage = "¿Realmente deseas eliminar los problemas seleccionados?"
problemEditDeleteSelectedProblemsRequireConfirmation = "Eliminar problemas seleccionados"
problemEditEditMarkdown = "Editar Redacción"
problemEditEditProblem = "Editar Problema"
problemEditEmailClarifications = "Recibir preguntas acerca del problema por correo"
Expand Down
4 changes: 3 additions & 1 deletion frontend/templates/pseudo.lang

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2bc420a

Please sign in to comment.