Skip to content

Commit

Permalink
feat: grade submission
Browse files Browse the repository at this point in the history
  • Loading branch information
Okabe-Rintarou-0 committed Mar 4, 2024
1 parent a8b4682 commit 6be91bd
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 15 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@types/react-syntax-highlighter": "^15.5.11",
"antd": "^5.14.2",
"docx-preview": "^0.3.0",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"js-base64": "^3.7.7",
"jszip": "^3.10.1",
"libarchive.js": "^2.0.2",
Expand Down
56 changes: 49 additions & 7 deletions src-tauri/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{fs, io::Write, path::Path};

use reqwest::Response;
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Serialize};

use crate::{
error::Result,
Expand All @@ -21,10 +21,10 @@ impl Client {
}
}

async fn get_request(
async fn get_request<T: Serialize + ?Sized>(
&self,
url: &str,
query: Option<&Vec<(String, String)>>,
query: Option<&T>,
token: &str,
) -> Result<Response> {
let mut req = self
Expand All @@ -40,6 +40,48 @@ impl Client {
Ok(res)
}

pub async fn post_form<T: Serialize + ?Sized, Q: Serialize + ?Sized>(
&self,
url: &str,
query: Option<&Q>,
form: &T,
token: &str,
) -> Result<Response> {
let mut request = self
.cli
.post(url)
.header("Authorization".to_owned(), format!("Bearer {}", token))
.form(form);
if let Some(query) = query {
request = request.query(query);
}
let response = request.send().await?;
Ok(response)
}

pub async fn update_grade(
&self,
course_id: i32,
assignment_id: i32,
student_id: i32,
grade: &str,
token: &str,
) -> Result<()> {
let url = format!(
"{}/api/v1/courses/{}/assignments/{}/submissions/update_grades",
BASE_URL, course_id, assignment_id
);
self.post_form(
&url,
None::<&str>,
&[(format!("grade_data[{}][posted_grade]", student_id), grade)],
token,
)
.await?
.error_for_status()?;
Ok(())
}

pub async fn download_file<F: Fn(ProgressPayload) + Send>(
&self,
file: &File,
Expand All @@ -48,7 +90,7 @@ impl Client {
progress_handler: F,
) -> Result<()> {
let mut response = self
.get_request(&file.url, None, token)
.get_request(&file.url, None::<&str>, token)
.await?
.error_for_status()?;

Expand Down Expand Up @@ -85,8 +127,8 @@ impl Client {
.get_request(
url,
Some(&vec![
("page".to_owned(), page.to_string()),
("per_page".to_owned(), "100".to_owned()),
("page", page.to_string()),
("per_page", "100".to_owned()),
]),
token,
)
Expand Down Expand Up @@ -155,7 +197,7 @@ impl Client {
let all_courses = self.list_items(&url, token).await?;
let filtered_courses: Vec<Course> = all_courses
.into_iter()
.filter(|course:&Course| !course.is_access_restricted())
.filter(|course: &Course| !course.is_access_restricted())
.collect();

Ok(filtered_courses)
Expand Down
30 changes: 30 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ impl App {
.await
}

async fn update_grade(
&self,
course_id: i32,
assignment_id: i32,
student_id: i32,
grade: &str,
) -> Result<()> {
self.client
.update_grade(
course_id,
assignment_id,
student_id,
grade,
&self.config.read().await.token,
)
.await
}

async fn list_ta_courses(&self) -> Result<Vec<Course>> {
self.client
.list_ta_courses(&self.config.read().await.token)
Expand Down Expand Up @@ -283,6 +301,17 @@ async fn save_config(config: AppConfig) -> Result<()> {
Ok(())
}

#[tauri::command]
async fn update_grade(
course_id: i32,
assignment_id: i32,
student_id: i32,
grade: String,
) -> Result<()> {
APP.update_grade(course_id, assignment_id, student_id, &grade)
.await
}

fn main() {
tracing_subscriber::fmt::init();
tauri::Builder::default()
Expand All @@ -303,6 +332,7 @@ fn main() {
download_file,
check_path,
export_users,
update_grade,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ pub struct Submission {
pub id: i64,
#[serde(default)]
pub submitted_at: Option<String>,
#[serde(default)]
pub grade: Option<String>,
pub assignment_id: i64,
pub user_id: i64,
pub late: bool,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"package": {
"productName": "SJTU Canvas Helper",
"version": "1.0.7"
"version": "1.0.8"
},
"tauri": {
"allowlist": {
Expand Down
64 changes: 64 additions & 0 deletions src/components/grade_statistic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { GradeStatistic } from "../lib/model"
import ReactEcharts from "echarts-for-react";

export default function GradeStatisticChart({ statistic }: {
statistic: GradeStatistic
}) {
const computeGradeDistribution = (grades: number[]) => {
const gradeMap = new Map<number, number>();
if (grades.length === 0) {
return gradeMap;
}
const step = 0.5;
grades.map(grade => {
let section = grade / step - (grade % step ? 0 : 1);
let count = gradeMap.get(section) ?? 0;
gradeMap.set(section, count + 1);
});

return gradeMap;
}

const getGradeDistributionOptions = (grades: number[]) => {
let distribution = computeGradeDistribution(grades);
let maxSection = 0;
for (let section of distribution.keys()) {
if (section > maxSection) {
maxSection = section;
}
}
let labels = [];
let values = [];
const step = 0.5;

for (let i = 0; i <= maxSection; i++) {
labels.push(`${step * i}-${step * (i + 1)}`);
values.push(distribution.get(i) ?? 0);
}

return {
xAxis: {
data: labels
},
yAxis: {},
legend: {
data: ['成绩分布']
},
series: [
{
name: '成绩分布',
type: 'bar',
data: values
}
]
};
}

const graded = statistic.grades.length;
const average = graded > 0 ? statistic.grades.reduce((g1, g2) => g1 + g2) / graded : 0;
const options = getGradeDistributionOptions(statistic.grades);

return <>{statistic.total > 0 && <span><b>{graded}/{statistic.total}</b>个作业已记分(平均<b>{average.toFixed(2)}</b>分)</span>}
{graded > 0 && <ReactEcharts option={options} />}
</>
}
8 changes: 8 additions & 0 deletions src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export type WorkflowState = "submitted" | "unsubmitted" | "graded";
export interface Submission {
id: number;
key: number;
grade: string | null;
submitted_at?: string;
assignment_id: number;
user_id: number;
Expand All @@ -83,9 +84,16 @@ export interface Submission {
workflow_state: WorkflowState;
}

export interface GradeStatistic {
grades: number[];
total: number;
}

export interface Attachment {
user?: string;
user_id: number;
submitted_at?: string;
grade: string | null;
id: number;
key: number;
late: boolean;
Expand Down
Loading

0 comments on commit 6be91bd

Please sign in to comment.