Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure useSubscriptions and useEnterpriseCourseEnrollments uses queryOptions.select from args #1235

Merged
merged 5 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/app/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const ASSIGNMENT_TYPES = {
EXPIRED: 'expired',
ERRORED: 'errored',
EXPIRING: 'expiring',
REVERSED: 'reversed',
};

// When the start date is before this number of days before today, display the alternate start date (fixed to today).
Expand Down
98 changes: 77 additions & 21 deletions src/components/app/data/hooks/useEnterpriseCourseEnrollments.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,90 @@ export const transformAllEnrollmentsByStatus = ({
/**
* Retrieves the relevant enterprise course enrollments, subsidy requests (e.g., license
* requests), and content assignments for the active enterprise customer user.
* @param {Types.UseQueryOptions} queryOptions The query options.
* @returns {Types.UseQueryResult} The query results.
*/
export default function useEnterpriseCourseEnrollments(queryOptions = {}) {
const isEnabled = queryOptions.enabled;
const {
enrollmentQueryOptions = {},
licenseRequestQueryOptions = {},
couponCodeRequestQueryOptions = {},
contentAssignmentQueryOptions = {},
} = queryOptions;
const { select: selectEnrollment, ...enrollmentQueryOptionsRest } = enrollmentQueryOptions;
const { select: selectLicenseRequest, ...licenseRequestQueryOptionsRest } = licenseRequestQueryOptions;
const { select: selectCouponCodeRequest, ...couponCodeRequestQueryOptionsRest } = couponCodeRequestQueryOptions;
const { select: selectContentAssignment, ...contentAssignmentQueryOptionsRest } = contentAssignmentQueryOptions;

const { data: enterpriseCustomer } = useEnterpriseCustomer();
const bffQueryFallback = {
...queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid),
...queryOptions,
select: (data) => data.map(transformCourseEnrollment),
enabled: isEnabled,
};

const { data: enterpriseCourseEnrollments } = useBFF({
bffQueryOptions: {
...queryOptions,
select: (data) => data.enterpriseCourseEnrollments.map(transformCourseEnrollment),
enabled: isEnabled,
select: (data) => {
const transformedData = data.enterpriseCourseEnrollments.map(transformCourseEnrollment);
if (selectEnrollment) {
return selectEnrollment({
original: data,
transformed: transformedData,
});
}
return transformedData;
},
...enrollmentQueryOptionsRest,
},
fallbackQueryConfig: {
...queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid),
...queryOptions,
select: (data) => {
const transformedData = data.map(transformCourseEnrollment);
if (selectEnrollment) {
return selectEnrollment({
original: data,
transformed: transformedData,
});
}
return transformedData;
},
...enrollmentQueryOptionsRest,
},
fallbackQueryConfig: bffQueryFallback,
});

const { data: { requests } } = useBrowseAndRequest({
subscriptionLicensesQueryOptions: {
select: (data) => data.map((subsidyRequest) => transformSubsidyRequest({
subsidyRequest,
slug: enterpriseCustomer.slug,
})),
enabled: isEnabled,
select: (data) => {
const transformedData = data.map((subsidyRequest) => transformSubsidyRequest({
subsidyRequest,
slug: enterpriseCustomer.slug,
}));
if (selectLicenseRequest) {
return selectLicenseRequest({
original: data,
transformed: transformedData,
});
}
return transformedData;
},
...licenseRequestQueryOptionsRest,
},
couponCodesQueryOptions: {
select: (data) => data.map((subsidyRequest) => transformSubsidyRequest({
subsidyRequest,
slug: enterpriseCustomer.slug,
})),
enabled: isEnabled,
select: (data) => {
const transformedData = data.map((subsidyRequest) => transformSubsidyRequest({
subsidyRequest,
slug: enterpriseCustomer.slug,
}));
if (selectCouponCodeRequest) {
return selectCouponCodeRequest({
original: data,
transformed: transformedData,
});
}
return transformedData;
},
...couponCodeRequestQueryOptionsRest,
},
});

const { data: contentAssignments } = useRedeemablePolicies({
select: (data) => {
const { learnerContentAssignments } = data;
Expand All @@ -78,10 +127,17 @@ export default function useEnterpriseCourseEnrollments(queryOptions = {}) {
enterpriseCustomer.slug,
));
});
if (selectContentAssignment) {
return selectContentAssignment({
original: data,
transformed: transformedAssignments,
});
}
return transformedAssignments;
},
enabled: isEnabled,
...contentAssignmentQueryOptionsRest,
});

