Skip to content

Commit

Permalink
create new achievements for first student match
Browse files Browse the repository at this point in the history
  • Loading branch information
dhenkel92 committed Oct 20, 2024
1 parent 0a66dce commit a816211
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 269 deletions.
73 changes: 26 additions & 47 deletions common/achievement/derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ const GhostAchievements: { [key: string]: achievement_template } = {
group: PupilNewMatchGroup,
groupOrder: 1,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_1.png',
image: 'gamification/achievements/release/new_match/five_pieces/empty_state.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
description:
'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!',
'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!',
footer: null,
actionName: 'Anfrage stellen',
actionRedirectLink: '/matching',
actionRedirectLink: '/request-match',
actionType: AchievementActionType.Action,
condition: 'false', // This will ensure that an evaluation will always fail
conditionDataAggregations: {},
Expand All @@ -50,7 +50,7 @@ const GhostAchievements: { [key: string]: achievement_template } = {
group: PupilNewMatchGroup,
groupOrder: 2,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_2.png',
image: 'gamification/achievements/release/new_match/five_pieces/step_1.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
Expand All @@ -74,15 +74,15 @@ const GhostAchievements: { [key: string]: achievement_template } = {
group: StudentNewMatchGroup,
groupOrder: 1,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_1.png',
image: 'gamification/achievements/release/new_match/five_pieces/empty_state.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
description:
'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!',
footer: null,
actionName: 'Anfrage stellen',
actionRedirectLink: '/matching',
actionRedirectLink: '/request-match',
actionType: AchievementActionType.Action,
condition: 'false', // This will ensure that an evaluation will always fail
conditionDataAggregations: {},
Expand All @@ -92,30 +92,6 @@ const GhostAchievements: { [key: string]: achievement_template } = {
achievedImage: null,
sequentialStepName: 'Anfrage stellen',
},
student_new_match_2: {
id: -1,
templateFor: AchievementTemplateFor.Match,
group: StudentNewMatchGroup,
groupOrder: 2,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_2.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
description:
'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!',
footer: null,
actionName: 'Termin buchen',
actionRedirectLink: 'https://calendly.com',
actionType: AchievementActionType.Action,
condition: 'false',
conditionDataAggregations: {},
isActive: true,
achievedDescription: null,
achievedFooter: null,
achievedImage: null,
sequentialStepName: 'Gespräch mit Lern-Fair absolvieren',
},
};

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

console.log('iso', hasRequest, hasSuccessfulScreening, achievement);

result.push({
id: -1,
templateId: -1,
Expand Down Expand Up @@ -211,6 +189,8 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
where: { pupilId: pupil.id, status: pupil_screening_status_enum.success, invalidated: false },
orderBy: { createdAt: 'desc' },
});
const hasSuccessfulScreenings = successfulScreenings.length > 0;
const totalMatchCount = await prisma.match.count({ where: { pupilId: pupil.id } });

const newMatchAchievements = userAchievements.filter(
(row) => row.template.group === PupilNewMatchGroup && row.template.groupOrder === PupilNewMatchGroupOrder
Expand All @@ -222,13 +202,17 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
if (successfulScreenings.length > 0) {
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
}
// This case happens when the student just registered and had a successful screening
if (pupil.openMatchRequestCount === 0 && totalMatchCount === 0) {
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
result.push(...ghosts);
}
for (let i = 0; i < pupil.openMatchRequestCount; i++) {
const ghosts = await generatePupilMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
const ghosts = await generatePupilMatching(null, user, hasRequest, hasSuccessfulScreenings, ctx);
result.push(...ghosts);
}

for (const userAchievement of newMatchAchievements) {
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
const ghosts = await generatePupilMatching(userAchievement, user, hasRequest, hasSuccessfulScreenings, ctx);
result.push(...ghosts);
}
}
Expand All @@ -243,6 +227,7 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
where: { studentId: student.id, success: true },
orderBy: { createdAt: 'desc' },
});
const totalMatchCount = await prisma.match.count({ where: { studentId: student.id } });

