Skip to content

Commit

Permalink
Merge pull request #4191 from albertgasset/MOBILE-3893
Browse files Browse the repository at this point in the history
MOBILE-3893 assign: Add button to remove submissions
  • Loading branch information
dpalou authored Oct 4, 2024
2 parents 34f9124 + 588df2d commit b5b44a8
Show file tree
Hide file tree
Showing 17 changed files with 493 additions and 84 deletions.
4 changes: 4 additions & 0 deletions scripts/langindex.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,12 @@
"addon.mod_assign.numwords": "moodle",
"addon.mod_assign.outof": "assign",
"addon.mod_assign.overdue": "assign",
"addon.mod_assign.removesubmission": "assign",
"addon.mod_assign.removesubmissionconfirm": "assign",
"addon.mod_assign.removesubmissionconfirmwithtimelimit": "assign",
"addon.mod_assign.submission": "assign",
"addon.mod_assign.submissioneditable": "assign",
"addon.mod_assign.submissionempty": "assign",
"addon.mod_assign.submissionnoteditable": "assign",
"addon.mod_assign.submissionnotsupported": "local_moodlemobileapp",
"addon.mod_assign.submissionslocked": "assign",
Expand Down
12 changes: 12 additions & 0 deletions src/addons/mod/assign/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ADDON_MOD_ASSIGN_GRADED_EVENT,
ADDON_MOD_ASSIGN_PAGE_NAME,
ADDON_MOD_ASSIGN_STARTED_EVENT,
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT,
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL,
Expand Down Expand Up @@ -126,6 +127,17 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
this.siteId,
);

this.savedObserver = CoreEvents.on(
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
(data) => {
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
// Assignment submission removed, refresh data.
this.showLoadingAndRefresh(true, false);
}
},
this.siteId,
);

