From 8f0c368080f1e63a3d5416b7d786f13aed8513f2 Mon Sep 17 00:00:00 2001 From: LucasFalkowsky <114646768+LucasFalkowsky@users.noreply.github.com> Date: Fri, 9 Feb 2024 08:48:32 +0100 Subject: [PATCH] feat: participation streak templates (#964) * fix: reset achievedAt * fix: schema adjustments and resolver update * fix: add missing semicolons --- common/achievement/bucket.ts | 8 +- common/achievement/get.ts | 25 +++-- graphql/types/achievement.ts | 4 +- .../migration.sql | 3 + prisma/schema.prisma | 4 +- seed-db.ts | 102 ++++++++---------- 6 files changed, 74 insertions(+), 72 deletions(-) create mode 100644 prisma/migrations/20240130140914_nullable_subtitle_achieved_image/migration.sql diff --git a/common/achievement/bucket.ts b/common/achievement/bucket.ts index 2a89b44ec..883bc71de 100644 --- a/common/achievement/bucket.ts +++ b/common/achievement/bucket.ts @@ -57,7 +57,8 @@ export const bucketCreatorDefs: BucketCreatorDefs = { const subcourseBuckets = context.subcourse .map((subcourse) => createLectureBuckets(subcourse, LectureBucketMessuringType.start)) .reduce((acc, val) => acc.concat(val), []); - return { bucketKind: 'time', buckets: [...matchBuckets, ...subcourseBuckets] }; + const buckets = [...matchBuckets, ...subcourseBuckets].sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + return { bucketKind: 'time', buckets: buckets }; }, }, by_lecture_participation: { @@ -67,9 +68,10 @@ export const bucketCreatorDefs: BucketCreatorDefs = { .map((match) => createLectureBuckets(match, LectureBucketMessuringType.participation)) .reduce((acc, val) => acc.concat(val), []); const subcourseBuckets = context.subcourse - .map((subcourse) => createLectureBuckets(subcourse, LectureBucketMessuringType.participation)) + .map((subcourse) => createLectureBuckets(subcourse, LectureBucketMessuringType.start)) .reduce((acc, val) => acc.concat(val), []); - return { bucketKind: 'time', buckets: [...matchBuckets, ...subcourseBuckets] }; + const buckets = [...matchBuckets, ...subcourseBuckets].sort((a, b) => b.startTime.getTime() - a.startTime.getTime()); + return { bucketKind: 'time', buckets: buckets }; }, }, by_weeks: { diff --git a/common/achievement/get.ts b/common/achievement/get.ts index 55d4b7d9c..1c6b94680 100644 --- a/common/achievement/get.ts +++ b/common/achievement/get.ts @@ -72,7 +72,7 @@ const getFurtherAchievements = async (user: User): Promise => { const dataAggr = template.conditionDataAggregations as Prisma.JsonObject; const maxValue = Object.keys(dataAggr) .map((key) => { - const val = dataAggr[key] as number; + const val = dataAggr[key]['valueToAchieve'] as number; return Number(val); }) .reduce((a, b) => a + b, 0); @@ -90,7 +90,7 @@ const getFurtherAchievements = async (user: User): Promise => { maxSteps: maxValue, currentStep: 0, isNewAchievement: null, - progressDescription: `Noch ${userAchievements.length - userAchievements.length} Schritte bis zum Abschluss`, + progressDescription: template.progressDescription, actionName: template.actionName, actionRedirectLink: template.actionRedirectLink, }; @@ -152,10 +152,6 @@ const assembleAchievementData = async (userAchievements: achievements_with_templ orderBy: { groupOrder: 'asc' }, }); - const state: achievement_state = getAchievementState(userAchievements, currentAchievementIndex); - - const isNewAchievement = state === achievement_state.COMPLETED && !userAchievements[currentAchievementIndex].isSeen; - const condition = userAchievements[currentAchievementIndex].recordValue ? userAchievements[currentAchievementIndex].template.condition.replace( 'recordValue', @@ -181,8 +177,8 @@ const assembleAchievementData = async (userAchievements: achievements_with_templ currentValue = dataAggregationKeys.map((key) => evaluationResult.resultObject[key]).reduce((a, b) => a + b, 0); maxValue = userAchievements[currentAchievementIndex].template.type === achievement_type_enum.STREAK - ? userAchievements[currentAchievementIndex].recordValue !== null && userAchievements[currentAchievementIndex].recordValue! > currentValue - ? userAchievements[currentAchievementIndex].recordValue! + ? userAchievements[currentAchievementIndex].recordValue !== null && userAchievements[currentAchievementIndex].recordValue > currentValue + ? userAchievements[currentAchievementIndex].recordValue : currentValue : dataAggregationKeys .map((key) => { @@ -192,22 +188,31 @@ const assembleAchievementData = async (userAchievements: achievements_with_templ ); }) .reduce((a, b) => a + b, 0); + if (currentValue < maxValue && userAchievements[currentAchievementIndex].achievedAt) { + await prisma.user_achievement.update({ + where: { id: userAchievements[currentAchievementIndex].id }, + data: { achievedAt: null, isSeen: false }, + }); + } } } else { currentValue = currentAchievementIndex; maxValue = achievementTemplates.length - 1; } + const state: achievement_state = getAchievementState(userAchievements, currentAchievementIndex); + const isNewAchievement = state === achievement_state.COMPLETED && !userAchievements[currentAchievementIndex].isSeen; + const achievementContext = transformPrismaJson( user, userAchievements[currentAchievementIndex].relation, userAchievements[currentAchievementIndex].context as Prisma.JsonObject ); - const leftProgress = maxValue - currentValue; + const leftProgress = maxValue - currentValue + 1; const currentAchievementTemplate = renderAchievementWithContext(userAchievements[currentAchievementIndex], achievementContext, { remainingProgress: leftProgress.toString(), progress: currentValue.toString(), - recordValue: maxValue.toString(), + maxValue: maxValue.toString(), }); return { diff --git a/graphql/types/achievement.ts b/graphql/types/achievement.ts index 8c13b60e3..ef700b0ce 100644 --- a/graphql/types/achievement.ts +++ b/graphql/types/achievement.ts @@ -27,8 +27,8 @@ class Achievement { @Field() name: string; - @Field() - subtitle: string; + @Field({ nullable: true }) + subtitle?: string; @Field() description: string; diff --git a/prisma/migrations/20240130140914_nullable_subtitle_achieved_image/migration.sql b/prisma/migrations/20240130140914_nullable_subtitle_achieved_image/migration.sql new file mode 100644 index 000000000..15686cd93 --- /dev/null +++ b/prisma/migrations/20240130140914_nullable_subtitle_achieved_image/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "achievement_template" ALTER COLUMN "subtitle" DROP NOT NULL, +ALTER COLUMN "achievedImage" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f51347aea..0c20454c7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -45,10 +45,10 @@ model achievement_template { stepName String @db.VarChar // what type of achievement is it type achievement_type_enum - subtitle String @db.VarChar + subtitle String? @db.VarChar description String @db.VarChar image String @db.VarChar - achievedImage String @db.VarChar + achievedImage String? @db.VarChar // some achievements show actions that a user can/must perform next. // therefore we need the action name, type (for the icon) and a redirect link actionName String? @db.VarChar diff --git a/seed-db.ts b/seed-db.ts index c324a7a41..38c24b48a 100644 --- a/seed-db.ts +++ b/seed-db.ts @@ -753,9 +753,9 @@ void (async function setupDevDB() { stepName: 'Verifizieren', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: '', - image: 'Puzzle_00', - achievedImage: '', + description: 'Dieser Text muss noch geliefert werden.', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/empty_state.png', + achievedImage: null, actionName: 'E-Mail erneut senden', actionRedirectLink: '', actionType: achievement_action_type_enum.Action, @@ -773,10 +773,9 @@ void (async function setupDevDB() { stepName: 'Kennenlerngespräch buchen', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', - image: 'Puzzle_01', - achievedImage: '', + description: 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/step_1.png', + achievedImage: null, actionName: 'Termin vereinbaren', actionRedirectLink: 'https://calendly.com', actionType: achievement_action_type_enum.Action, @@ -796,10 +795,9 @@ void (async function setupDevDB() { stepName: 'Screening absolvieren', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Wir sind gespannt darauf, dich kennenzulernen! In einem kurzen, 15-minütigen Zoom-Gespräch möchten wir dir gerne unsere vielfältigen Engagement-Möglichkeiten vorstellen und alle deine Fragen beantworten. Buche einfach einen Termin, um mehr zu erfahren und dann voller Tatendrang direkt durchzustarten. Falls dir etwas dazwischen kommt, sage den Termin bitte ab und buche dir einen neuen.', - image: 'Puzzle_02', - achievedImage: '', + description: 'Wir sind gespannt darauf, dich kennenzulernen! In einem kurzen, 15-minütigen Zoom-Gespräch möchten wir dir gerne unsere vielfältigen Engagement-Möglichkeiten vorstellen und alle deine Fragen beantworten. Buche einfach einen Termin, um mehr zu erfahren und dann voller Tatendrang direkt durchzustarten. Falls dir etwas dazwischen kommt, sage den Termin bitte ab und buche dir einen neuen.', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/step_2.png', + achievedImage: null, actionName: 'Screening absolvieren', actionRedirectLink: '', actionType: achievement_action_type_enum.Appointment, @@ -817,10 +815,9 @@ void (async function setupDevDB() { stepName: 'Führungszeugnis einreichen', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Der Schutz von Kindern und Jugendlichen liegt uns sehr am Herzen, daher benötigen wir von allen Ehrenamtlichen ein erweitertes Führungszeugnis. Im nächsten Schritt findest du eine Anleitung zur Beantragung sowie eine Bescheinigung zur Kostenübernahme für das erweiterte Führungszeugnis. Um deinen Account aktiv zu halten, bitten wir dich, das erweiterte Führungszeugnis bis zum {{date}} bei uns einzureichen. Gemeinsam setzen wir uns für eine sichere Umgebung ein, in der alle sich wohl und geschützt fühlen können.', - image: 'Puzzle_02', - achievedImage: '', + description: 'Der Schutz von Kindern und Jugendlichen liegt uns sehr am Herzen, daher benötigen wir von allen Ehrenamtlichen ein erweitertes Führungszeugnis. Im nächsten Schritt findest du eine Anleitung zur Beantragung sowie eine Bescheinigung zur Kostenübernahme für das erweiterte Führungszeugnis. Um deinen Account aktiv zu halten, bitten wir dich, das erweiterte Führungszeugnis bis zum {{date}} bei uns einzureichen. Gemeinsam setzen wir uns für eine sichere Umgebung ein, in der alle sich wohl und geschützt fühlen können.', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/step_3.png', + achievedImage: null, actionName: 'Zeugnis einreichen', actionRedirectLink: 'mailto:fz@lern-fair.de', actionType: achievement_action_type_enum.Action, @@ -838,10 +835,9 @@ void (async function setupDevDB() { stepName: 'Onboarding abgeschlossen', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Herzlichen Glückwunsch! Du hast alle Onboarding-Schritte erfolgreich gemeistert und dir das Abflugticket für Loki gesichert. Wir sind begeistert, dass du nun Teil unseres Teams bist und Schüler:innen auf ihrem Lernweg begleitest. Gemeinsam setzen wir uns für eine bessere Bildung in Deutschland ein. Du bist bereits jetzt ein:e Lern-Fair Held:in! ❤️ Danke für dein Engagement und deine Begeisterung!', - image: 'Flugticket', - achievedImage: '', + description: 'Herzlichen Glückwunsch! Du hast alle Onboarding-Schritte erfolgreich gemeistert und dir das Abflugticket für Loki gesichert. Wir sind begeistert, dass du nun Teil unseres Teams bist und Schüler:innen auf ihrem Lernweg begleitest. Gemeinsam setzen wir uns für eine bessere Bildung in Deutschland ein. Du bist bereits jetzt ein:e Lern-Fair Held:in! ❤️ Danke für dein Engagement und deine Begeisterung!', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/step_4.png', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -860,10 +856,9 @@ void (async function setupDevDB() { stepName: 'Verifizieren', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', - image: 'Puzzle_00', - achievedImage: '', + description: 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', + image: 'gamification/achievements/tmp/finish_onboarding/four_pieces/empty_state.png', + achievedImage: null, actionName: 'E-Mail erneut senden', actionRedirectLink: '', actionType: achievement_action_type_enum.Action, @@ -881,10 +876,9 @@ void (async function setupDevDB() { stepName: 'Kennenlerngespräch buchen', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', - image: 'Puzzle_01', - achievedImage: '', + description: 'Hurra! Am {{date}} haben wir eine E-Mail an deine Adresse {{email}} gesendet. Um deine E-Mail zu bestätigen, klicke einfach auf den Button in der Nachricht. Solltest du unsere E-Mail nicht finden, kannst du hier eine erneute Zustellung anfordern und voller Vorfreude auf unser Weiterkommen warten.', + image: 'gamification/achievements/tmp/finish_onboarding/three_pieces/step_1.png', + achievedImage: null, actionName: 'Termin vereinbaren', actionRedirectLink: 'https://calendly.com', actionType: achievement_action_type_enum.Action, @@ -904,10 +898,9 @@ void (async function setupDevDB() { stepName: 'Screening absolvieren', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Wir sind gespannt darauf, dich kennenzulernen! In einem kurzen, 15-minütigen Zoom-Gespräch möchten wir dir gerne unsere vielfältigen kostenlose Angebote vorstellen und dir die beste Unterstützung ermöglichen sowie alle deine Fragen beantworten. Buche einfach einen Termin, um mehr zu erfahren und dann voller Tatendrang direkt durchzustarten. Falls dir etwas dazwischen kommt, sage den Termin bitte ab und buche dir einen neuen.', - image: 'Puzzle_02', - achievedImage: '', + description: 'Wir sind gespannt darauf, dich kennenzulernen! In einem kurzen, 15-minütigen Zoom-Gespräch möchten wir dir gerne unsere vielfältigen kostenlose Angebote vorstellen und dir die beste Unterstützung ermöglichen sowie alle deine Fragen beantworten. Buche einfach einen Termin, um mehr zu erfahren und dann voller Tatendrang direkt durchzustarten. Falls dir etwas dazwischen kommt, sage den Termin bitte ab und buche dir einen neuen.', + image: 'gamification/achievements/tmp/finish_onboarding/three_pieces/step_2.png', + achievedImage: null, actionName: 'Screening absolvieren', actionRedirectLink: '', actionType: achievement_action_type_enum.Appointment, @@ -925,10 +918,9 @@ void (async function setupDevDB() { stepName: 'Onboarding abgeschlossen', type: achievement_type_enum.SEQUENTIAL, subtitle: 'Jetzt durchstarten', - description: - 'Herzlichen Glückwunsch! Du hast alle Onboarding-Schritte erfolgreich gemeistert und dir das Abflugticket für Loki gesichert. Wir sind begeistert, dass du nun Teil unserer Lerncommunity bist und hoffen dich gut auf deiner Lernreise begleiten zu können. Loki und unser Team werden immer für dich da sein!', - image: 'Flugticket', - achievedImage: '', + description: 'Herzlichen Glückwunsch! Du hast alle Onboarding-Schritte erfolgreich gemeistert und dir das Abflugticket für Loki gesichert. Wir sind begeistert, dass du nun Teil unserer Lerncommunity bist und hoffen dich gut auf deiner Lernreise begleiten zu können. Loki und unser Team werden immer für dich da sein!', + image: 'gamification/achievements/tmp/finish_onboarding/three_pieces/step_3.png', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -950,7 +942,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/one_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: 'Absolviere deinen ersten Termin, um diesen Erfolg zu erhalten', actionRedirectLink: null, actionType: achievement_action_type_enum.Action, @@ -979,7 +971,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/three_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1008,7 +1000,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/five_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1037,7 +1029,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/ten_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1066,7 +1058,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/fifteen_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1095,7 +1087,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/twentyfive_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1126,7 +1118,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/one_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: 'Absolviere deinen ersten Termin, um diesen Erfolg zu erhalten', actionRedirectLink: null, actionType: achievement_action_type_enum.Action, @@ -1155,7 +1147,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/three_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1184,7 +1176,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/five_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1213,7 +1205,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/ten_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1242,7 +1234,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/fifteen_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1271,7 +1263,7 @@ void (async function setupDevDB() { subtitle: '1:1 Lernunterstützungen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/x_lectures_held/twentyfive_lectures_held.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1368,7 +1360,7 @@ void (async function setupDevDB() { subtitle: 'Vermittle Wissen', description: 'Dieser Text muss noch geliefert werden.', image: 'gamification/achievements/tmp/offer_course/offer_course.jpg', - achievedImage: '', + achievedImage: null, actionName: 'Kurs anlegen', actionRedirectLink: '/create-course', actionType: achievement_action_type_enum.Action, @@ -1394,7 +1386,7 @@ void (async function setupDevDB() { description: 'Dieser Text muss noch geliefert werden! Wie cool, dass du dich ehrenamtlich engagieren möchtest, indem du Schüler:innen durch Nachhilfeunterricht unterstützt. Um mit der Lernunterstützung zu starten sind mehrere Aktionen nötig. Schließe jetzt den nächsten Schritt ab und komme dem Ziel einer neuen Lernunterstüzung ein Stück näher.', image: 'gamification/achievements/tmp/offer_course/offer_course.jpg', - achievedImage: '', + achievedImage: null, actionName: 'Kurs freigeben', actionRedirectLink: '/single-course/{{subcourseId}}', actionType: achievement_action_type_enum.Action, @@ -1420,7 +1412,7 @@ void (async function setupDevDB() { description: 'Dieser Text muss noch geliefert werden! Wie cool, dass du dich ehrenamtlich engagieren möchtest, indem du Schüler:innen durch Nachhilfeunterricht unterstützt. Um mit der Lernunterstützung zu starten sind mehrere Aktionen nötig. Schließe jetzt den nächsten Schritt ab und komme dem Ziel einer neuen Lernunterstüzung ein Stück näher.', image: 'gamification/achievements/tmp/offer_course/offer_course.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: achievement_action_type_enum.Wait, @@ -1446,7 +1438,7 @@ void (async function setupDevDB() { description: 'Dieser Text muss noch geliefert werden! Wie cool, dass du dich ehrenamtlich engagieren möchtest, indem du Schüler:innen durch Nachhilfeunterricht unterstützt. Um mit der Lernunterstützung zu starten sind mehrere Aktionen nötig. Schließe jetzt den nächsten Schritt ab und komme dem Ziel einer neuen Lernunterstüzung ein Stück näher.', image: 'gamification/achievements/tmp/offer_course/offer_course.jpg', - achievedImage: '', + achievedImage: null, actionName: null, actionRedirectLink: null, actionType: null, @@ -1470,7 +1462,7 @@ void (async function setupDevDB() { groupOrder: 1, stepName: '', type: achievement_type_enum.STREAK, - subtitle: '', + subtitle: null, description: 'Du warst bei {{progress}} Termin(en) in Folge dabei!

Behalte diesen großartigen Trend bei und steigere ihn noch weiter. Jedes Mal, wenn du zu einem Termin erscheinst, steigt deine Teilnahme-Serie. Deine konstante Ausdauer könnte dich bis zum Teilnahme-Marathon führen. Mach weiter so, du bist auf dem besten Weg zum Erfolg!', image: 'gamification/achievements/tmp/streaks/presence_set.png', @@ -1485,7 +1477,7 @@ void (async function setupDevDB() { conditionDataAggregations: { student_presence_events: { metric: 'student_participation_streak', - aggregator: 'count', + aggregator: 'lastStreakLength', createBuckets: 'by_lecture_participation', bucketAggregator: 'presenceOfEvents', }, @@ -1503,7 +1495,7 @@ void (async function setupDevDB() { groupOrder: 1, stepName: '', type: achievement_type_enum.STREAK, - subtitle: '', + subtitle: null, description: 'Du warst bei {{progress}} Termin(en) in Folge dabei!

Behalte diesen großartigen Trend bei und steigere ihn noch weiter. Jedes Mal, wenn du zu einem Termin erscheinst, steigt deine Teilnahme-Serie. Deine konstante Ausdauer könnte dich bis zum Teilnahme-Marathon führen. Mach weiter so, du bist auf dem besten Weg zum Erfolg!', image: 'gamification/achievements/tmp/streaks/presence_set.png', @@ -1518,7 +1510,7 @@ void (async function setupDevDB() { conditionDataAggregations: { pupil_presence_events: { metric: 'pupil_participation_streak', - aggregator: 'count', + aggregator: 'lastStreakLength', createBuckets: 'by_lecture_participation', bucketAggregator: 'presenceOfEvents', },