const newMatchAchievements = userAchievements.filter(
(row) => row.template.group === StudentNewMatchGroup && row.template.groupOrder === StudentNewMatchGroupOrder
Expand All @@ -254,13 +239,19 @@ async function deriveStudentMatching(user: User, student: Student, result: achie
if (successfulScreenings.length > 0) {
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
}
// This case happens when the student just registered and had a successful screening
if (student.openMatchRequestCount === 0 && totalMatchCount === 0) {
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
result.push(...ghosts);
}
// This will
for (let i = 0; i < student.openMatchRequestCount; i++) {
const ghosts = await generateStudentMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
const ghosts = await generateStudentMatching(null, user, hasRequest, ctx);
result.push(...ghosts);
}

for (const userAchievement of newMatchAchievements) {
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, ctx);
result.push(...ghosts);
}
}
Expand All @@ -269,7 +260,6 @@ async function generateStudentMatching(
achievement: achievement_with_template | null,
user: User,
hasRequest: boolean,
hasSuccessfulScreening: boolean,
ctx: StudentNewMatchGhostContext
): Promise<achievement_with_template[]> {
const result: achievement_with_template[] = [];
Expand Down Expand Up @@ -306,16 +296,5 @@ async function generateStudentMatching(
relation: achievement?.relation ?? randomRelation,
});

result.push({
id: -1,
templateId: -1,
userId: user.userID,
isSeen: true,
template: GhostAchievements.student_new_match_2,
context: ctx,
recordValue: null,
achievedAt: hasSuccessfulScreening || achievement ? new Date() : null,
relation: achievement?.relation ?? randomRelation,
});
return result;
}
9 changes: 4 additions & 5 deletions common/achievement/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export async function getUserAchievementsWithTemplates(user: User, byType: Achie
},
include: { template: true },
});

const derivedAchievements = await deriveAchievements(user, userAchievementsWithTemplates);
userAchievementsWithTemplates.push(...derivedAchievements);

