Skip to content

Commit

Permalink
feat: show all enterprise budgets regardless of plan and route correctly
Browse files Browse the repository at this point in the history
fix: full page refresh issue when clicking 'Budgets', added test coverage and fixed lint issues
  • Loading branch information
jajjibhai008 committed Sep 20, 2023
1 parent 0eca22c commit f19a8fe
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 244 deletions.
7 changes: 7 additions & 0 deletions src/components/EnterpriseApp/EnterpriseAppRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PlotlyAnalyticsPage } from '../PlotlyAnalytics';
import { ROUTE_NAMES } from './data/constants';
import BulkEnrollmentResultsDownloadPage from '../BulkEnrollmentResultsDownloadPage';
import LearnerCreditManagement from '../learner-credit-management';
import BudgetDetailPage from '../learner-credit-management/BudgetDetailPage';
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';
import ContentHighlights from '../ContentHighlights';

Expand Down Expand Up @@ -105,6 +106,12 @@ const EnterpriseAppRoutes = ({
/>
)}

<Route
exact
path={`${baseUrl}/admin/${ROUTE_NAMES.learnerCredit}/:id`}
component={BudgetDetailPage}
/>

{enableContentHighlightsPage && (
<Route
path={`${baseUrl}/admin/${ROUTE_NAMES.contentHighlights}`}
Expand Down
71 changes: 42 additions & 29 deletions src/components/EnterpriseSubsidiesContext/data/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,51 @@ export const useEnterpriseOffers = ({ enablePortalLearnerCreditManagementScreen,
}),
]);

// If there are no subsidies in enterprise, fall back to the e-commerce API.
let { results } = camelCaseObject(enterpriseSubsidyResponse.data);
let source = 'subsidyApi';

if (results.length === 0) {
results = camelCaseObject(ecommerceApiResponse.data.results);
source = 'ecommerceApi';
// We have to conside both type of offers active and inactive.

const enterpriseResults = camelCaseObject(enterpriseSubsidyResponse.data).results;
const ecommerceResults = camelCaseObject(ecommerceApiResponse.data.results);

const offerData = [];

for (let i = 0; i < enterpriseResults.length; i++) {
const subsidy = enterpriseResults[i];
const source = 'subsidyApi';
const { isActive } = subsidy; // Always check isActive for enterprise subsidies
const isCurrent = isActive; // You can adjust this based on your specific requirements
const activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent,
source,
};
offerData.push(activeSubsidyData);
if (isActive) {
setCanManageLearnerCredit(true);
}
}
let activeSubsidyFound = false;
if (results.length !== 0) {
let subsidy = results[0];
const offerData = [];
let activeSubsidyData = {};
for (let i = 0; i < results.length; i++) {
subsidy = results[i];
activeSubsidyFound = source === 'ecommerceApi'
? subsidy.isCurrent
: subsidy.isActive;
if (activeSubsidyFound === true) {
activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent: activeSubsidyFound,
};
offerData.push(activeSubsidyData);
setCanManageLearnerCredit(true);
}

for (let i = 0; i < ecommerceResults.length; i++) {
const subsidy = ecommerceResults[i];
const source = 'ecommerceApi';
const { isCurrent } = subsidy;
const activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent,
source,
};
offerData.push(activeSubsidyData);
if (isCurrent) {
setCanManageLearnerCredit(true);
}
setOffers(offerData);
}

setOffers(offerData);
} catch (error) {
logError(error);
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import {
screen,
render,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { MemoryRouter } from 'react-router-dom';
import BudgetDetailPage from '../../../learner-credit-management/BudgetDetailPage';
import { useOfferSummary, useOfferRedemptions } from '../../../learner-credit-management/data/hooks';
import { EXEC_ED_OFFER_TYPE } from '../../../learner-credit-management/data/constants';
import { EnterpriseSubsidiesContext } from '../..';

jest.mock('../../../learner-credit-management/data/hooks');

useOfferSummary.mockReturnValue({
isLoading: false,
offerSummary: null,
});
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: {
itemCount: 0,
pageCount: 0,
results: [],
},
fetchOfferRedemptions: jest.fn(),
});

