Skip to content

Commit bedd1dc

Browse files
feat: server-side pagination and search for contract codes (#3530)
1 parent 7ee90c5 commit bedd1dc

15 files changed

Lines changed: 812 additions & 359 deletions

File tree

frontends/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"ol-test-utilities": "0.0.0"
3232
},
3333
"dependencies": {
34-
"@mitodl/mitxonline-api-axios": "2026.6.17",
34+
"@mitodl/mitxonline-api-axios": "2026.6.30-1",
3535
"@tanstack/react-query": "^5.66.0",
3636
"axios": "^1.12.2",
3737
"tiny-invariant": "^1.3.3"

frontends/api/src/mitxonline/hooks/organizations/index.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ const useBulkAssignSeats = () => {
3939
) => b2bApi.b2bManagerOrganizationsContractsCodesBulkAssignCreate(opts),
4040
onSettled: (_data, _err, vars) => {
4141
queryClient.invalidateQueries({
42-
queryKey: managerOrganizationKeys.contractCodes({
43-
id: vars.id,
44-
parent_lookup_organization: vars.parent_lookup_organization,
45-
}),
42+
queryKey: managerOrganizationKeys.contractCodesForContract(
43+
vars.id,
44+
vars.parent_lookup_organization,
45+
),
4646
})
4747
},
4848
})
@@ -57,10 +57,10 @@ const useRemindCode = () => {
5757
) => b2bApi.b2bManagerOrganizationsContractsCodesRemindCreate(opts),
5858
onSettled: (_data, _err, vars) => {
5959
queryClient.invalidateQueries({
60-
queryKey: managerOrganizationKeys.contractCodes({
61-
id: vars.id,
62-
parent_lookup_organization: vars.parent_lookup_organization,
63-
}),
60+
queryKey: managerOrganizationKeys.contractCodesForContract(
61+
vars.id,
62+
vars.parent_lookup_organization,
63+
),
6464
})
6565
},
6666
})
@@ -75,10 +75,10 @@ const useRevokeCode = () => {
7575
) => b2bApi.b2bManagerOrganizationsContractsCodesRevokeDestroy(opts),
7676
onSettled: (_data, _err, vars) => {
7777
queryClient.invalidateQueries({
78-
queryKey: managerOrganizationKeys.contractCodes({
79-
id: vars.id,
80-
parent_lookup_organization: vars.parent_lookup_organization,
81-
}),
78+
queryKey: managerOrganizationKeys.contractCodesForContract(
79+
vars.id,
80+
vars.parent_lookup_organization,
81+
),
8282
})
8383
},
8484
})
@@ -97,10 +97,10 @@ const useReassignCode = () => {
9797
) => b2bApi.b2bManagerOrganizationsContractsCodesReassignUpdate(opts),
9898
onSettled: (_data, _err, vars) => {
9999
queryClient.invalidateQueries({
100-
queryKey: managerOrganizationKeys.contractCodes({
101-
id: vars.id,
102-
parent_lookup_organization: vars.parent_lookup_organization,
103-
}),
100+
queryKey: managerOrganizationKeys.contractCodesForContract(
101+
vars.id,
102+
vars.parent_lookup_organization,
103+
),
104104
})
105105
},
106106
})
@@ -115,4 +115,7 @@ export {
115115
useRemindCode,
116116
useRevokeCode,
117117
}
118-
export type { ContractCode } from "./queries"
118+
export type {
119+
ManagerEnrollmentCode,
120+
PaginatedManagerEnrollmentCodeList,
121+
} from "@mitodl/mitxonline-api-axios/v2"

frontends/api/src/mitxonline/hooks/organizations/queries.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ import { b2bApi } from "../../clients"
33
import {
44
OrganizationPage,
55
ManagerContractDetail,
6-
ManagerEnrollmentCode,
6+
PaginatedManagerEnrollmentCodeList,
77
B2bApiB2bOrganizationsRetrieveRequest,
88
B2bApiB2bManagerOrganizationsContractsRetrieveRequest,
99
B2bApiB2bManagerOrganizationsContractsCodesListRequest,
1010
} from "@mitodl/mitxonline-api-axios/v2"
1111