// TODO: Talk about how we don't have access to weeksToComplete on the dashboard page.
const allEnrollmentsByStatus = useMemo(() => transformAllEnrollmentsByStatus({
enterpriseCourseEnrollments,
Expand Down
110 changes: 81 additions & 29 deletions src/components/app/data/hooks/useEnterpriseCourseEnrollments.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jest.mock('./useBFF');

const mockEnterpriseCustomer = enterpriseCustomerFactory();
const mockAuthenticatedUser = authenticatedUserFactory();
const mockCourseEnrollments = {
const mockCourseEnrollment = {
displayName: 'Education',
micromastersTitle: 'Demo in higher education',
courseRunUrl: 'test-course-url',
Expand Down Expand Up @@ -114,6 +114,16 @@ const mockRedeemablePolicies = {
},
};

const expectedTransformedRequests = (request) => ({
courseRunId: request.courseId,
title: request.courseTitle,
orgName: request.coursePartners?.map(partner => partner.name).join(', '),
courseRunStatus: COURSE_STATUSES.requested,
linkToCourse: `${mockEnterpriseCustomer.slug}/course/${request.courseId}`,
created: request.created,
notifications: [],
});

describe('useEnterpriseCourseEnrollments', () => {
const Wrapper = ({ children }) => (
<QueryClientProvider client={queryClient()}>
Expand All @@ -125,35 +135,28 @@ describe('useEnterpriseCourseEnrollments', () => {
beforeEach(() => {
jest.clearAllMocks();
useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer });
fetchEnterpriseCourseEnrollments.mockResolvedValue([mockCourseEnrollments]);
fetchEnterpriseCourseEnrollments.mockResolvedValue([mockCourseEnrollment]);
fetchBrowseAndRequestConfiguration.mockResolvedValue(mockBrowseAndRequestConfiguration);
fetchLicenseRequests.mockResolvedValue([mockLicenseRequests]);
fetchCouponCodeRequests.mockResolvedValue([mockCouponCodeRequests]);
fetchRedeemablePolicies.mockResolvedValue(mockRedeemablePolicies);
useBFF.mockReturnValue({ data: [mockCourseEnrollments].map(transformCourseEnrollment) });
useBFF.mockReturnValue({ data: [mockCourseEnrollment].map(transformCourseEnrollment) });
});
it('should return transformed return values from course enrollments API', async () => {
const { result, waitForNextUpdate } = renderHook(() => useEnterpriseCourseEnrollments(), { wrapper: Wrapper });
await waitForNextUpdate();
const expectedEnterpriseCourseEnrollments = {
title: mockCourseEnrollments.displayName,
microMastersTitle: mockCourseEnrollments.micromastersTitle,
linkToCourse: mockCourseEnrollments.courseRunUrl,
linkToCertificate: mockCourseEnrollments.certificateDownloadUrl,
hasEmailsEnabled: mockCourseEnrollments.emailsEnabled,
notifications: mockCourseEnrollments.dueDates,
canUnenroll: canUnenrollCourseEnrollment(mockCourseEnrollments),

it.each([
{ hasQueryOptions: false },
{ hasQueryOptions: true },
])('should return transformed enrollments data (%s)', async ({ hasQueryOptions }) => {
const expectedEnterpriseCourseEnrollments = [{
title: mockCourseEnrollment.displayName,
microMastersTitle: mockCourseEnrollment.micromastersTitle,
linkToCourse: mockCourseEnrollment.courseRunUrl,
linkToCertificate: mockCourseEnrollment.certificateDownloadUrl,
hasEmailsEnabled: mockCourseEnrollment.emailsEnabled,
notifications: mockCourseEnrollment.dueDates,
canUnenroll: canUnenrollCourseEnrollment(mockCourseEnrollment),
isCourseAssigned: false,
};
const expectedTransformedRequests = (request) => ({
courseRunId: request.courseId,
title: request.courseTitle,
orgName: request.coursePartners?.map(partner => partner.name).join(', '),
courseRunStatus: COURSE_STATUSES.requested,
linkToCourse: `${mockEnterpriseCustomer.slug}/course/${request.courseId}`,
created: request.created,
notifications: [],
});
}];
const expectedRequests = {
couponCodes: [expectedTransformedRequests(mockCouponCodeRequests)],
subscriptionLicenses: [expectedTransformedRequests(mockLicenseRequests)],
Expand All @@ -177,7 +180,7 @@ describe('useEnterpriseCourseEnrollments', () => {
uuid: mockContentAssignment.uuid,
learnerAcknowledged: mockContentAssignment.learnerAcknowledged,
};
const expectedContentAssignment = {
const expectedContentAssignmentData = {
acceptedAssignments: [],
allocatedAssignments: [expectedTransformedLearnerContentAssignment],
assignmentsForDisplay: [expectedTransformedLearnerContentAssignment],
Expand All @@ -187,15 +190,64 @@ describe('useEnterpriseCourseEnrollments', () => {
expiredAssignments: [],
};

const mockSelectEnrollment = jest.fn().mockReturnValue(expectedEnterpriseCourseEnrollments);
const mockSelectLicenseRequest = jest.fn().mockReturnValue(expectedRequests.subscriptionLicenses);
const mockSelectCouponCodeRequest = jest.fn().mockReturnValue(expectedRequests.couponCodes);
const mockSelectContentAssignment = jest.fn().mockReturnValue(expectedContentAssignmentData);
const mockQueryOptions = {
enrollmentQueryOptions: { select: mockSelectEnrollment },
licenseRequestQueryOptions: { select: mockSelectLicenseRequest },
couponCodeRequestQueryOptions: { select: mockSelectCouponCodeRequest },
contentAssignmentQueryOptions: { select: mockSelectContentAssignment },
};
const queryOptions = hasQueryOptions ? mockQueryOptions : undefined;
const { result, waitForNextUpdate } = renderHook(
() => {
if (hasQueryOptions) {
return useEnterpriseCourseEnrollments(queryOptions);
}
return useEnterpriseCourseEnrollments();
},
{ wrapper: Wrapper },
);
await waitForNextUpdate();

// Call the mocked useBFF's input select functions
const useBFFArgs = useBFF.mock.calls[0][0];
const { select: selectBFFQuery } = useBFFArgs.bffQueryOptions;
const { select: selectFallbackBFFQuery } = useBFFArgs.fallbackQueryConfig;
selectBFFQuery({ enterpriseCourseEnrollments: [mockCourseEnrollment] });
selectFallbackBFFQuery([mockCourseEnrollment]);

// Assert that passed select fn query options were called with the correct arguments
if (hasQueryOptions) {
expect(mockSelectLicenseRequest).toHaveBeenCalledWith({
original: [mockLicenseRequests],
transformed: expectedRequests.subscriptionLicenses,
});
expect(mockSelectCouponCodeRequest).toHaveBeenCalledWith({
original: [mockCouponCodeRequests],
transformed: expectedRequests.couponCodes,
});
expect(mockSelectContentAssignment).toHaveBeenCalledWith({
original: mockRedeemablePolicies,
transformed: expectedContentAssignmentData,
});
expect(mockSelectEnrollment).toHaveBeenCalledWith({
original: [mockCourseEnrollment],
transformed: expectedEnterpriseCourseEnrollments,
});
}

const expectedTransformedAllEnrollmentsByStatus = transformAllEnrollmentsByStatus({
enterpriseCourseEnrollments: [expectedEnterpriseCourseEnrollments],
enterpriseCourseEnrollments: expectedEnterpriseCourseEnrollments,
requests: expectedRequests,
contentAssignments: expectedContentAssignment,
contentAssignments: expectedContentAssignmentData,
});

expect(result.current.data.allEnrollmentsByStatus).toEqual(expectedTransformedAllEnrollmentsByStatus);
expect(result.current.data.enterpriseCourseEnrollments).toEqual([expectedEnterpriseCourseEnrollments]);
expect(result.current.data.contentAssignments).toEqual(expectedContentAssignment);
expect(result.current.data.enterpriseCourseEnrollments).toEqual(expectedEnterpriseCourseEnrollments);
expect(result.current.data.contentAssignments).toEqual(expectedContentAssignmentData);
expect(result.current.data.requests).toEqual(expectedRequests);
});
});
24 changes: 20 additions & 4 deletions src/components/app/data/hooks/useSubscriptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,28 @@ import { transformSubscriptionsData } from '../services';
*/
export default function useSubscriptions(queryOptions = {}) {
const { data: enterpriseCustomer } = useEnterpriseCustomer();
const { select, ...queryOptionsRest } = queryOptions;

return useBFF({
bffQueryOptions: {
select: (data) => transformSubscriptionsData(
data?.enterpriseCustomerUserSubsidies?.subscriptions,
{ isBFFData: true },
),
...queryOptionsRest,
select: (data) => {
const transformedData = transformSubscriptionsData(
data?.enterpriseCustomerUserSubsidies?.subscriptions,
{ isBFFData: true },
);

// When custom `select` function is provided in `queryOptions`, call it with original and transformed data.
if (select) {
return select({
original: data,
transformed: transformedData,
});
}
Comment on lines +25 to +30
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the primary fix for the bug ticket.


// Otherwise, return the transformed data.
return transformedData;
},
},
fallbackQueryConfig: {
...querySubscriptions(enterpriseCustomer.uuid),
Expand Down
Loading
Loading