Skip to content

Commit 40e36e2

Browse files
jabbadizzleCoderschlaefliTheHummel
authored
enhance(packages/shared-components): replace tagcloud with wordcloud and filter user input (#4947)
Co-authored-by: Roland Schlaefli <rolandschlaefli@gmail.com> Co-authored-by: TheHummel <jannisalsbach@gmail.com>
1 parent 8119fd5 commit 40e36e2

39 files changed

Lines changed: 3652 additions & 947 deletions

.github/workflows/check-types.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ jobs:
4949
pnpm run build
5050
cd ../hatchet
5151
pnpm run build
52+
cd ../word-cloud
53+
pnpm run build
5254
5355
- name: Check the typescript types of all packages and apps
5456
shell: bash

.lintstagedrc.mjs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
import { lstatSync } from 'fs'
2+
13
export default {
2-
'*': ['prettier --config .prettierrc.mjs --check'],
4+
'*': (stagedFiles) => {
5+
const nonSymlinks = stagedFiles.filter((f) => {
6+
try {
7+
return !lstatSync(f).isSymbolicLink()
8+
} catch {
9+
return false
10+
}
11+
})
12+
if (nonSymlinks.length === 0) return []
13+
return [
14+
`prettier --config .prettierrc.mjs --ignore-unknown --check ${nonSymlinks.join(' ')}`,
15+
]
16+
},
317
}

.prettierignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ email/
1313
project/
1414
.agents/skills/**/.agnix.toml
1515

16+
# Symlinks
17+
.github/skills
18+
CLAUDE.md
19+
1620
ops.schema.json
1721
ops.ts
1822
schema.graphql
@@ -52,3 +56,12 @@ pnpm-lock.yaml
5256
# Dumps
5357
*.tar
5458
*.gpg
59+
60+
# Unsupported file types
61+
**/*.toml
62+
**/*.gitignore
63+
**/Dockerfile*
64+
**/*.prisma
65+
**/*.py
66+
**/*.txt
67+
**/.codex/

.versionrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = {
2121
'packages/lti/',
2222
'packages/prisma/',
2323
'packages/markdown/',
24+
'packages/word-cloud',
2425
'packages/shared-components',
2526
'packages/next-config',
2627
'packages/i18n',

apps/frontend-manage/src/components/evaluation/ElementChart.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { ElementInstanceEvaluation } from '@klicker-uzh/graphql/dist/ops'
22
import ElementBarChart from '@klicker-uzh/shared-components/src/charts/ElementBarChart'
33
import ElementHistogram from '@klicker-uzh/shared-components/src/charts/ElementHistogram'
44
import ElementTableChart from '@klicker-uzh/shared-components/src/charts/ElementTableChart'
5-
import ElementWordcloud from '@klicker-uzh/shared-components/src/charts/ElementWordcloud'
5+
import ElementWordCloud from '@klicker-uzh/shared-components/src/charts/ElementWordcloud'
66
import { ChartType } from '@klicker-uzh/shared-components/src/constants'
77
import EvaluationExplanation from '@klicker-uzh/shared-components/src/evaluation/EvaluationExplanation'
88
import { useTranslations } from 'next-intl'
9+
import { useRouter } from 'next/router'
910
import React from 'react'
1011
import { twMerge } from 'tailwind-merge'
1112
import { ShowStatisticsType } from './elements/NREvaluation'
@@ -31,6 +32,7 @@ function ElementChart({
3132
className,
3233
}: ElementChartProps): React.ReactElement {
3334
const t = useTranslations()
35+
const router = useRouter()
3436

3537
if (chartType === ChartType.TABLE) {
3638
return (
@@ -45,9 +47,8 @@ function ElementChart({
4547
)
4648
} else if (chartType === ChartType.WORD_CLOUD) {
4749
return (
48-
<ElementWordcloud
50+
<ElementWordCloud
4951
instance={instanceEvaluation}
50-
showSolution={showSolution}
5152
showExplanation={showExplanation}
5253
textSize={{
5354
text: textSize.text,
@@ -56,6 +57,7 @@ function ElementChart({
5657
max: textSize.max,
5758
}}
5859
className={className}
60+
locale={router.locale}
5961
/>
6062
)
6163
} else if (chartType === ChartType.BAR_CHART) {

apps/frontend-manage/src/components/evaluation/ElementEvaluation.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ function ElementEvaluation({
213213
isAssessmentEnabled={isAssessmentEnabled}
214214
pinCode={pinCode}
215215
type={type}
216+
// TODO: word cloud for case study?
216217
/>
217218
)}
218219

@@ -232,6 +233,7 @@ function ElementEvaluation({
232233
key={currentInstance.id}
233234
evaluation={currentInstance}
234235
textSize={textSize}
236+
// TODO: word cloud for content?
235237
/>
236238
)}
237239
</div>

cypress/cypress.config.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { prisma } from '@klicker-uzh/prisma'
22
import {
33
AchievementType,
44
CourseAuthType,
5+
ElementBlockStatus,
56
ElementType,
67
ObjectAccess,
78
PermissionLevel,
@@ -21,6 +22,7 @@ import {
2122
ElementOptionsSelection,
2223
} from '@klicker-uzh/types'
2324
import bcrypt from 'bcryptjs'
25+
import { createHash } from 'crypto'
2426
import { defineConfig } from 'cypress'
2527
import cypressSplit from 'cypress-split'
2628
// import cypressCodeCoverage from '@cypress/code-coverage/task'
@@ -1708,6 +1710,132 @@ export default defineConfig({
17081710
throw error
17091711
}
17101712
},
1713+
async seedWordCloudLiveQuizResponses({
1714+
freeTextAnswer,
1715+
freeTextTitle,
1716+
numericalAnswer,
1717+
numericalTitle,
1718+
quizName,
1719+
secondFreeTextAnswer,
1720+
secondFreeTextTitle,
1721+
}: {
1722+
freeTextAnswer: string
1723+
freeTextTitle: string
1724+
numericalAnswer: string
1725+
numericalTitle: string
1726+
quizName: string
1727+
secondFreeTextAnswer: string
1728+
secondFreeTextTitle: string
1729+
}) {
1730+
try {
1731+
const liveQuiz = await prisma.liveQuiz.findFirst({
1732+
where: { name: quizName, isDeleted: false },
1733+
include: {
1734+
blocks: {
1735+
include: { elements: true },
1736+
orderBy: { order: 'asc' },
1737+
},
1738+
},
1739+
})
1740+
1741+
if (!liveQuiz) {
1742+
throw new Error(`Live quiz ${quizName} not found`)
1743+
}
1744+
1745+
const block = liveQuiz.blocks[0]
1746+
if (!block) {
1747+
throw new Error(`Live quiz ${quizName} has no blocks`)
1748+
}
1749+
1750+
const instances = liveQuiz.blocks.flatMap((block) => block.elements)
1751+
const getInstance = (title: string, type: ElementType) => {
1752+
const instance = instances.find(
1753+
(element) =>
1754+
element.elementType === type &&
1755+
typeof element.elementData === 'object' &&
1756+
element.elementData !== null &&
1757+
'name' in element.elementData &&
1758+
element.elementData.name === title
1759+
)
1760+
1761+
if (!instance) {
1762+
throw new Error(
1763+
`Instance ${title} (${type}) not found in live quiz ${quizName}`
1764+
)
1765+
}
1766+
1767+
return instance
1768+
}
1769+
1770+
const numericalInstance = getInstance(
1771+
numericalTitle,
1772+
ElementType.NUMERICAL
1773+
)
1774+
const freeTextInstance = getInstance(
1775+
freeTextTitle,
1776+
ElementType.FREE_TEXT
1777+
)
1778+
const secondFreeTextInstance = getInstance(
1779+
secondFreeTextTitle,
1780+
ElementType.FREE_TEXT
1781+
)
1782+
1783+
const openResults = (value: string, normalize: boolean) => {
1784+
const normalizedValue = normalize
1785+
? value.trim().toLowerCase()
1786+
: String(parseFloat(value))
1787+
const hash = createHash('md5')
1788+
.update(normalizedValue)
1789+
.digest('hex')
1790+
1791+
return {
1792+
responses: {
1793+
[hash]: {
1794+
value: normalizedValue,
1795+
count: 1,
1796+
},
1797+
},
1798+
total: 1,
1799+
}
1800+
}
1801+
1802+
await prisma.$transaction([
1803+
prisma.elementInstance.update({
1804+
where: { id: numericalInstance.id },
1805+
data: {
1806+
anonymousResults: openResults(numericalAnswer, false),
1807+
},
1808+
}),
1809+
prisma.elementInstance.update({
1810+
where: { id: freeTextInstance.id },
1811+
data: {
1812+
anonymousResults: openResults(freeTextAnswer, true),
1813+
},
1814+
}),
1815+
prisma.elementInstance.update({
1816+
where: { id: secondFreeTextInstance.id },
1817+
data: {
1818+
anonymousResults: openResults(secondFreeTextAnswer, true),
1819+
},
1820+
}),
1821+
prisma.elementBlock.update({
1822+
where: { id: block.id },
1823+
data: {
1824+
closedAt: new Date(),
1825+
status: ElementBlockStatus.EXECUTED,
1826+
},
1827+
}),
1828+
prisma.liveQuiz.update({
1829+
where: { id: liveQuiz.id },
1830+
data: { activeBlockId: null },
1831+
}),
1832+
])
1833+
1834+
return true
1835+
} catch (error) {
1836+
throw error
1837+
}
1838+
},
17111839
async deleteLiveQuiz({ name }: { name: string }) {
17121840
try {
17131841
const liveQuiz = await prisma.liveQuiz.findFirst({

0 commit comments

Comments
 (0)