12-
type ContractCode = ManagerEnrollmentCode & {
13-
redemption_status: "unassigned" | "assigned" | "redeemed"
14-
}
15-
1612
const organizationKeys = {
1713
root: ["mitxonline", "organizations"],
1814
organizationsRetrieve: (opts?: B2bApiB2bOrganizationsRetrieveRequest) => [
@@ -50,14 +46,20 @@ const managerOrganizationKeys = {
5046
contractCodes: (
5147
opts: B2bApiB2bManagerOrganizationsContractsCodesListRequest,
5248
) => [...managerOrganizationKeys.contractCodesRoot(), opts] as const,
49+
// Prefix key used for invalidation — matches all pages/search states for one contract
50+
contractCodesForContract: (id: number, orgId: number) =>
51+
managerOrganizationKeys.contractCodes({
52+
id,
53+
parent_lookup_organization: orgId,
54+
}),
5355
}
5456

5557
const managerOrganizationQueries = {
5658
managerOrganizationsList: () =>
5759
queryOptions({
5860
queryKey: managerOrganizationKeys.list(),
5961
queryFn: async (): Promise<OrganizationPage[]> =>
60-
b2bApi.b2bManagerOrganizationsList().then((res) => res.data),
62+
b2bApi.b2bManagerOrganizationsList().then((res) => res.data.results),
6163
}),
6264
managerContractDetail: (
6365
opts: B2bApiB2bManagerOrganizationsContractsRetrieveRequest,
@@ -74,10 +76,10 @@ const managerOrganizationQueries = {
7476
) =>
7577
queryOptions({
7678
queryKey: managerOrganizationKeys.contractCodes(opts),
77-
queryFn: async (): Promise<ContractCode[]> =>
79+
queryFn: async (): Promise<PaginatedManagerEnrollmentCodeList> =>
7880
b2bApi
7981
.b2bManagerOrganizationsContractsCodesList(opts)
80-
.then((res) => res.data as ContractCode[]),
82+
.then((res) => res.data),
8183
}),
8284
}
8385

@@ -87,5 +89,3 @@ export {
8789
managerOrganizationQueries,
8890
managerOrganizationKeys,
8991
}
90-
91-
export type { ContractCode }

frontends/api/src/mitxonline/test-utils/factories/contracts.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import type {
33
BulkAssignError,
44
BulkAssignResult,
55
ContractPage,
6+
ManagerEnrollmentCode,
7+
PaginatedManagerEnrollmentCodeList,
68
} from "@mitodl/mitxonline-api-axios/v2"
79
import { makePaginatedFactory } from "ol-test-utilities"
8-
import type { ContractCode } from "../../hooks/organizations"
910

1011
const contract = (overrides: Partial<ContractPage> = {}): ContractPage => ({
1112
id: faker.number.int(),
1213
contract_end: faker.date.future().toISOString(),
1314
contract_start: faker.date.past().toISOString(),
1415
description: faker.lorem.sentence(),
15-
integration_type: "non-sso" as const,
1616
name: faker.company.name(),
1717
organization: faker.number.int(),
1818
slug: faker.lorem.slug(),
@@ -26,7 +26,19 @@ const contract = (overrides: Partial<ContractPage> = {}): ContractPage => ({
2626

2727
const contracts = makePaginatedFactory(contract)
2828

29-
const contractCode = (overrides: Partial<ContractCode> = {}): ContractCode => {
29+
const paginatedContractCodes = (
30+
results: ManagerEnrollmentCode[],
31+
opts: { count?: number; next?: string | null; previous?: string | null } = {},
32+
): PaginatedManagerEnrollmentCodeList => ({
33+
count: opts.count ?? results.length,
34+
next: opts.next ?? null,
35+
previous: opts.previous ?? null,
36+
results,
37+
})
38+
39+
const contractCode = (
40+
overrides: Partial<ManagerEnrollmentCode> = {},
41+
): ManagerEnrollmentCode => {
3042
const status = overrides.redemption_status ?? "assigned"
3143
const isAssigned = status !== "unassigned"
3244
const isRedeemed = status === "redeemed"
@@ -61,4 +73,11 @@ const bulkAssignResult = (
6173
...overrides,
6274
})
6375

64-
export { contract, contracts, contractCode, bulkAssignError, bulkAssignResult }
76+
export {
77+
contract,
78+
contracts,
79+
contractCode,
80+
paginatedContractCodes,
81+
bulkAssignError,
82+
bulkAssignResult,
83+
}

frontends/api/src/mitxonline/test-utils/urls.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,24 @@ const contracts = {
9090
contractsList: () => `${getApiBaseUrl()}/api/v0/b2b/contracts/`,
9191
managerContractDetail: (orgId: number, contractId: number) =>
9292
`${getApiBaseUrl()}/api/v0/b2b/manager/organizations/${orgId}/contracts/${contractId}/`,
93-
managerContractCodes: (orgId: number, contractId: number) =>
94-
`${getApiBaseUrl()}/api/v0/b2b/manager/organizations/${orgId}/contracts/${contractId}/codes/`,
93+
managerContractCodes: (
94+
orgId: number,
95+
contractId: number,
96+
params?: {
97+
page?: number
98+
page_size?: number
99+
search_term?: string
100+
status?: string
101+
},
102+
) => {
103+
// queryify does not filter undefined values, so strip them first to avoid e.g. search_term=undefined in the URL
104+
const clean = params
105+
? Object.fromEntries(
106+
Object.entries(params).filter(([, v]) => v !== undefined),
107+
)
108+
: undefined
109+
return `${getApiBaseUrl()}/api/v0/b2b/manager/organizations/${orgId}/contracts/${contractId}/codes/${queryify(clean)}`
110+
},
95111
managerContractBulkAssign: (orgId: number, contractId: number) =>
96112
`${getApiBaseUrl()}/api/v0/b2b/manager/organizations/${orgId}/contracts/${contractId}/codes/bulk_assign/`,
97113
managerContractCodeRemind: (

frontends/main/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@floating-ui/react": "^0.27.16",
1717
"@mitodl/arithmix": "^0.2.2",
1818
"@mitodl/course-search-utils": "^3.5.2",
19-
"@mitodl/mitxonline-api-axios": "2026.6.17",
19+
"@mitodl/mitxonline-api-axios": "2026.6.30-1",
2020
"@mitodl/smoot-design": "^6.27.0",
2121
"@mui/base": "5.0.0-beta.70",
2222
"@mui/material": "^6.4.5",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
email
2+
testuser1@example.com
3+
testuser2@example.com
4+
testuser3@example.com
5+
testuser4@example.com
6+
testuser5@example.com

0 commit comments

Comments
 (0)