Skip to content

Commit

Permalink
refactor(frontend): Components for import tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Clashsoft committed Sep 13, 2023
1 parent 82e4599 commit 07bb574
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import {assignmentChildRoutes} from './assignment-routes';
import {AssignmentComponent} from './assignment/assignment.component';
import {DeleteModalComponent} from './delete-modal/delete-modal.component';
import {ImportModalComponent} from './import-modal/import-modal.component';
import {ImportGithubComponent} from "./import-github/import-github.component";
import {ImportFilesComponent} from "./import-files/import-files.component";
import {ImportEmbeddingsComponent} from "./import-embeddings/import-embeddings.component";
import {ImportMossComponent} from "./import-moss/import-moss.component";
import {ImportConsentComponent} from "./import-consent/import-consent.component";

export const importChildren = [
{path: 'github', component: ImportGithubComponent, data: {title: 'GitHub'}},
{path: 'files', component: ImportFilesComponent, data: {title: 'Files'}},
{path: 'embeddings', component: ImportEmbeddingsComponent, data: {title: 'Embeddings'}},
{path: 'moss', component: ImportMossComponent, data: {title: 'MOSS'}},
{path: 'consent', component: ImportConsentComponent, data: {title: 'Consent'}},
];

const routes: Routes = [
{
Expand All @@ -14,7 +27,15 @@ const routes: Routes = [
children: [
...assignmentChildRoutes,
{path: 'token', component: TokenModalComponent, data: {title: 'Authorization Required'}},
{path: 'import', component: ImportModalComponent, data: {title: 'Import Solutions'}},
{
path: 'import',
component: ImportModalComponent,
data: {title: 'Import'},
children: [
...importChildren,
{path: '', redirectTo: 'github', pathMatch: 'full'},
],
},
{path: 'delete', component: DeleteModalComponent, data: {title: 'Delete Assignment'}},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import {StatisticsBlockComponent} from './statistics-block/statistics-block.comp
import {StatisticsComponent} from './statistics/statistics.component';
import {SubmitModalComponent} from './submit-modal/submit-modal.component';
import {AssignmentTasksComponent} from './tasks/tasks.component';
import { ImportGithubComponent } from './import-github/import-github.component';
import { ImportFilesComponent } from './import-files/import-files.component';
import { ImportMossComponent } from './import-moss/import-moss.component';
import { ImportEmbeddingsComponent } from './import-embeddings/import-embeddings.component';
import { ImportConsentComponent } from './import-consent/import-consent.component';


@NgModule({
Expand All @@ -39,6 +44,11 @@ import {AssignmentTasksComponent} from './tasks/tasks.component';
ImportModalComponent,
StatisticsBlockComponent,
DeleteModalComponent,
ImportGithubComponent,
ImportFilesComponent,
ImportMossComponent,
ImportEmbeddingsComponent,
ImportConsentComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<p>
Import consent flags by pasting tab-, space- or comma-separated values below.
</p>
<details>
<summary>More Info</summary>
<p>
The first line should specify the column names (case insensitive), for example:
</p>
<p>
<code>github,demonstration,scientific,3p</code>
</p>
<p>
Instead of or in addition to <code>github</code>,
you can also use <code>name</code>, <code>studentid</code> or <code>email</code>,
but at least one of must be specified.
</p>
<p>
The columns <code>demonstration</code>, <code>scientific</code> and <code>3p</code> are optional
and accept boolean values like <code>true</code> or <code>false</code> (case-insensitive).
</p>
</details>
<textarea class="form-control" rows="10" [(ngModel)]="consentText"></textarea>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {Component} from '@angular/core';
import {Observable} from "rxjs";
import Solution, {
AuthorInfo,
authorInfoProperties,
Consent,
consentKeys,
ImportSolution
} from "../../../model/solution";
import {map} from "rxjs/operators";
import {SolutionService} from "../../../services/solution.service";
import {ActivatedRoute} from "@angular/router";

@Component({
selector: 'app-import-consent',
templateUrl: './import-consent.component.html',
styleUrls: ['./import-consent.component.scss']
})
export class ImportConsentComponent {
consentText = '';

constructor(
private solutionService: SolutionService,
private route: ActivatedRoute,
) {
}

import(): Observable<ImportSolution[]> {
const assignment = this.route.snapshot.params.aid;
const lines = this.consentText.split('\n');
const splitter = /[\s,;]/;
const columns = lines[0].split(splitter);
const updates: Partial<Solution>[] = [];
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(splitter);
const author: AuthorInfo = {};
const consent: Consent = {};
for (let j = 0; j < columns.length; j++) {
const column = columns[j].toLowerCase();
const value = values[j];
if (authorInfoProperties.find(([, key]) => key === column)) {
author[column as keyof AuthorInfo] = value;
}
if (consentKeys.includes(column as keyof Consent)) {
consent[column] = Boolean(value.toLowerCase());
}
}
if (Object.keys(author).length) {
updates.push({author, consent});
}
}
return this.solutionService.updateMany(assignment, updates).pipe(map(results => results.filter(s => s)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<p>
After importing from GitHub or Files, you can import embeddings.
</p>
<p>
Here is an estimate of the token costs.
Actual costs may be <strong>lower</strong> if the embeddings are already cached,
or <strong>slightly higher</strong> due to some tokens like filenames being added to improve results.
Charges will be applied to your OpenAI account.
</p>
<p>
Please review the results below and click <b>Import</b> to confirm.
</p>
<div class="row" *ngIf="estimatedCosts">
<app-statistic-value class="col" label="Solutions" [value]="estimatedCosts.solutions" [standalone]="true"></app-statistic-value>
<app-statistic-value class="col" label="Files" [value]="estimatedCosts.files" [standalone]="true"></app-statistic-value>
<app-statistic-value class="col" label="Tokens" [value]="estimatedCosts.tokens" [standalone]="true"></app-statistic-value>
<app-statistic-value class="col" label="Estimated Cost" [value]="estimatedCosts.estimatedCost | currency:'USD':true:'0.7'" [standalone]="true"></app-statistic-value>
</div>
<ng-container *ngIf="finalCosts">
<hr/>
<p>
The import has been completed.
The following costs have been applied to your OpenAI account.
</p>
<div class="row">
<app-statistic-value class="col" label="Tokens" [value]="finalCosts.tokens" [standalone]="true"></app-statistic-value>
<app-statistic-value class="col" label="Cost" [value]="finalCosts.estimatedCost | currency:'USD':true:'0.7'" [standalone]="true"></app-statistic-value>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Component, OnInit} from '@angular/core';
import {EstimatedCosts} from "../../../model/solution";
import {SolutionService} from "../../../services/solution.service";
import {ActivatedRoute} from "@angular/router";
import {tap} from "rxjs/operators";

@Component({
selector: 'app-import-embeddings',
templateUrl: './import-embeddings.component.html',
styleUrls: ['./import-embeddings.component.scss']
})
export class ImportEmbeddingsComponent implements OnInit {
estimatedCosts?: EstimatedCosts;
finalCosts?: EstimatedCosts;

constructor(
private solutionService: SolutionService,
private route: ActivatedRoute,
) {
}

ngOnInit() {
this.solutionService.importEmbeddings(this.route.snapshot.params.aid, true).subscribe(costs => this.estimatedCosts = costs);
}

import() {
const assignmentId = this.route.snapshot.params.aid;
return this.solutionService.importEmbeddings(assignmentId).pipe(
tap(result => this.finalCosts = result),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<p>
Select multiple zip or jar files from your disk to upload.
The file name may have any of the following formats:
</p>
<ul>
<li>
Digits, e.g. <code>12345678.zip</code>, interpreted as Student IDs.
</li>
<li>
Prefixed with the same GitHub Classroom prefix as the assignment,
e.g. <code>assignment-1-Student.zip</code>,
which extracts the GitHub Username like <code>Student</code>.
</li>
<li>
Anything else, with the file name without extension becoming the student name.
</li>
</ul>
<input type="file" class="form-control" id="files" multiple accept="application/zip" #input
(change)="setFiles(input.files!)">
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {Component} from '@angular/core';
import {SolutionService} from "../../../services/solution.service";
import {ActivatedRoute} from "@angular/router";

@Component({
selector: 'app-import-files',
templateUrl: './import-files.component.html',
styleUrls: ['./import-files.component.scss']
})
export class ImportFilesComponent {
files: File[] = [];

constructor(
private solutionService: SolutionService,
private route: ActivatedRoute,
) {
}

setFiles(files: FileList) {
this.files = Array.from(files);
}

import() {
const assignmentId = this.route.snapshot.params.aid;
return this.solutionService.import(assignmentId, this.files);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<p>
Imports solutions from GitHub using the organisation and prefix configured in the
<a routerLink="../edit/classroom">assignment settings</a>.
No further configuration is required.
</p>
<details #previewDetails (toggle)="previewDetails.open && previewGitHubImport()">
<summary>Select Students</summary>
<ul class="list-unstyled">
<li *ngFor="let solution of previewSolutions" class="form-check">
<input class="form-check-input" type="checkbox" [id]="'check-' + solution.author.github"
[(ngModel)]="checkedUsernames[solution.author.github!]">
<label class="form-check-label" [for]="'check-' + solution.author.github">
{{ solution.author.github }}
<span class="text-muted"> &bull; {{ solution.timestamp | date:'short' }}</span>
</label>
</li>
</ul>
</details>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Component} from '@angular/core';
import {ImportSolution} from "../../../model/solution";
import {SolutionService} from "../../../services/solution.service";
import {ActivatedRoute} from "@angular/router";

@Component({
selector: 'app-import-github',
templateUrl: './import-github.component.html',
styleUrls: ['./import-github.component.scss']
})
export class ImportGithubComponent {
checkedUsernames: Partial<Record<string, boolean>> = {};
previewSolutions: ImportSolution[];

constructor(
private solutionService: SolutionService,
private route: ActivatedRoute,
) {
}

previewGitHubImport() {
this.solutionService.previewImport(this.route.snapshot.params.aid).subscribe(solutions => this.previewSolutions = solutions);
}

import() {
const assignmentId = this.route.snapshot.params.aid;
const usernames = Object.keys(this.checkedUsernames).filter(username => this.checkedUsernames[username]);
return this.solutionService.import(assignmentId, undefined, usernames);
}
}
Loading

0 comments on commit 07bb574

Please sign in to comment.