return userAchievementsWithTemplates;
}
export type achievements_with_template = ThenArg<ReturnType<typeof getUserAchievementsWithTemplates>>;
Expand All @@ -43,8 +47,6 @@ const getAchievementById = async (user: User, achievementId: number): Promise<Pu
// Next step achievements are sequential achievements that are currently active and not yet completed. They get displayed in the next step card section.
const getNextStepAchievements = async (user: User): Promise<PublicAchievement[]> => {
const userAchievements = await getUserAchievementsWithTemplates(user, AchievementType.SEQUENTIAL);
const derivedAchievements = await deriveAchievements(user, userAchievements);
userAchievements.push(...derivedAchievements);

const userAchievementGroups: { [groupRelation: string]: achievements_with_template } = {};
userAchievements.forEach((ua) => {
Expand Down Expand Up @@ -111,9 +113,6 @@ const getFurtherAchievements = async (user: User): Promise<PublicAchievement[]>
// User achievements are already started by the user and are either active or completed.
const getUserAchievements = async (user: User): Promise<PublicAchievement[]> => {
const userAchievements = await getUserAchievementsWithTemplates(user);
const derivedAchievements = await deriveAchievements(user, userAchievements);
userAchievements.push(...derivedAchievements);

const userAchievementGroups: { [group: string]: achievements_with_template } = {};
userAchievements.forEach((ua) => {
if (!userAchievementGroups[`${ua.template.group}/${ua.relation}`]) {
Expand Down
15 changes: 15 additions & 0 deletions common/achievement/metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const batchOfMetrics = [
createMetric('student_onboarding_screened', ['student_screening_appointment_done', 'tutor_screening_success', 'instructor_screening_success'], () => {
return 1;
}),
createMetric('student_onboarding_tutor_screened', ['tutor_screening_success'], () => {
return 1;
}),
createMetric('student_onboarding_coc_success', ['student_coc_updated'], () => {
return 1;
}),
Expand All @@ -49,6 +52,9 @@ const batchOfMetrics = [
),

/* CONDUCTED MATCH APPOINTMENT */
createMetric('student_add_match_appointment', ['student_add_appointment_match_with_pupil'], () => {
return 1;
}),
createMetric('student_conducted_match_appointment', ['student_joined_match_meeting'], () => {
return 1;
}),
Expand Down Expand Up @@ -106,9 +112,18 @@ const batchOfMetrics = [
}),

/* Matching */
createMetric('pupil_create_new_match_chat', ['pupil_create_new_match_chat'], () => {
return 1;
}),
createMetric('student_create_new_match_chat', ['student_create_new_match_chat'], () => {
return 1;
}),
createMetric('pupil_match_create', ['tutee_matching_success'], () => {
return 1;
}),
createMetric('student_match_requested', ['tutor_match_requested'], () => {
return 1;
}),
createMetric('student_match_create', ['tutor_matching_success'], () => {
return 1;
}),
Expand Down
8 changes: 8 additions & 0 deletions common/appointment/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getMatch, getPupil, getStudent } from '../../graphql/util';
import { PrerequisiteError, RedundantError } from '../../common/util/error';
import { getContextForGroupAppointmentReminder, getContextForMatchAppointmentReminder } from './util';
import { getNotificationContextForSubcourse } from '../../common/courses/notifications';
import { createRelation, EventRelationType } from '../achievement/relation';

const logger = getLogger();

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

if (!silent) {
await Notification.actionTaken(userForPupil(pupil), 'student_add_appointment_match', {
relation: createRelation(EventRelationType.Match, matchId),
student,
matchId: matchId.toString(),
});
Expand All @@ -110,6 +112,12 @@ export const createMatchAppointments = async (matchId: number, appointmentsToBeC
...(await getContextForMatchAppointmentReminder(appointment)),
pupil,
});
await Notification.actionTaken(userForStudent(student), 'student_add_appointment_match_with_pupil', {
relation: createRelation(EventRelationType.Match, matchId),
pupil,
match: { id: matchId.toString() },
lecture: appointment,
});
}
}

Expand Down
18 changes: 18 additions & 0 deletions common/chat/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import systemMessages from './localization';
import { getLogger } from '../logger/logger';
import assert from 'assert';
import { createHmac } from 'crypto';
import { actionTaken } from '../notification';
import { createRelation, EventRelationType } from '../achievement/relation';

const logger = getLogger('Chat');
const getOrCreateOneOnOneConversation = async (
Expand Down Expand Up @@ -201,6 +203,22 @@ async function createContactChat(meUser: User, contactUser: User): Promise<strin
},
};

if (contact.match) {
if (meUser.studentId) {
await actionTaken(meUser, 'student_create_new_match_chat', {
user: meUser,
match: { id: `${contact.match.matchId}` },
relation: createRelation(EventRelationType.Match, contact.match.matchId),
});
} else {
await actionTaken(meUser, 'pupil_create_new_match_chat', {
user: meUser,
match: { id: `${contact.match.matchId}` },
relation: createRelation(EventRelationType.Match, contact.match.matchId),
});
}
}

const conversation = await getOrCreateOneOnOneConversation([meUser, contactUser], conversationInfos, ContactReason.CONTACT);
logger.info(`Contact conversation was created by ${meUser} with ${contactUser} with ID ${conversation.id} `);
return conversation.id;
Expand Down
24 changes: 23 additions & 1 deletion common/notification/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,12 +567,20 @@ const _notificationActions = {
},
},
student_add_appointment_match: {
description: 'Tutee / Tutor added Match Appointment',
description: 'Tutee / Tutor added Match Appointment (Pupil event)',
sampleContext: {
student: sampleUser,
matchId: '1',
},
},
student_add_appointment_match_with_pupil: {
description: 'Tutee / Tutor added Match Appointment (Tutee / Tutor event)',
sampleContext: {
pupil: sampleUser,
match: { id: '1' },
lecture: {},
},
},
pupil_decline_appointment_group: {
description: 'Instructor / Group Appointment declined by Participant',
sampleContext: {
Expand Down Expand Up @@ -618,6 +626,20 @@ const _notificationActions = {
appointment: sampleAppointment,
},
},
pupil_create_new_match_chat: {
description: 'User has clicked on new chat with a match partner',
sampleContext: {
user: sampleUser,
match: { id: '1' },
},
},
student_create_new_match_chat: {
description: 'User has clicked on new chat with a match partner',
sampleContext: {
user: sampleUser,
match: { id: '1' },
},
},
missed_one_on_one_chat_message: {
description: 'Missed message in 1:1 chat',
sampleContext: sampleMissedOneOnOneMessage,
Expand Down
Loading

0 comments on commit a816211

Please sign in to comment.