this.submittedObserver = CoreEvents.on(
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
(data) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,20 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
<div class="list-item-limited-width" *ngIf="canEdit || canSubmit">
<ng-container *ngIf="canEdit">
<ng-container *ngIf=" !unsupportedEditPlugins.length && !showErrorStatementEdit">
<!-- If has offline data, show edit. -->
<ion-button expand="block" class="ion-text-wrap" *ngIf="hasOffline" (click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
<!-- If has offline data, show edit and remove. -->
<div *ngIf="editedOffline" class="adaptable-buttons-row">
<ion-button expand="block" class="ion-margin ion-text-wrap" (click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
<ion-button *ngIf="isRemoveAvailable" expand="block" class="ion-margin ion-text-wrap"
(click)="remove()">
{{ 'addon.mod_assign.removesubmission' | translate }}
</ion-button>
</div>
<!-- If no submission or is new, show add submission. -->
<ion-button expand="block" class="ion-text-wrap" (click)="goToEdit()" *ngIf="!hasOffline &&
(!userSubmission || !userSubmission!.status || userSubmission!.status === statusNew)">
<ion-button expand="block" class="ion-text-wrap" (click)="goToEdit()" *ngIf="!editedOffline &&
(removedOffline || !userSubmission || !userSubmission!.status ||
userSubmission!.status === statusNew)">
<ng-container *ngIf="!assign?.timelimit || userSubmission?.timestarted">
{{ 'addon.mod_assign.addsubmission' | translate }}
</ng-container>
Expand All @@ -182,7 +189,7 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
</ng-container>
</ion-button>
<!-- If reopened, show addfromprevious and addnewattempt. -->
<ng-container *ngIf="!hasOffline && userSubmission?.status === statusReopened">
<ng-container *ngIf="!editedOffline && !removedOffline && userSubmission?.status === statusReopened">
<ion-button *ngIf="!isPreviousAttemptEmpty" expand="block" class="ion-text-wrap"
(click)="copyPrevious()">
{{ 'addon.mod_assign.addnewattemptfromprevious' | translate }}
Expand All @@ -191,12 +198,18 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
{{ 'addon.mod_assign.addnewattempt' | translate }}
</ion-button>
</ng-container>
<!-- Else show editsubmission. -->
<ion-button expand="block" class="ion-text-wrap" *ngIf="!hasOffline && userSubmission &&
userSubmission!.status && userSubmission!.status !== statusNew &&
userSubmission!.status !== statusReopened" (click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
<!-- Else show editsubmission and removesubmission. -->
<div *ngIf="!editedOffline && !removedOffline && userSubmission && userSubmission!.status
&& userSubmission!.status !== statusNew && userSubmission!.status !== statusReopened"
class="adaptable-buttons-row">
<ion-button expand="block" class="ion-margin ion-text-wrap" (click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
<ion-button *ngIf="isRemoveAvailable" expand="block" class="ion-margin ion-text-wrap"
(click)="remove()">
{{ 'addon.mod_assign.removesubmission' | translate }}
</ion-button>
</div>
</ng-container>
<ion-item class="core-danger-item ion-text-wrap"
*ngIf="(unsupportedEditPlugins.length && !showErrorStatementEdit)|| showErrorStatementEdit">
Expand Down
72 changes: 59 additions & 13 deletions src/addons/mod/assign/components/submission/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
ADDON_MOD_ASSIGN_GRADED_EVENT,
ADDON_MOD_ASSIGN_MANUAL_SYNCED,
ADDON_MOD_ASSIGN_PAGE_NAME,
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
ADDON_MOD_ASSIGN_UNLIMITED_ATTEMPTS,
} from '../../constants';
Expand Down Expand Up @@ -96,8 +97,9 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
isSubmittedForGrading = false; // Whether the submission has been submitted for grading.
acceptStatement = false; // Statement accepted (for grading).
feedback?: AddonModAssignSubmissionFeedbackFormatted; // The feedback.
hasOffline = false; // Whether there is offline data.
editedOffline = false; // Whether the submission was added or edited in offline.
submittedOffline = false; // Whether it was submitted in offline.
removedOffline = false; // Whether the submission was removed in offline.
fromDate?: string; // Readable date when the assign started accepting submissions.
currentAttempt = 0; // The current attempt number.
maxAttemptsText: string; // The text for maximum attempts.
Expand All @@ -108,6 +110,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
membersToSubmitBlind: number[] = []; // Team members that need to submit the assignment (blindmarking).
canSubmit = false; // Whether the user can submit for grading.
canEdit = false; // Whether the user can edit the submission.
isRemoveAvailable = false; // Whether WS to remove submission is available.
submissionStatement?: string; // The submission statement.
showErrorStatementEdit = false; // Whether to show an error in edit due to submission statement.
showErrorStatementSubmit = false; // Whether to show an error in submit due to submission statement.
Expand Down Expand Up @@ -406,6 +409,47 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
);
}

/**
* Remove submisson.
*/
async remove(): Promise<void> {
if (!this.assign || !this.userSubmission) {
return;
}
const message = this.assign?.timelimit ?
'addon.mod_assign.removesubmissionconfirmwithtimelimit' :
'addon.mod_assign.removesubmissionconfirm';
try {
await CoreDomUtils.showDeleteConfirm(message);
} catch {
return;
}

const modal = await CoreLoadings.show('core.sending', true);

try {
const sent = await AddonModAssign.removeSubmission(this.assign, this.userSubmission);

if (sent) {
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'assign' });
}

CoreEvents.trigger(
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
{
assignmentId: this.assign.id,
submissionId: this.userSubmission.id,
userId: this.currentUserId,
},
CoreSites.getCurrentSiteId(),
);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error removing submission.');
} finally {
modal.dismiss();
}
}

