Skip to content

Commit 16669e1

Browse files
committed
create new achievements for first student match
1 parent cfdb0b2 commit 16669e1

File tree

11 files changed

+516
-269
lines changed

11 files changed

+516
-269
lines changed

common/achievement/derive.ts

Lines changed: 26 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ const GhostAchievements: { [key: string]: achievement_template } = {
2626
group: PupilNewMatchGroup,
2727
groupOrder: 1,
2828
type: AchievementType.SEQUENTIAL,
29-
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_1.png',
29+
image: 'gamification/achievements/release/new_match/five_pieces/empty_state.png',
3030
tagline: 'Starte eine Lernpatenschaft',
3131
title: 'Neue Lernunterstützung',
3232
subtitle: null,
3333
description:
34-
'Es war großartig, dich am {{date}} besser kennenzulernen und freuen uns, dass du gemeinsam mit uns die Bildungschancen von Schüler:innen verbessern möchtest. Um dir eine:n passende:n Lernpartner:in zuzuweisen, bitten wir dich zunächst, eine Anfrage auf unserer Plattform zu stellen. Hier kannst du die Fächer und Jahrgangsstufe angeben, die für dich passend sind. Wir freuen uns auf den Start!',
34+
'Damit wir dir den:die perfekte:n Lernpartner:in zuweisen können, musst du zunächst eine Anfrage auf unserer Plattform stellen. Dort kannst du ganz einfach die Fächer angeben, die für dich wichtig sind und in denen wir dir helfen können. Wir freuen uns darauf, mit dir gemeinsam durchzustarten und die Lernreise zu beginnen!',
3535
footer: null,
3636
actionName: 'Anfrage stellen',
37-
actionRedirectLink: '/matching',
37+
actionRedirectLink: '/request-match',
3838
actionType: AchievementActionType.Action,
3939
condition: 'false', // This will ensure that an evaluation will always fail
4040
conditionDataAggregations: {},
@@ -50,7 +50,7 @@ const GhostAchievements: { [key: string]: achievement_template } = {
5050
group: PupilNewMatchGroup,
5151
groupOrder: 2,
5252
type: AchievementType.SEQUENTIAL,
53-
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_2.png',
53+
image: 'gamification/achievements/release/new_match/five_pieces/step_1.png',
5454
tagline: 'Starte eine Lernpatenschaft',
5555
title: 'Neue Lernunterstützung',
5656
subtitle: null,
@@ -74,15 +74,15 @@ const GhostAchievements: { [key: string]: achievement_template } = {
7474
group: StudentNewMatchGroup,
7575
groupOrder: 1,
7676
type: AchievementType.SEQUENTIAL,
77-
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_1.png',
77+
image: 'gamification/achievements/release/new_match/five_pieces/empty_state.png',
7878
tagline: 'Starte eine Lernpatenschaft',
7979
title: 'Neue Lernunterstützung',
8080
subtitle: null,
8181
description:
8282
'Es war großartig, dich am {{date}} besser kennenzulernen und freuen uns, dass du gemeinsam mit uns die Bildungschancen von Schüler:innen verbessern möchtest. Um dir eine:n passende:n Lernpartner:in zuzuweisen, bitten wir dich zunächst, eine Anfrage auf unserer Plattform zu stellen. Hier kannst du die Fächer und Jahrgangsstufe angeben, die für dich passend sind. Wir freuen uns auf den Start!',
8383
footer: null,
8484
actionName: 'Anfrage stellen',
85-
actionRedirectLink: '/matching',
85+
actionRedirectLink: '/request-match',
8686
actionType: AchievementActionType.Action,
8787
condition: 'false', // This will ensure that an evaluation will always fail
8888
conditionDataAggregations: {},
@@ -92,30 +92,6 @@ const GhostAchievements: { [key: string]: achievement_template } = {
9292
achievedImage: null,
9393
sequentialStepName: 'Anfrage stellen',
9494
},
95-
student_new_match_2: {
96-
id: -1,
97-
templateFor: AchievementTemplateFor.Match,
98-
group: StudentNewMatchGroup,
99-
groupOrder: 2,
100-
type: AchievementType.SEQUENTIAL,
101-
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_2.png',
102-
tagline: 'Starte eine Lernpatenschaft',
103-
title: 'Neue Lernunterstützung',
104-
subtitle: null,
105-
description:
106-
'Fantastisch, deine Anfrage ist eingegangen! Bevor wir dir deine:n ideale:n Lernpartner:in vermitteln können, möchten wir gerne kurz per Zoom mit dir sprechen. Unser Ziel ist es, die perfekte Person für dich zu finden und genau zu verstehen, was du dir wünschst. Buche doch gleich einen Termin für unser Gespräch – wir sind schon ganz gespannt auf dich!',
107-
footer: null,
108-
actionName: 'Termin buchen',
109-
actionRedirectLink: 'https://calendly.com',
110-
actionType: AchievementActionType.Action,
111-
condition: 'false',
112-
conditionDataAggregations: {},
113-
isActive: true,
114-
achievedDescription: null,
115-
achievedFooter: null,
116-
achievedImage: null,
117-
sequentialStepName: 'Gespräch mit Lern-Fair absolvieren',
118-
},
11995
};
12096

12197
// Large parts of our user communication are event based, i.e. users get a notification for an appointment,
@@ -175,6 +151,8 @@ async function generatePupilMatching(
175151
});
176152
}
177153

