placementt-core 1.400.1123 → 1.400.1125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/callables/e2e.d.ts +150 -9
- package/lib/callables/e2e.js +69 -0
- package/lib/callables/e2e.js.map +1 -1
- package/lib/callables/events.d.ts +22 -0
- package/lib/callables/events.js +12 -0
- package/lib/callables/events.js.map +1 -1
- package/lib/callables/registry.d.ts +298 -0
- package/lib/eventRequestMetrics.d.ts +68 -0
- package/lib/eventRequestMetrics.js +92 -0
- package/lib/eventRequestMetrics.js.map +1 -0
- package/lib/eventRequestMetrics.test.d.ts +1 -0
- package/lib/eventRequestMetrics.test.js +52 -0
- package/lib/eventRequestMetrics.test.js.map +1 -0
- package/lib/features/jobs/jobsSlice.js +9 -1
- package/lib/features/jobs/jobsSlice.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/typeDefinitions.d.ts +1 -1
- package/package.json +1 -1
- package/src/callables/e2e.ts +71 -0
- package/src/callables/events.ts +12 -0
- package/src/eventRequestMetrics.test.ts +53 -0
- package/src/eventRequestMetrics.ts +122 -0
- package/src/features/jobs/jobsSlice.ts +9 -1
- package/src/index.ts +1 -0
- package/src/typeDefinitions.ts +1 -1
package/lib/callables/e2e.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
/**
|
|
3
|
-
* Contracts for the end-to-end and UX-audit harness
|
|
4
|
-
* (`Placementt-Backend/functions/src/e2e.ts`).
|
|
5
|
-
*
|
|
6
|
-
* `redeemSignInLink` is public: the token is the credential. Links are only
|
|
7
|
-
* ever minted by the harness (service account, straight into Firestore) and
|
|
8
|
-
* only ever sign in a reserved `e2e.*@careerthread.co.uk` user, so the UX audit
|
|
9
|
-
* can walk a registered school without anyone typing a password.
|
|
10
|
-
*/
|
|
11
2
|
export declare const e2eCallables: {
|
|
12
3
|
"e2e-redeemSignInLink": {
|
|
13
4
|
request: z.ZodObject<{
|
|
@@ -30,4 +21,154 @@ export declare const e2eCallables: {
|
|
|
30
21
|
authToken?: string | undefined;
|
|
31
22
|
}>;
|
|
32
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Admin only. Tears down one institute whose billing email is a reserved
|
|
26
|
+
* E2E address. Each step reports true (cleared), false (nothing to do) or
|
|
27
|
+
* the error message.
|
|
28
|
+
*/
|
|
29
|
+
"e2e-deleteE2EAccount": {
|
|
30
|
+
request: z.ZodObject<{
|
|
31
|
+
oId: z.ZodString;
|
|
32
|
+
}, "strip", z.ZodTypeAny, {
|
|
33
|
+
oId: string;
|
|
34
|
+
}, {
|
|
35
|
+
oId: string;
|
|
36
|
+
}>;
|
|
37
|
+
response: z.ZodObject<{
|
|
38
|
+
deleted: z.ZodLiteral<true>;
|
|
39
|
+
oId: z.ZodString;
|
|
40
|
+
email: z.ZodString;
|
|
41
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
deleted: true;
|
|
44
|
+
email: string;
|
|
45
|
+
oId: string;
|
|
46
|
+
steps: Record<string, string | boolean>;
|
|
47
|
+
}, {
|
|
48
|
+
deleted: true;
|
|
49
|
+
email: string;
|
|
50
|
+
oId: string;
|
|
51
|
+
steps: Record<string, string | boolean>;
|
|
52
|
+
}>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Admin only. Tears down a whole run: every institute billed to
|
|
56
|
+
* `e2e.<runId>@` or `e2e.<runId>-*@`, their careers invites and hub links,
|
|
57
|
+
* the run's employers, provider accounts, event requests (and PDFs),
|
|
58
|
+
* attendees, Auth users and Brevo contacts. Institute steps are reported
|
|
59
|
+
* as `<oId>/<step>`.
|
|
60
|
+
*/
|
|
61
|
+
"e2e-deleteE2ERun": {
|
|
62
|
+
request: z.ZodObject<{
|
|
63
|
+
runId: z.ZodString;
|
|
64
|
+
}, "strip", z.ZodTypeAny, {
|
|
65
|
+
runId: string;
|
|
66
|
+
}, {
|
|
67
|
+
runId: string;
|
|
68
|
+
}>;
|
|
69
|
+
response: z.ZodObject<{
|
|
70
|
+
deleted: z.ZodLiteral<true>;
|
|
71
|
+
runId: z.ZodString;
|
|
72
|
+
institutes: z.ZodArray<z.ZodObject<{
|
|
73
|
+
oId: z.ZodString;
|
|
74
|
+
email: z.ZodString;
|
|
75
|
+
}, "strip", z.ZodTypeAny, {
|
|
76
|
+
email: string;
|
|
77
|
+
oId: string;
|
|
78
|
+
}, {
|
|
79
|
+
email: string;
|
|
80
|
+
oId: string;
|
|
81
|
+
}>, "many">;
|
|
82
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
83
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
84
|
+
}, "strip", z.ZodTypeAny, {
|
|
85
|
+
deleted: true;
|
|
86
|
+
institutes: {
|
|
87
|
+
email: string;
|
|
88
|
+
oId: string;
|
|
89
|
+
}[];
|
|
90
|
+
runId: string;
|
|
91
|
+
steps: Record<string, string | boolean>;
|
|
92
|
+
providerContactIds: string[];
|
|
93
|
+
}, {
|
|
94
|
+
deleted: true;
|
|
95
|
+
institutes: {
|
|
96
|
+
email: string;
|
|
97
|
+
oId: string;
|
|
98
|
+
}[];
|
|
99
|
+
runId: string;
|
|
100
|
+
steps: Record<string, string | boolean>;
|
|
101
|
+
providerContactIds: string[];
|
|
102
|
+
}>;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Admin only. Seeds an active careers hub for a run
|
|
106
|
+
* (`e2e.<runId>-hub@careerthread.co.uk`) with North and South groups and
|
|
107
|
+
* six employers, five approved and one awaiting review.
|
|
108
|
+
*/
|
|
109
|
+
"e2e-seedCareersHub": {
|
|
110
|
+
request: z.ZodObject<{
|
|
111
|
+
runId: z.ZodString;
|
|
112
|
+
/** Defaults to the harness password, `E2e!<runId>Aa1`. */
|
|
113
|
+
password: z.ZodOptional<z.ZodString>;
|
|
114
|
+
/** A public externalActivities id. Defaults to the first public employer activity. */
|
|
115
|
+
activityId: z.ZodOptional<z.ZodString>;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
runId: string;
|
|
118
|
+
password?: string | undefined;
|
|
119
|
+
activityId?: string | undefined;
|
|
120
|
+
}, {
|
|
121
|
+
runId: string;
|
|
122
|
+
password?: string | undefined;
|
|
123
|
+
activityId?: string | undefined;
|
|
124
|
+
}>;
|
|
125
|
+
response: z.ZodObject<{
|
|
126
|
+
hubOId: z.ZodString;
|
|
127
|
+
adminUid: z.ZodString;
|
|
128
|
+
adminEmail: z.ZodString;
|
|
129
|
+
groupIds: z.ZodArray<z.ZodString, "many">;
|
|
130
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
131
|
+
activityId: z.ZodString;
|
|
132
|
+
}, "strip", z.ZodTypeAny, {
|
|
133
|
+
providerContactIds: string[];
|
|
134
|
+
activityId: string;
|
|
135
|
+
hubOId: string;
|
|
136
|
+
adminUid: string;
|
|
137
|
+
adminEmail: string;
|
|
138
|
+
groupIds: string[];
|
|
139
|
+
}, {
|
|
140
|
+
providerContactIds: string[];
|
|
141
|
+
activityId: string;
|
|
142
|
+
hubOId: string;
|
|
143
|
+
adminUid: string;
|
|
144
|
+
adminEmail: string;
|
|
145
|
+
groupIds: string[];
|
|
146
|
+
}>;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Admin only. Sends one event's post-event feedback requests now, instead
|
|
150
|
+
* of waiting for the daily cron. The event's institute must be an E2E account.
|
|
151
|
+
*/
|
|
152
|
+
"e2e-runEventFeedback": {
|
|
153
|
+
request: z.ZodObject<{
|
|
154
|
+
eventId: z.ZodString;
|
|
155
|
+
}, "strip", z.ZodTypeAny, {
|
|
156
|
+
eventId: string;
|
|
157
|
+
}, {
|
|
158
|
+
eventId: string;
|
|
159
|
+
}>;
|
|
160
|
+
response: z.ZodObject<{
|
|
161
|
+
eventId: z.ZodString;
|
|
162
|
+
oId: z.ZodString;
|
|
163
|
+
sent: z.ZodLiteral<true>;
|
|
164
|
+
}, "strip", z.ZodTypeAny, {
|
|
165
|
+
oId: string;
|
|
166
|
+
sent: true;
|
|
167
|
+
eventId: string;
|
|
168
|
+
}, {
|
|
169
|
+
oId: string;
|
|
170
|
+
sent: true;
|
|
171
|
+
eventId: string;
|
|
172
|
+
}>;
|
|
173
|
+
};
|
|
33
174
|
};
|
package/lib/callables/e2e.js
CHANGED
|
@@ -11,6 +11,10 @@ const zod_1 = require("zod");
|
|
|
11
11
|
* only ever sign in a reserved `e2e.*@careerthread.co.uk` user, so the UX audit
|
|
12
12
|
* can walk a registered school without anyone typing a password.
|
|
13
13
|
*/
|
|
14
|
+
/** A run id as the harness derives it from E2E_RUN_ID. */
|
|
15
|
+
const e2eRunId = zod_1.z.string().min(1).max(100).regex(/^[a-z0-9-]+$/);
|
|
16
|
+
/** Per-step teardown outcome: true cleared, false nothing to do, string the error. */
|
|
17
|
+
const teardownSteps = zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.boolean(), zod_1.z.string()]));
|
|
14
18
|
exports.e2eCallables = {
|
|
15
19
|
"e2e-redeemSignInLink": {
|
|
16
20
|
request: zod_1.z.object({
|
|
@@ -23,5 +27,70 @@ exports.e2eCallables = {
|
|
|
23
27
|
authToken: zod_1.z.string().optional(),
|
|
24
28
|
}),
|
|
25
29
|
},
|
|
30
|
+
/**
|
|
31
|
+
* Admin only. Tears down one institute whose billing email is a reserved
|
|
32
|
+
* E2E address. Each step reports true (cleared), false (nothing to do) or
|
|
33
|
+
* the error message.
|
|
34
|
+
*/
|
|
35
|
+
"e2e-deleteE2EAccount": {
|
|
36
|
+
request: zod_1.z.object({ oId: zod_1.z.string().min(1) }),
|
|
37
|
+
response: zod_1.z.object({
|
|
38
|
+
deleted: zod_1.z.literal(true),
|
|
39
|
+
oId: zod_1.z.string(),
|
|
40
|
+
email: zod_1.z.string(),
|
|
41
|
+
steps: teardownSteps,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Admin only. Tears down a whole run: every institute billed to
|
|
46
|
+
* `e2e.<runId>@` or `e2e.<runId>-*@`, their careers invites and hub links,
|
|
47
|
+
* the run's employers, provider accounts, event requests (and PDFs),
|
|
48
|
+
* attendees, Auth users and Brevo contacts. Institute steps are reported
|
|
49
|
+
* as `<oId>/<step>`.
|
|
50
|
+
*/
|
|
51
|
+
"e2e-deleteE2ERun": {
|
|
52
|
+
request: zod_1.z.object({ runId: e2eRunId }),
|
|
53
|
+
response: zod_1.z.object({
|
|
54
|
+
deleted: zod_1.z.literal(true),
|
|
55
|
+
runId: zod_1.z.string(),
|
|
56
|
+
institutes: zod_1.z.array(zod_1.z.object({ oId: zod_1.z.string(), email: zod_1.z.string() })),
|
|
57
|
+
providerContactIds: zod_1.z.array(zod_1.z.string()),
|
|
58
|
+
steps: teardownSteps,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Admin only. Seeds an active careers hub for a run
|
|
63
|
+
* (`e2e.<runId>-hub@careerthread.co.uk`) with North and South groups and
|
|
64
|
+
* six employers, five approved and one awaiting review.
|
|
65
|
+
*/
|
|
66
|
+
"e2e-seedCareersHub": {
|
|
67
|
+
request: zod_1.z.object({
|
|
68
|
+
runId: e2eRunId,
|
|
69
|
+
/** Defaults to the harness password, `E2e!<runId>Aa1`. */
|
|
70
|
+
password: zod_1.z.string().min(8).max(128).optional(),
|
|
71
|
+
/** A public externalActivities id. Defaults to the first public employer activity. */
|
|
72
|
+
activityId: zod_1.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).optional(),
|
|
73
|
+
}),
|
|
74
|
+
response: zod_1.z.object({
|
|
75
|
+
hubOId: zod_1.z.string(),
|
|
76
|
+
adminUid: zod_1.z.string(),
|
|
77
|
+
adminEmail: zod_1.z.string(),
|
|
78
|
+
groupIds: zod_1.z.array(zod_1.z.string()),
|
|
79
|
+
providerContactIds: zod_1.z.array(zod_1.z.string()),
|
|
80
|
+
activityId: zod_1.z.string(),
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Admin only. Sends one event's post-event feedback requests now, instead
|
|
85
|
+
* of waiting for the daily cron. The event's institute must be an E2E account.
|
|
86
|
+
*/
|
|
87
|
+
"e2e-runEventFeedback": {
|
|
88
|
+
request: zod_1.z.object({ eventId: zod_1.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/) }),
|
|
89
|
+
response: zod_1.z.object({
|
|
90
|
+
eventId: zod_1.z.string(),
|
|
91
|
+
oId: zod_1.z.string(),
|
|
92
|
+
sent: zod_1.z.literal(true),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
26
95
|
};
|
|
27
96
|
//# sourceMappingURL=e2e.js.map
|
package/lib/callables/e2e.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"e2e.js","sourceRoot":"","sources":["../../src/callables/e2e.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"e2e.js","sourceRoot":"","sources":["../../src/callables/e2e.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,4 +44,26 @@ export declare const eventsCallables: {
|
|
|
44
44
|
geohash?: string | undefined;
|
|
45
45
|
}>, "many">;
|
|
46
46
|
};
|
|
47
|
+
"events-employerDeclineEvent": {
|
|
48
|
+
request: z.ZodObject<{
|
|
49
|
+
oId: z.ZodString;
|
|
50
|
+
eventId: z.ZodString;
|
|
51
|
+
attendeeId: z.ZodString;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
oId: string;
|
|
54
|
+
eventId: string;
|
|
55
|
+
attendeeId: string;
|
|
56
|
+
}, {
|
|
57
|
+
oId: string;
|
|
58
|
+
eventId: string;
|
|
59
|
+
attendeeId: string;
|
|
60
|
+
}>;
|
|
61
|
+
response: z.ZodObject<{
|
|
62
|
+
code: z.ZodEnum<["declined", "notFound"]>;
|
|
63
|
+
}, "strip", z.ZodTypeAny, {
|
|
64
|
+
code: "declined" | "notFound";
|
|
65
|
+
}, {
|
|
66
|
+
code: "declined" | "notFound";
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
47
69
|
};
|
package/lib/callables/events.js
CHANGED
|
@@ -24,5 +24,17 @@ exports.eventsCallables = {
|
|
|
24
24
|
country: zod_1.z.string().optional(),
|
|
25
25
|
})),
|
|
26
26
|
},
|
|
27
|
+
// Public: the invited attendee id in the email link is the credential,
|
|
28
|
+
// as for events-employerJoinEvent. Only an "invited" attendee can decline.
|
|
29
|
+
"events-employerDeclineEvent": {
|
|
30
|
+
request: zod_1.z.object({
|
|
31
|
+
oId: zod_1.z.string().min(1),
|
|
32
|
+
eventId: zod_1.z.string().min(1),
|
|
33
|
+
attendeeId: zod_1.z.string().min(1),
|
|
34
|
+
}),
|
|
35
|
+
response: zod_1.z.object({
|
|
36
|
+
code: zod_1.z.enum(["declined", "notFound"]),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
27
39
|
};
|
|
28
40
|
//# sourceMappingURL=events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/callables/events.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;GAGG;AACU,QAAA,eAAe,GAAG;IAC3B,qBAAqB,EAAE;QACnB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;SACpB,CAAC;QACF,qEAAqE;QACrE,QAAQ,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC;YACvB,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE;YACd,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC3B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACtC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACtC,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAClC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC/B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC,CAAC;KACN;CACoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/callables/events.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;GAGG;AACU,QAAA,eAAe,GAAG;IAC3B,qBAAqB,EAAE;QACnB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;SACpB,CAAC;QACF,qEAAqE;QACrE,QAAQ,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC;YACvB,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE;YACd,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC3B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACtC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACtC,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAClC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC/B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC,CAAC;KACN;IACD,uEAAuE;IACvE,2EAA2E;IAC3E,6BAA6B,EAAE;QAC3B,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACtB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SAChC,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,IAAI,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;SACzC,CAAC;KACL;CACoB,CAAC"}
|
|
@@ -231,6 +231,133 @@ export declare const allCallables: {
|
|
|
231
231
|
authToken?: string | undefined;
|
|
232
232
|
}>;
|
|
233
233
|
};
|
|
234
|
+
"e2e-deleteE2EAccount": {
|
|
235
|
+
request: z.ZodObject<{
|
|
236
|
+
oId: z.ZodString;
|
|
237
|
+
}, "strip", z.ZodTypeAny, {
|
|
238
|
+
oId: string;
|
|
239
|
+
}, {
|
|
240
|
+
oId: string;
|
|
241
|
+
}>;
|
|
242
|
+
response: z.ZodObject<{
|
|
243
|
+
deleted: z.ZodLiteral<true>;
|
|
244
|
+
oId: z.ZodString;
|
|
245
|
+
email: z.ZodString;
|
|
246
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
247
|
+
}, "strip", z.ZodTypeAny, {
|
|
248
|
+
deleted: true;
|
|
249
|
+
email: string;
|
|
250
|
+
oId: string;
|
|
251
|
+
steps: Record<string, string | boolean>;
|
|
252
|
+
}, {
|
|
253
|
+
deleted: true;
|
|
254
|
+
email: string;
|
|
255
|
+
oId: string;
|
|
256
|
+
steps: Record<string, string | boolean>;
|
|
257
|
+
}>;
|
|
258
|
+
};
|
|
259
|
+
"e2e-deleteE2ERun": {
|
|
260
|
+
request: z.ZodObject<{
|
|
261
|
+
runId: z.ZodString;
|
|
262
|
+
}, "strip", z.ZodTypeAny, {
|
|
263
|
+
runId: string;
|
|
264
|
+
}, {
|
|
265
|
+
runId: string;
|
|
266
|
+
}>;
|
|
267
|
+
response: z.ZodObject<{
|
|
268
|
+
deleted: z.ZodLiteral<true>;
|
|
269
|
+
runId: z.ZodString;
|
|
270
|
+
institutes: z.ZodArray<z.ZodObject<{
|
|
271
|
+
oId: z.ZodString;
|
|
272
|
+
email: z.ZodString;
|
|
273
|
+
}, "strip", z.ZodTypeAny, {
|
|
274
|
+
email: string;
|
|
275
|
+
oId: string;
|
|
276
|
+
}, {
|
|
277
|
+
email: string;
|
|
278
|
+
oId: string;
|
|
279
|
+
}>, "many">;
|
|
280
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
281
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
282
|
+
}, "strip", z.ZodTypeAny, {
|
|
283
|
+
deleted: true;
|
|
284
|
+
institutes: {
|
|
285
|
+
email: string;
|
|
286
|
+
oId: string;
|
|
287
|
+
}[];
|
|
288
|
+
runId: string;
|
|
289
|
+
steps: Record<string, string | boolean>;
|
|
290
|
+
providerContactIds: string[];
|
|
291
|
+
}, {
|
|
292
|
+
deleted: true;
|
|
293
|
+
institutes: {
|
|
294
|
+
email: string;
|
|
295
|
+
oId: string;
|
|
296
|
+
}[];
|
|
297
|
+
runId: string;
|
|
298
|
+
steps: Record<string, string | boolean>;
|
|
299
|
+
providerContactIds: string[];
|
|
300
|
+
}>;
|
|
301
|
+
};
|
|
302
|
+
"e2e-seedCareersHub": {
|
|
303
|
+
request: z.ZodObject<{
|
|
304
|
+
runId: z.ZodString;
|
|
305
|
+
password: z.ZodOptional<z.ZodString>;
|
|
306
|
+
activityId: z.ZodOptional<z.ZodString>;
|
|
307
|
+
}, "strip", z.ZodTypeAny, {
|
|
308
|
+
runId: string;
|
|
309
|
+
password?: string | undefined;
|
|
310
|
+
activityId?: string | undefined;
|
|
311
|
+
}, {
|
|
312
|
+
runId: string;
|
|
313
|
+
password?: string | undefined;
|
|
314
|
+
activityId?: string | undefined;
|
|
315
|
+
}>;
|
|
316
|
+
response: z.ZodObject<{
|
|
317
|
+
hubOId: z.ZodString;
|
|
318
|
+
adminUid: z.ZodString;
|
|
319
|
+
adminEmail: z.ZodString;
|
|
320
|
+
groupIds: z.ZodArray<z.ZodString, "many">;
|
|
321
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
322
|
+
activityId: z.ZodString;
|
|
323
|
+
}, "strip", z.ZodTypeAny, {
|
|
324
|
+
providerContactIds: string[];
|
|
325
|
+
activityId: string;
|
|
326
|
+
hubOId: string;
|
|
327
|
+
adminUid: string;
|
|
328
|
+
adminEmail: string;
|
|
329
|
+
groupIds: string[];
|
|
330
|
+
}, {
|
|
331
|
+
providerContactIds: string[];
|
|
332
|
+
activityId: string;
|
|
333
|
+
hubOId: string;
|
|
334
|
+
adminUid: string;
|
|
335
|
+
adminEmail: string;
|
|
336
|
+
groupIds: string[];
|
|
337
|
+
}>;
|
|
338
|
+
};
|
|
339
|
+
"e2e-runEventFeedback": {
|
|
340
|
+
request: z.ZodObject<{
|
|
341
|
+
eventId: z.ZodString;
|
|
342
|
+
}, "strip", z.ZodTypeAny, {
|
|
343
|
+
eventId: string;
|
|
344
|
+
}, {
|
|
345
|
+
eventId: string;
|
|
346
|
+
}>;
|
|
347
|
+
response: z.ZodObject<{
|
|
348
|
+
eventId: z.ZodString;
|
|
349
|
+
oId: z.ZodString;
|
|
350
|
+
sent: z.ZodLiteral<true>;
|
|
351
|
+
}, "strip", z.ZodTypeAny, {
|
|
352
|
+
oId: string;
|
|
353
|
+
sent: true;
|
|
354
|
+
eventId: string;
|
|
355
|
+
}, {
|
|
356
|
+
oId: string;
|
|
357
|
+
sent: true;
|
|
358
|
+
eventId: string;
|
|
359
|
+
}>;
|
|
360
|
+
};
|
|
234
361
|
"hubImpactReport-createHubImpactReport": {
|
|
235
362
|
request: z.ZodObject<{
|
|
236
363
|
oId: z.ZodOptional<z.ZodString>;
|
|
@@ -1416,6 +1543,28 @@ export declare const allCallables: {
|
|
|
1416
1543
|
geohash?: string | undefined;
|
|
1417
1544
|
}>, "many">;
|
|
1418
1545
|
};
|
|
1546
|
+
"events-employerDeclineEvent": {
|
|
1547
|
+
request: z.ZodObject<{
|
|
1548
|
+
oId: z.ZodString;
|
|
1549
|
+
eventId: z.ZodString;
|
|
1550
|
+
attendeeId: z.ZodString;
|
|
1551
|
+
}, "strip", z.ZodTypeAny, {
|
|
1552
|
+
oId: string;
|
|
1553
|
+
eventId: string;
|
|
1554
|
+
attendeeId: string;
|
|
1555
|
+
}, {
|
|
1556
|
+
oId: string;
|
|
1557
|
+
eventId: string;
|
|
1558
|
+
attendeeId: string;
|
|
1559
|
+
}>;
|
|
1560
|
+
response: z.ZodObject<{
|
|
1561
|
+
code: z.ZodEnum<["declined", "notFound"]>;
|
|
1562
|
+
}, "strip", z.ZodTypeAny, {
|
|
1563
|
+
code: "declined" | "notFound";
|
|
1564
|
+
}, {
|
|
1565
|
+
code: "declined" | "notFound";
|
|
1566
|
+
}>;
|
|
1567
|
+
};
|
|
1419
1568
|
"placement-propose": {
|
|
1420
1569
|
request: z.ZodObject<{
|
|
1421
1570
|
data: z.ZodAny;
|
|
@@ -1571,6 +1720,28 @@ export declare const callableGroups: {
|
|
|
1571
1720
|
geohash?: string | undefined;
|
|
1572
1721
|
}>, "many">;
|
|
1573
1722
|
};
|
|
1723
|
+
"events-employerDeclineEvent": {
|
|
1724
|
+
request: z.ZodObject<{
|
|
1725
|
+
oId: z.ZodString;
|
|
1726
|
+
eventId: z.ZodString;
|
|
1727
|
+
attendeeId: z.ZodString;
|
|
1728
|
+
}, "strip", z.ZodTypeAny, {
|
|
1729
|
+
oId: string;
|
|
1730
|
+
eventId: string;
|
|
1731
|
+
attendeeId: string;
|
|
1732
|
+
}, {
|
|
1733
|
+
oId: string;
|
|
1734
|
+
eventId: string;
|
|
1735
|
+
attendeeId: string;
|
|
1736
|
+
}>;
|
|
1737
|
+
response: z.ZodObject<{
|
|
1738
|
+
code: z.ZodEnum<["declined", "notFound"]>;
|
|
1739
|
+
}, "strip", z.ZodTypeAny, {
|
|
1740
|
+
code: "declined" | "notFound";
|
|
1741
|
+
}, {
|
|
1742
|
+
code: "declined" | "notFound";
|
|
1743
|
+
}>;
|
|
1744
|
+
};
|
|
1574
1745
|
};
|
|
1575
1746
|
surveyCallables: {
|
|
1576
1747
|
"surveys-createSurveyCycle": {
|
|
@@ -2755,6 +2926,133 @@ export declare const callableGroups: {
|
|
|
2755
2926
|
authToken?: string | undefined;
|
|
2756
2927
|
}>;
|
|
2757
2928
|
};
|
|
2929
|
+
"e2e-deleteE2EAccount": {
|
|
2930
|
+
request: z.ZodObject<{
|
|
2931
|
+
oId: z.ZodString;
|
|
2932
|
+
}, "strip", z.ZodTypeAny, {
|
|
2933
|
+
oId: string;
|
|
2934
|
+
}, {
|
|
2935
|
+
oId: string;
|
|
2936
|
+
}>;
|
|
2937
|
+
response: z.ZodObject<{
|
|
2938
|
+
deleted: z.ZodLiteral<true>;
|
|
2939
|
+
oId: z.ZodString;
|
|
2940
|
+
email: z.ZodString;
|
|
2941
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
2942
|
+
}, "strip", z.ZodTypeAny, {
|
|
2943
|
+
deleted: true;
|
|
2944
|
+
email: string;
|
|
2945
|
+
oId: string;
|
|
2946
|
+
steps: Record<string, string | boolean>;
|
|
2947
|
+
}, {
|
|
2948
|
+
deleted: true;
|
|
2949
|
+
email: string;
|
|
2950
|
+
oId: string;
|
|
2951
|
+
steps: Record<string, string | boolean>;
|
|
2952
|
+
}>;
|
|
2953
|
+
};
|
|
2954
|
+
"e2e-deleteE2ERun": {
|
|
2955
|
+
request: z.ZodObject<{
|
|
2956
|
+
runId: z.ZodString;
|
|
2957
|
+
}, "strip", z.ZodTypeAny, {
|
|
2958
|
+
runId: string;
|
|
2959
|
+
}, {
|
|
2960
|
+
runId: string;
|
|
2961
|
+
}>;
|
|
2962
|
+
response: z.ZodObject<{
|
|
2963
|
+
deleted: z.ZodLiteral<true>;
|
|
2964
|
+
runId: z.ZodString;
|
|
2965
|
+
institutes: z.ZodArray<z.ZodObject<{
|
|
2966
|
+
oId: z.ZodString;
|
|
2967
|
+
email: z.ZodString;
|
|
2968
|
+
}, "strip", z.ZodTypeAny, {
|
|
2969
|
+
email: string;
|
|
2970
|
+
oId: string;
|
|
2971
|
+
}, {
|
|
2972
|
+
email: string;
|
|
2973
|
+
oId: string;
|
|
2974
|
+
}>, "many">;
|
|
2975
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
2976
|
+
steps: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
2977
|
+
}, "strip", z.ZodTypeAny, {
|
|
2978
|
+
deleted: true;
|
|
2979
|
+
institutes: {
|
|
2980
|
+
email: string;
|
|
2981
|
+
oId: string;
|
|
2982
|
+
}[];
|
|
2983
|
+
runId: string;
|
|
2984
|
+
steps: Record<string, string | boolean>;
|
|
2985
|
+
providerContactIds: string[];
|
|
2986
|
+
}, {
|
|
2987
|
+
deleted: true;
|
|
2988
|
+
institutes: {
|
|
2989
|
+
email: string;
|
|
2990
|
+
oId: string;
|
|
2991
|
+
}[];
|
|
2992
|
+
runId: string;
|
|
2993
|
+
steps: Record<string, string | boolean>;
|
|
2994
|
+
providerContactIds: string[];
|
|
2995
|
+
}>;
|
|
2996
|
+
};
|
|
2997
|
+
"e2e-seedCareersHub": {
|
|
2998
|
+
request: z.ZodObject<{
|
|
2999
|
+
runId: z.ZodString;
|
|
3000
|
+
password: z.ZodOptional<z.ZodString>;
|
|
3001
|
+
activityId: z.ZodOptional<z.ZodString>;
|
|
3002
|
+
}, "strip", z.ZodTypeAny, {
|
|
3003
|
+
runId: string;
|
|
3004
|
+
password?: string | undefined;
|
|
3005
|
+
activityId?: string | undefined;
|
|
3006
|
+
}, {
|
|
3007
|
+
runId: string;
|
|
3008
|
+
password?: string | undefined;
|
|
3009
|
+
activityId?: string | undefined;
|
|
3010
|
+
}>;
|
|
3011
|
+
response: z.ZodObject<{
|
|
3012
|
+
hubOId: z.ZodString;
|
|
3013
|
+
adminUid: z.ZodString;
|
|
3014
|
+
adminEmail: z.ZodString;
|
|
3015
|
+
groupIds: z.ZodArray<z.ZodString, "many">;
|
|
3016
|
+
providerContactIds: z.ZodArray<z.ZodString, "many">;
|
|
3017
|
+
activityId: z.ZodString;
|
|
3018
|
+
}, "strip", z.ZodTypeAny, {
|
|
3019
|
+
providerContactIds: string[];
|
|
3020
|
+
activityId: string;
|
|
3021
|
+
hubOId: string;
|
|
3022
|
+
adminUid: string;
|
|
3023
|
+
adminEmail: string;
|
|
3024
|
+
groupIds: string[];
|
|
3025
|
+
}, {
|
|
3026
|
+
providerContactIds: string[];
|
|
3027
|
+
activityId: string;
|
|
3028
|
+
hubOId: string;
|
|
3029
|
+
adminUid: string;
|
|
3030
|
+
adminEmail: string;
|
|
3031
|
+
groupIds: string[];
|
|
3032
|
+
}>;
|
|
3033
|
+
};
|
|
3034
|
+
"e2e-runEventFeedback": {
|
|
3035
|
+
request: z.ZodObject<{
|
|
3036
|
+
eventId: z.ZodString;
|
|
3037
|
+
}, "strip", z.ZodTypeAny, {
|
|
3038
|
+
eventId: string;
|
|
3039
|
+
}, {
|
|
3040
|
+
eventId: string;
|
|
3041
|
+
}>;
|
|
3042
|
+
response: z.ZodObject<{
|
|
3043
|
+
eventId: z.ZodString;
|
|
3044
|
+
oId: z.ZodString;
|
|
3045
|
+
sent: z.ZodLiteral<true>;
|
|
3046
|
+
}, "strip", z.ZodTypeAny, {
|
|
3047
|
+
oId: string;
|
|
3048
|
+
sent: true;
|
|
3049
|
+
eventId: string;
|
|
3050
|
+
}, {
|
|
3051
|
+
oId: string;
|
|
3052
|
+
sent: true;
|
|
3053
|
+
eventId: string;
|
|
3054
|
+
}>;
|
|
3055
|
+
};
|
|
2758
3056
|
};
|
|
2759
3057
|
wondeCallables: {
|
|
2760
3058
|
"wonde-requestConnection": {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { SchoolGroupInput } from "./groups";
|
|
2
|
+
import { ExternalEventRequest } from "./typeDefinitions";
|
|
3
|
+
/**
|
|
4
|
+
* Engagement metrics for employer offers (`externalEventRequests`) a careers hub
|
|
5
|
+
* passes on to its schools. The web app and the backend impact report both count
|
|
6
|
+
* from here, so a figure on screen and the same figure in the PDF always agree.
|
|
7
|
+
*
|
|
8
|
+
* Definitions:
|
|
9
|
+
* - An offer was fanned out when the dispatch stamped it: `resolvedSchools` holds
|
|
10
|
+
* at least one school and `numberSharedWith` is set. The migration backfilled
|
|
11
|
+
* `resolvedSchools` from `schools` on every document, including ones that were
|
|
12
|
+
* never sent, so `resolvedSchools` alone is not proof of a send; only the
|
|
13
|
+
* dispatch writes `numberSharedWith`.
|
|
14
|
+
* - The schools an offer reached are its `resolvedSchools`.
|
|
15
|
+
* - A view or download only counts for a school the offer reached, when the offer
|
|
16
|
+
* records who it reached. Without `resolvedSchools` (a school's own offer shared
|
|
17
|
+
* with its students, keyed by student id) every truthy key counts.
|
|
18
|
+
*/
|
|
19
|
+
type EngagementInput = Pick<ExternalEventRequest, "resolvedSchools" | "viewedBy" | "downloadedBy">;
|
|
20
|
+
/**
|
|
21
|
+
* Whether an offer was actually sent out to schools by the hub dispatch.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} request the offer.
|
|
24
|
+
* @returns {boolean} true when the offer reached at least one school.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isRequestFannedOut: (request: Pick<ExternalEventRequest, "resolvedSchools" | "numberSharedWith">) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The ids that viewed or downloaded an offer, limited to schools it reached when
|
|
29
|
+
* that is recorded.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} request the offer.
|
|
32
|
+
* @param {"viewedBy"|"downloadedBy"} field which engagement map to read.
|
|
33
|
+
* @returns {string[]} the counted ids, each once.
|
|
34
|
+
*/
|
|
35
|
+
export declare const countedEngagement: (request: EngagementInput | undefined, field: "viewedBy" | "downloadedBy") => string[];
|
|
36
|
+
export type EmployerRequestSummary = {
|
|
37
|
+
/** Offers that were fanned out to schools. */
|
|
38
|
+
shared: number;
|
|
39
|
+
/** Distinct schools reached across every shared offer. */
|
|
40
|
+
schoolsReached: string[];
|
|
41
|
+
/** Distinct schools that viewed at least one shared offer they were sent. */
|
|
42
|
+
schoolsViewed: string[];
|
|
43
|
+
/** Distinct schools that downloaded at least one shared offer they were sent. */
|
|
44
|
+
schoolsDownloaded: string[];
|
|
45
|
+
/** Shared offers viewed by at least one school they reached. */
|
|
46
|
+
viewedByAnySchool: number;
|
|
47
|
+
/** Shared offers downloaded by at least one school they reached. */
|
|
48
|
+
downloadedByAnySchool: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Summarise a set of employer offers for hub reporting.
|
|
52
|
+
*
|
|
53
|
+
* @param {object[]} requests the offers, already narrowed to the hub and period.
|
|
54
|
+
* @returns {EmployerRequestSummary} the counts, per the definitions above.
|
|
55
|
+
*/
|
|
56
|
+
export declare const summariseEmployerRequests: (requests: Pick<ExternalEventRequest, "resolvedSchools" | "numberSharedWith" | "viewedBy" | "downloadedBy">[]) => EmployerRequestSummary;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a school may see an employer offer a hub shared. `resolvedSchools` is
|
|
59
|
+
* the record when present. Older offers without it fall back to the selection
|
|
60
|
+
* rules the school's offer list used to query on.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} request the offer.
|
|
63
|
+
* @param {string} schoolId the school asking.
|
|
64
|
+
* @param {object|undefined} school the school's institute document, for its groups.
|
|
65
|
+
* @returns {boolean} true when the offer was sent to that school.
|
|
66
|
+
*/
|
|
67
|
+
export declare const requestReachedSchool: (request: Pick<ExternalEventRequest, "oId" | "status" | "resolvedSchools" | "schoolSelectionType" | "schoolGroups" | "schools">, schoolId: string, school: SchoolGroupInput | undefined) => boolean;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestReachedSchool = exports.summariseEmployerRequests = exports.countedEngagement = exports.isRequestFannedOut = void 0;
|
|
4
|
+
const groups_1 = require("./groups");
|
|
5
|
+
/**
|
|
6
|
+
* Whether an offer was actually sent out to schools by the hub dispatch.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} request the offer.
|
|
9
|
+
* @returns {boolean} true when the offer reached at least one school.
|
|
10
|
+
*/
|
|
11
|
+
const isRequestFannedOut = (request) => Boolean(request.resolvedSchools?.length) && typeof request.numberSharedWith === "number";
|
|
12
|
+
exports.isRequestFannedOut = isRequestFannedOut;
|
|
13
|
+
/**
|
|
14
|
+
* The ids that viewed or downloaded an offer, limited to schools it reached when
|
|
15
|
+
* that is recorded.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} request the offer.
|
|
18
|
+
* @param {"viewedBy"|"downloadedBy"} field which engagement map to read.
|
|
19
|
+
* @returns {string[]} the counted ids, each once.
|
|
20
|
+
*/
|
|
21
|
+
const countedEngagement = (request, field) => {
|
|
22
|
+
const ids = Object.entries(request?.[field] || {}).filter(([, v]) => v).map(([id]) => id);
|
|
23
|
+
if (!request?.resolvedSchools)
|
|
24
|
+
return ids;
|
|
25
|
+
const reached = new Set(request.resolvedSchools);
|
|
26
|
+
return ids.filter((id) => reached.has(id));
|
|
27
|
+
};
|
|
28
|
+
exports.countedEngagement = countedEngagement;
|
|
29
|
+
/**
|
|
30
|
+
* Summarise a set of employer offers for hub reporting.
|
|
31
|
+
*
|
|
32
|
+
* @param {object[]} requests the offers, already narrowed to the hub and period.
|
|
33
|
+
* @returns {EmployerRequestSummary} the counts, per the definitions above.
|
|
34
|
+
*/
|
|
35
|
+
const summariseEmployerRequests = (requests) => {
|
|
36
|
+
const shared = requests.filter(exports.isRequestFannedOut);
|
|
37
|
+
const reached = new Set();
|
|
38
|
+
const viewed = new Set();
|
|
39
|
+
const downloaded = new Set();
|
|
40
|
+
let viewedByAnySchool = 0;
|
|
41
|
+
let downloadedByAnySchool = 0;
|
|
42
|
+
shared.forEach((r) => {
|
|
43
|
+
(r.resolvedSchools || []).forEach((id) => reached.add(id));
|
|
44
|
+
const v = (0, exports.countedEngagement)(r, "viewedBy");
|
|
45
|
+
const d = (0, exports.countedEngagement)(r, "downloadedBy");
|
|
46
|
+
v.forEach((id) => viewed.add(id));
|
|
47
|
+
d.forEach((id) => downloaded.add(id));
|
|
48
|
+
if (v.length)
|
|
49
|
+
viewedByAnySchool++;
|
|
50
|
+
if (d.length)
|
|
51
|
+
downloadedByAnySchool++;
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
shared: shared.length,
|
|
55
|
+
schoolsReached: [...reached],
|
|
56
|
+
schoolsViewed: [...viewed],
|
|
57
|
+
schoolsDownloaded: [...downloaded],
|
|
58
|
+
viewedByAnySchool,
|
|
59
|
+
downloadedByAnySchool,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
exports.summariseEmployerRequests = summariseEmployerRequests;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a school may see an employer offer a hub shared. `resolvedSchools` is
|
|
65
|
+
* the record when present. Older offers without it fall back to the selection
|
|
66
|
+
* rules the school's offer list used to query on.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} request the offer.
|
|
69
|
+
* @param {string} schoolId the school asking.
|
|
70
|
+
* @param {object|undefined} school the school's institute document, for its groups.
|
|
71
|
+
* @returns {boolean} true when the offer was sent to that school.
|
|
72
|
+
*/
|
|
73
|
+
const requestReachedSchool = (request, schoolId, school) => {
|
|
74
|
+
if (request.resolvedSchools)
|
|
75
|
+
return request.resolvedSchools.includes(schoolId);
|
|
76
|
+
if (!["sentToSchools", "linkedWithEvent"].includes(request.status))
|
|
77
|
+
return false;
|
|
78
|
+
const link = school?.careersHubLinks?.[request.oId];
|
|
79
|
+
if (link?.status !== "approved")
|
|
80
|
+
return false;
|
|
81
|
+
if (request.schoolSelectionType === "allSchools")
|
|
82
|
+
return true;
|
|
83
|
+
if (request.schoolSelectionType === "selectGroups") {
|
|
84
|
+
const groups = (0, groups_1.getSchoolGroupIds)(school, request.oId);
|
|
85
|
+
return (request.schoolGroups || []).some((g) => groups.includes(g));
|
|
86
|
+
}
|
|
87
|
+
if (request.schoolSelectionType === "specificSchools")
|
|
88
|
+
return (request.schools || []).includes(schoolId);
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
exports.requestReachedSchool = requestReachedSchool;
|
|
92
|
+
//# sourceMappingURL=eventRequestMetrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventRequestMetrics.js","sourceRoot":"","sources":["../src/eventRequestMetrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const eventRequestMetrics_1 = require("./eventRequestMetrics");
|
|
4
|
+
describe("isRequestFannedOut", () => {
|
|
5
|
+
it("needs both resolvedSchools and a dispatch stamp", () => {
|
|
6
|
+
expect((0, eventRequestMetrics_1.isRequestFannedOut)({ resolvedSchools: ["a"], numberSharedWith: 1 })).toBe(true);
|
|
7
|
+
expect((0, eventRequestMetrics_1.isRequestFannedOut)({ resolvedSchools: ["a"] })).toBe(false);
|
|
8
|
+
expect((0, eventRequestMetrics_1.isRequestFannedOut)({ resolvedSchools: [], numberSharedWith: 0 })).toBe(false);
|
|
9
|
+
expect((0, eventRequestMetrics_1.isRequestFannedOut)({ numberSharedWith: 3 })).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe("countedEngagement", () => {
|
|
13
|
+
it("drops schools the offer never reached", () => {
|
|
14
|
+
expect((0, eventRequestMetrics_1.countedEngagement)({ resolvedSchools: ["a", "b"], viewedBy: { a: true, c: true, b: false } }, "viewedBy")).toEqual(["a"]);
|
|
15
|
+
});
|
|
16
|
+
it("counts every truthy key without resolvedSchools", () => {
|
|
17
|
+
expect((0, eventRequestMetrics_1.countedEngagement)({ downloadedBy: { s1: true, s2: true, s3: false } }, "downloadedBy")).toEqual(["s1", "s2"]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("summariseEmployerRequests", () => {
|
|
21
|
+
it("only counts fanned out offers and unions their schools", () => {
|
|
22
|
+
const summary = (0, eventRequestMetrics_1.summariseEmployerRequests)([
|
|
23
|
+
{ resolvedSchools: ["a", "b"], numberSharedWith: 2, viewedBy: { a: true, z: true }, downloadedBy: { z: true } },
|
|
24
|
+
{ resolvedSchools: ["b", "c"], numberSharedWith: 1, viewedBy: { c: true }, downloadedBy: { b: true } },
|
|
25
|
+
// Migrated from `schools` but never sent.
|
|
26
|
+
{ resolvedSchools: ["d"], viewedBy: { d: true } },
|
|
27
|
+
{ viewedBy: { e: true } },
|
|
28
|
+
]);
|
|
29
|
+
expect(summary.shared).toBe(2);
|
|
30
|
+
expect(summary.schoolsReached.sort()).toEqual(["a", "b", "c"]);
|
|
31
|
+
expect(summary.schoolsViewed.sort()).toEqual(["a", "c"]);
|
|
32
|
+
expect(summary.schoolsDownloaded).toEqual(["b"]);
|
|
33
|
+
expect(summary.viewedByAnySchool).toBe(2);
|
|
34
|
+
expect(summary.downloadedByAnySchool).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("requestReachedSchool", () => {
|
|
38
|
+
const school = { careersHubLinks: { hub: { status: "approved", groups: ["g1"] } } };
|
|
39
|
+
it("uses resolvedSchools when present", () => {
|
|
40
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", resolvedSchools: ["s1"], schoolSelectionType: "allSchools" }, "s2", school)).toBe(false);
|
|
41
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", resolvedSchools: ["s2"] }, "s2", school)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it("falls back to the selection rules for legacy offers", () => {
|
|
44
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", schoolSelectionType: "allSchools" }, "s2", school)).toBe(true);
|
|
45
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "pending", schoolSelectionType: "allSchools" }, "s2", school)).toBe(false);
|
|
46
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", schoolSelectionType: "selectGroups", schoolGroups: ["g1"] }, "s2", school)).toBe(true);
|
|
47
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", schoolSelectionType: "selectGroups", schoolGroups: ["g2"] }, "s2", school)).toBe(false);
|
|
48
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "hub", status: "sentToSchools", schoolSelectionType: "specificSchools", schools: ["s2"] }, "s2", school)).toBe(true);
|
|
49
|
+
expect((0, eventRequestMetrics_1.requestReachedSchool)({ oId: "other", status: "sentToSchools", schoolSelectionType: "allSchools" }, "s2", school)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=eventRequestMetrics.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventRequestMetrics.test.js","sourceRoot":"","sources":["../src/eventRequestMetrics.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,7 +38,15 @@ exports.JobsSlice = (0, toolkit_1.createSlice)({
|
|
|
38
38
|
state.values[action.payload.jobId].updated = (new Date()).toISOString();
|
|
39
39
|
},
|
|
40
40
|
updateJob: (state, action) => {
|
|
41
|
-
|
|
41
|
+
// Callers (e.g. the error path in useExecuteCallableJob) send a
|
|
42
|
+
// deliberately partial payload and write it to Firestore with a
|
|
43
|
+
// merge-style update() - this has to merge the same way, or a
|
|
44
|
+
// partial update wipes fields like viewedBy from the local copy
|
|
45
|
+
// even though the real document still has them.
|
|
46
|
+
state.values[action.payload.jobId] = {
|
|
47
|
+
...state.values[action.payload.jobId],
|
|
48
|
+
...action.payload.data,
|
|
49
|
+
};
|
|
42
50
|
},
|
|
43
51
|
},
|
|
44
52
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jobsSlice.js","sourceRoot":"","sources":["../../../src/features/jobs/jobsSlice.ts"],"names":[],"mappings":";;;;AAAA,8CAA6C;AA+B7C,MAAM,YAAY,GAAoB;IAClC,MAAM,EAAE,EAAE;CACb,CAAC;AAGW,QAAA,SAAS,GAAG,IAAA,qBAAW,EAAC;IACjC,IAAI,EAAE,MAAM;IACZ,YAAY;IAEZ,QAAQ,EAAE;QACN,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACvB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAClC,CAAC;QAED,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACtB,MAAM,EAAC,GAAG,EAAE,KAAK,EAAC,GAAG,MAAM,CAAC,OAAO,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAEtB,MAAM,OAAO,GAAG,EAAC,GAAG,KAAK,CAAC,MAAM,EAAC,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;YAErB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtH,CAAC;QAED,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC3B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9C,gEAAgE;YAChE,iEAAiE;YACjE,iEAAiE;YACjE,2DAA2D;YAC3D,2DAA2D;YAC3D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO;YAChD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5E,CAAC;QAED,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO;YAChD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAClE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5E,CAAC;QACD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACzB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"jobsSlice.js","sourceRoot":"","sources":["../../../src/features/jobs/jobsSlice.ts"],"names":[],"mappings":";;;;AAAA,8CAA6C;AA+B7C,MAAM,YAAY,GAAoB;IAClC,MAAM,EAAE,EAAE;CACb,CAAC;AAGW,QAAA,SAAS,GAAG,IAAA,qBAAW,EAAC;IACjC,IAAI,EAAE,MAAM;IACZ,YAAY;IAEZ,QAAQ,EAAE;QACN,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACvB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAClC,CAAC;QAED,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACtB,MAAM,EAAC,GAAG,EAAE,KAAK,EAAC,GAAG,MAAM,CAAC,OAAO,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAEtB,MAAM,OAAO,GAAG,EAAC,GAAG,KAAK,CAAC,MAAM,EAAC,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;YAErB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtH,CAAC;QAED,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC3B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9C,gEAAgE;YAChE,iEAAiE;YACjE,iEAAiE;YACjE,2DAA2D;YAC3D,2DAA2D;YAC3D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO;YAChD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5E,CAAC;QAED,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO;YAChD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAClE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5E,CAAC;QACD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACzB,gEAAgE;YAChE,gEAAgE;YAChE,8DAA8D;YAC9D,gEAAgE;YAChE,gDAAgD;YAChD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG;gBACjC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBACrC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI;aACzB,CAAC;QACN,CAAC;KAEJ;CAEJ,CAAC,CAAC;AAEU,KAA0D,iBAAS,CAAC,OAAO,EAA1E,eAAO,eAAE,cAAM,cAAE,mBAAW,mBAAE,oBAAY,oBAAE,iBAAS,gBAAsB;AAEzF,kBAAe,iBAAS,CAAC,OAAO,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export * from "./productResolver";
|
|
|
32
32
|
export * from "./pricing";
|
|
33
33
|
export * from "./invoicing";
|
|
34
34
|
export * from "./groups";
|
|
35
|
+
export * from "./eventRequestMetrics";
|
|
35
36
|
export * from "./emailSections";
|
|
36
37
|
export * from "./exportTransformers";
|
|
37
38
|
export * from "./compassTemplate";
|
package/lib/index.js
CHANGED
|
@@ -52,6 +52,7 @@ __exportStar(require("./productResolver"), exports);
|
|
|
52
52
|
__exportStar(require("./pricing"), exports);
|
|
53
53
|
__exportStar(require("./invoicing"), exports);
|
|
54
54
|
__exportStar(require("./groups"), exports);
|
|
55
|
+
__exportStar(require("./eventRequestMetrics"), exports);
|
|
55
56
|
__exportStar(require("./emailSections"), exports);
|
|
56
57
|
__exportStar(require("./exportTransformers"), exports);
|
|
57
58
|
__exportStar(require("./compassTemplate"), exports);
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,4CAA0B;AAC1B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,wDAAsC;AACtC,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,4CAA0B;AAC1B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -3267,7 +3267,7 @@ export type ExternalEventAttendee = {
|
|
|
3267
3267
|
startDate: string;
|
|
3268
3268
|
endDate?: string;
|
|
3269
3269
|
}[];
|
|
3270
|
-
status: "pendingInvite" | "invited" | "requestedDates" | "providerAvailable" | "finalConfirmationSent" | "providerConfirmed" | "feedbackSent";
|
|
3270
|
+
status: "pendingInvite" | "invited" | "requestedDates" | "providerAvailable" | "finalConfirmationSent" | "providerConfirmed" | "feedbackSent" | "providerDeclined";
|
|
3271
3271
|
emails: {
|
|
3272
3272
|
[k in "invited" | "finalConfirmationSent" | "feedbackSent"]: {
|
|
3273
3273
|
sent: number;
|
package/package.json
CHANGED
package/src/callables/e2e.ts
CHANGED
|
@@ -10,6 +10,12 @@ import {CallableGroup} from "./types";
|
|
|
10
10
|
* only ever sign in a reserved `e2e.*@careerthread.co.uk` user, so the UX audit
|
|
11
11
|
* can walk a registered school without anyone typing a password.
|
|
12
12
|
*/
|
|
13
|
+
/** A run id as the harness derives it from E2E_RUN_ID. */
|
|
14
|
+
const e2eRunId = z.string().min(1).max(100).regex(/^[a-z0-9-]+$/);
|
|
15
|
+
|
|
16
|
+
/** Per-step teardown outcome: true cleared, false nothing to do, string the error. */
|
|
17
|
+
const teardownSteps = z.record(z.string(), z.union([z.boolean(), z.string()]));
|
|
18
|
+
|
|
13
19
|
export const e2eCallables = {
|
|
14
20
|
"e2e-redeemSignInLink": {
|
|
15
21
|
request: z.object({
|
|
@@ -22,4 +28,69 @@ export const e2eCallables = {
|
|
|
22
28
|
authToken: z.string().optional(),
|
|
23
29
|
}),
|
|
24
30
|
},
|
|
31
|
+
/**
|
|
32
|
+
* Admin only. Tears down one institute whose billing email is a reserved
|
|
33
|
+
* E2E address. Each step reports true (cleared), false (nothing to do) or
|
|
34
|
+
* the error message.
|
|
35
|
+
*/
|
|
36
|
+
"e2e-deleteE2EAccount": {
|
|
37
|
+
request: z.object({oId: z.string().min(1)}),
|
|
38
|
+
response: z.object({
|
|
39
|
+
deleted: z.literal(true),
|
|
40
|
+
oId: z.string(),
|
|
41
|
+
email: z.string(),
|
|
42
|
+
steps: teardownSteps,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* Admin only. Tears down a whole run: every institute billed to
|
|
47
|
+
* `e2e.<runId>@` or `e2e.<runId>-*@`, their careers invites and hub links,
|
|
48
|
+
* the run's employers, provider accounts, event requests (and PDFs),
|
|
49
|
+
* attendees, Auth users and Brevo contacts. Institute steps are reported
|
|
50
|
+
* as `<oId>/<step>`.
|
|
51
|
+
*/
|
|
52
|
+
"e2e-deleteE2ERun": {
|
|
53
|
+
request: z.object({runId: e2eRunId}),
|
|
54
|
+
response: z.object({
|
|
55
|
+
deleted: z.literal(true),
|
|
56
|
+
runId: z.string(),
|
|
57
|
+
institutes: z.array(z.object({oId: z.string(), email: z.string()})),
|
|
58
|
+
providerContactIds: z.array(z.string()),
|
|
59
|
+
steps: teardownSteps,
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* Admin only. Seeds an active careers hub for a run
|
|
64
|
+
* (`e2e.<runId>-hub@careerthread.co.uk`) with North and South groups and
|
|
65
|
+
* six employers, five approved and one awaiting review.
|
|
66
|
+
*/
|
|
67
|
+
"e2e-seedCareersHub": {
|
|
68
|
+
request: z.object({
|
|
69
|
+
runId: e2eRunId,
|
|
70
|
+
/** Defaults to the harness password, `E2e!<runId>Aa1`. */
|
|
71
|
+
password: z.string().min(8).max(128).optional(),
|
|
72
|
+
/** A public externalActivities id. Defaults to the first public employer activity. */
|
|
73
|
+
activityId: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).optional(),
|
|
74
|
+
}),
|
|
75
|
+
response: z.object({
|
|
76
|
+
hubOId: z.string(),
|
|
77
|
+
adminUid: z.string(),
|
|
78
|
+
adminEmail: z.string(),
|
|
79
|
+
groupIds: z.array(z.string()),
|
|
80
|
+
providerContactIds: z.array(z.string()),
|
|
81
|
+
activityId: z.string(),
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Admin only. Sends one event's post-event feedback requests now, instead
|
|
86
|
+
* of waiting for the daily cron. The event's institute must be an E2E account.
|
|
87
|
+
*/
|
|
88
|
+
"e2e-runEventFeedback": {
|
|
89
|
+
request: z.object({eventId: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/)}),
|
|
90
|
+
response: z.object({
|
|
91
|
+
eventId: z.string(),
|
|
92
|
+
oId: z.string(),
|
|
93
|
+
sent: z.literal(true),
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
25
96
|
} satisfies CallableGroup;
|
package/src/callables/events.ts
CHANGED
|
@@ -23,4 +23,16 @@ export const eventsCallables = {
|
|
|
23
23
|
country: z.string().optional(),
|
|
24
24
|
})),
|
|
25
25
|
},
|
|
26
|
+
// Public: the invited attendee id in the email link is the credential,
|
|
27
|
+
// as for events-employerJoinEvent. Only an "invited" attendee can decline.
|
|
28
|
+
"events-employerDeclineEvent": {
|
|
29
|
+
request: z.object({
|
|
30
|
+
oId: z.string().min(1),
|
|
31
|
+
eventId: z.string().min(1),
|
|
32
|
+
attendeeId: z.string().min(1),
|
|
33
|
+
}),
|
|
34
|
+
response: z.object({
|
|
35
|
+
code: z.enum(["declined", "notFound"]),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
26
38
|
} satisfies CallableGroup;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {countedEngagement, isRequestFannedOut, requestReachedSchool, summariseEmployerRequests} from "./eventRequestMetrics";
|
|
2
|
+
|
|
3
|
+
describe("isRequestFannedOut", () => {
|
|
4
|
+
it("needs both resolvedSchools and a dispatch stamp", () => {
|
|
5
|
+
expect(isRequestFannedOut({resolvedSchools: ["a"], numberSharedWith: 1})).toBe(true);
|
|
6
|
+
expect(isRequestFannedOut({resolvedSchools: ["a"]})).toBe(false);
|
|
7
|
+
expect(isRequestFannedOut({resolvedSchools: [], numberSharedWith: 0})).toBe(false);
|
|
8
|
+
expect(isRequestFannedOut({numberSharedWith: 3})).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("countedEngagement", () => {
|
|
13
|
+
it("drops schools the offer never reached", () => {
|
|
14
|
+
expect(countedEngagement({resolvedSchools: ["a", "b"], viewedBy: {a: true, c: true, b: false}}, "viewedBy")).toEqual(["a"]);
|
|
15
|
+
});
|
|
16
|
+
it("counts every truthy key without resolvedSchools", () => {
|
|
17
|
+
expect(countedEngagement({downloadedBy: {s1: true, s2: true, s3: false}}, "downloadedBy")).toEqual(["s1", "s2"]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("summariseEmployerRequests", () => {
|
|
22
|
+
it("only counts fanned out offers and unions their schools", () => {
|
|
23
|
+
const summary = summariseEmployerRequests([
|
|
24
|
+
{resolvedSchools: ["a", "b"], numberSharedWith: 2, viewedBy: {a: true, z: true}, downloadedBy: {z: true}},
|
|
25
|
+
{resolvedSchools: ["b", "c"], numberSharedWith: 1, viewedBy: {c: true}, downloadedBy: {b: true}},
|
|
26
|
+
// Migrated from `schools` but never sent.
|
|
27
|
+
{resolvedSchools: ["d"], viewedBy: {d: true}},
|
|
28
|
+
{viewedBy: {e: true}},
|
|
29
|
+
]);
|
|
30
|
+
expect(summary.shared).toBe(2);
|
|
31
|
+
expect(summary.schoolsReached.sort()).toEqual(["a", "b", "c"]);
|
|
32
|
+
expect(summary.schoolsViewed.sort()).toEqual(["a", "c"]);
|
|
33
|
+
expect(summary.schoolsDownloaded).toEqual(["b"]);
|
|
34
|
+
expect(summary.viewedByAnySchool).toBe(2);
|
|
35
|
+
expect(summary.downloadedByAnySchool).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("requestReachedSchool", () => {
|
|
40
|
+
const school = {careersHubLinks: {hub: {status: "approved", groups: ["g1"]}}} as never;
|
|
41
|
+
it("uses resolvedSchools when present", () => {
|
|
42
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", resolvedSchools: ["s1"], schoolSelectionType: "allSchools"}, "s2", school)).toBe(false);
|
|
43
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", resolvedSchools: ["s2"]}, "s2", school)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it("falls back to the selection rules for legacy offers", () => {
|
|
46
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", schoolSelectionType: "allSchools"}, "s2", school)).toBe(true);
|
|
47
|
+
expect(requestReachedSchool({oId: "hub", status: "pending", schoolSelectionType: "allSchools"}, "s2", school)).toBe(false);
|
|
48
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", schoolSelectionType: "selectGroups", schoolGroups: ["g1"]}, "s2", school)).toBe(true);
|
|
49
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", schoolSelectionType: "selectGroups", schoolGroups: ["g2"]}, "s2", school)).toBe(false);
|
|
50
|
+
expect(requestReachedSchool({oId: "hub", status: "sentToSchools", schoolSelectionType: "specificSchools", schools: ["s2"]}, "s2", school)).toBe(true);
|
|
51
|
+
expect(requestReachedSchool({oId: "other", status: "sentToSchools", schoolSelectionType: "allSchools"}, "s2", school)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {getSchoolGroupIds, SchoolGroupInput} from "./groups";
|
|
2
|
+
import {ExternalEventRequest} from "./typeDefinitions";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Engagement metrics for employer offers (`externalEventRequests`) a careers hub
|
|
6
|
+
* passes on to its schools. The web app and the backend impact report both count
|
|
7
|
+
* from here, so a figure on screen and the same figure in the PDF always agree.
|
|
8
|
+
*
|
|
9
|
+
* Definitions:
|
|
10
|
+
* - An offer was fanned out when the dispatch stamped it: `resolvedSchools` holds
|
|
11
|
+
* at least one school and `numberSharedWith` is set. The migration backfilled
|
|
12
|
+
* `resolvedSchools` from `schools` on every document, including ones that were
|
|
13
|
+
* never sent, so `resolvedSchools` alone is not proof of a send; only the
|
|
14
|
+
* dispatch writes `numberSharedWith`.
|
|
15
|
+
* - The schools an offer reached are its `resolvedSchools`.
|
|
16
|
+
* - A view or download only counts for a school the offer reached, when the offer
|
|
17
|
+
* records who it reached. Without `resolvedSchools` (a school's own offer shared
|
|
18
|
+
* with its students, keyed by student id) every truthy key counts.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type EngagementInput = Pick<ExternalEventRequest, "resolvedSchools"|"viewedBy"|"downloadedBy">;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether an offer was actually sent out to schools by the hub dispatch.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} request the offer.
|
|
27
|
+
* @returns {boolean} true when the offer reached at least one school.
|
|
28
|
+
*/
|
|
29
|
+
export const isRequestFannedOut = (request: Pick<ExternalEventRequest, "resolvedSchools"|"numberSharedWith">): boolean =>
|
|
30
|
+
Boolean(request.resolvedSchools?.length) && typeof request.numberSharedWith === "number";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The ids that viewed or downloaded an offer, limited to schools it reached when
|
|
34
|
+
* that is recorded.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} request the offer.
|
|
37
|
+
* @param {"viewedBy"|"downloadedBy"} field which engagement map to read.
|
|
38
|
+
* @returns {string[]} the counted ids, each once.
|
|
39
|
+
*/
|
|
40
|
+
export const countedEngagement = (request: EngagementInput|undefined, field: "viewedBy"|"downloadedBy"): string[] => {
|
|
41
|
+
const ids = Object.entries(request?.[field] || {}).filter(([, v]) => v).map(([id]) => id);
|
|
42
|
+
if (!request?.resolvedSchools) return ids;
|
|
43
|
+
const reached = new Set(request.resolvedSchools);
|
|
44
|
+
return ids.filter((id) => reached.has(id));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type EmployerRequestSummary = {
|
|
48
|
+
/** Offers that were fanned out to schools. */
|
|
49
|
+
shared: number,
|
|
50
|
+
/** Distinct schools reached across every shared offer. */
|
|
51
|
+
schoolsReached: string[],
|
|
52
|
+
/** Distinct schools that viewed at least one shared offer they were sent. */
|
|
53
|
+
schoolsViewed: string[],
|
|
54
|
+
/** Distinct schools that downloaded at least one shared offer they were sent. */
|
|
55
|
+
schoolsDownloaded: string[],
|
|
56
|
+
/** Shared offers viewed by at least one school they reached. */
|
|
57
|
+
viewedByAnySchool: number,
|
|
58
|
+
/** Shared offers downloaded by at least one school they reached. */
|
|
59
|
+
downloadedByAnySchool: number,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Summarise a set of employer offers for hub reporting.
|
|
64
|
+
*
|
|
65
|
+
* @param {object[]} requests the offers, already narrowed to the hub and period.
|
|
66
|
+
* @returns {EmployerRequestSummary} the counts, per the definitions above.
|
|
67
|
+
*/
|
|
68
|
+
export const summariseEmployerRequests = (
|
|
69
|
+
requests: Pick<ExternalEventRequest, "resolvedSchools"|"numberSharedWith"|"viewedBy"|"downloadedBy">[]
|
|
70
|
+
): EmployerRequestSummary => {
|
|
71
|
+
const shared = requests.filter(isRequestFannedOut);
|
|
72
|
+
const reached = new Set<string>();
|
|
73
|
+
const viewed = new Set<string>();
|
|
74
|
+
const downloaded = new Set<string>();
|
|
75
|
+
let viewedByAnySchool = 0;
|
|
76
|
+
let downloadedByAnySchool = 0;
|
|
77
|
+
shared.forEach((r) => {
|
|
78
|
+
(r.resolvedSchools || []).forEach((id) => reached.add(id));
|
|
79
|
+
const v = countedEngagement(r, "viewedBy");
|
|
80
|
+
const d = countedEngagement(r, "downloadedBy");
|
|
81
|
+
v.forEach((id) => viewed.add(id));
|
|
82
|
+
d.forEach((id) => downloaded.add(id));
|
|
83
|
+
if (v.length) viewedByAnySchool++;
|
|
84
|
+
if (d.length) downloadedByAnySchool++;
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
shared: shared.length,
|
|
88
|
+
schoolsReached: [...reached],
|
|
89
|
+
schoolsViewed: [...viewed],
|
|
90
|
+
schoolsDownloaded: [...downloaded],
|
|
91
|
+
viewedByAnySchool,
|
|
92
|
+
downloadedByAnySchool,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether a school may see an employer offer a hub shared. `resolvedSchools` is
|
|
98
|
+
* the record when present. Older offers without it fall back to the selection
|
|
99
|
+
* rules the school's offer list used to query on.
|
|
100
|
+
*
|
|
101
|
+
* @param {object} request the offer.
|
|
102
|
+
* @param {string} schoolId the school asking.
|
|
103
|
+
* @param {object|undefined} school the school's institute document, for its groups.
|
|
104
|
+
* @returns {boolean} true when the offer was sent to that school.
|
|
105
|
+
*/
|
|
106
|
+
export const requestReachedSchool = (
|
|
107
|
+
request: Pick<ExternalEventRequest, "oId"|"status"|"resolvedSchools"|"schoolSelectionType"|"schoolGroups"|"schools">,
|
|
108
|
+
schoolId: string,
|
|
109
|
+
school: SchoolGroupInput|undefined
|
|
110
|
+
): boolean => {
|
|
111
|
+
if (request.resolvedSchools) return request.resolvedSchools.includes(schoolId);
|
|
112
|
+
if (!["sentToSchools", "linkedWithEvent"].includes(request.status)) return false;
|
|
113
|
+
const link = school?.careersHubLinks?.[request.oId];
|
|
114
|
+
if (link?.status !== "approved") return false;
|
|
115
|
+
if (request.schoolSelectionType === "allSchools") return true;
|
|
116
|
+
if (request.schoolSelectionType === "selectGroups") {
|
|
117
|
+
const groups = getSchoolGroupIds(school, request.oId);
|
|
118
|
+
return (request.schoolGroups || []).some((g) => groups.includes(g));
|
|
119
|
+
}
|
|
120
|
+
if (request.schoolSelectionType === "specificSchools") return (request.schools || []).includes(schoolId);
|
|
121
|
+
return false;
|
|
122
|
+
};
|
|
@@ -70,7 +70,15 @@ export const JobsSlice = createSlice({
|
|
|
70
70
|
state.values[action.payload.jobId].updated = (new Date()).toISOString();
|
|
71
71
|
},
|
|
72
72
|
updateJob: (state, action) => {
|
|
73
|
-
|
|
73
|
+
// Callers (e.g. the error path in useExecuteCallableJob) send a
|
|
74
|
+
// deliberately partial payload and write it to Firestore with a
|
|
75
|
+
// merge-style update() - this has to merge the same way, or a
|
|
76
|
+
// partial update wipes fields like viewedBy from the local copy
|
|
77
|
+
// even though the real document still has them.
|
|
78
|
+
state.values[action.payload.jobId] = {
|
|
79
|
+
...state.values[action.payload.jobId],
|
|
80
|
+
...action.payload.data,
|
|
81
|
+
};
|
|
74
82
|
},
|
|
75
83
|
|
|
76
84
|
},
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ export * from "./productResolver";
|
|
|
34
34
|
export * from "./pricing";
|
|
35
35
|
export * from "./invoicing";
|
|
36
36
|
export * from "./groups";
|
|
37
|
+
export * from "./eventRequestMetrics";
|
|
37
38
|
export * from "./emailSections";
|
|
38
39
|
export * from "./exportTransformers";
|
|
39
40
|
export * from "./compassTemplate";
|
package/src/typeDefinitions.ts
CHANGED
|
@@ -3264,7 +3264,7 @@ export type ExternalEventAttendee = {
|
|
|
3264
3264
|
startDate: string,
|
|
3265
3265
|
endDate?: string,
|
|
3266
3266
|
}[],
|
|
3267
|
-
status: "pendingInvite"|"invited"|"requestedDates"|"providerAvailable"|"finalConfirmationSent"|"providerConfirmed"|"feedbackSent"
|
|
3267
|
+
status: "pendingInvite"|"invited"|"requestedDates"|"providerAvailable"|"finalConfirmationSent"|"providerConfirmed"|"feedbackSent"|"providerDeclined"
|
|
3268
3268
|
emails: {
|
|
3269
3269
|
[k in "invited"|"finalConfirmationSent"|"feedbackSent"]: {
|
|
3270
3270
|
sent: number, // If below 3, keep sending reminders.
|