Skip to content

Commit

Permalink
Merge pull request #146 from satikaj/add-numbas-integration
Browse files Browse the repository at this point in the history
Adjust Unit Chair admin page to allow upload of numbas test
  • Loading branch information
maddernd authored Mar 18, 2024
2 parents 61ae1d9 + 375b076 commit dc7171e
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 7 deletions.
24 changes: 24 additions & 0 deletions src/app/api/models/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export class TaskDefinition extends Entity {
groupSet: GroupSet = null;
hasTaskSheet: boolean;
hasTaskResources: boolean;
hasEnabledNumbasTest: boolean;
hasUploadedNumbasTest: boolean;
hasUnlimitedRetriesForNumbas: boolean;
hasTimeDelayForNumbas: boolean;
isNumbasRestrictedTo1Attempt: boolean;
numbasTimeDelay: string = 'no delay';
hasTaskAssessmentResources: boolean;
isGraded: boolean;
maxQualityPts: number;
Expand Down Expand Up @@ -150,6 +156,13 @@ export class TaskDefinition extends Entity {
}`;
}

public getNumbasTestUrl(asAttachment: boolean = false) {
const constants = AppInjector.get(DoubtfireConstants);
return `${constants.API_URL}/units/${this.unit.id}/task_definitions/${this.id}/numbas_test.json${
asAttachment ? '?as_attachment=true' : ''
}`;
}

public get targetGradeText(): string {
return Grade.GRADES[this.targetGrade];
}
Expand All @@ -174,6 +187,12 @@ export class TaskDefinition extends Entity {
}/task_resources`;
}

