Skip to content

Commit 876f4e9

Browse files
committed
Complete client admin
1 parent 4ae3385 commit 876f4e9

File tree

12 files changed

+277
-62
lines changed

12 files changed

+277
-62
lines changed

clients/frontend/admin/components/problem/ProblemForm.jsx

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from "react";
2-
import {e} from "react-router/dist/production/fog-of-war-BLArG-qZ";
32

43
export function ProblemInitialState() {
54
return {
@@ -61,7 +60,10 @@ export function ProblemReducer(problem, action) {
6160
return p
6261

6362
case "add_group":
64-
p.test_groups.push({})
63+
p.test_groups.push({
64+
"scoring_type": 1,
65+
"feedback_type": 3,
66+
})
6567
return p
6668

6769
case "group":
@@ -84,15 +86,21 @@ export function ProblemReducer(problem, action) {
8486
if (newScoringType !== 1) {
8587
removeRequired(idx)
8688
}
89+
p.test_groups[idx][action.groupAction] = newScoringType
90+
return p
8791

8892
// Score fields
89-
case "score":
93+
case "group_score":
9094
case "test_score":
91-
delete p.test_groups[idx]["score"]
95+
console.log(action)
96+
delete p.test_groups[idx]["group_score"]
9297
delete p.test_groups[idx]["test_score"]
93-
if (action.value !== "") {
94-
p[action.groupAction] = parseFloat(action.value)
98+
if (action.value === "") {
99+
delete p.test_groups[idx][action.groupAction]
100+
} else {
101+
p.test_groups[idx][action.groupAction] = parseFloat(action.value)
95102
}
103+
console.log(p)
96104
return p
97105

98106
case "add_required":
@@ -178,7 +186,7 @@ export function RenderProblemForm(
178186
<option value="" key="none"></option>
179187
)}
180188
{values.map((value, index) => (
181-
<option key={index} value={value.value}>value.name</option>
189+
<option key={index} value={value.value}>{value.name}</option>
182190
))}
183191
</select>
184192
</div>
@@ -217,23 +225,31 @@ function renderGroups(problem, changeProblem) {
217225
return null
218226
}
219227
return (
220-
<table>
221-
<thead>
222-
<tr>
223-
<th scope="row">Name</th>
224-
<th scope="row">First test</th>
225-
<th scope="row">Last test</th>
226-
<th scope="row">Score</th>
227-
<th scope="row">Scoring Type</th>
228-
<th scope="row">Feedback Type</th>
229-
<th scope="row">Required</th>
230-
<th scope="row">Delete</th>
231-
</tr>
232-
</thead>
233-
<tbody>
234-
{problem.test_groups.map((group, index) => renderGroup(group, index, problem, changeProblem))}
235-
</tbody>
236-
</table>
228+
<>
229+
<table className="table table-striped">
230+
<thead>
231+
<tr>
232+
<th style={{width: "10%"}} scope="row">Name</th>
233+
<th style={{width: "10%"}} scope="row">First test</th>
234+
<th style={{width: "10%"}} scope="row">Last test</th>
235+
<th style={{width: "10%"}} scope="row">Score</th>
236+
<th style={{width: "10%"}} scope="row">Scoring Type</th>
237+
<th style={{width: "10%"}} scope="row">Feedback Type</th>
238+
<th style={{width: "30%"}} scope="row">Required</th>
239+
<th style={{width: "10%"}} scope="row">Delete</th>
240+
</tr>
241+
</thead>
242+
<tbody>
243+
{problem.test_groups.map((group, index) => renderGroup(group, index, problem, changeProblem))}
244+
</tbody>
245+
</table>
246+
<a href="#" onClick={(e) => {
247+
e.preventDefault();
248+
changeProblem({
249+
action: "add_group",
250+
})
251+
}}>Add group</a>
252+
</>
237253
)
238254
}
239255

@@ -246,7 +262,7 @@ function renderGroup(group, index, problem, changeProblem) {
246262
name={name}
247263
type={type}
248264
required={required}
249-
value={group[name] || ""}
265+
value={group[name] == null ? "" : group[name]}
250266
onChange={(e) => {
251267
changeProblem({
252268
action: "group",
@@ -288,9 +304,9 @@ function renderGroup(group, index, problem, changeProblem) {
288304
{tableInput("first_test", "number", true)}
289305
{tableInput("last_test", "number", true)}
290306
{group.scoring_type === 2 ? (
291-
tableInput("test_score", "number", true, "Test score")
307+
tableInput("test_score", "number", true)
292308
) : (
293-
tableInput("test_score", "number", true, "Group score")
309+
tableInput("group_score", "number", true)
294310
)}
295311
{tableSelect("scoring_type", [
296312
{value: 1, name: "Complete"},
@@ -311,7 +327,7 @@ function renderGroup(group, index, problem, changeProblem) {
311327
changeProblem({
312328
action: "group",
313329
groupIndex: index,
314-
groupAction: "delete",
330+
groupAction: "remove",
315331
})
316332
}}>Delete</a>
317333
</td>
@@ -323,7 +339,7 @@ function renderRequiredGroups(group, index, problem, changeProblem) {
323339
let canAdd = []
324340
for (let i = 0; i < index; i++) {
325341
const gName = problem.test_groups[i].name
326-
if (problem.test_groups[i].scoring_type === 1 || !required.includes(gName)) {
342+
if (problem.test_groups[i].scoring_type === 1 && !required.includes(gName)) {
327343
canAdd.push(gName)
328344
}
329345
}

clients/frontend/admin/components/submission/CompilationData.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export function RenderCompilationData(compilationData, submission, changeCompila
7373
</table>
7474
{compilationData.show ? (
7575
<>
76+
{RenderResource("Error", null, compilationResult.error)}
7677
{renderSourceCode(submission, compilationData["source"])}
7778
{RenderResource(
7879
"Compilation message",

clients/frontend/admin/pages/NewProblem.jsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import React, {useState} from "react";
1+
import React, {useReducer, useState} from "react";
22
import axios from "axios";
33
import {useNavigate} from "react-router-dom";
44
import ChangeAlert, {SendAlertRequest} from "../components/ChangeAlert";
55
import Body from "../components/Body";
6-
import ProblemForm from "../components/problem/ProblemForm";
6+
import {ProblemInitialState, ProblemReducer, RenderProblemForm} from "../components/problem/ProblemForm";
77

88
export default function NewProblem() {
9-
const [problem, setProblem] = useState({
10-
"problem_type": 1
11-
})
9+
const [problem, changeProblem] = useReducer(ProblemReducer, ProblemInitialState())
10+
1211
const navigate = useNavigate();
1312
const [alert, setAlert] = useState({
1413
hasAlert: false,
@@ -35,7 +34,7 @@ export default function NewProblem() {
3534
</div>
3635
<hr className="mt-4 mb-4"/>
3736
<div className="px-4 px-sm-5 mx-2 pb-5">
38-
{ProblemForm(problem, setProblem, newProblem, "Create")}
37+
{RenderProblemForm(problem, changeProblem, newProblem, "Create")}
3938
<div className="row mb-md-3 mb-0">{ChangeAlert(alert)}</div>
4039
</div>
4140
</div>

clients/frontend/admin/pages/Problem.jsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import React, {useEffect, useState} from "react";
1+
import React, {useEffect, useReducer, useState} from "react";
22
import {Link, useParams} from "react-router-dom";
33
import axios from "axios";
4-
import ProblemForm from "../components/problem/ProblemForm";
4+
import {ProblemInitialState, ProblemReducer, RenderProblemForm} from "../components/problem/ProblemForm";
55
import Body from "../components/Body";
66
import ChangeAlert, {SendAlertRequest} from "../components/ChangeAlert";
77

@@ -14,7 +14,7 @@ export default function Problems() {
1414
error: null,
1515
});
1616

17-
const [problem, setProblem] = useState({})
17+
const [problem, changeProblem] = useReducer(ProblemReducer, ProblemInitialState())
1818

1919
useEffect(() => {
2020
const apiURL = `/api/get/problem/${id}`
@@ -23,7 +23,10 @@ export default function Problems() {
2323
loading: false,
2424
error: resp.data.error,
2525
})
26-
setProblem(resp.data.response);
26+
changeProblem({
27+
action: "problem",
28+
problem: resp.data.response,
29+
})
2730
}).catch(
2831
(err) => {
2932
setState({
@@ -38,7 +41,7 @@ export default function Problems() {
3841
hasAlert: false,
3942
})
4043

41-
const changeProblem = () => {
44+
const modifyProblem = () => {
4245
const apiUrl = `/api/modify/problem/${id}`
4346
SendAlertRequest(axios.post(apiUrl, problem), setAlert, null)
4447
}
@@ -77,7 +80,7 @@ export default function Problems() {
7780

7881
return wrapContent(
7982
<div>
80-
{ProblemForm(problem, setProblem, changeProblem, "Save")}
83+
{RenderProblemForm(problem, changeProblem, modifyProblem, "Save")}
8184
<div className="row mb-md-3 mb-0">{ChangeAlert(alert)}</div>
8285
</div>
8386
)

clients/frontend/admin/pages/Submission.jsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,37 @@ export default function Submission() {
9999
</tbody>
100100
</table>
101101
{RenderCompilationData(compilationData, submission, changeCompilationData)}
102+
{submission.group_results ? (
103+
<>
104+
<h5 className="mb-3">Group results</h5>
105+
<div className="row">
106+
<div className="col-12 col-md-10 col-lg-8">
107+
<table className="table mb-3">
108+
<thead>
109+
<tr>
110+
<th scope="row">Name</th>
111+
<th scope="row">Score</th>
112+
<th scope="row">Passed</th>
113+
</tr>
114+
</thead>
115+
<tbody>
116+
{submission.group_results.map((group, index) => (
117+
<tr key={index}>
118+
<td>{group.group_name}</td>
119+
<td>{group.points}</td>
120+
<td>{group.passed ? (
121+
<span className="text-success">Yes</span>
122+
) : (
123+
<span className="text-danger">No</span>
124+
)}</td>
125+
</tr>
126+
))}
127+
</tbody>
128+
</table>
129+
</div>
130+
</div>
131+
</>
132+
) : null}
102133
{submission.test_results ? (
103134
<>
104135
<h5 className="mb-3">Test results</h5>

clients/resources/static/admin/admin.js

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/tsapi/problem.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ func (h *Handler) modifyProblem(c *gin.Context) {
7474
return
7575
}
7676
problem.ID = oldProblem.ID
77+
if !checkProblemIsOK(c, problem) {
78+
return
79+
}
7780
err := h.base.DB.WithContext(c).Save(&problem).Error
7881
if err != nil {
7982
respError(
@@ -166,3 +169,83 @@ func (h *Handler) findProblem(c *gin.Context, id string) (*models.Problem, bool)
166169

167170
return h.findProblemByID(c, uint(problemID))
168171
}
172+
173+
func checkProblemIsOK(c *gin.Context, problem models.Problem) bool {
174+
switch problem.ProblemType {
175+
case models.ProblemTypeICPC:
176+
return true
177+
case models.ProblemTypeIOI:
178+
lastTest := uint64(0)
179+
usedGroupNames := make(map[string]struct{})
180+
for _, group := range problem.TestGroups {
181+
_, ok := usedGroupNames[group.Name]
182+
if ok {
183+
respError(c, http.StatusBadRequest, "Group %s is used more than once", group.Name)
184+
return false
185+
}
186+
187+
if group.FirstTest != lastTest+1 {
188+
respError(c, http.StatusBadRequest,
189+
"Group %s first test is incorrect, should be previous group last test + 1", group.Name,
190+
)
191+
return false
192+
}
193+
if group.FirstTest > group.LastTest {
194+
respError(c, http.StatusBadRequest, "Group %s first test is greater than last test", group.Name)
195+
}
196+
lastTest = group.LastTest
197+
switch group.ScoringType {
198+
case models.TestGroupScoringTypeComplete, models.TestGroupScoringTypeMin:
199+
if group.GroupScore == nil {
200+
respError(c, http.StatusBadRequest, "Group %s has no group score", group.Name)
201+
return false
202+
}
203+
case models.TestGroupScoringTypeEachTest:
204+
if group.TestScore == nil {
205+
respError(c, http.StatusBadRequest, "Group %s has no test score", group.Name)
206+
return false
207+
}
208+
default:
209+
respError(c, http.StatusBadRequest, "Group %s has invalid scoring type", group.Name)
210+
return false
211+
}
212+
213+
switch group.FeedbackType {
214+
case models.TestGroupFeedbackTypeNone,
215+
models.TestGroupFeedbackTypePoints,
216+
models.TestGroupFeedbackTypeICPC,
217+
models.TestGroupFeedbackTypeComplete,
218+
models.TestGroupFeedbackTypeFull:
219+
// skip
220+
default:
221+
respError(c, http.StatusBadRequest, "Group %s has invalid feedback type", group.Name)
222+
return false
223+
}
224+
225+
usedRequiredGroups := make(map[string]struct{})
226+
for _, required := range group.RequiredGroupNames {
227+
if _, ok = usedGroupNames[required]; !ok {
228+
respError(c, http.StatusBadRequest,
229+
"Group %s has required group name %s which is not present", group.Name, required,
230+
)
231+
return false
232+
}
233+
if _, ok = usedRequiredGroups[required]; ok {
234+
respError(c, http.StatusBadRequest,
235+
"Group %s has duplicate required group name %s", group.Name, required,
236+
)
237+
}
238+
}
239+
240+
usedGroupNames[group.Name] = struct{}{}
241+
}
242+
if lastTest != problem.TestsNumber {
243+
respError(c, http.StatusBadRequest, "Not all tests are present in all groups")
244+
return false
245+
}
246+
return true
247+
default:
248+
respError(c, http.StatusBadRequest, "Invalid problem type")
249+
return false
250+
}
251+
}

common/db/models/problem.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ func (t TestGroups) GormDBDataType(db *gorm.DB, field *schema.Field) string {
8383
}
8484

8585
type Problem struct {
86-
ID uint `gorm:"primarykey" json:"ID" yaml:"ID"`
87-
CreatedAt time.Time `json:"created_at" yaml:"CreatedAt"`
86+
ID uint `gorm:"primarykey" json:"id" yaml:"id"`
87+
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
8888
UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
8989
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" yaml:"-"`
9090

common/db/models/submission.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type TestResult struct {
1919
Memory *customfields.Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
2020
WallTime *customfields.Time `json:"wall_time,omitempty" yaml:"wall_time,omitempty"`
2121
Error string `json:"error,omitempty" yaml:"error,omitempty"`
22-
ExitCode *int `json:"exit_code,omitempty" yaml:"exit_code"`
22+
ExitCode *int `json:"exit_code,omitempty" yaml:"exit_code,omitempty"`
2323
}
2424

2525
func (t TestResult) Value() (driver.Value, error) {
@@ -113,5 +113,5 @@ type Submission struct {
113113
Verdict verdict.Verdict `json:"verdict" yaml:"verdict"`
114114
TestResults TestResults `json:"test_results" yaml:"test_results"`
115115
CompilationResult *TestResult `json:"compilation_result" yaml:"compilation_result"`
116-
GroupResults GroupResults `json:"group_results,omitempty" yaml:"group_results,omitempty"`
116+
GroupResults GroupResults `json:"group_results,omitempty" yaml:"group_results,omitempty"`
117117
}

0 commit comments

Comments
 (0)