/**
* Check if there's data to save (grade).
*
Expand Down Expand Up @@ -633,13 +677,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
try {
const submission = await AddonModAssignOffline.getSubmission(this.assign.id, this.submitId);

this.hasOffline = submission && submission.plugindata && Object.keys(submission.plugindata).length > 0;

this.submittedOffline = !!submission?.submitted;
this.removedOffline = submission && Object.keys(submission.plugindata).length == 0;
this.editedOffline = submission && !this.removedOffline;
this.submittedOffline = !!submission?.submitted && !this.removedOffline;
} catch (error) {
// No offline data found.
this.hasOffline = false;
this.editedOffline = false;
this.submittedOffline = false;
this.removedOffline = false;
}
}

Expand Down Expand Up @@ -821,14 +866,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
return;
}

if (this.hasOffline || this.submittedOffline) {
// Offline data.
if (this.editedOffline || this.submittedOffline) {
// Added, edited or submitted offline.
this.statusTranslated = Translate.instant('core.notsent');
this.statusColor = CoreIonicColorNames.WARNING;
} else if (!this.assign.teamsubmission) {

// Single submission.
if (this.userSubmission && this.userSubmission.status != this.statusNew) {
if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) {
this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status);
this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status);
} else {
Expand All @@ -844,10 +889,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
} else {

// Team submission.
if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup) {
if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup && !this.removedOffline) {
this.statusTranslated = Translate.instant('addon.mod_assign.nosubmission');
this.statusColor = AddonModAssign.getSubmissionStatusColor(AddonModAssignSubmissionStatusValues.NO_SUBMISSION);
} else if (this.userSubmission && this.userSubmission.status != this.statusNew) {
} else if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) {
this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status);
this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status);
} else {
Expand Down Expand Up @@ -907,7 +952,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
this.courseId,
acceptStatement,
this.userSubmission.timemodified,
this.hasOffline,
this.editedOffline,
);

// Submitted, trigger event.
Expand Down Expand Up @@ -1142,11 +1187,12 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
this.assign.requiresubmissionstatement = 0;
}

this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (lastAttempt.cansubmit ||
(this.hasOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus)));
this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && !this.removedOffline &&
(lastAttempt.cansubmit || (this.editedOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus)));

this.canEdit = !this.isSubmittedForGrading && lastAttempt.canedit &&
(!this.submittedOffline || !this.assign.submissiondrafts);
this.isRemoveAvailable = AddonModAssign.isRemoveSubmissionAvailable();

// Get submission statement if needed.
if (this.assign.requiresubmissionstatement && this.assign.submissiondrafts && this.submitId == this.currentUserId) {
Expand Down
1 change: 1 addition & 0 deletions src/addons/mod/assign/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL = 'warnoptional';

// Events.
export const ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT = 'addon_mod_assign_submission_saved';
export const ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT = 'addon_mod_assign_submission_removed';
export const ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT = 'addon_mod_assign_submitted_for_grading';
export const ADDON_MOD_ASSIGN_GRADED_EVENT = 'addon_mod_assign_graded';
export const ADDON_MOD_ASSIGN_STARTED_EVENT = 'addon_mod_assign_started';
Expand Down
4 changes: 4 additions & 0 deletions src/addons/mod/assign/lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@
"numwords": "{{$a}} words",
"outof": "{{$a.current}} out of {{$a.total}}",
"overdue": "Assignment is overdue by: {{$a}}",
"removesubmission": "Remove submission",
"removesubmissionconfirm": "Are you sure you want to remove your submission?",
"removesubmissionconfirmwithtimelimit": "Are you sure you want to remove your submission? Please note that this will not reset your time limit.",
"submission": "Submission",
"submissioneditable": "Student can edit this submission",
"submissionempty": "Nothing was submitted",
"submissionnoteditable": "Student cannot edit this submission",
"submissionnotsupported": "This submission is not supported by the app and may not contain all the information.",
"submissionslocked": "This assignment is not accepting submissions",
Expand Down
15 changes: 6 additions & 9 deletions src/addons/mod/assign/pages/edit/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,8 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
this.timeLimitEndTime = 0;
}

try {
// Check if there's any offline data for this submission.
const offlineData = await AddonModAssignOffline.getSubmission(this.assign.id, this.userId);

this.hasOffline = offlineData?.plugindata && Object.keys(offlineData.plugindata).length > 0;
} catch {
// No offline data found.
this.hasOffline = false;
}
// Check if there's any offline data for this submission.
this.hasOffline = await CoreUtils.promiseWorks(AddonModAssignOffline.getSubmission(this.assign.id, this.userId));

CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
Expand Down Expand Up @@ -398,6 +391,10 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
throw Translate.instant('addon.mod_assign.acceptsubmissionstatement');
}

if (AddonModAssignHelper.isSubmissionEmptyForEdit(this.assign!, this.userSubmission!, inputData)) {
throw Translate.instant('addon.mod_assign.submissionempty');
}

let modal = await CoreLoadings.show();
let size = -1;

Expand Down
Loading

0 comments on commit b5b44a8

Please sign in to comment.