263 lines
10 KiB
TypeScript
263 lines
10 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { Params } from '@angular/router';
|
|
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
|
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { Translate } from '@singletons';
|
|
import { CoreIonicColorNames } from '@singletons/colors';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import {
|
|
AddonModAssign,
|
|
AddonModAssignAssign,
|
|
AddonModAssignGradingStates,
|
|
AddonModAssignSubmission,
|
|
AddonModAssignSubmissionStatusValues,
|
|
} from '../services/assign';
|
|
import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../services/assign-helper';
|
|
import { AddonModAssignOffline } from '../services/assign-offline';
|
|
import { AddonModAssignSync, AddonModAssignSyncProvider } from '../services/assign-sync';
|
|
|
|
/**
|
|
* Provides a collection of assignment submissions.
|
|
*/
|
|
export class AddonModAssignSubmissionsSource extends CoreRoutedItemsManagerSource<AddonModAssignSubmissionForList> {
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
static getSourceId(courseId: number, moduleId: number, selectedStatus?: AddonModAssignListFilterName): string {
|
|
const statusId = selectedStatus ?? '__empty__';
|
|
|
|
return `submissions-${courseId}-${moduleId}-${statusId}`;
|
|
}
|
|
|
|
readonly COURSE_ID: number;
|
|
readonly MODULE_ID: number;
|
|
readonly SELECTED_STATUS: AddonModAssignListFilterName | undefined;
|
|
|
|
assign?: AddonModAssignAssign;
|
|
groupId = 0;
|
|
groupInfo: CoreGroupInfo = {
|
|
groups: [],
|
|
separateGroups: false,
|
|
visibleGroups: false,
|
|
defaultGroupId: 0,
|
|
canAccessAllGroups: false,
|
|
};
|
|
|
|
protected submissionsData: { canviewsubmissions: boolean; submissions?: AddonModAssignSubmission[] } = {
|
|
canviewsubmissions: false,
|
|
};
|
|
|
|
constructor(courseId: number, moduleId: number, selectedStatus?: AddonModAssignListFilterName) {
|
|
super();
|
|
|
|
this.COURSE_ID = courseId;
|
|
this.MODULE_ID = moduleId;
|
|
this.SELECTED_STATUS = selectedStatus;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
getItemPath(submission: AddonModAssignSubmissionForList): string {
|
|
return String(submission.submitid);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
getItemQueryParams(submission: AddonModAssignSubmissionForList): Params {
|
|
return {
|
|
blindId: submission.blindid,
|
|
groupId: this.groupId,
|
|
selectedStatus: this.SELECTED_STATUS,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Invalidate assignment cache.
|
|
*/
|
|
async invalidateCache(): Promise<void> {
|
|
await Promise.all([
|
|
AddonModAssign.invalidateAssignmentData(this.COURSE_ID),
|
|
this.assign && AddonModAssign.invalidateAllSubmissionData(this.assign.id),
|
|
this.assign && AddonModAssign.invalidateAssignmentUserMappingsData(this.assign.id),
|
|
this.assign && AddonModAssign.invalidateAssignmentGradesData(this.assign.id),
|
|
this.assign && AddonModAssign.invalidateListParticipantsData(this.assign.id),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Load assignment.
|
|
*/
|
|
async loadAssignment(sync: boolean = false): Promise<void> {
|
|
// Get assignment data.
|
|
this.assign = await AddonModAssign.getAssignment(this.COURSE_ID, this.MODULE_ID);
|
|
|
|
if (sync) {
|
|
try {
|
|
// Try to synchronize data.
|
|
const result = await AddonModAssignSync.syncAssign(this.assign.id);
|
|
|
|
if (result && result.updated) {
|
|
CoreEvents.trigger(
|
|
AddonModAssignSyncProvider.MANUAL_SYNCED,
|
|
{
|
|
assignId: this.assign.id,
|
|
warnings: result.warnings,
|
|
gradesBlocked: result.gradesBlocked,
|
|
context: 'submission-list',
|
|
},
|
|
CoreSites.getCurrentSiteId(),
|
|
);
|
|
}
|
|
} catch (error) {
|
|
// Ignore errors, probably user is offline or sync is blocked.
|
|
}
|
|
}
|
|
|
|
// Get assignment submissions.
|
|
this.submissionsData = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.assign.cmid });
|
|
|
|
if (!this.submissionsData.canviewsubmissions) {
|
|
// User shouldn't be able to reach here.
|
|
throw new Error('Cannot view submissions.');
|
|
}
|
|
|
|
// Check if groupmode is enabled to avoid showing wrong numbers.
|
|
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false);
|
|
|
|
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
|
|
|
|
await this.reload();
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
protected async loadPageItems(): Promise<{ items: AddonModAssignSubmissionForList[] }> {
|
|
const assign = this.assign;
|
|
|
|
if (!assign) {
|
|
throw new Error('Can\'t load submissions without assignment');
|
|
}
|
|
|
|
// Fetch submissions and grades.
|
|
let submissions =
|
|
await AddonModAssignHelper.getSubmissionsUserData(
|
|
assign,
|
|
this.submissionsData.submissions,
|
|
this.groupId,
|
|
);
|
|
|
|
// Get assignment grades only if workflow is not enabled to check grading date.
|
|
let grades = !assign.markingworkflow
|
|
? await AddonModAssign.getAssignmentGrades(assign.id, { cmId: assign.cmid })
|
|
: [];
|
|
|
|
// Remove grades (not graded) and sort by timemodified DESC to allow finding quicker.
|
|
grades = grades.filter((grade) => parseInt(grade.grade, 10) >= 0).sort((a, b) => b.timemodified - a.timemodified);
|
|
// Filter the submissions to get only the ones with the right status and add some extra data.
|
|
if (this.SELECTED_STATUS == AddonModAssignListFilterName.NEED_GRADING) {
|
|
const promises: Promise<void>[] = submissions.map(async (submission: AddonModAssignSubmissionForList) => {
|
|
// Only show the submissions that need to be graded.
|
|
submission.needsGrading = await AddonModAssign.needsSubmissionToBeGraded(submission, assign.id);
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
|
|
submissions = submissions.filter((submission: AddonModAssignSubmissionForList) => submission.needsGrading);
|
|
} else if (this.SELECTED_STATUS) {
|
|
const searchStatus = this.SELECTED_STATUS == AddonModAssignListFilterName.DRAFT
|
|
? AddonModAssignSubmissionStatusValues.DRAFT
|
|
: AddonModAssignSubmissionStatusValues.SUBMITTED;
|
|
|
|
submissions = submissions.filter((submission: AddonModAssignSubmissionForList) => submission.status == searchStatus);
|
|
}
|
|
|
|
const showSubmissions: AddonModAssignSubmissionForList[] = await Promise.all(
|
|
submissions.map(async (submission: AddonModAssignSubmissionForList) => {
|
|
const gradeData =
|
|
await CoreUtils.ignoreErrors(AddonModAssignOffline.getSubmissionGrade(assign.id, submission.userid));
|
|
|
|
// Load offline grades.
|
|
const notSynced = !!gradeData && submission.timemodified < gradeData.timemodified;
|
|
|
|
if (!assign.markingworkflow) {
|
|
// Get the last grade of the submission.
|
|
const grade = grades.find((grade) => grade.userid == submission.userid);
|
|
|
|
if (grade) {
|
|
// Override status if grade is found.
|
|
submission.gradingstatus = grade.timemodified < submission.timemodified
|
|
? AddonModAssignGradingStates.GRADED_FOLLOWUP_SUBMIT
|
|
: AddonModAssignGradingStates.GRADED;
|
|
}
|
|
}
|
|
submission.statusColor = AddonModAssign.getSubmissionStatusColor(submission.status);
|
|
submission.gradingColor = AddonModAssign.getSubmissionGradingStatusColor(
|
|
submission.gradingstatus,
|
|
);
|
|
|
|
submission.statusTranslated = Translate.instant(
|
|
'addon.mod_assign.submissionstatus_' + submission.status,
|
|
);
|
|
|
|
if (notSynced) {
|
|
submission.gradingStatusTranslationId = 'addon.mod_assign.gradenotsynced';
|
|
submission.gradingColor = '';
|
|
} else if (submission.statusColor != CoreIonicColorNames.DANGER ||
|
|
submission.gradingColor != CoreIonicColorNames.DANGER) {
|
|
// Show grading status if one of the statuses is not done.
|
|
submission.gradingStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId(
|
|
submission.gradingstatus,
|
|
);
|
|
} else {
|
|
submission.gradingStatusTranslationId = '';
|
|
}
|
|
|
|
return submission;
|
|
}),
|
|
);
|
|
|
|
return { items: showSubmissions };
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Calculated data for an assign submission.
|
|
*/
|
|
export type AddonModAssignSubmissionForList = AddonModAssignSubmissionFormatted & {
|
|
statusColor?: string; // Calculated in the app. Color of the submission status.
|
|
gradingColor?: string; // Calculated in the app. Color of the submission grading status.
|
|
statusTranslated?: string; // Calculated in the app. Translated text of the submission status.
|
|
gradingStatusTranslationId?: string; // Calculated in the app. Key of the text of the submission grading status.
|
|
needsGrading?: boolean; // Calculated in the app. If submission and grading status means that it needs grading.
|
|
};
|
|
|
|
/**
|
|
* List filter by status name.
|
|
*/
|
|
export enum AddonModAssignListFilterName {
|
|
ALL = '',
|
|
NEED_GRADING = 'needgrading',
|
|
DRAFT = 'draft',
|
|
SUBMITTED = 'submitted',
|
|
};
|