-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add query key and hooks for BFF layer (#1223)
* feat: add query key factory and hooks for BFF layer * chore: Add tests * chore: PR Feedback
- Loading branch information
1 parent
7f8451d
commit 1077c0a
Showing
10 changed files
with
476 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useLocation, useParams } from 'react-router-dom'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { resolveBFFQuery } from '../../routes/data/utils'; | ||
|
||
/** | ||
* Uses the route to determine which API call to make for the BFF | ||
* Populates the queryKey with the appropriate enterprise customer uuid once BFF call is resolved | ||
* @param queryOptions | ||
* @returns {Types.UseQueryResult}} The query results for the routes BFF. | ||
*/ | ||
export function useBFF(queryOptions = {}) { | ||
const { select, ...queryOptionsRest } = queryOptions; | ||
const location = useLocation(); | ||
const params = useParams(); | ||
// Determine the BFF query to use based on the current location | ||
const matchedBFFQuery = resolveBFFQuery(location.pathname); | ||
return useQuery({ | ||
...matchedBFFQuery(params), | ||
...queryOptionsRest, | ||
select: (data) => { | ||
if (!data) { | ||
return data; | ||
} | ||
|
||
// TODO: Determine if returned data needs further transformations | ||
const transformedData = structuredClone(data); | ||
if (select) { | ||
return select({ | ||
original: data, | ||
transformed: transformedData, | ||
}); | ||
} | ||
return transformedData; | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { QueryClientProvider } from '@tanstack/react-query'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import { useLocation, useParams } from 'react-router-dom'; | ||
import { enterpriseCustomerFactory } from '../services/data/__factories__'; | ||
import useEnterpriseCustomer from './useEnterpriseCustomer'; | ||
import { queryClient } from '../../../../utils/tests'; | ||
import { fetchEnterpriseLearnerDashboard } from '../services'; | ||
import { useBFF } from './useBFF'; | ||
import { resolveBFFQuery } from '../../routes/data/utils'; | ||
import { queryEnterpriseLearnerDashboardBFF } from '../queries'; | ||
|
||
jest.mock('./useEnterpriseCustomer'); | ||
jest.mock('../../routes/data/utils', () => ({ | ||
...jest.requireActual('../services'), | ||
resolveBFFQuery: jest.fn(), | ||
})); | ||
jest.mock('../services', () => ({ | ||
...jest.requireActual('../services'), | ||
fetchEnterpriseLearnerDashboard: jest.fn().mockResolvedValue(null), | ||
})); | ||
jest.mock('react-router-dom', () => ({ | ||
useLocation: jest.fn(), | ||
matchPath: jest.fn(), | ||
useParams: jest.fn(), | ||
})); | ||
|
||
const mockEnterpriseCustomer = enterpriseCustomerFactory(); | ||
const mockCustomerAgreementUuid = uuidv4(); | ||
const mockSubscriptionCatalogUuid = uuidv4(); | ||
const mockSubscriptionLicenseUuid = uuidv4(); | ||
const mockSubscriptionPlanUuid = uuidv4(); | ||
const mockActivationKey = uuidv4(); | ||
const mockBFFDashboardData = { | ||
enterpriseCustomerUserSubsidies: { | ||
subscriptions: { | ||
customerAgreement: { | ||
uuid: mockCustomerAgreementUuid, | ||
availableSubscriptionCatalogs: [ | ||
mockSubscriptionCatalogUuid, | ||
], | ||
defaultEnterpriseCatalogUuid: null, | ||
netDaysUntilExpiration: 328, | ||
disableExpirationNotifications: false, | ||
enableAutoAppliedSubscriptionsWithUniversalLink: true, | ||
subscriptionForAutoAppliedLicenses: null, | ||
}, | ||
subscriptionLicenses: [ | ||
{ | ||
uuid: mockSubscriptionLicenseUuid, | ||
status: 'activated', | ||
userEmail: '[email protected]', | ||
activationDate: '2024-04-08T20:49:57.593412Z', | ||
lastRemindDate: '2024-04-08T20:49:57.593412Z', | ||
revokedDate: null, | ||
activationKey: mockActivationKey, | ||
subscriptionPlan: { | ||
uuid: mockSubscriptionPlanUuid, | ||
title: 'Another Subscription Plan', | ||
enterpriseCatalogUuid: mockSubscriptionCatalogUuid, | ||
isActive: true, | ||
isCurrent: true, | ||
startDate: '2024-01-18T15:09:41Z', | ||
expirationDate: '2025-03-31T15:09:47Z', | ||
daysUntilExpiration: 131, | ||
daysUntilExpirationIncludingRenewals: 131, | ||
shouldAutoApplyLicenses: false, | ||
}, | ||
}, | ||
], | ||
subscriptionLicensesByStatus: { | ||
activated: [ | ||
{ | ||
uuid: mockSubscriptionLicenseUuid, | ||
status: 'activated', | ||
userEmail: '[email protected]', | ||
activationDate: '2024-04-08T20:49:57.593412Z', | ||
lastRemindDate: '2024-04-08T20:49:57.593412Z', | ||
revokedDate: null, | ||
activationKey: mockActivationKey, | ||
subscriptionPlan: { | ||
uuid: '6e5debf9-a407-4655-98c1-d510880f5fa6', | ||
title: 'Another Subscription Plan', | ||
enterpriseCatalogUuid: mockSubscriptionCatalogUuid, | ||
isActive: true, | ||
isCurrent: true, | ||
startDate: '2024-01-18T15:09:41Z', | ||
expirationDate: '2025-03-31T15:09:47Z', | ||
daysUntilExpiration: 131, | ||
daysUntilExpirationIncludingRenewals: 131, | ||
shouldAutoApplyLicenses: false, | ||
}, | ||
}, | ||
], | ||
assigned: [], | ||
expired: [], | ||
revoked: [], | ||
}, | ||
}, | ||
}, | ||
enterpriseCourseEnrollments: [ | ||
{ | ||
courseRunId: 'course-v1:edX+DemoX+3T2022', | ||
courseKey: 'edX+DemoX', | ||
courseType: 'executive-education-2u', | ||
orgName: 'edX', | ||
courseRunStatus: 'completed', | ||
displayName: 'Really original course name', | ||
emailsEnabled: true, | ||
certificateDownloadUrl: null, | ||
created: '2023-06-14T15:48:31.672317Z', | ||
startDate: '2022-10-26T00:00:00Z', | ||
endDate: '2022-12-04T23:59:59Z', | ||
mode: 'unpaid-executive-education', | ||
isEnrollmentActive: true, | ||
productSource: '2u', | ||
enrollBy: null, | ||
pacing: 'instructor', | ||
courseRunUrl: 'https://fake-url.com/account?org_id=n0tr3a1', | ||
resumeCourseRunUrl: null, | ||
isRevoked: false, | ||
}, | ||
], | ||
errors: [], | ||
warnings: [], | ||
}; | ||
describe('useBFF', () => { | ||
const Wrapper = ({ children }) => ( | ||
<QueryClientProvider client={queryClient()}> | ||
{children} | ||
</QueryClientProvider> | ||
); | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer }); | ||
fetchEnterpriseLearnerDashboard.mockResolvedValue(mockBFFDashboardData); | ||
useLocation.mockReturnValue({ pathname: '/test-enterprise' }); | ||
useParams.mockReturnValue({ enterpriseSlug: 'test-enterprise' }); | ||
resolveBFFQuery.mockReturnValue(null); | ||
}); | ||
it('should handle resolved value correctly for the dashboard route', async () => { | ||
resolveBFFQuery.mockReturnValue(queryEnterpriseLearnerDashboardBFF); | ||
const { result, waitForNextUpdate } = renderHook(() => useBFF(), { wrapper: Wrapper }); | ||
await waitForNextUpdate(); | ||
|
||
expect(result.current).toEqual( | ||
expect.objectContaining({ | ||
data: mockBFFDashboardData, | ||
isLoading: false, | ||
isFetching: false, | ||
}), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { getConfig } from '@edx/frontend-platform/config'; | ||
import { logError } from '@edx/frontend-platform/logging'; | ||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; | ||
import { camelCaseObject } from '@edx/frontend-platform/utils'; | ||
|
||
export const baseLearnerBFFResponse = { | ||
enterpriseCustomerUserSubsidies: { | ||
subscriptions: { | ||
customerAgreement: {}, | ||
subscriptionLicenses: [], | ||
subscriptionLicensesByStatus: {}, | ||
}, | ||
}, | ||
errors: [], | ||
warnings: [], | ||
}; | ||
|
||
export const learnerDashboardBFFResponse = { | ||
...baseLearnerBFFResponse, | ||
enterpriseCourseEnrollments: [], | ||
}; | ||
|
||
export async function fetchEnterpriseLearnerDashboard(customerIdentifiers) { | ||
const { ENTERPRISE_ACCESS_BASE_URL } = getConfig(); | ||
const url = `${ENTERPRISE_ACCESS_BASE_URL}/api/v1/bffs/learner/dashboard/`; | ||
try { | ||
const params = { | ||
enterprise_customer_uuid: customerIdentifiers?.enterpriseId, | ||
enterprise_customer_slug: customerIdentifiers?.enterpriseSlug, | ||
}; | ||
|
||
const result = await getAuthenticatedHttpClient().post(url, params); | ||
return camelCaseObject(result.data); | ||
} catch (error) { | ||
logError(error); | ||
return learnerDashboardBFFResponse; | ||
} | ||
} |
Oops, something went wrong.