const mockStore = configureMockStore([thunk]);
const getMockStore = store => mockStore(store);
const enterpriseId = 'test-enterprise';
const enterpriseUUID = '1234';
const initialStore = {
portalConfiguration: {
enterpriseId,
enterpriseSlug: enterpriseId,

},
};
const store = getMockStore({ ...initialStore });

const mockEnterpriseOfferId = '123';

const mockOfferDisplayName = 'Test Enterprise Offer';
const mockOfferSummary = {
totalFunds: 5000,
redeemedFunds: 200,
remainingFunds: 4800,
percentUtilized: 0.04,
offerType: EXEC_ED_OFFER_TYPE,
};

const defaultEnterpriseSubsidiesContextValue = {
isLoading: false,
};

const BudgetDetailPageWrapper = ({
enterpriseSubsidiesContextValue = defaultEnterpriseSubsidiesContextValue,
...rest
}) => (
<MemoryRouter initialEntries={['/test-enterprise/admin/learner-credit/1234']}>

<Provider store={store}>
<IntlProvider locale="en">
<EnterpriseSubsidiesContext.Provider value={enterpriseSubsidiesContextValue}>
<BudgetDetailPage {...rest} />
</EnterpriseSubsidiesContext.Provider>
</IntlProvider>
</Provider>
</MemoryRouter>
);

describe('<BudgetDetailPage />', () => {
describe('with enterprise offer', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('displays table on clicking view budget', async () => {
const mockOffer = {
id: mockEnterpriseOfferId,
name: mockOfferDisplayName,
start: '2022-01-01',
end: '2023-01-01',
};
useOfferSummary.mockReturnValue({
isLoading: false,
offerSummary: mockOfferSummary,
});
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: {
itemCount: 0,
pageCount: 0,
results: [],
},
fetchOfferRedemptions: jest.fn(),
});
render(<BudgetDetailPageWrapper
enterpriseUUID={enterpriseUUID}
enterpriseSlug={enterpriseId}
offer={mockOffer}
/>);
expect(screen.getByText('Learner Credit Budget Detail'));
expect(screen.getByText('Overview'));
expect(screen.getByText('No results found'));
});
});
});
106 changes: 68 additions & 38 deletions src/components/EnterpriseSubsidiesContext/data/tests/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('useEnterpriseOffers', () => {
start: '2021-05-15T19:56:09Z',
end: '2100-05-15T19:56:09Z',
isCurrent: true,
source: 'ecommerceApi',
}];