154+
console.log('iso', hasRequest, hasSuccessfulScreening, achievement);
155+
178156
result.push({
179157
id: -1,
180158
templateId: -1,
@@ -211,6 +189,8 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
211189
where: { pupilId: pupil.id, status: pupil_screening_status_enum.success, invalidated: false },
212190
orderBy: { createdAt: 'desc' },
213191
});
192+
const hasSuccessfulScreenings = successfulScreenings.length > 0;
193+
const totalMatchCount = await prisma.match.count({ where: { pupilId: pupil.id } });
214194

215195
const newMatchAchievements = userAchievements.filter(
216196
(row) => row.template.group === PupilNewMatchGroup && row.template.groupOrder === PupilNewMatchGroupOrder
@@ -222,13 +202,17 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
222202
if (successfulScreenings.length > 0) {
223203
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
224204
}
205+
// This case happens when the student just registered and had a successful screening
206+
if (pupil.openMatchRequestCount === 0 && totalMatchCount === 0) {
207+
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
208+
result.push(...ghosts);
209+
}
225210
for (let i = 0; i < pupil.openMatchRequestCount; i++) {
226-
const ghosts = await generatePupilMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
211+
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
227212
result.push(...ghosts);
228213
}
229-
230214
for (const userAchievement of newMatchAchievements) {
231-
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
215+
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, hasSuccessfulScreenings, ctx);
232216
result.push(...ghosts);
233217
}
234218
}
@@ -243,6 +227,7 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
243227
where: { studentId: student.id, success: true },
244228
orderBy: { createdAt: 'desc' },
245229
});
230+
const totalMatchCount = await prisma.match.count({ where: { studentId: student.id } });
246231

247232
const newMatchAchievements = userAchievements.filter(
248233
(row) => row.template.group === StudentNewMatchGroup && row.template.groupOrder === StudentNewMatchGroupOrder
@@ -254,13 +239,19 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
254239
if (successfulScreenings.length > 0) {
255240
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
256241
}
242+
// This case happens when the student just registered and had a successful screening
243+
if (student.openMatchRequestCount === 0 && totalMatchCount === 0) {
244+
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
245+
result.push(...ghosts);
246+
}
247+
// This will
257248
for (let i = 0; i < student.openMatchRequestCount; i++) {
258-
const ghosts = await generateStudentMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
249+
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
259250
result.push(...ghosts);
260251
}
261252

262253
for (const userAchievement of newMatchAchievements) {
263-
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
254+
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, ctx);
264255
result.push(...ghosts);
265256
}
266257
}
@@ -269,7 +260,6 @@ async function generateStudentMatching(
269260
achievement: achievement_with_template | null,
270261
user: User,
271262
hasRequest: boolean,
272-
hasSuccessfulScreening: boolean,
273263
ctx: StudentNewMatchGhostContext
274264
): Promise<achievement_with_template[]> {
275265
const result: achievement_with_template[] = [];
@@ -306,16 +296,5 @@ async function generateStudentMatching(
306296
relation: achievement?.relation ?? randomRelation,
307297
});
308298

