diff --git a/src/app/api/models/task-definition.ts b/src/app/api/models/task-definition.ts index c8f88d1e9e..704884b229 100644 --- a/src/app/api/models/task-definition.ts +++ b/src/app/api/models/task-definition.ts @@ -1,333 +1,208 @@ -import {HttpClient} from '@angular/common/http'; -import {Entity, EntityCache, EntityMapping} from 'ngx-entity-service'; -import {Observable, tap} from 'rxjs'; -import {AppInjector} from 'src/app/app-injector'; -import {AlertService} from 'src/app/common/services/alert.service'; -import {DoubtfireConstants} from 'src/app/config/constants/doubtfire-constants'; -import {TaskDefinitionService} from '../services/task-definition.service'; -import {Grade, GroupSet, LearningOutcome, Project, TutorialStream, Unit} from './doubtfire-model'; -import {Task} from './doubtfire-model'; -import {TaskPrerequisite} from './task-prerequisite'; -import {DiscussionPrompt} from './discussion-prompt'; -import {OverseerStep} from './overseer/overseer-step'; - -export type UploadRequirement = { - key: string; - name: string; - type: string; - tiiCheck?: boolean; - tiiPct?: number; -}; - -export type SimilarityCheck = {key: string; type: string; pattern: string}; - -export class TaskDefinition extends Entity { - id: number; - seq: number; - abbreviation: string; - name: string; - description: string; - weighting: number; - targetGrade: number; - targetDate: Date; - dueDate: Date; - startDate: Date; - uploadRequirements: UploadRequirement[] = []; - tutorialStream: TutorialStream = null; - plagiarismChecks: SimilarityCheck[] = []; - plagiarismReportUrl: string; - plagiarismWarnPct: number; - restrictStatusUpdates: boolean; - // groupSetId: number; - groupSet: GroupSet = null; - hasTaskSheet: boolean; - hasTaskResources: boolean; - scormEnabled: boolean; - hasScormData: boolean; - scormAllowReview: boolean; - scormBypassTest: boolean; - scormTimeDelayEnabled: boolean; - scormAttemptLimit: number = 0; - hasTaskAssessmentResources: boolean; - hasTaskAssessmentScript: boolean; - isGraded: boolean; - maxQualityPts: number; - overseerImageId: number; - assessmentEnabled: boolean; - similarityLanguage: string = 'c'; - hasJplagReport: boolean; - assessInPortfolioOnly: boolean; - requiresDiscussion: boolean; - useResourcesForJplagBaseCode: boolean; - lockAssessmentsToTutorialStream: boolean; - discussionPromptsCount: number; - overseerResourceFiles: string[] = []; - - // pTargetDate: Date; - cTargetDate: Date; - dTargetDate: Date; - hdTargetDate: Date; - - cStartDate: Date; - dStartDate: Date; - hdStartDate: Date; - - public readonly taskPrerequisitesCache: EntityCache = - new EntityCache(); - - public readonly discussionPromptsCache: EntityCache = - new EntityCache(); - - public readonly learningOutcomesCache: EntityCache = - new EntityCache(); - - public readonly overseerStepsCache: EntityCache = new EntityCache(); - - readonly unit: Unit; - - constructor(unit: Unit) { - super(); - this.unit = unit; - } - - public toJson(mappingData: EntityMapping, ignoreKeys?: string[]): object { - return { - task_def: super.toJson(mappingData, ignoreKeys), - }; - } - - /** - * Save the task definition - */ - public save(): Observable { - const svc = AppInjector.get(TaskDefinitionService); - - if (this.isNew) { - // TODO: add progress modal - return svc.create( - { - unitId: this.unit.id, - }, - { - entity: this, - cache: this.unit.taskDefinitionCache, - constructorParams: this.unit, - }, - ); - } else { - return svc.update( - { - unitId: this.unit.id, - id: this.id, - }, - {entity: this}, - ); - } - } - - private originalSaveData: string; - - public get hasOriginalSaveData(): boolean { - return this.originalSaveData !== undefined && this.originalSaveData !== null; - } - - /** - * To check if things have changed, we need to get the initial save data... as it - * isn't empty by default. We can then use - * this to check if there are changes. - * - * @param mapping the mapping to get changes - */ - public setOriginalSaveData(mapping: EntityMapping) { - this.originalSaveData = JSON.stringify(this.toJson(mapping)); - } - - public hasChanges(mapping: EntityMapping): boolean { - if (!this.originalSaveData) { - return false; - } - - return this.originalSaveData != JSON.stringify(this.toJson(mapping)); - } - - public refresh(): void { - const alerts = AppInjector.get(AlertService); - AppInjector.get(TaskDefinitionService) - .fetch({ - unitId: this.unit.id, - id: this.id, - }) - .subscribe({ - next: (taskDefinition) => { - console.log(taskDefinition.name); - }, - error: (message) => alerts.error(message, 6000), - }); - } - - public get isNew(): boolean { - return !this.id; - } - - public get unitId(): number { - return this.unit.id; - } - - public localDueDate(): Date { - return this.targetDate; - } - - public localDeadlineDate(): Date { - return this.dueDate; - } - - public get dueWeek(): number { - const startDate = this.unit.startDate; - const dueDate = this.localDueDate() || this.unit.endDate; - - const diffInMs = dueDate.getTime() - startDate.getTime(); - return Math.ceil(diffInMs / (1000 * 60 * 60 * 24 * 7)); // Convert ms to weeks - } - - public matches(text: string): boolean { - return ( - this.abbreviation.toLowerCase().indexOf(text) !== -1 || - this.name.toLowerCase().indexOf(text) !== -1 - ); - } - - /** - * The final deadline for task submission. - * - * @returns the final due date - */ - public finalDeadlineDate(): Date { - return this.dueDate; // now in due date - } - - public isGroupTask(): boolean { - return this.groupSet !== null && this.groupSet !== undefined; - } - - public getTaskPDFUrl(asAttachment: boolean = false): string { - const constants = AppInjector.get(DoubtfireConstants); - return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/task_pdf.json${ - asAttachment ? '?as_attachment=true' : '' - }`; - } - - public getTaskResourcesUrl(asAttachment: boolean = false) { - const constants = AppInjector.get(DoubtfireConstants); - return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/task_resources.json${ - asAttachment ? '?as_attachment=true' : '' - }`; - } - - public getScormDataUrl(asAttachment: boolean = false) { - const constants = AppInjector.get(DoubtfireConstants); - return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/scorm_data.json${ - asAttachment ? '?as_attachment=true' : '' - }`; - } - - public getOutcomeBatchUploadUrl(): string { - const constants = AppInjector.get(DoubtfireConstants); - return `${constants.API_URL}/task_definitions/${this.id}/outcomes/csv`; - } - - public getFeedbackTemplateBatchUploadUrl(): string { - const constants = AppInjector.get(DoubtfireConstants); - return `${constants.API_URL}/task_definitions/${this.id}/feedback_chips/csv`; - } - - /** - * Open the SCORM test in a new tab - using preview mode. - */ - public previewScormTest(): void { - window.open(`#/task_def_id/${this.id}/preview-scorm`, '_blank'); - } - - public get targetGradeText(): string { - return Grade.GRADES[this.targetGrade]; - } - - public hasPlagiarismCheck(): boolean { - return this.plagiarismChecks?.length > 0; - } - - public get needsJplag(): boolean { - return this.uploadRequirements.some((upreq) => upreq.type === 'code' && upreq.tiiCheck); - } - - public get taskSheetUploadUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/task_sheet`; - } - - public get taskResourcesUploadUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/task_resources`; - } - - public get scormDataUploadUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/scorm_data`; - } - - public get taskPrerequisiteUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/prerequisites`; - } - - public get taskOverseerResourcesUploadUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/task_assessment_resources`; - } - - public getOverseerResourcesUrl(): string { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ - this.id - }/task_assessment_resources.json`; - } - - public get taskOverseerExecutionScriptUrl() { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${this.id}/overseer_script`; - } - - public getJplagReportUrl() { - return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${this.id}/jplag_report`; - } - - public deleteTaskSheet(): Observable { - const httpClient = AppInjector.get(HttpClient); - return httpClient.delete(this.taskSheetUploadUrl).pipe(tap(() => (this.hasTaskSheet = false))); - } - - public deleteTaskResources(): Observable { - const httpClient = AppInjector.get(HttpClient); - return httpClient - .delete(this.taskResourcesUploadUrl) - .pipe(tap(() => (this.hasTaskResources = false))); - } - - public deleteScormData(): Observable { - const httpClient = AppInjector.get(HttpClient); - return httpClient.delete(this.scormDataUploadUrl).pipe(tap(() => (this.hasScormData = false))); - } - - public deleteOverseerResources(): Observable { - const httpClient = AppInjector.get(HttpClient); - return httpClient - .delete(this.taskOverseerResourcesUploadUrl) - .pipe(tap(() => (this.hasTaskAssessmentResources = false))); - } - - public projectTask(project?: Project): Task | undefined { - return project?.tasks?.find((p) => p.definition.id === this.id); - } -} +import { HttpClient } from '@angular/common/http'; +import { Entity, EntityMapping } from 'ngx-entity-service'; +import { Observable, tap } from 'rxjs'; +import { AppInjector } from 'src/app/app-injector'; +import { DoubtfireConstants } from 'src/app/config/constants/doubtfire-constants'; +import { Grade, GroupSet, TutorialStream, Unit } from './doubtfire-model'; +import { TaskDefinitionService } from '../services/task-definition.service'; + +export type UploadRequirement = { key: string; name: string; type: string; tiiCheck?: boolean; tiiPct?: number }; + +export type SimilarityCheck = { key: string; type: string; pattern: string }; + +export class TaskDefinition extends Entity { + id: number; + seq: number; + abbreviation: string; + name: string; + description: string; + weighting: number; + estimated_days: number = null; + estimated_hours: number = null; + targetGrade: number; + targetDate: Date; + dueDate: Date; + startDate: Date; + uploadRequirements: UploadRequirement[]; + tutorialStream: TutorialStream = null; + plagiarismChecks: SimilarityCheck[] = []; + plagiarismReportUrl: string; + plagiarismWarnPct: number; + restrictStatusUpdates: boolean; + // groupSetId: number; + groupSet: GroupSet = null; + hasTaskSheet: boolean; + hasTaskResources: boolean; + hasTaskAssessmentResources: boolean; + isGraded: boolean; + maxQualityPts: number; + overseerImageId: number; + assessmentEnabled: boolean; + mossLanguage: string = 'moss c'; + + readonly unit: Unit; + + constructor(unit: Unit) { + super(); + this.unit = unit; + } + + public toJson(mappingData: EntityMapping, ignoreKeys?: string[]): object { + return { + task_def: super.toJson(mappingData, ignoreKeys), + }; + } + + /** + * Save the task definition + */ + public save(): Observable { + const svc = AppInjector.get(TaskDefinitionService); + if (this.isNew) { + // TODO: add progress modal + return svc.create( + { + unitId: this.unit.id, + }, + { + entity: this, + cache: this.unit.taskDefinitionCache, + constructorParams: this.unit, + } + ); + } else { + return svc.update( + { + unitId: this.unit.id, + id: this.id, + }, + { entity: this } + ); + } + } + + private originalSaveData: string; + + public get hasOriginalSaveData(): boolean { + return this.originalSaveData !== undefined && this.originalSaveData !== null; + } + + /** + * To check if things have changed, we need to get the initial save data... as it + * isn't empty by default. We can then use + * this to check if there are changes. + * + * @param mapping the mapping to get changes + */ + public setOriginalSaveData(mapping: EntityMapping) { + this.originalSaveData = JSON.stringify(this.toJson(mapping)); + } + + public hasChanges(mapping: EntityMapping): boolean { + if (!this.originalSaveData) { + return false; + } + + return this.originalSaveData != JSON.stringify(this.toJson(mapping)); + } + + public get isNew(): boolean { + return !this.id; + } + + public get unitId(): number { + return this.unit.id; + } + + public localDueDate(): Date { + return this.targetDate; + } + + public localDeadlineDate(): Date { + return this.dueDate; + } + + public matches(text: string): boolean { + return this.abbreviation.toLowerCase().indexOf(text) !== -1 || this.name.toLowerCase().indexOf(text) !== -1; + } + + /** + * The final deadline for task submission. + * + * @returns the final due date + */ + public finalDeadlineDate(): Date { + return this.dueDate; // now in due date + } + + public isGroupTask(): boolean { + return this.groupSet !== null && this.groupSet !== undefined; + } + + public getTaskPDFUrl(asAttachment: boolean = false): string { + const constants = AppInjector.get(DoubtfireConstants); + return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/task_pdf.json${ + asAttachment ? '?as_attachment=true' : '' + }`; + } + + public getTaskResourcesUrl(asAttachment: boolean = false) { + const constants = AppInjector.get(DoubtfireConstants); + return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/task_resources.json${ + asAttachment ? '?as_attachment=true' : '' + }`; + } + + public get targetGradeText(): string { + return Grade.GRADES[this.targetGrade]; + } + + public hasPlagiarismCheck(): boolean { + return this.plagiarismChecks?.length > 0; + } + + public get needsMoss(): boolean { + return this.uploadRequirements.some((upreq) => upreq.type === 'code' && upreq.tiiCheck); + } + + public get taskSheetUploadUrl(): string { + return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ + this.id + }/task_sheet`; + } + + public get taskResourcesUploadUrl(): string { + return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ + this.id + }/task_resources`; + } + + public get taskAssessmentResourcesUploadUrl(): string { + return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ + this.id + }/task_assessment_resources`; + } + + public getTaskAssessmentResourcesUrl(): string { + return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${ + this.id + }/task_assessment_resources.json`; + } + + public deleteTaskSheet(): Observable { + const httpClient = AppInjector.get(HttpClient); + return httpClient.delete(this.taskSheetUploadUrl).pipe(tap(() => (this.hasTaskSheet = false))); + } + + public deleteTaskResources(): Observable { + const httpClient = AppInjector.get(HttpClient); + return httpClient.delete(this.taskResourcesUploadUrl).pipe(tap(() => (this.hasTaskResources = false))); + } + + public deleteTaskAssessmentResources(): Observable { + const httpClient = AppInjector.get(HttpClient); + return httpClient + .delete(this.taskAssessmentResourcesUploadUrl) + .pipe(tap(() => (this.hasTaskAssessmentResources = false))); + } +} diff --git a/src/app/api/services/task-definition.service.ts b/src/app/api/services/task-definition.service.ts index d420f2c1fc..ac6fb27a6f 100644 --- a/src/app/api/services/task-definition.service.ts +++ b/src/app/api/services/task-definition.service.ts @@ -1,276 +1,133 @@ -import {CachedEntityService} from 'ngx-entity-service'; -import { - LearningOutcomeService, - TaskDefinition, - TaskStatusEnum, - Unit, -} from 'src/app/api/models/doubtfire-model'; -import {Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import API_URL from 'src/app/config/constants/apiUrl'; -import {MappingFunctions} from './mapping-fn'; -import {AppInjector} from 'src/app/app-injector'; -import {Observable} from 'rxjs'; -import {TaskPrerequisiteService} from './task-prerequisite.service'; -import {TaskPrerequisite} from '../models/task-prerequisite'; -import {DoubtfireConstants} from 'src/app/config/constants/doubtfire-constants'; -import {SidekiqJob} from '../models/sidekiq-job'; -import {OverseerStepService} from './overseer-step.service'; - -@Injectable() -export class TaskDefinitionService extends CachedEntityService { - protected readonly endpointFormat = 'units/:unitId:/task_definitions/:id:'; - - constructor( - httpClient: HttpClient, - private learningOutcomeService: LearningOutcomeService, - private taskPrerequisiteService: TaskPrerequisiteService, - private overseerStepService: OverseerStepService, - ) { - super(httpClient, API_URL); - - this.mapping.addKeys( - 'id', - 'abbreviation', - 'name', - 'description', - 'weighting', - 'targetGrade', - 'similarityLanguage', - 'hasJplagReport', - 'assessInPortfolioOnly', - 'requiresDiscussion', - { - keys: 'targetDate', - toEntityFn: MappingFunctions.mapDateToEndOfDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'dueDate', - toEntityFn: MappingFunctions.mapDateToEndOfDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'startDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'uploadRequirements', - toJsonFn: (taskDef: TaskDefinition, key: string) => { - return JSON.stringify( - taskDef.uploadRequirements?.map((upreq) => { - return { - key: upreq.key, - name: upreq.name, - type: upreq.type, - tii_check: upreq.tiiCheck, - tii_pct: upreq.tiiPct, - }; - }), - ); - }, - toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { - return ( - data[key] as Array<{ - key: string; - name: string; - type: string; - tii_check: boolean; - tii_pct: number; - }> - )?.map((upreq) => { - return { - key: upreq.key, - name: upreq.name, - type: upreq.type, - tiiCheck: upreq.tii_check, - tiiPct: upreq.tii_pct, - }; - }); - }, - }, - { - keys: ['tutorialStream', 'tutorial_stream_abbr'], - toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { - return taskDef.unit.tutorialStreamsCache.get(data[key]); - }, - toJsonFn: (taskDef: TaskDefinition, key: string) => { - return taskDef.tutorialStream?.abbreviation; - }, - }, - 'plagiarismWarnPct', - 'restrictStatusUpdates', - { - keys: ['groupSet', 'group_set_id'], - toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { - if (data[key]) { - return taskDef.unit.groupSetsCache.get(data[key]); - } else { - return data[key]; - } - }, - toJsonFn: (taskDef: TaskDefinition, key: string) => { - return taskDef.groupSet?.id; - }, - }, - 'hasTaskSheet', - 'hasTaskResources', - 'hasTaskAssessmentResources', - 'hasTaskAssessmentScript', - 'scormEnabled', - 'hasScormData', - 'scormAllowReview', - 'scormBypassTest', - 'scormTimeDelayEnabled', - 'scormAttemptLimit', - 'isGraded', - 'maxQualityPts', - 'overseerImageId', - 'assessmentEnabled', - 'discussionPromptsCount', - { - keys: 'ilos', - toEntityOp: (data: object, key: string, taskDefinition: TaskDefinition) => { - data[key]?.forEach((ilo) => { - taskDefinition.learningOutcomesCache.getOrCreate( - ilo['id'], - this.learningOutcomeService, - ilo, - ); - }); - }, - }, - 'useResourcesForJplagBaseCode', - 'lockAssessmentsToTutorialStream', - { - keys: 'overseerSteps', - toEntityOp: (data: object, key: string, taskDefinition: TaskDefinition) => { - data[key]?.forEach((overseerStep) => { - taskDefinition.overseerStepsCache.getOrCreate( - overseerStep['id'], - this.overseerStepService, - overseerStep, - { - constructorParams: taskDefinition, - }, - ); - }); - }, - }, - 'overseerResourceFiles', - // { - // keys: 'pTargetDate', - // toEntityFn: MappingFunctions.mapDateToDay, - // toJsonFn: MappingFunctions.mapDayToJson, - // }, - { - keys: 'cTargetDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'dTargetDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'hdTargetDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - - { - keys: 'cStartDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'dStartDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - { - keys: 'hdStartDate', - toEntityFn: MappingFunctions.mapDateToDay, - toJsonFn: MappingFunctions.mapDayToJson, - }, - ); - - this.mapping.mapAllKeysToJsonExcept( - 'id', - 'hasTaskSheet', - 'hasTaskResources', - 'hasTaskAssessmentResources', - 'hasScormData', - ); - } - - public override createInstanceFrom(json: object, other?: any): TaskDefinition { - return new TaskDefinition(other as Unit); - } - - public uploadTaskSheet(taskDefinition: TaskDefinition, file: File): Observable { - const formData = new FormData(); - formData.append('file', file); - return AppInjector.get(HttpClient).post(taskDefinition.taskSheetUploadUrl, formData); - } - - public uploadTaskResources(taskDefinition: TaskDefinition, file: File): Observable { - const formData = new FormData(); - formData.append('file', file); - return AppInjector.get(HttpClient).post( - taskDefinition.taskResourcesUploadUrl, - formData, - ); - } - - public uploadOverseerResources(taskDefinition: TaskDefinition, file: File): Observable { - const formData = new FormData(); - formData.append('file', file); - return AppInjector.get(HttpClient).post( - taskDefinition.taskOverseerResourcesUploadUrl, - formData, - ); - } - - public uploadScormData(taskDefinition: TaskDefinition, file: File): Observable { - const formData = new FormData(); - formData.append('file', file); - return AppInjector.get(HttpClient).post(taskDefinition.scormDataUploadUrl, formData); - } - - public addTaskPrerequisite( - taskDefinition: TaskDefinition, - prerequsite: TaskDefinition, - ): Observable { - return AppInjector.get(HttpClient).post(taskDefinition.taskPrerequisiteUrl, { - task_def_id: taskDefinition.id, - prerequisite_id: prerequsite.id, - }); - } - - public updateTaskPrerequisite( - taskPrerequisiteLink: TaskPrerequisite, - taskStatus: TaskStatusEnum, - ): Observable { - return AppInjector.get(HttpClient).put( - `${taskPrerequisiteLink.taskDefinition.taskPrerequisiteUrl}/${taskPrerequisiteLink.id}`, - { - task_status_required: taskStatus, - }, - ); - } - - public zipSubmissionFiles(taskDefinition: TaskDefinition): Observable { - const url = `${AppInjector.get(DoubtfireConstants).API_URL}/submission/units/${taskDefinition.unit.id}/task_definitions/${taskDefinition.id}/download_submissions/zip`; - const httpClient = AppInjector.get(HttpClient); - return httpClient.get(url); - } - - public zipSubmissionPdfs(taskDefinition: TaskDefinition): Observable { - const url = `${AppInjector.get(DoubtfireConstants).API_URL}/submission/units/${taskDefinition.unit.id}/task_definitions/${taskDefinition.id}/student_pdfs/zip`; - const httpClient = AppInjector.get(HttpClient); - return httpClient.get(url); - } -} +import { CachedEntityService } from 'ngx-entity-service'; +import { TaskDefinition, Unit } from 'src/app/api/models/doubtfire-model'; +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import API_URL from 'src/app/config/constants/apiURL'; +import { MappingFunctions } from './mapping-fn'; +import { AppInjector } from 'src/app/app-injector'; +import { Observable } from 'rxjs'; + +@Injectable() +export class TaskDefinitionService extends CachedEntityService { + protected readonly endpointFormat = 'units/:unitId:/task_definitions/:id:'; + + constructor(httpClient: HttpClient) { + super(httpClient, API_URL); + + this.mapping.addKeys( + 'id', + 'abbreviation', + 'name', + 'description', + 'weighting', + 'estimated_days', + 'estimated_hours', + 'targetGrade', + 'mossLanguage', + { + keys: 'targetDate', + toEntityFn: MappingFunctions.mapDateToEndOfDay, + toJsonFn: MappingFunctions.mapDayToJson, + }, + { + keys: 'dueDate', + toEntityFn: MappingFunctions.mapDateToEndOfDay, + toJsonFn: MappingFunctions.mapDayToJson, + }, + { + keys: 'startDate', + toEntityFn: MappingFunctions.mapDateToDay, + toJsonFn: MappingFunctions.mapDayToJson, + }, + { + keys: 'uploadRequirements', + toJsonFn: (taskDef: TaskDefinition, key: string) => { + return JSON.stringify( + taskDef.uploadRequirements.map((upreq) => { + return { + key: upreq.key, + name: upreq.name, + type: upreq.type, + tii_check: upreq.tiiCheck, + tii_pct: upreq.tiiPct, + }; + }) + ); + }, + toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { + return ( + data[key] as Array<{ key: string; name: string; type: string; tii_check: boolean; tii_pct: number }> + ).map((upreq) => { + return { + key: upreq.key, + name: upreq.name, + type: upreq.type, + tiiCheck: upreq.tii_check, + tiiPct: upreq.tii_pct, + }; + }); + }, + }, + { + keys: ['tutorialStream', 'tutorial_stream_abbr'], + toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { + return taskDef.unit.tutorialStreamsCache.get(data[key]); + }, + toJsonFn: (taskDef: TaskDefinition, key: string) => { + return taskDef.tutorialStream?.abbreviation; + }, + }, + 'plagiarismWarnPct', + 'restrictStatusUpdates', + { + keys: ['groupSet', 'group_set_id'], + toEntityFn: (data: object, key: string, taskDef: TaskDefinition, params?: any) => { + if (data[key]) { + return taskDef.unit.groupSetsCache.get(data[key]); + } else { + return data[key]; + } + }, + toJsonFn: (taskDef: TaskDefinition, key: string) => { + return taskDef.groupSet?.id; + }, + }, + 'hasTaskSheet', + 'hasTaskResources', + 'hasTaskAssessmentResources', + 'isGraded', + 'maxQualityPts', + 'overseerImageId', + 'assessmentEnabled' + ); + + this.mapping.mapAllKeysToJsonExcept( + 'id', + 'hasTaskSheet', + 'hasTaskResources', + 'hasTaskAssessmentResources' + ); + } + + public override createInstanceFrom(json: object, other?: any): TaskDefinition { + return new TaskDefinition(other as Unit); + } + + public uploadTaskSheet(taskDefinition: TaskDefinition, file: File): Observable { + const formData = new FormData(); + formData.append('file', file); + return AppInjector.get(HttpClient).post(taskDefinition.taskSheetUploadUrl, formData); + } + + public uploadTaskResources(taskDefinition: TaskDefinition, file: File): Observable { + const formData = new FormData(); + formData.append('file', file); + return AppInjector.get(HttpClient).post(taskDefinition.taskResourcesUploadUrl, formData); + } + + public uploadOverseerResources(taskDefinition: TaskDefinition, file: File): Observable { + const formData = new FormData(); + formData.append('file', file); + return AppInjector.get(HttpClient).post(taskDefinition.taskAssessmentResourcesUploadUrl, formData); + } +} diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component.html b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component.html index 5ec2551e03..8413f48bd6 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component.html +++ b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component.html @@ -28,3 +28,31 @@ Task description + +
+
Expected duration
+
+ + Days + + + + + Hours + + +
+