Skip to content

Commit

Permalink
feat(apps/frontend-manage): add demo questions on first login (#3876)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjschlapbach authored Sep 27, 2023
1 parent c797161 commit 9b9f040
Show file tree
Hide file tree
Showing 2 changed files with 337 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/frontend-manage/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function SuspendedFirstLoginModal() {
shortname: values.shortname,
locale: values.locale,
},
refetchQueries: [GetUserQuestionsDocument],
})

if (!result) {
Expand Down
340 changes: 336 additions & 4 deletions packages/graphql/src/services/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as DB from '@klicker-uzh/prisma'
import { Locale, UserLoginScope, UserRole } from '@klicker-uzh/prisma'
import bcrypt from 'bcryptjs'
import dayjs from 'dayjs'
Expand All @@ -7,6 +8,7 @@ import isEmail from 'validator/lib/isEmail'
import normalizeEmail from 'validator/lib/normalizeEmail'
import { Context, ContextWithUser } from '../lib/context'
import { sendTeamsNotifications } from '../lib/util'
import { prepareInitialInstanceResults, processQuestionData } from './sessions'

const COOKIE_SETTINGS: CookieOptions = {
domain: process.env.COOKIE_DOMAIN,
Expand Down Expand Up @@ -469,9 +471,6 @@ export async function changeInitialSettings(
where: { shortname },
})

console.log('query user id: ', ctx.user.sub)
console.log('Found user with same shortname: ', existingUser)

if (existingUser && existingUser.id !== ctx.user.sub) {
// another user already uses the shortname this user wants
const user = await ctx.prisma.user.update({
Expand All @@ -481,10 +480,343 @@ export async function changeInitialSettings(
return user
}

// seed demo questions
await seedDemoQuestions(ctx)

const user = await ctx.prisma.user.update({
where: { id: ctx.user.sub },
data: { shortname, locale, firstLogin: false },
data: {
shortname,
locale,
firstLogin: false,
},
})

return user
}

async function seedDemoQuestions(ctx: ContextWithUser) {
// create single choice demo question
const questionSC = await ctx.prisma.question.create({
data: {
name: 'Demoquestion SC',
type: DB.QuestionType.SC,
displayMode: DB.QuestionDisplayMode.GRID,
content:
'Which of the following statements is applicable to _KlickerUZH_?',
options: {
choices: [
{
ix: 0,
value: 'KlickerUZH is owned by Google',
correct: false,
feedback: 'False!',
},
{
ix: 1,
value: 'KlickerUZH is an open-source audience response system',
correct: true,
feedback: 'Correct! The source code is available on GitHub.',
},
{
ix: 2,
value: 'KlickerUZH cannot be used by everyone',
correct: false,
feedback: 'False!',
},
{
ix: 3,
value: 'KlickerUZH is not a project of the University of Zurich',
correct: false,
feedback: 'False!',
},
],
},
explanation:
'For Single Choice questions, you can specify a correct solution, answer feedbacks and a general explanation. All of those texts can be formatted using the editor or Markdown and LaTeX syntax and can contain images.',
pointsMultiplier: 1,
hasSampleSolution: true,
hasAnswerFeedbacks: true,
owner: {
connect: {
id: ctx.user.sub,
},
},
tags: {
connectOrCreate: {
where: {
ownerId_name: {
ownerId: ctx.user.sub,
name: 'Demo Tag',
},
},
create: {
name: 'Demo Tag',
owner: {
connect: {
id: ctx.user.sub,
},
},
},
},
},
},
})

// create multiple choice demo question
const questionMC = await ctx.prisma.question.create({
data: {
name: 'Demoquestion MC',
type: DB.QuestionType.MC,
content:
'Which of the following formulas have the form of a Taylor polynomial of some degree $$n$$: $$T_n f(x;a)$$? (multiple answers are possible)',
options: {
choices: [
{
ix: 0,
correct: false,
value:
'$$T_n f(x;a) = \\sum_{|\\alpha| = 0}^{n} (x - a)^\\alpha D^\\alpha f(a-x)$$',
feedback: 'False!',
},
{
ix: 1,
correct: true,
value:
"$$T_n f(x;a) = f(a) + \\frac{f'(a)}{1!}(x - a) + \\frac{f''(a)}{2!}(x - a)^2 + ... + \\frac{f^{(n)}(a)}{n!}(x - a)^n$$",
feedback:
'Correct! This is the general form of a Taylor polynomial of degree $$n$$.',
},
{
ix: 2,
correct: true,
value: '$$T_4 sin(x;0) = x - \\frac{x^3}{6}$$',
feedback:
'Correct! This is the Taylor polynomial of degree $$4$$ of $$sin(x)$$ around $$x = 0$$.',
},
{
ix: 3,
correct: false,
value: '$$T_4 cos(x;0) = x + \\frac{x^3}{6}$$',
feedback: 'False! This is not a Taylor polynomial of $$cos(x)$$.',
},
],
},
explanation:
'Multiple Choice questions can have multiple correct answers. You can specify answer feedbacks and a general explanation. All of those texts can be formatted using the editor or Markdown and LaTeX syntax and can contain images.',
pointsMultiplier: 2,
hasSampleSolution: true,
hasAnswerFeedbacks: true,
owner: {
connect: {
id: ctx.user.sub,
},
},
tags: {
connect: {
ownerId_name: {
ownerId: ctx.user.sub,
name: 'Demo Tag',
},
},
},
},
})

// create KPRIM demo question
const questionKPRIM = await ctx.prisma.question.create({
data: {
name: 'Demoquestion KPRIM',
type: DB.QuestionType.KPRIM,
content:
'Which of the following statements is applicable to _KlickerUZH_? (multiple correct answers possible)',
options: {
choices: [
{
ix: 0,
value: 'KlickerUZH is owned by Google',
correct: false,
feedback: 'False!',
},
{
ix: 1,
value: 'KlickerUZH is an open-source audience response system',
correct: true,
feedback: 'Correct! The source code is available on GitHub.',
},
{
ix: 2,
value: 'KlickerUZH cannot be used by everyone',
correct: false,
feedback: 'False!',
},
{
ix: 3,
value:
'KlickerUZH can be used in lecture settings with serveral hundred students',
correct: true,
feedback:
'Correct! KlickerUZH is designed for large audiences and can handle thousands of concurrent users.',
},
],
},
explanation:
'KPRIM questions differ from Multiple Choice questions in that they use a different grading approach and consist of exactly four answer possibilities, which have to be selected to be true or false. You can specify answer feedbacks and a general explanation. All of those texts can be formatted using the editor or Markdown and LaTeX syntax and can contain images.',
pointsMultiplier: 3,
hasSampleSolution: true,
hasAnswerFeedbacks: true,
owner: {
connect: {
id: ctx.user.sub,
},
},
tags: {
connect: {
ownerId_name: {
ownerId: ctx.user.sub,
name: 'Demo Tag',
},
},
},
},
})

// create Numerical demo question
const questionNR = await ctx.prisma.question.create({
data: {
name: 'Demoquestion NR',
type: DB.QuestionType.NUMERICAL,
content:
'Estimate the length of the **longest** river in the world (answer in kilometres).',
options: {
unit: 'km',
accuracy: 0,
restrictions: { max: 10000, min: 0 },
solutionRanges: [{ max: 6600, min: 6500 }],
},
explanation:
'Numerical questions can contain additional restrictions, like minimum and maximum values as well as display units. It is also possible to specify valid ranges, which are considered to be correct for graded and gamified settings, as well as a general explanation. All of those texts can be formatted using the editor or Markdown and LaTeX syntax and can contain images.',
pointsMultiplier: 4,
hasSampleSolution: true,
owner: {
connect: {
id: ctx.user.sub,
},
},
tags: {
connect: {
ownerId_name: {
ownerId: ctx.user.sub,
name: 'Demo Tag',
},
},
},
},
})

// create Free Text demo question
const questionFT = await ctx.prisma.question.create({
data: {
name: 'Demoquestion FT',
type: DB.QuestionType.FREE_TEXT,
content: 'Describe a main principle of a social market economy.',
options: {
solutions: ['fair competition', 'private companies', 'balance'],
restrictions: { maxLength: 150 },
},
explanation:
'Free Text questions can contain additional restrictions, like a maximum length, as well as sample solutions for graded and gamified settings. All of those texts can be formatted using the editor or Markdown and LaTeX syntax and can contain images.',
pointsMultiplier: 4,
hasSampleSolution: true,
owner: {
connect: {
id: ctx.user.sub,
},
},
tags: {
connect: {
ownerId_name: {
ownerId: ctx.user.sub,
name: 'Demo Tag',
},
},
},
},
})

const blockData = [
{
questions: [questionSC, questionMC],
timeLimit: 100,
randomSelection: null,
},
{
questions: [questionKPRIM, questionNR, questionFT],
timeLimit: null,
randomSelection: null,
},
{
questions: [questionSC],
timeLimit: 50,
randomSelection: null,
},
{
questions: [questionMC],
timeLimit: 20,
randomSelection: null,
},
{
questions: [questionKPRIM],
timeLimit: null,
randomSelection: null,
},
]

await ctx.prisma.liveSession.create({
data: {
name: 'Demo Session',
displayName: 'Demo Session Display Name',
description: 'Demo Session Description',
pointsMultiplier: 2,
isGamificationEnabled: true,
blocks: {
create: blockData.map(
({ questions, randomSelection, timeLimit }, blockIx) => {
const newInstances = questions.map((question, ix) => {
const processedQuestionData = processQuestionData(question)

return {
order: ix,
type: DB.QuestionInstanceType.SESSION,
pointsMultiplier: 2 * question.pointsMultiplier,
questionData: processedQuestionData,
results: prepareInitialInstanceResults(processedQuestionData),
question: {
connect: { id: question.id },
},
owner: {
connect: { id: ctx.user.sub },
},
}
})

return {
order: blockIx,
randomSelection,
timeLimit,
instances: {
create: newInstances,
},
}
}
),
},
owner: {
connect: { id: ctx.user.sub },
},
},
include: {
blocks: true,
},
})
}

0 comments on commit 9b9f040

Please sign in to comment.