309-
result.push({
310-
id: -1,
311-
templateId: -1,
312-
userId: user.userID,
313-
isSeen: true,
314-
template: GhostAchievements.student_new_match_2,
315-
context: ctx,
316-
recordValue: null,
317-
achievedAt: hasSuccessfulScreening || achievement ? new Date() : null,
318-
relation: achievement?.relation ?? randomRelation,
319-
});
320299
return result;
321300
}

common/achievement/get.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export async function getUserAchievementsWithTemplates(user: User, byType: Achie
2727
},
2828
include: { template: true },
2929
});
30+
31+
const derivedAchievements = await deriveAchievements(user, userAchievementsWithTemplates);
32+
userAchievementsWithTemplates.push(...derivedAchievements);
33+
3034
return userAchievementsWithTemplates;
3135
}
3236
export type achievements_with_template = ThenArg<ReturnType<typeof getUserAchievementsWithTemplates>>;
@@ -43,8 +47,6 @@ const getAchievementById = async (user: User, achievementId: number): Promise<Pu
4347
// Next step achievements are sequential achievements that are currently active and not yet completed. They get displayed in the next step card section.
4448
const getNextStepAchievements = async (user: User): Promise<PublicAchievement[]> => {
4549
const userAchievements = await getUserAchievementsWithTemplates(user, AchievementType.SEQUENTIAL);
46-
const derivedAchievements = await deriveAchievements(user, userAchievements);
47-
userAchievements.push(...derivedAchievements);
4850

4951
const userAchievementGroups: { [groupRelation: string]: achievements_with_template } = {};
5052
userAchievements.forEach((ua) => {
@@ -111,9 +113,6 @@ const getFurtherAchievements = async (user: User): Promise<PublicAchievement[]>
111113
// User achievements are already started by the user and are either active or completed.
112114
const getUserAchievements = async (user: User): Promise<PublicAchievement[]> => {
113115
const userAchievements = await getUserAchievementsWithTemplates(user);
114-
const derivedAchievements = await deriveAchievements(user, userAchievements);
115-
userAchievements.push(...derivedAchievements);
116-
117116
const userAchievementGroups: { [group: string]: achievements_with_template } = {};
118117
userAchievements.forEach((ua) => {
119118
if (!userAchievementGroups[`${ua.template.group}/${ua.relation}`]) {

common/achievement/metric.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const batchOfMetrics = [
2929
createMetric('student_onboarding_screened', ['student_screening_appointment_done', 'tutor_screening_success', 'instructor_screening_success'], () => {
3030
return 1;
3131
}),
32+
createMetric('student_onboarding_tutor_screened', ['tutor_screening_success'], () => {
33+
return 1;
34+
}),
3235
createMetric('student_onboarding_coc_success', ['student_coc_updated'], () => {
3336
return 1;
3437
}),
@@ -49,6 +52,9 @@ const batchOfMetrics = [
4952
),
5053

5154
/* CONDUCTED MATCH APPOINTMENT */
55+
createMetric('student_add_match_appointment', ['student_add_appointment_match_with_pupil'], () => {
56+
return 1;
57+
}),
5258
createMetric('student_conducted_match_appointment', ['student_joined_match_meeting'], () => {
5359
return 1;
5460
}),
@@ -106,9 +112,18 @@ const batchOfMetrics = [
106112
}),
107113

108114
/* Matching */
115+
createMetric('pupil_create_new_match_chat', ['pupil_create_new_match_chat'], () => {
116+
return 1;
117+
}),
118+
createMetric('student_create_new_match_chat', ['student_create_new_match_chat'], () => {
119+
return 1;
120+
}),
109121
createMetric('pupil_match_create', ['tutee_matching_success'], () => {
110122
return 1;
111123
}),
124+
createMetric('student_match_requested', ['tutor_match_requested'], () => {
125+
return 1;
126+
}),
112127
createMetric('student_match_create', ['tutor_matching_success'], () => {
113128
return 1;
114129
}),

common/appointment/create.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getMatch, getPupil, getStudent } from '../../graphql/util';
1414
import { PrerequisiteError, RedundantError } from '../../common/util/error';
1515
import { getContextForGroupAppointmentReminder, getContextForMatchAppointmentReminder } from './util';
1616
import { getNotificationContextForSubcourse } from '../../common/courses/notifications';
17+
import { createRelation, EventRelationType } from '../achievement/relation';
1718

1819
const logger = getLogger();
1920

@@ -96,6 +97,7 @@ export const createMatchAppointments = async (matchId: number, appointmentsToBeC
9697

9798
if (!silent) {
9899
await Notification.actionTaken(userForPupil(pupil), 'student_add_appointment_match', {
100+
relation: createRelation(EventRelationType.Match, matchId),
99101
student,
100102
matchId: matchId.toString(),
101103
});
@@ -110,6 +112,12 @@ export const createMatchAppointments = async (matchId: number, appointmentsToBeC
110112
...(await getContextForMatchAppointmentReminder(appointment)),
111113
pupil,
112114
});
115+
await Notification.actionTaken(userForStudent(student), 'student_add_appointment_match_with_pupil', {
116+
relation: createRelation(EventRelationType.Match, matchId),
117+
pupil,
118+
match: { id: matchId.toString() },
119+
lecture: appointment,
120+
});
113121
}
114122
}
115123

common/chat/create.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import systemMessages from './localization';
99
import { getLogger } from '../logger/logger';
1010
import assert from 'assert';
1111
import { createHmac } from 'crypto';
12+
import { actionTaken } from '../notification';
13+
import { createRelation, EventRelationType } from '../achievement/relation';
1214

1315
const logger = getLogger('Chat');
1416
const getOrCreateOneOnOneConversation = async (
@@ -201,6 +203,22 @@ async function createContactChat(meUser: User, contactUser: User): Promise<strin
201203
},
202204
};
203205

206+
if (contact.match) {
207+
if (meUser.studentId) {
208+
await actionTaken(meUser, 'student_create_new_match_chat', {
209+
user: meUser,
210+
match: { id: `${contact.match.matchId}` },
211+
relation: createRelation(EventRelationType.Match, contact.match.matchId),
212+
});
213+
} else {
214+
await actionTaken(meUser, 'pupil_create_new_match_chat', {
215+
user: meUser,
216+
match: { id: `${contact.match.matchId}` },
217+
relation: createRelation(EventRelationType.Match, contact.match.matchId),
218+
});
219+
}
220+
}
221+
204222
const conversation = await getOrCreateOneOnOneConversation([meUser, contactUser], conversationInfos, ContactReason.CONTACT);
205223
logger.info(`Contact conversation was created by ${meUser} with ${contactUser} with ID ${conversation.id} `);
206224
return conversation.id;

common/notification/actions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,12 +595,20 @@ const _notificationActions = {
595595
},
596596
},
597597
student_add_appointment_match: {
598-
description: 'Tutee / Tutor added Match Appointment',
598+
description: 'Tutee / Tutor added Match Appointment (Pupil event)',
599599
sampleContext: {
600600
student: sampleUser,
601601
matchId: '1',
602602
},
603603
},
604+
student_add_appointment_match_with_pupil: {
605+
description: 'Tutee / Tutor added Match Appointment (Tutee / Tutor event)',
606+
sampleContext: {
607+
pupil: sampleUser,
608+
match: { id: '1' },
609+
lecture: {},
610+
},
611+
},
604612
pupil_decline_appointment_group: {
605613
description: 'Instructor / Group Appointment declined by Participant',
606614
sampleContext: {
@@ -646,6 +654,20 @@ const _notificationActions = {
646654
appointment: sampleAppointment,
647655
},
648656
},
657+
pupil_create_new_match_chat: {
658+
description: 'User has clicked on new chat with a match partner',
659+
sampleContext: {
660+
user: sampleUser,
661+
match: { id: '1' },
662+
},
663+
},
664+
student_create_new_match_chat: {
665+
description: 'User has clicked on new chat with a match partner',
666+
sampleContext: {
667+
user: sampleUser,
668+
match: { id: '1' },
669+
},
670+
},
649671
missed_one_on_one_chat_message: {
650672
description: 'Missed message in 1:1 chat',
651673
sampleContext: sampleMissedOneOnOneMessage,

0 commit comments

Comments
 (0)