public get numbasTestUploadUrl(): string {
return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${
this.id
}/numbas_test`;
}

public get taskAssessmentResourcesUploadUrl(): string {
return `${AppInjector.get(DoubtfireConstants).API_URL}/units/${this.unit.id}/task_definitions/${
this.id
Expand All @@ -196,6 +215,11 @@ export class TaskDefinition extends Entity {
return httpClient.delete(this.taskResourcesUploadUrl).pipe(tap(() => (this.hasTaskResources = false)));
}

public deleteNumbasTest(): Observable<any> {
const httpClient = AppInjector.get(HttpClient);
return httpClient.delete(this.numbasTestUploadUrl).pipe(tap(() => (this.hasUploadedNumbasTest = false)));
}

public deleteTaskAssessmentResources(): Observable<any> {
const httpClient = AppInjector.get(HttpClient);
return httpClient
Expand Down
2 changes: 2 additions & 0 deletions src/app/doubtfire-angular.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ import { TaskDefinitionUploadComponent } from './units/states/edit/directives/un
import { TaskDefinitionOptionsComponent } from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component';
import { TaskDefinitionResourcesComponent } from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-resources/task-definition-resources.component';
import { TaskDefinitionOverseerComponent } from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component';
import { TaskDefinitionNumbasComponent } from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-numbas/task-definition-numbas.component';
import { UnitAnalyticsComponent } from './units/states/analytics/unit-analytics-route.component';
import { FileDropComponent } from './common/file-drop/file-drop.component';
import { UnitTaskEditorComponent } from './units/states/edit/directives/unit-tasks-editor/unit-task-editor.component';
Expand Down Expand Up @@ -266,6 +267,7 @@ import { TasksViewerComponent } from './units/states/tasks/tasks-viewer/tasks-vi
TaskDefinitionOptionsComponent,
TaskDefinitionResourcesComponent,
TaskDefinitionOverseerComponent,
TaskDefinitionNumbasComponent,
UnitAnalyticsComponent,
StudentTutorialSelectComponent,
StudentCampusSelectComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ <h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Due dates</h3>
</div>

<div class="w-full">
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Upload requirements</h3>
<p class="font-normal text-gray-700">What do students need to upload?</p>
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Task description and resources</h3>
<p class="font-normal text-gray-700">Upload task descriptions and resources</p>
<div class="bg-white border border-gray-200 rounded-xl shadow p-6">
<f-task-definition-upload [taskDefinition]="taskDefinition"></f-task-definition-upload>
<f-task-definition-resources [taskDefinition]="taskDefinition"></f-task-definition-resources>
</div>
</div>
</div>
Expand All @@ -80,10 +80,10 @@ <h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Upload requirem
</div>

<div class="w-full">
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Task description and resources</h3>
<p class="font-normal text-gray-700">Upload task descriptions and resources</p>
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Upload Numbas test</h3>
<p class="font-normal text-gray-700">Upload the corresponding Numbas test</p>
<div class="bg-white border border-gray-200 rounded-xl shadow p-6">
<f-task-definition-resources [taskDefinition]="taskDefinition"></f-task-definition-resources>
<f-task-definition-numbas [taskDefinition]="taskDefinition"></f-task-definition-numbas>
</div>
</div>
</div>
Expand All @@ -97,6 +97,24 @@ <h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Task descriptio
</div>
</div>

<div class="w-full">
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Upload requirements</h3>
<p class="font-normal text-gray-700">What do students need to upload?</p>
<div class="bg-white border border-gray-200 rounded-xl shadow p-6">
<f-task-definition-upload [taskDefinition]="taskDefinition"></f-task-definition-upload>
</div>
</div>
</div>

<div class="flex mt-10">
<div class="mr-6">
<div
class="font-bold text-formatif-blue text-3xl rounded-full bg-formatif-blue-lighter flex items-center justify-center w-16 h-16"
>
7
</div>
</div>

<div class="w-full">
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Task assessment automation</h3>
<p class="font-normal text-gray-700">Configure automated assessment</p>
Expand All @@ -111,7 +129,7 @@ <h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900">Task assessment
<div
class="font-bold text-formatif-blue text-3xl rounded-full bg-formatif-blue-lighter flex items-center justify-center w-16 h-16"
>
7
8
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="flex flex-col gap-4">
<mat-checkbox matInput required [(ngModel)]="taskDefinition.hasEnabledNumbasTest">
Enable Numbas Test
</mat-checkbox>

<div class="basis-1/2 flex flex-col">
<f-file-drop
mode="event"
(filesDropped)="uploadNumbasTest($event)"
accept="application/zip"
[desiredFileName]="'Numbas zip'"
/>
@if (taskDefinition.hasUploadedNumbasTest) {
<div class="flex flex-row gap-4 pt-4">
<button mat-flat-button color="warn" (click)="removeNumbasTest()" class="flex-grow me-4">
Delete Numbas Zip
</button>
<button mat-flat-button color="accent" (click)="downloadNumbasTest()" class="flex-grow ms-4">
Download Numbas Zip
</button>
</div>
}
</div>

<div class="flex flex-row items-center">
<span> Select test rules: </span>
<mat-checkbox matInput [(ngModel)]="taskDefinition.hasUnlimitedRetriesForNumbas">Unlimited retries</mat-checkbox>
<mat-checkbox matInput [(ngModel)]="taskDefinition.hasTimeDelayForNumbas">Time delay</mat-checkbox>
<mat-checkbox matInput [(ngModel)]="taskDefinition.isNumbasRestrictedTo1Attempt">Restrict to 1 attempt</mat-checkbox>
</div>

@if (taskDefinition.hasTimeDelayForNumbas) {
<mat-form-field appearance="outline">
<mat-label>Time delay</mat-label>
<mat-select [(ngModel)]="taskDefinition.numbasTimeDelay">
<mat-option value="no delay">No delay</mat-option>
<mat-option value="30 min">30 min</mat-option>
<mat-option value="2 hours">2 hours</mat-option>
<mat-option value="1 day">1 day</mat-option>
<mat-option value="see tutor">See tutor</mat-option>
</mat-select>
</mat-form-field>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Component, Inject, Input } from '@angular/core';
import { alertService } from 'src/app/ajs-upgraded-providers';
import { TaskDefinition } from 'src/app/api/models/task-definition';
import { Unit } from 'src/app/api/models/unit';
import { TaskDefinitionService } from 'src/app/api/services/task-definition.service';
import { FileDownloaderService } from 'src/app/common/file-downloader/file-downloader.service';

@Component({
selector: 'f-task-definition-numbas',
templateUrl: 'task-definition-numbas.component.html',
styleUrls: ['task-definition-numbas.component.scss'],
})
export class TaskDefinitionNumbasComponent {
@Input() taskDefinition: TaskDefinition;

constructor(
private fileDownloaderService: FileDownloaderService,
@Inject(alertService) private alerts: any,
private taskDefinitionService: TaskDefinitionService
) {}

public get unit(): Unit {
return this.taskDefinition?.unit;
}

public downloadNumbasTest() {
this.fileDownloaderService.downloadFile(
this.taskDefinition.getNumbasTestUrl(true),
this.taskDefinition.name + '-Numbas.zip',
);
}

public removeNumbasTest() {
this.taskDefinition.deleteNumbasTest().subscribe({
next: () => this.alerts.add('success', 'Deleted Numbas test', 2000),
error: (message) => this.alerts.add('danger', message, 6000),
});
}

public uploadNumbasTest(files: FileList) {
const validFiles = Array.from(files as ArrayLike<File>).filter((f) => f.type === 'application/zip');
if (validFiles.length > 0) {
const file = validFiles[0];
// Temporary until Numbas backend is fixed: save uploaded file to local Downloads folder
this.saveZipFile(file);
this.taskDefinition.hasUploadedNumbasTest = true;
// this.taskDefinitionService.uploadNumbasTest(this.taskDefinition, file).subscribe({
// next: () => this.alerts.add('success', 'Uploaded Numbas test', 2000),
// error: (message) => this.alerts.add('danger', message, 6000),
// });
} else {
this.alerts.add('danger', 'Please drop a ZIP to upload for this task', 6000);
}
}

private saveZipFile(zipData) {
const blob = new Blob([zipData], {type: 'application/zip'});

// Create an anchor element and set its href to the blob URL
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'numbas.zip';

// Append the link to the document, trigger the download, then remove the link
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}

0 comments on commit dc7171e

Please sign in to comment.