Skip to content

Commit 06920c0

Browse files
committed
Show rating results on project list for admins
1 parent 656bb32 commit 06920c0

4 files changed

Lines changed: 116 additions & 14 deletions

File tree

backend/src/controllers/dto.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,22 @@ export class RatingDTO {
628628
public rating!: number;
629629
}
630630

631+
class CriterionAvgDTO {
632+
@Expose()
633+
@Type(() => CriterionDTO)
634+
public criterion!: CriterionDTO;
635+
@Expose()
636+
public average!: number;
637+
}
638+
631639
// Do not send all ratings to the client,
632640
// because peoples opinion on the projects should be anonymous
633641
export class ProjectRatingResultDTO {
634642
@Expose()
635643
@Type(() => ProjectDTO)
636644
public project!: ProjectDTO;
645+
@IsArray()
646+
@Type(() => CriterionAvgDTO)
637647
@Expose()
638-
public criterionIdToAvg!: Record<number, number>;
648+
public averagesPerCriterion!: CriterionAvgDTO[];
639649
}

backend/src/services/rating-service.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@ import { IService } from ".";
55
import { DatabaseServiceToken, IDatabaseService } from "./database-service";
66
import { ISettingsService, SettingsServiceToken } from "./settings-service";
77
import { Rating } from "../entities/rating";
8-
import { RatingDTO, convertBetweenEntityAndDTO } from "../controllers/dto";
8+
import { RatingDTO, ProjectRatingResultDTO, convertBetweenEntityAndDTO } from "../controllers/dto";
99
import { User } from "../entities/user";
1010
import { Team } from "../entities/team";
1111
import { Project } from "../entities/project";
1212
import { Criterion } from "../entities/criterion";
1313
import { UserRole } from "../entities/user-role";
1414

15-
export interface ProjectRatingResult {
16-
project: Project;
17-
criterionIdToAvg: Record<number, number>;
18-
}
19-
2015
export interface IRatingService extends IService {
2116
/**
2217
* Get the ratings for a specific project, cast by a specific user.
@@ -38,7 +33,7 @@ export interface IRatingService extends IService {
3833
/**
3934
* Get all ratings for every project
4035
*/
41-
getRatingResults(): Promise<readonly ProjectRatingResult[]>;
36+
getRatingResults(): Promise<readonly ProjectRatingResultDTO[]>;
4237
}
4338

