Skip to content

Commit 5a7abb1

Browse files
committed
Merge branch 'total-usage-counter'
2 parents cb35bc5 + 09afe02 commit 5a7abb1

File tree

10 files changed

+105
-15
lines changed

10 files changed

+105
-15
lines changed

src/client/components/Courses/Course/Stats.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useState } from 'react'
2-
import { Box, Paper, Typography, TableBody, TableCell, TableHead, TableRow, Table, Button } from '@mui/material'
2+
import { Box, Paper, Typography, TableBody, TableCell, TableHead, TableRow, Table, Button, Tooltip as MUITooltip } from '@mui/material'
33
import { useTranslation } from 'react-i18next'
44
import { BarChart, Bar, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
55

66
import useCurrentUser from '../../../hooks/useCurrentUser'
77
import useCourse, { useCourseEnrolments, useCourseStatistics } from '../../../hooks/useCourse'
88
import MaxTokenUsageStudents from './MaxTokenUsageStudents'
9+
import { HelpOutline } from '@mui/icons-material'
910

1011
const Stats = ({ courseId }: { courseId: string }) => {
1112
const { t } = useTranslation()
@@ -47,11 +48,11 @@ const Stats = ({ courseId }: { courseId: string }) => {
4748
</Typography>
4849

4950
<Typography sx={{ my: 1 }}>
50-
{t('course:averageTokenUsage')} {Math.round(average) ?? t('course:noData')}
51+
{t('course:averageTokenUsage')}: <strong>{Math.round(average) ?? t('course:noData')}</strong>
5152
</Typography>
5253

5354
<Typography>
54-
{t('course:usagePercentage')} {usagePercentage ? `${Math.round(usagePercentage * 100 * 10) / 10}%` : t('course:noData')}
55+
{t('course:usagePercentage')}: <strong>{usagePercentage ? `${Math.round(usagePercentage * 100 * 10) / 10}%` : t('course:noData')}</strong>
5556
</Typography>
5657

5758
{usages && usages.length > 3 && usagePercentage > 0.2 && (
@@ -102,8 +103,38 @@ const Stats = ({ courseId }: { courseId: string }) => {
102103
<TableCell>
103104
<strong>{t('admin:firstNames')}</strong>
104105
</TableCell>
105-
<TableCell>
106-
<strong>{t('admin:usage')}</strong>
106+
<TableCell align='right'>
107+
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', justifyContent: 'flex-end' }}>
108+
<strong>{t('admin:usage')}</strong>
109+
<MUITooltip
110+
arrow
111+
placement="top"
112+
title={
113+
<Typography variant="body2" sx={{ p: 1 }}>
114+
{t('course:usageToolTip')}
115+
</Typography>
116+
}
117+
>
118+
<HelpOutline fontSize="small" sx={{ color: 'inherit', opacity: 0.7 }} />
119+
</MUITooltip>
120+
</Box>
121+
</TableCell>
122+
123+
<TableCell align='right'>
124+
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', justifyContent: 'flex-end' }}>
125+
<strong>{t('admin:totalUsage')}</strong>
126+
<MUITooltip
127+
arrow
128+
placement="top"
129+
title={
130+
<Typography variant="body2" sx={{ p: 1 }}>
131+
{t('course:totalUsageToolTip')}
132+
</Typography>
133+
}
134+
>
135+
<HelpOutline fontSize="small" sx={{ color: 'inherit', opacity: 0.7 }} />
136+
</MUITooltip>
137+
</Box>
107138
</TableCell>
108139
</TableRow>
109140
</TableHead>
@@ -113,7 +144,8 @@ const Stats = ({ courseId }: { courseId: string }) => {
113144
<TableCell>{enrolled.student_number}</TableCell>
114145
<TableCell>{enrolled.last_name}</TableCell>
115146
<TableCell>{enrolled.first_names}</TableCell>
116-
<TableCell>{usageByUser[enrolled.id] ? usageByUser[enrolled.id].usageCount : 0}</TableCell>
147+
<TableCell align='right'>{usageByUser[enrolled.id] ? usageByUser[enrolled.id].usageCount : 0}</TableCell>
148+
<TableCell align='right'>{usageByUser[enrolled.id] ? usageByUser[enrolled.id].totalUsageCount : 0}</TableCell>
117149
</TableRow>
118150
))}
119151
</TableBody>

src/client/locales/en.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
"courseId": "Course ID",
230230
"courseIdInfo": "Course ID (Course Unit Realisation)",
231231
"usage": "Usage",
232+
"totalUsage": "Total usage",
232233
"username": "Username",
233234
"usageCount": "Usage count",
234235
"reset": "Reset",
@@ -284,8 +285,10 @@
284285
"curreEnabledTab": "CurreChat enabled",
285286
"endedTab": "Ended",
286287
"statistics": "Statistics",
287-
"averageTokenUsage": "Average token usage: ",
288-
"usagePercentage": "Percentage of students who have used CurreChat: ",
288+
"averageTokenUsage": "Average token usage by total usage",
289+
"usagePercentage": "Percentage of students who have used CurreChat",
290+
"usageToolTip": "Number of tokens consumed since the start of the course or since the last reset. Resets to zero when refreshed.",
291+
"totalUsageToolTip": "Total tokens consumed over the lifetime of the course. This value never resets.",
289292
"usageChartTitle": "Student token usages as percentages",
290293
"noData": "No data",
291294
"oneMandatoryPrompt": "Course can have only one mandatory prompt",

src/client/locales/fi.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
"courseId": "Kurssin ID",
227227
"courseIdInfo": "Kurssin ID (Course Unit Realisation)",
228228
"usage": "Käyttö",
229+
"totalUsage": "Kokonaiskäyttö",
229230
"username": "Käyttäjätunnus",
230231
"firstNames": "Etunimet",
231232
"lastName": "Sukunimi",
@@ -285,8 +286,10 @@
285286
"curreEnabledTab": "CurreChat käytössä",
286287
"endedTab": "Päättyneet",
287288
"statistics": "Tilastoja",
288-
"averageTokenUsage": "Käytettyjen tokeneiden keskiarvo: ",
289-
"usagePercentage": "CurreChatin käyttöön ottaneiden opiskelijoiden osuus: ",
289+
"averageTokenUsage": "Käytettyjen tokeneiden keskiarvo kokonaiskäytön mukaan",
290+
"usagePercentage": "CurreChatin käyttöön ottaneiden opiskelijoiden osuus",
291+
"usageToolTip": "Käytettyjen tokenien määrä kurssin alusta lähtien tai viimeisestä nollauksesta lähtien. Nollautuu, kun laskuri palautetaan.",
292+
"totalUsageToolTip": "Tokenien kokonaismäärä koko kurssin ajalta. Tämä arvo ei koskaan nollaudu.",
290293
"usageChartTitle": "Opiskelijoiden käytetyt tokenit prosenttiosuutena käyttörajasta",
291294
"noData": "Ei dataa",
292295
"editMandatoryPrompt": "Tee alustuksesta pakollinen opiskelijoille",

src/client/locales/sv.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
"courseId": "Kurs-ID",
204204
"courseIdInfo": "Kurs-ID (Curse Unit Realisation)",
205205
"usage": "Användande",
206+
"totalUsage": "Total användning",
206207
"username": "Användarnamn",
207208
"usageCount": "Användningsräkning",
208209
"reset": "Återställa",
@@ -250,8 +251,10 @@
250251
"curreEnabledTab": "CurreChat aktiverat",
251252
"endedTab": "Slutade",
252253
"statistics": "Statistik",
253-
"averageTokenUsage": "Genomsnittlig tokenanvändning: ",
254-
"usagePercentage": "Andel elever som har använt CurreChat: ",
254+
"averageTokenUsage": "Genomsnittlig tokenanvändning enligt total användning",
255+
"usagePercentage": "Andel elever som har använt CurreChat",
256+
"usageToolTip": "Antal använda tokens sedan kursens start eller sedan senaste återställning. Nollställs när räknaren återställs.",
257+
"totalUsageToolTip": "Totalt antal tokens som använts under hela kursen. Detta värde nollställs aldrig.",
255258
"usageChartTitle": "Studenttokenanvändning i procent",
256259
"noData": "Inga data",
257260
"editMandatoryPrompt": "Gör en uppmaning obligatorisk",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { DataTypes } from 'sequelize'
2+
3+
import type { Migration } from '../connection'
4+
5+
export const up: Migration = async ({ context: queryInterface }) => {
6+
await queryInterface.addColumn('user_chat_instance_usages', 'totalUsageCount', {
7+
type: DataTypes.INTEGER,
8+
allowNull: false,
9+
defaultValue: 0,
10+
})
11+
}
12+
13+
export const down: Migration = async ({ context: queryInterface }) => {
14+
await queryInterface.removeColumn('user_chat_instance_usages', 'totalUsageCount')
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DataTypes } from 'sequelize'
2+
3+
import type { Migration } from '../connection'
4+
5+
export const up: Migration = async ({ context: queryInterface }) => {
6+
await queryInterface.renameColumn('user_chat_instance_usages', 'totalUsageCount', 'total_usage_count')
7+
}
8+
9+
export const down: Migration = async ({ context: queryInterface }) => {
10+
await queryInterface.renameColumn('user_chat_instance_usages', 'total_usage_count', 'totalUsageCount')
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Migration } from '../connection'
2+
3+
export const up: Migration = async ({ context: queryInterface }) => {
4+
await queryInterface.sequelize.query(`
5+
UPDATE user_chat_instance_usages
6+
SET total_usage_count = usage_count
7+
WHERE total_usage_count IS NULL OR total_usage_count = 0;
8+
`)
9+
}
10+
11+
export const down: Migration = async ({ context: queryInterface }) => {
12+
await queryInterface.sequelize.query(`
13+
UPDATE user_chat_instance_usages
14+
SET total_usage_count = 0;
15+
`)
16+
}

src/server/db/models/userChatInstanceUsage.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class UserChatInstanceUsage extends Model<InferAttributes<UserChatInstanceUsage>
1010
declare chatInstanceId: string
1111

1212
declare usageCount: CreationOptional<number>
13+
14+
declare totalUsageCount: CreationOptional<number>
1315
}
1416

1517
UserChatInstanceUsage.init(
@@ -33,6 +35,11 @@ UserChatInstanceUsage.init(
3335
allowNull: false,
3436
defaultValue: 0,
3537
},
38+
totalUsageCount: {
39+
type: DataTypes.INTEGER,
40+
allowNull: false,
41+
defaultValue: 0,
42+
},
3643
},
3744
{
3845
underscored: true,

src/server/routes/course.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ courseRouter.get('/statistics/:id', async (req, res) => {
6969
where: { chatInstanceId: chatInstance.id },
7070
})
7171

72-
const enrolledUsages = usages.filter((usage) => enrolments.map((e) => e.userId).includes(usage.userId)).filter((u) => u.usageCount > 0)
72+
const enrolledUsages = usages.filter((usage) => enrolments.map((e) => e.userId).includes(usage.userId)).filter((u) => u.totalUsageCount > 0)
7373

7474
const usagePercentage = enrolledUsages.length / enrolments.length
7575

76-
const average = enrolledUsages.map((u) => u.usageCount).reduce((a, b) => a + b, 0) / enrolledUsages.length
76+
const average = enrolledUsages.map((u) => u.totalUsageCount).reduce((a, b) => a + b, 0) / enrolledUsages.length
7777

7878
const normalizedUsage = enrolledUsages.map((usage) => ({
7979
...usage,

src/server/services/chatInstances/usage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const incrementCourseUsage = async (chatInstance: ChatInstance, tokenCoun
7171
if (!chatInstance.currentUserUsage) {
7272
throw ApplicationError.InternalServerError('chatInstance.currentUserUsage undefined. This shouldnt happen!')
7373
}
74-
await chatInstance.currentUserUsage.increment({ usageCount: tokenCount })
74+
await chatInstance.currentUserUsage.increment({ usageCount: tokenCount, totalUsageCount: tokenCount })
7575
}
7676

7777
export const getUserStatus = async (user: UserType, courseId: string) => {

0 commit comments

Comments
 (0)