Skip to content

Commit 33e027a

Browse files
committed
Harden admin integrations against drifting engagement and university contracts
The admin UI still had a few runtime assumptions left after the earlier contract cleanup: engagement stats were returned flat while the dashboard expected a nested `stats` object, and one university department lookup still called a route shape that no longer exists in admin-v2. This change normalizes the engagement payload and points department lookups at the live nested university route. Constraint: The dashboard component expects a nested engagement payload and the admin-v2 backend only exposes departments under /universities/:id/departments Rejected: Patch the dashboard component only | the service layer is the shared normalization point and should absorb backend shape differences Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep admin service modules responsible for normalizing backend payload shapes before UI components consume them Tested: pnpm exec jest --runInBand --watchman=false __tests__/app/services/dashboard-bff.test.ts; pnpm build Not-tested: Live dashboard and university department interactions in production
1 parent 4bbba6a commit 33e027a

3 files changed

Lines changed: 66 additions & 9 deletions

File tree

__tests__/app/services/dashboard-bff.test.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@ describe('dashboard and kpi services use BFF client', () => {
1717

1818
await dashboardService.getSummary();
1919

20-
expect(adminGet).toHaveBeenCalledWith('/admin/dashboard/summary');
20+
expect(adminGet).toHaveBeenCalledWith('/admin/v2/dashboard/summary');
2121
});
2222

2323
it('loads latest KPI report through adminGet', async () => {
2424
(adminGet as jest.Mock).mockResolvedValue({ week: 10, year: 2026 });
2525

2626
await AdminService.kpiReport.getLatest();
2727

28-
expect(adminGet).toHaveBeenCalledWith('/admin/kpi-report/latest');
28+
expect(adminGet).toHaveBeenCalledWith('/admin/v2/kpi-report/latest');
2929
});
3030

3131
it('generates KPI report through adminPost', async () => {
3232
(adminPost as jest.Mock).mockResolvedValue({ week: 10, year: 2026 });
3333

3434
await AdminService.kpiReport.generate(2026, 10);
3535

36-
expect(adminPost).toHaveBeenCalledWith('/admin/kpi-report/generate', {
36+
expect(adminPost).toHaveBeenCalledWith('/admin/v2/kpi-report/generate', {
3737
year: 2026,
3838
week: 10,
3939
});
@@ -47,11 +47,49 @@ describe('dashboard and kpi services use BFF client', () => {
4747
limit: 1,
4848
});
4949

50-
expect(adminGet).toHaveBeenCalledWith('/admin/university-verification/pending', {
50+
expect(adminGet).toHaveBeenCalledWith('/admin/v2/profile-review/university-verification/pending', {
5151
page: '1',
5252
limit: '1',
53-
name: undefined,
54-
university: undefined,
53+
name: '',
54+
university: '',
55+
});
56+
});
57+
58+
it('normalizes engagement stats into the nested dashboard shape', async () => {
59+
(adminGet as jest.Mock).mockResolvedValue({
60+
data: {
61+
likesPerUser: { mean: 3.2, median: 2 },
62+
mutualLikesPerUser: { mean: 1.4, median: 1 },
63+
chatOpensPerUser: { mean: 0.8, median: 0 },
64+
likeEngagement: { activeUsers: 10, totalUsers: 20, rate: 50 },
65+
mutualLikeEngagement: { activeUsers: 6, totalUsers: 20, rate: 30 },
66+
chatOpenEngagement: { activeUsers: 4, totalUsers: 20, rate: 20 },
67+
startDate: '2026-04-01',
68+
endDate: '2026-04-05',
69+
},
70+
});
71+
72+
await expect(
73+
AdminService.userEngagement.getStats('2026-04-01', '2026-04-05', false),
74+
).resolves.toEqual({
75+
stats: {
76+
likesPerUser: { mean: 3.2, median: 2 },
77+
mutualLikesPerUser: { mean: 1.4, median: 1 },
78+
chatOpensPerUser: { mean: 0.8, median: 0 },
79+
likeEngagement: { activeUsers: 10, totalUsers: 20, rate: 50 },
80+
mutualLikeEngagement: { activeUsers: 6, totalUsers: 20, rate: 30 },
81+
chatOpenEngagement: { activeUsers: 4, totalUsers: 20, rate: 20 },
82+
periodEngagement: undefined,
83+
},
84+
startDate: '2026-04-01',
85+
endDate: '2026-04-05',
86+
periodType: 'custom',
87+
});
88+
89+
expect(adminGet).toHaveBeenCalledWith('/admin/v2/stats/engagement', {
90+
from: '2026-04-01',
91+
to: '2026-04-05',
92+
includeDeleted: 'false',
5593
});
5694
});
5795
});

app/services/admin/system.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ export const universities = {
213213
return result.data.clusters;
214214
},
215215

216-
getDepartments: async (university: string) => {
217-
const result = await adminGet<{ data: any }>('/admin/v2/universities/departments', { university });
216+
getDepartments: async (universityId: string) => {
217+
const result = await adminGet<{ data: any }>(`/admin/v2/universities/${universityId}/departments`);
218218
return result.data;
219219
},
220220

app/services/admin/users.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,26 @@ export const userEngagement = {
922922
if (includeDeleted !== undefined) params.includeDeleted = String(includeDeleted);
923923

924924
const res = await adminGet<{ data: any }>('/admin/v2/stats/engagement', params);
925-
return res.data;
925+
const payload = res.data ?? {};
926+
return {
927+
stats: {
928+
likesPerUser: payload.likesPerUser ?? { mean: 0, median: 0 },
929+
mutualLikesPerUser: payload.mutualLikesPerUser ?? { mean: 0, median: 0 },
930+
chatOpensPerUser: payload.chatOpensPerUser ?? { mean: 0, median: 0 },
931+
likeEngagement: payload.likeEngagement ?? { activeUsers: 0, totalUsers: 0, rate: 0 },
932+
mutualLikeEngagement:
933+
payload.mutualLikeEngagement ?? { activeUsers: 0, totalUsers: 0, rate: 0 },
934+
chatOpenEngagement:
935+
payload.chatOpenEngagement ?? { activeUsers: 0, totalUsers: 0, rate: 0 },
936+
periodEngagement: payload.periodEngagement,
937+
},
938+
startDate: payload.startDate ?? startDate ?? null,
939+
endDate:
940+
payload.endDate ??
941+
endDate ??
942+
new Date().toISOString().split('T')[0],
943+
periodType: payload.periodType ?? (startDate || endDate ? 'custom' : 'all'),
944+
};
926945
} catch (error: any) {
927946
throw error;
928947
}

0 commit comments

Comments
 (0)