4439
/**
@@ -162,17 +157,23 @@ export class RatingService implements IRatingService {
162157
/**
163158
* Get the average ratings for each project
164159
*/
165-
public async getRatingResults(): Promise<readonly ProjectRatingResult[]> {
160+
public async getRatingResults(): Promise<readonly ProjectRatingResultDTO[]> {
166161
const allProjects = await this._projects.find();
167162
const allRatings = await this._ratings.find();
168163

169164
const result = [];
170165

166+
const idToCriterion: Record<number, Criterion> = {};
167+
171168
for (const project of allProjects) {
169+
const averagesPerCriterion = [];
170+
172171
// Sum up
173172
const criterionIdToSum: Record<number, number> = {}
174173
const criterionIdToCount: Record<number, number> = {}
175174
for (const rating of allRatings) {
175+
idToCriterion[rating.criterion.id] = rating.criterion;
176+
176177
if (rating.project.id !== project.id) {
177178
continue;
178179
}
@@ -190,12 +191,14 @@ export class RatingService implements IRatingService {
190191
// Calculate average
191192
const criterionIdToAvg: Record<number, number> = {}
192193
for (const criterionId in criterionIdToSum) {
193-
criterionIdToAvg[criterionId] = criterionIdToSum[criterionId] / criterionIdToCount[criterionId];
194+
const average = criterionIdToSum[criterionId] / criterionIdToCount[criterionId];
195+
const criterion = idToCriterion[criterionId];
196+
averagesPerCriterion.push({ criterion, average });
194197
}
195198

196199
result.push({
197200
project,
198-
criterionIdToAvg
201+
averagesPerCriterion
199202
});
200203
}
201204

frontend/src/components/pages/projects.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
import styled from "@emotion/styled";
2+
import {
3+
Table,
4+
TableBody,
5+
TableCell,
6+
TableContainer,
7+
TableHead,
8+
TableRow,
9+
Paper,
10+
} from '@mui/material';
211
import * as React from "react";
312
import { useState, useEffect } from "react";
413
import { Chip, Grid } from "@mui/material";
@@ -16,12 +25,87 @@ import { Routes } from "../../routes";
1625
import { TeamDTO } from "../../api/types/dto";
1726
import { api } from "../../hooks/use-api";
1827
import { PageHeader } from "../base/page-header";
28+
import { useLoginContext } from "../../contexts/login-context";
29+
import { UserRole } from "../../api/types/enums";
1930

2031
const HeaderContainer = styled(NonGrowingFlexContainer)`
2132
justify-content: space-between;
2233
flex-direction: row;
2334
`;
2435

36+
const arraySum = (array) => {
37+
return array.reduce((partialSum, a) => partialSum + a, 0);
38+
}
39+
40+
/**
41+
* A table displaying the average rating per criterion, and the total sum
42+
* for each project.
43+
*/
44+
const RatingResults = () => {
45+
const [ratingResults, setRatingResults] = useState([]);
46+
const [criteria, setCriteria] = React.useState([]);
47+
48+
useEffect(
49+
() => {
50+
api.getRatingResults().then((stuff) => {
51+
setRatingResults(stuff)
52+
});
53+
54+
api.getAllCriteria().then((criteria) => {
55+
setCriteria(criteria);
56+
});
57+
},
58+
[]
59+
);
60+
61+
return (
62+
<div style={{ marginTop: "2em" }}>
63+
<h2>Results</h2>
64+
<p>(Only visible to admins)</p>
65+
{
66+
<TableContainer component={Paper}>
67+
<Table>
68+
<TableHead>
69+
<TableRow>
70+
<TableCell>Project</TableCell>
71+
{criteria.map(criterion => (
72+
<TableCell key={criterion.id} align="center">
73+
{criterion.title}
74+
</TableCell>
75+
))}
76+
<TableCell key="CriterionSum" align="center">
77+
Sum
78+
</TableCell>
79+
</TableRow>
80+
</TableHead>
81+
<TableBody>
82+
{ratingResults.map(resultForProject => (
83+
<TableRow key={resultForProject.project.id}>
84+
<TableCell>
85+
{resultForProject.project.title} #{resultForProject.project.id}
86+
</TableCell>
87+
{criteria.map(criterion => (
88+
<TableCell key={criterion.id} align="center">
89+
{
90+
resultForProject
91+
.averagesPerCriterion
92+
.find((a) => a.criterion.id == criterion.id)?.average
93+
}
94+
</TableCell>
95+
))}
96+
<TableCell key="CriterionSum" align="center">
97+
{arraySum(resultForProject.averagesPerCriterion.map(({ average }) => average))}
98+
</TableCell>
99+
</TableRow>
100+
))}
101+
</TableBody>
102+
</Table>
103+
</TableContainer>
104+
}
105+
</div>
106+
)
107+
}
108+
25109
/**
26110
* - Show all projects visible to the user (owned + rating allowed)
27111
* - Let users rate them
@@ -32,6 +116,8 @@ const HeaderContainer = styled(NonGrowingFlexContainer)`
32116
export const Projects = () => {
33117
const [allProjects, setAllProjects] = useState([]);
34118
const [settings, setSettings] = useState({});
119+
const loginState = useLoginContext();
120+
const { user } = loginState;
35121

36122
// Do this only on mount
37123
useEffect(
@@ -111,6 +197,9 @@ export const Projects = () => {
111197
</Grid>
112198
))}
113199
</Grid>
200+
{user.role === UserRole.Root && (
201+
<RatingResults />
202+
)}
114203
</Page>
115204
);
116205
};

frontend/src/components/pages/rating-form.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import {
55
RadioGroup,
66
FormControlLabel,
77
Radio,
8-
Button,
98
Alert,
109
Typography,
1110
Tooltip,
1211
} from "@mui/material";
1312
import { api } from "../../hooks/use-api";
1413
import { useLoginContext } from "../../contexts/login-context";
14+
import { Button } from "../base/button";
1515

1616
/**
1717
* Component that allows users to submit and edit ratings for projects.
@@ -96,9 +96,9 @@ export const RatingForm = ({
9696
variant="contained"
9797
onClick={handleSubmit}
9898
disabled={isSubmitting}
99-
sx={{ alignSelf: { xs: "stretch", sm: "auto" } }}
99+
loading={isSubmitting}
100100
>
101-
{isSubmitting ? "Submitting..." : "Submit"}
101+
Submit
102102
</Button>
103103
</Stack>
104104

0 commit comments

Comments
 (0)