|
3 | 3 | import * as vscode from 'vscode'; |
4 | 4 | import { ApiClient } from './apiClient'; |
5 | 5 |
|
6 | | -interface Course { |
7 | | - semester: string; |
8 | | - title: string; |
9 | | - display_name: string; |
10 | | - display_semester: string; |
11 | | - user_group: number; |
12 | | - registration_section: string; |
13 | | -} |
14 | | - |
15 | | -interface ApiResponse { |
16 | | - status: string; |
17 | | - data: { |
18 | | - unarchived_courses: Course[]; |
19 | | - dropped_courses: Course[]; |
20 | | - }; |
21 | | - message?: string; |
22 | | -} |
23 | | - |
24 | | -interface LoginResponse { |
25 | | - status: string; |
26 | | - data: { |
27 | | - token: string; |
28 | | - }; |
29 | | - message?: string; |
30 | | -} |
| 6 | +import { CourseResponse, LoginResponse, GradableResponse } from '../interfaces/Responses'; |
| 7 | +import { AutoGraderDetails } from '../interfaces/AutoGraderDetails'; |
| 8 | + |
31 | 9 |
|
32 | 10 | export class ApiService { |
33 | 11 | private client: ApiClient; |
@@ -70,48 +48,117 @@ export class ApiService { |
70 | 48 | } |
71 | 49 | } |
72 | 50 |
|
| 51 | + async fetchMe(): Promise<any> { |
| 52 | + try { |
| 53 | + const response = await this.client.get<any>('/api/me'); |
| 54 | + return response.data; |
| 55 | + } catch (error: any) { |
| 56 | + throw new Error(error.response?.data?.message || 'Failed to fetch me.'); |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + |
73 | 61 | /** |
74 | 62 | * Fetch all courses for the authenticated user |
75 | 63 | */ |
76 | | - async fetchCourses(token: string): Promise<ApiResponse> { |
| 64 | + async fetchCourses(token?: string): Promise<CourseResponse> { |
77 | 65 | try { |
78 | | - const response = await this.client.get<ApiResponse>('/api/courses', { |
79 | | - headers: { |
80 | | - Authorization: token, |
81 | | - }, |
82 | | - }); |
| 66 | + const response = await this.client.get<CourseResponse>('/api/courses'); |
83 | 67 | return response.data; |
84 | 68 | } catch (error: any) { |
85 | 69 | console.error('Error fetching courses:', error); |
86 | 70 | throw new Error(error.response?.data?.message || 'Failed to fetch courses.'); |
87 | 71 | } |
88 | 72 | } |
89 | 73 |
|
| 74 | + async fetchGradables(courseId: string, term: string): Promise<GradableResponse> { |
| 75 | + try { |
| 76 | + const url = `/api/${term}/${courseId}/gradeables`; |
| 77 | + const response = await this.client.get<GradableResponse>(url); |
| 78 | + return response.data; |
| 79 | + } catch (error: any) { |
| 80 | + console.error('Error fetching gradables:', error); |
| 81 | + throw new Error(error.response?.data?.message || 'Failed to fetch gradables.'); |
| 82 | + } |
| 83 | + } |
| 84 | + |
90 | 85 | /** |
91 | 86 | * Fetch grade details for a specific homework assignment |
92 | 87 | */ |
93 | | - async fetchGradeDetails(hw: string): Promise<any> { |
94 | | - // Hardcoded grade details |
95 | | - return { |
96 | | - score: '25/40', |
97 | | - tests: [ |
98 | | - { name: 'Test 1', passed: true }, |
99 | | - { name: 'Test 2', passed: false }, |
100 | | - { name: 'Test 3', passed: true }, |
101 | | - { name: 'Test 4', passed: false }, |
102 | | - ] |
103 | | - }; |
| 88 | + async fetchGradeDetails(term: string, courseId: string, gradeableId: string): Promise<AutoGraderDetails> { |
| 89 | + try { |
| 90 | + const response = await this.client.get<AutoGraderDetails>(`/api/${term}/${courseId}/gradeable/${gradeableId}/values`); |
| 91 | + return response.data; |
| 92 | + } catch (error: any) { |
| 93 | + console.error('Error fetching grade details:', error); |
| 94 | + throw new Error(error.response?.data?.message || 'Failed to fetch grade details.'); |
| 95 | + } |
104 | 96 | } |
105 | 97 |
|
| 98 | + /** |
| 99 | + * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. |
| 100 | + * @param intervalMs Delay between requests (default 2000) |
| 101 | + * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout |
| 102 | + * @returns The final AutoGraderDetails with complete data |
| 103 | + */ |
| 104 | + async pollGradeDetailsUntilComplete( |
| 105 | + term: string, |
| 106 | + courseId: string, |
| 107 | + gradeableId: string, |
| 108 | + options?: { intervalMs?: number; timeoutMs?: number; token?: vscode.CancellationToken } |
| 109 | + ): Promise<AutoGraderDetails> { |
| 110 | + const intervalMs = options?.intervalMs ?? 2000; |
| 111 | + const timeoutMs = options?.timeoutMs ?? 300000; |
| 112 | + const token = options?.token; |
| 113 | + const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; |
| 114 | + |
| 115 | + const isComplete = (res: AutoGraderDetails): boolean => |
| 116 | + res?.data?.autograding_complete === true && |
| 117 | + Array.isArray(res.data.test_cases) && |
| 118 | + res.data.test_cases.length > 0; |
| 119 | + |
| 120 | + for (;;) { |
| 121 | + if (token?.isCancellationRequested) { |
| 122 | + throw new Error('Cancelled'); |
| 123 | + } |
| 124 | + if (deadline > 0 && Date.now() >= deadline) { |
| 125 | + throw new Error('Autograding did not complete within the timeout.'); |
| 126 | + } |
| 127 | + |
| 128 | + const result = await this.fetchGradeDetails(term, courseId, gradeableId); |
| 129 | + if (isComplete(result)) { |
| 130 | + return result; |
| 131 | + } |
| 132 | + |
| 133 | + await new Promise((r) => setTimeout(r, intervalMs)); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + async submitVCSGradable(term: string, courseId: string, gradeableId: string): Promise<any> { |
| 138 | + try { |
| 139 | + // git_repo_id is literally not used, but is required by the API *ugh* |
| 140 | + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; |
| 141 | + const response = await this.client.post<any>(url); |
| 142 | + return response.data; |
| 143 | + } catch (error: any) { |
| 144 | + console.error('Error submitting VCS gradable:', error); |
| 145 | + throw new Error(error.response?.data?.message || 'Failed to submit VCS gradable.'); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + |
106 | 150 | /** |
107 | 151 | * Fetch previous attempts for a specific homework assignment |
108 | 152 | */ |
109 | | - async fetchPreviousAttempts(hw: string): Promise<any[]> { |
110 | | - // Hardcoded previous attempts |
111 | | - return [ |
112 | | - { score: '15/40', tests: [{ name: 'Test 1', passed: false }, { name: 'Test 2', passed: false }, { name: 'Test 3', passed: true }, { name: 'Test 4', passed: false }] }, |
113 | | - { score: '25/40', tests: [{ name: 'Test 1', passed: true }, { name: 'Test 2', passed: false }, { name: 'Test 3', passed: true }, { name: 'Test 4', passed: false }] } |
114 | | - ]; |
| 153 | + async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise<any[]> { |
| 154 | + try { |
| 155 | + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; |
| 156 | + const response = await this.client.get<any>(url); |
| 157 | + return response.data; |
| 158 | + } catch (error: any) { |
| 159 | + console.error('Error fetching previous attempts:', error); |
| 160 | + throw new Error(error.response?.data?.message || 'Failed to fetch previous attempts.'); |
| 161 | + } |
115 | 162 | } |
116 | 163 |
|
117 | 164 | static getInstance(context: vscode.ExtensionContext, apiBaseUrl: string): ApiService { |
|
0 commit comments