Skip to content

Commit dbfb1f0

Browse files
committed
create new achievements for first student match
1 parent 8ad1771 commit dbfb1f0

File tree

11 files changed

+520
-271
lines changed

11 files changed

+520
-271
lines changed

common/achievement/derive.ts

Lines changed: 30 additions & 49 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,
@@ -183,7 +161,8 @@ async function generatePupilMatching(
183161
template: GhostAchievements.pupil_new_match_1,
184162
context: ctx,
185163
recordValue: null,
186-
achievedAt: hasRequest || achievement ? new Date() : null,
164+
achievedAt: new Date(),
165+
// achievedAt: hasRequest || achievement ? new Date() : null,
187166
relation: achievement?.relation ?? randomRelation,
188167
});
189168

@@ -195,7 +174,8 @@ async function generatePupilMatching(
195174
template: GhostAchievements.pupil_new_match_2,
196175
context: ctx,
197176
recordValue: null,
198-
achievedAt: hasSuccessfulScreening || achievement ? new Date() : null,
177+
// achievedAt: hasSuccessfulScreening || achievement ? new Date() : null,
178+
achievedAt: new Date(),
199179
relation: achievement?.relation ?? randomRelation,
200180
});
201181
return result;
@@ -211,6 +191,8 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
211191
where: { pupilId: pupil.id, status: pupil_screening_status_enum.success, invalidated: false },
212192
orderBy: { createdAt: 'desc' },
213193
});
194+
const hasSuccessfulScreenings = successfulScreenings.length > 0;
195+
const totalMatchCount = await prisma.match.count({ where: { pupilId: pupil.id } });
214196

215197
const newMatchAchievements = userAchievements.filter(
216198
(row) => row.template.group === PupilNewMatchGroup && row.template.groupOrder === PupilNewMatchGroupOrder
@@ -222,13 +204,17 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
222204
if (successfulScreenings.length > 0) {
223205
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
224206
}
207+
// This case happens when the student just registered and had a successful screening
208+
if (pupil.openMatchRequestCount === 0 && totalMatchCount === 0) {
209+
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
210+
result.push(...ghosts);
211+
}
225212
for (let i = 0; i < pupil.openMatchRequestCount; i++) {
226-
const ghosts = await generatePupilMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
213+
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
227214
result.push(...ghosts);
228215
}
229-
230216
for (const userAchievement of newMatchAchievements) {
231-
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
217+
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, hasSuccessfulScreenings, ctx);
232218
result.push(...ghosts);
233219
}
234220
}
@@ -243,6 +229,7 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
243229
where: { studentId: student.id, success: true },
244230
orderBy: { createdAt: 'desc' },
245231
});
232+
const totalMatchCount = await prisma.match.count({ where: { studentId: student.id } });
246233

247234
const newMatchAchievements = userAchievements.filter(
248235
(row) => row.template.group === StudentNewMatchGroup && row.template.groupOrder === StudentNewMatchGroupOrder
@@ -254,13 +241,19 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
254241
if (successfulScreenings.length > 0) {
255242
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
256243
}
244+
// This case happens when the student just registered and had a successful screening
245+
if (student.openMatchRequestCount === 0 && totalMatchCount === 0) {
246+
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
247+
result.push(...ghosts);
248+
}
249+
// This will
257250
for (let i = 0; i < student.openMatchRequestCount; i++) {
258-
const ghosts = await generateStudentMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
251+
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
259252
result.push(...ghosts);
260253
}
261254

262255
for (const userAchievement of newMatchAchievements) {
263-
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
256+
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, ctx);
264257
result.push(...ghosts);
265258
}
266259
}
@@ -269,7 +262,6 @@ async function generateStudentMatching(
269262
achievement: achievement_with_template | null,
270263
user: User,
271264
hasRequest: boolean,
272-
hasSuccessfulScreening: boolean,
273265
ctx: StudentNewMatchGhostContext
274266
): Promise<achievement_with_template[]> {
275267
const result: achievement_with_template[] = [];
@@ -306,16 +298,5 @@ async function generateStudentMatching(
306298
relation: achievement?.relation ?? randomRelation,
307299
});
308300

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-
});
320301
return result;
321302
}

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
@@ -551,12 +551,20 @@ const _notificationActions = {
551551
},
552552
},
553553
student_add_appointment_match: {
554-
description: 'Tutee / Tutor added Match Appointment',
554+
description: 'Tutee / Tutor added Match Appointment (Pupil event)',
555555
sampleContext: {
556556
student: sampleUser,
557557
matchId: '1',
558558
},
559559
},
560+
student_add_appointment_match_with_pupil: {
561+
description: 'Tutee / Tutor added Match Appointment (Tutee / Tutor event)',
562+
sampleContext: {
563+
pupil: sampleUser,
564+
match: { id: '1' },
565+
lecture: {},
566+
},
567+
},
560568
pupil_decline_appointment_group: {
561569
description: 'Instructor / Group Appointment declined by Participant',
562570
sampleContext: {
@@ -602,6 +610,20 @@ const _notificationActions = {
602610
appointment: sampleAppointment,
603611
},
604612
},
613+
pupil_create_new_match_chat: {
614+
description: 'User has clicked on new chat with a match partner',
615+
sampleContext: {
616+
user: sampleUser,
617+
match: { id: '1' },
618+
},
619+
},
620+
student_create_new_match_chat: {
621+
description: 'User has clicked on new chat with a match partner',
622+
sampleContext: {
623+
user: sampleUser,
624+
match: { id: '1' },
625+
},
626+
},
605627
missed_one_on_one_chat_message: {
606628
description: 'Missed message in 1:1 chat',
607629
sampleContext: sampleMissedOneOnOneMessage,

0 commit comments

Comments
 (0)