Skip to content

Commit

Permalink
Feat: Add new actions for DaZ Matches and Instructors Only Users (#1159)
Browse files Browse the repository at this point in the history
* feat: Add new actions

* feat: Execute actions for new DaZ matches

* feat: Add new action for instructors completing their first group appointment

* feat: Pass notification context to hooks

* feat: Add hook to invite instructors to reflection meetings

* fix: Adjust condition to invite users to reflection meeting
  • Loading branch information
JeangelLF authored Nov 15, 2024
1 parent 3801116 commit 1d13527
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 12 deletions.
14 changes: 11 additions & 3 deletions common/match/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PrerequisiteError } from '../util/error';
import type { ConcreteMatchPool } from './pool';
import { invalidateAllScreeningsOfPupil } from '../pupil/screening';
import { userForPupil, userForStudent } from '../user';
import { DAZ } from '../util/subjectsutils';

const logger = getLogger('Match');

Expand Down Expand Up @@ -58,9 +59,9 @@ export async function createMatch(pupil: Pupil, student: Student, pool: Concrete
await removeInterest(pupil);

const callURL = getJitsiTutoringLink(match);
const matchSubjects = getOverlappingSubjects(pupil, student)
.map((it) => it.name)
.join('/');
const subjects = getOverlappingSubjects(pupil, student).map((it) => it.name);

const matchSubjects = subjects.join('/');

const tutorFirstMatch = (await prisma.match.count({ where: { studentId: student.id } })) === 1;
const tuteeFirstMatch = (await prisma.match.count({ where: { pupilId: pupil.id } })) === 1;
Expand All @@ -81,6 +82,13 @@ export async function createMatch(pupil: Pupil, student: Student, pool: Concrete
};

await Notification.actionTaken(userForStudent(student), `tutor_matching_success`, tutorContext);

await Notification.actionTaken(
userForStudent(student),
subjects.includes(DAZ) ? 'tutor_daz_matching_success' : 'tutor_standard_matching_success',
tutorContext
);

await Notification.actionTaken(userForStudent(student), `tutor_matching_${pool.name}`, tutorContext);

const tuteeContext = {
Expand Down
28 changes: 28 additions & 0 deletions common/notification/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ const _notificationActions = {
},
},
},
instructor_first_appointment_completed: {
description: 'Instructor / Completed first appointment',
sampleContext: {
uniqueId: 'REQUIRED',
appointment: sampleAppointment,
...sampleCourse,
},
},
instructor_course_ended: {
description: 'Instructor / Course ended',
sampleContext: sampleCourse,
Expand Down Expand Up @@ -431,6 +439,26 @@ const _notificationActions = {
firstMatch: true,
},
},
tutor_daz_matching_success: {
description: 'Tutor / DaZ Match success',
sampleContext: {
pupil: sampleUser,
pupilGrade: '3. Klasse',
matchHash: '...',
matchDate: '...',
firstMatch: true,
},
},
tutor_standard_matching_success: {
description: 'Tutor / Standard Match success',
sampleContext: {
pupil: sampleUser,
pupilGrade: '3. Klasse',
matchHash: '...',
matchDate: '...',
firstMatch: true,
},
},
'tutor_matching_lern-fair-now': {
description: 'Tutor / Lern-Fair Now Match success',
sampleContext: {
Expand Down
17 changes: 9 additions & 8 deletions common/notification/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,35 @@

import { student as Student, pupil as Pupil } from '@prisma/client';
import { getPupil, getStudent, User } from '../user';
import { NotificationContext } from './types';

type NotificationHook = { fn: (user: User) => Promise<void>; description: string };
type NotificationHook = { fn: (user: User, context: NotificationContext) => Promise<void>; description: string };

const hooks: { [hookID: string]: NotificationHook } = {};

export const hookExists = (hookID: string) => hookID in hooks;
export const getHookDescription = (hookID: string) => hooks[hookID]?.description;

export async function triggerHook(hookID: string, user: User) {
export async function triggerHook(hookID: string, user: User, context: NotificationContext) {
if (!hookExists(hookID)) {
throw new Error(`Unknown hook ${hookID}`);
}

const hook = hooks[hookID];

await hook.fn(user);
await hook.fn(user, context);
}

export function registerHook(hookID: string, description: string, fn: (user: User) => Promise<void>) {
export function registerHook(hookID: string, description: string, fn: (user: User, context: NotificationContext) => Promise<void>) {
if (hookExists(hookID)) {
throw new Error(`Hook may only be registered once`);
}

hooks[hookID] = { description, fn };
}

export const registerStudentHook = (hookID: string, description: string, hook: (student: Student) => Promise<void>) =>
registerHook(hookID, description, (user) => getStudent(user).then(hook));
export const registerStudentHook = (hookID: string, description: string, hook: (student: Student, context: NotificationContext) => Promise<void>) =>
registerHook(hookID, description, (user, context) => getStudent(user).then((student) => hook(student, context)));

export const registerPupilHook = (hookID: string, description: string, hook: (pupil: Pupil) => Promise<void>) =>
registerHook(hookID, description, (user) => getPupil(user).then(hook));
export const registerPupilHook = (hookID: string, description: string, hook: (pupil: Pupil, context: NotificationContext) => Promise<void>) =>
registerHook(hookID, description, (user, context) => getPupil(user).then((pupil) => hook(pupil, context)));
26 changes: 26 additions & 0 deletions common/notification/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { registerPupilHook, registerStudentHook } from './hook';
// this ensures that the hooks are always registered when the Notification is loaded (i.e. in the jobs Deno, which only loads parts of the backend)
import { deactivateStudent } from '../student/activation';
import { cancelRemissionRequest } from '../remission-request';
import { prisma } from '../prisma';
import { userForStudent } from '../user';
import * as Notification from '../../common/notification';
import { SpecificNotificationContext } from './actions';

registerStudentHook(
'deactivate-student',
Expand All @@ -17,6 +21,28 @@ registerStudentHook('cancel-remission-request', 'Cancels the remission request(s
await cancelRemissionRequest(student);
});

registerStudentHook(
'attempt-invite-instructor-to-reflection-meeting',
'Instructors (without matches) are invited to participate in a reflection meeting after their very first group appointment',
async (student, context) => {
const activeMatches = await prisma.match.count({ where: { studentId: student.id, dissolved: false } });
if (activeMatches > 0) {
return;
}
const user = userForStudent(student);
const appointmentContext = context as SpecificNotificationContext<'student_group_appointment_starts'>;
const triggeredAppointmentId = Number(context.uniqueId);
const firstInstructorAppointment = await prisma.lecture.findFirst({
where: { organizerIds: { has: user.userID }, isCanceled: false, appointmentType: 'group' },
orderBy: { start: 'asc' },
});
const isFirstInstructorAppointment = triggeredAppointmentId === firstInstructorAppointment.id;
if (isFirstInstructorAppointment) {
await Notification.actionTaken(user, 'instructor_first_appointment_completed', appointmentContext);
}
}
);

import { deletePupilMatchRequest } from '../match/request';
import { deactivatePupil } from '../pupil/activation';
registerPupilHook('revoke-pupil-match-request', 'Match Request is taken back, pending Pupil Screenings are invalidated', async (pupil) => {
Expand Down
3 changes: 2 additions & 1 deletion common/notification/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ async function deliverNotification(
try {
// Always trigger the hook, no matter whether we actually send something to the user
if (notification.hookID) {
await triggerHook(notification.hookID, user);
logger.debug(`Running Hook(${notification.hookID}) for ConcreteNotification(${concreteNotification.id})`);
await triggerHook(notification.hookID, user, context);
}

const channelPreferencesForMessageType = await getNotificationChannelPreferences(user, concreteNotification);
Expand Down
2 changes: 2 additions & 0 deletions common/util/subjectsutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,5 @@ export function parseSubjectString(subjects: string): Subject[] {
};
});
}

export const DAZ = 'Deutsch als Zweitsprache';

0 comments on commit 1d13527

Please sign in to comment.