SubsidyApiService.getSubsidyByCustomerUUID.mockResolvedValueOnce({
Expand Down Expand Up @@ -80,25 +81,35 @@ describe('useEnterpriseOffers', () => {
});

it('should fetch enterprise offers for the enterprise when data available in enterprise-subsidy', async () => {
const mockOffers = [
const mockEnterpriseSubsidyResponse = [
{
id: 'offer-id',
name: 'offer-name',
start: '2021-05-15T19:56:09Z',
end: '2100-05-15T19:56:09Z',
isCurrent: true,
uuid: 'offer-id',
title: 'offer-name',
activeDatetime: '2021-05-15T19:56:09Z',
expirationDatetime: '2100-05-15T19:56:09Z',
isActive: true,
},
];
const mockSubsidyServiceResponse = [{
uuid: 'offer-id',
title: 'offer-name',
active_datetime: '2021-05-15T19:56:09Z',
expiration_datetime: '2100-05-15T19:56:09Z',
is_active: true,
}];

const mockEcommerceResponse = [
{
id: 'uuid',
display_name: 'offer-name',
start_datetime: '2021-05-15T19:56:09Z',
end_datetime: '2100-05-15T19:56:09Z',
is_current: true,
},
];

SubsidyApiService.getSubsidyByCustomerUUID.mockResolvedValueOnce({
data: {
results: mockSubsidyServiceResponse,
results: mockEnterpriseSubsidyResponse,
},
});

EcommerceApiService.fetchEnterpriseOffers.mockResolvedValueOnce({
data: {
results: mockEcommerceResponse,
},
});

Expand All @@ -113,36 +124,39 @@ describe('useEnterpriseOffers', () => {
TEST_ENTERPRISE_UUID,
{ subsidyType: 'learner_credit' },
);

const expectedOffers = [
{
id: 'offer-id',
name: 'offer-name',
start: '2021-05-15T19:56:09Z',
end: '2100-05-15T19:56:09Z',
isCurrent: true,
source: 'subsidyApi',
},
{
id: 'uuid',
name: 'offer-name',
start: '2021-05-15T19:56:09Z',
end: '2100-05-15T19:56:09Z',
isCurrent: true,
source: 'ecommerceApi',
},
];

expect(result.current).toEqual({
offers: mockOffers,
offers: expectedOffers,
isLoading: false,
canManageLearnerCredit: true,
});
});

it('should set canManageLearnerCredit to false if active enterprise offer or subsidy not found', async () => {
const mockOffers = [{ subsidyUuid: 'offer-1' }, { subsidyUuid: 'offer-2' }];
const mockSubsidyServiceResponse = [
{
uuid: 'offer-1',
title: 'offer-name',
active_datetime: '2005-05-15T19:56:09Z',
expiration_datetime: '2006-05-15T19:56:09Z',
is_active: false,
},
{
uuid: 'offer-2',
title: 'offer-name-2',
active_datetime: '2006-05-15T19:56:09Z',
expiration_datetime: '2007-05-15T19:56:09Z',
is_active: false,
},
];
const mockOfferData = [];
const mockSubsidyServiceResponse = [];

EcommerceApiService.fetchEnterpriseOffers.mockResolvedValueOnce({
data: {
results: mockOffers,
results: [],
},
});
SubsidyApiService.getSubsidyByCustomerUUID.mockResolvedValueOnce({
Expand All @@ -162,15 +176,22 @@ describe('useEnterpriseOffers', () => {
TEST_ENTERPRISE_UUID,
{ subsidyType: 'learner_credit' },
);

const hasActiveOffersOrSubsidies = mockSubsidyServiceResponse.some(offer => offer.is_active);
let canManageLearnerCredit = false;

if (hasActiveOffersOrSubsidies) {
canManageLearnerCredit = true;
}

expect(result.current).toEqual({
offers: mockOfferData,
offers: [],
isLoading: false,
canManageLearnerCredit: false,
canManageLearnerCredit,
});
});

it('should return the active enterprise offer or subsidy when multiple available', async () => {
const mockOffers = [{ subsidyUuid: 'offer-1' }, { subsidyUuid: 'offer-2' }];
const mockSubsidyServiceResponse = [
{
uuid: 'offer-1',
Expand All @@ -188,18 +209,27 @@ describe('useEnterpriseOffers', () => {
},
];
const mockOfferData = [
{
id: 'offer-1',
name: 'offer-name',
start: '2005-05-15T19:56:09Z',
end: '2006-05-15T19:56:09Z',
isCurrent: false,
source: 'subsidyApi',
},
{
id: 'offer-2',
name: 'offer-name-2',
start: '2006-05-15T19:56:09Z',
end: '2099-05-15T19:56:09Z',
isCurrent: true,
source: 'subsidyApi',
},
];

EcommerceApiService.fetchEnterpriseOffers.mockResolvedValueOnce({
data: {
results: mockOffers,
results: [],
},
});
SubsidyApiService.getSubsidyByCustomerUUID.mockResolvedValueOnce({
Expand Down
Loading

0 comments on commit f19a8fe

Please sign in to comment.