From 0f4ed2c55f87385ed4d70bf763ddeded714bb864 Mon Sep 17 00:00:00 2001
From: Alexander J Sheehan
Date: Sat, 27 Apr 2024 19:09:48 +0000
Subject: [PATCH] feat: subsidy group members download csv functionality
---
.../useEnterpriseGroupMembersTableData.js | 34 +++--
.../learner-credit-management/data/utils.js | 1 +
.../BudgetDetailMembersTabContents.jsx | 12 +-
.../GroupMembersCsvDownloadTableAction.jsx | 132 ++++++++++++++++++
.../LearnerCreditGroupMembersTable.jsx | 14 +-
.../members-tab/MembersTableSwitchFilter.jsx | 32 +++++
.../members-tab/tests/MembersTab.test.jsx | 118 +++++++++-------
.../tests/BudgetDetailPage.test.jsx | 4 +-
.../services/EnterpriseAccessApiService.js | 6 +
9 files changed, 270 insertions(+), 83 deletions(-)
create mode 100644 src/components/learner-credit-management/members-tab/GroupMembersCsvDownloadTableAction.jsx
create mode 100644 src/components/learner-credit-management/members-tab/MembersTableSwitchFilter.jsx
diff --git a/src/components/learner-credit-management/data/hooks/useEnterpriseGroupMembersTableData.js b/src/components/learner-credit-management/data/hooks/useEnterpriseGroupMembersTableData.js
index 33074a5a19..0afd433e10 100644
--- a/src/components/learner-credit-management/data/hooks/useEnterpriseGroupMembersTableData.js
+++ b/src/components/learner-credit-management/data/hooks/useEnterpriseGroupMembersTableData.js
@@ -6,13 +6,11 @@ import { camelCaseObject } from '@edx/frontend-platform/utils';
import { logError } from '@edx/frontend-platform/logging';
import debounce from 'lodash.debounce';
-import LmsApiService from '../../../../data/services/LmsApiService';
+import EnterpriseAccessApiService from '../../../../data/services/EnterpriseAccessApiService';
import { transformGroupMembersTableResults } from '../utils';
-const useEnterpriseGroupMembersTableData = ({ groupId, refresh }) => {
+const useEnterpriseGroupMembersTableData = ({ policyUuid, groupId, refresh }) => {
const [isLoading, setIsLoading] = useState(true);
- const [showRemoved, setShowRemoved] = useState(false);
- const handleSwitchChange = e => setShowRemoved(e.target.checked);
const [enterpriseGroupMembersTableData, setEnterpriseGroupMembersTableData] = useState({
itemCount: 0,
pageCount: 0,
@@ -22,28 +20,30 @@ const useEnterpriseGroupMembersTableData = ({ groupId, refresh }) => {
const fetch = async () => {
try {
setIsLoading(true);
- const options = {};
- if (args?.filters.length > 0) {
- options.user_query = args.filters[0].value;
- }
+ const options = { group_uuid: groupId };
if (args?.sortBy.length > 0) {
const sortByValue = args.sortBy[0].id;
options.sort_by = _.snakeCase(sortByValue);
if (!args.sortBy[0].desc) {
- options.is_reversed = args.sortBy[0].desc;
+ options.is_reversed = !args.sortBy[0].desc;
}
- } if (showRemoved) {
- options.show_removed = true;
}
+ args.filters.forEach((filter) => {
+ const { id, value } = filter;
+ if (id === 'status') {
+ options.show_removed = value;
+ } else if (id === 'memberDetails') {
+ options.user_query = value;
+ }
+ });
+
options.page = args.pageIndex + 1;
- const response = await LmsApiService.fetchEnterpriseGroupLearners(groupId, options);
+ const response = await EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData(policyUuid, options);
const data = camelCaseObject(response.data);
const transformedTableResults = transformGroupMembersTableResults(data.results);
setEnterpriseGroupMembersTableData({
itemCount: data.count,
- // If the data comes from the subsidy transactions endpoint, the number of pages is calculated
- // TODO: https://2u-internal.atlassian.net/browse/ENT-8106
pageCount: data.numPages ?? Math.floor(data.count / options.pageSize),
results: transformedTableResults,
});
@@ -53,10 +53,10 @@ const useEnterpriseGroupMembersTableData = ({ groupId, refresh }) => {
setIsLoading(false);
}
};
- if (groupId) {
+ if (policyUuid) {
fetch();
}
- }, [groupId, showRemoved]);
+ }, [groupId, policyUuid]);
const debouncedFetchEnterpriseGroupMembersData = useMemo(
() => debounce(fetchEnterpriseGroupMembersData, 300),
@@ -66,8 +66,6 @@ const useEnterpriseGroupMembersTableData = ({ groupId, refresh }) => {
return {
isLoading,
- showRemoved,
- handleSwitchChange,
enterpriseGroupMembersTableData,
fetchEnterpriseGroupMembersTableData: debouncedFetchEnterpriseGroupMembersData,
};
diff --git a/src/components/learner-credit-management/data/utils.js b/src/components/learner-credit-management/data/utils.js
index fca740d7fe..55071c12a5 100644
--- a/src/components/learner-credit-management/data/utils.js
+++ b/src/components/learner-credit-management/data/utils.js
@@ -107,6 +107,7 @@ export const transformGroupMembersTableResults = results => results.map(result =
status: result.status,
recentAction: result.recentAction,
memberEnrollments: result.memberEnrollments,
+ enrollmentCount: result.enrollmentCount,
}));
/**
diff --git a/src/components/learner-credit-management/members-tab/BudgetDetailMembersTabContents.jsx b/src/components/learner-credit-management/members-tab/BudgetDetailMembersTabContents.jsx
index df9fb9d184..c044cf54cf 100644
--- a/src/components/learner-credit-management/members-tab/BudgetDetailMembersTabContents.jsx
+++ b/src/components/learner-credit-management/members-tab/BudgetDetailMembersTabContents.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { Form } from '@edx/paragon';
import LearnerCreditGroupMembersTable from './LearnerCreditGroupMembersTable';
import { useEnterpriseGroupMembersTableData, useBudgetId, useSubsidyAccessPolicy } from '../data';
@@ -12,13 +11,12 @@ const BudgetDetailMembersTabContents = ({ enterpriseUUID, refresh, setRefresh })
const groupId = subsidyAccessPolicy.groupAssociations[0];
const {
isLoading,
- showRemoved,
- handleSwitchChange,
enterpriseGroupMembersTableData,
fetchEnterpriseGroupMembersTableData,
} = useEnterpriseGroupMembersTableData({
enterpriseUUID,
subsidyAccessPolicyId,
+ policyUuid: subsidyAccessPolicy.uuid,
groupId,
refresh,
});
@@ -31,14 +29,6 @@ const BudgetDetailMembersTabContents = ({ enterpriseUUID, refresh, setRefresh })
Members choose what to learn from the catalog and spend from the budget to enroll.
-
- Show removed
-
{
+ const [alertModalOpen, setAlertModalOpen] = useState(false);
+ const [alertModalExc, setAlertModalException] = useState('');
+ const { subsidyAccessPolicyId } = useBudgetId();
+ const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
+ const groupId = subsidyAccessPolicy.groupAssociations[0];
+
+ const getCsvFileName = () => {
+ const titleNoWhitespace = subsidyAccessPolicy.displayName.replace(/\s+/g, '');
+ const currentDate = new Date();
+ const year = currentDate.getUTCFullYear();
+ const month = currentDate.getUTCMonth() + 1;
+ const day = currentDate.getUTCDate();
+ return `${titleNoWhitespace}-${year}-${month}-${day}.csv`;
+ };
+
+ const csvDownloadOnClick = () => {
+ const options = {
+ format_csv: true,
+ traverse_pagination: true,
+ group_uuid: groupId,
+ };
+ // Apply the table state to the request args
+ // sortBy can support multiple values, the members table will only ever have one applied
+ // so we can grab the data from the first index should it exist
+ if (tableInstance.state.sortBy[0]) {
+ options.sort_by = snakeCase(tableInstance.state.sortBy[0].id);
+ // IFF we're doing sorting, check if it's in reverse order
+ if (!tableInstance.state.sortBy[0].desc) {
+ options.is_reversed = !tableInstance.state.sortBy[0].desc;
+ }
+ }
+ tableInstance.state.filters.forEach((filter) => {
+ if (filter.id === 'status') {
+ options.show_removed = filter.value;
+ } else if (filter.id === 'memberDetails') {
+ options.user_query = snakeCase(filter.value);
+ }
+ });
+
+ EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData(
+ subsidyAccessPolicyId,
+ options,
+ ).then(response => {
+ // download CSV
+ const blob = new Blob([response.data], {
+ type: 'text/csv',
+ });
+ saveAs(blob, getCsvFileName());
+ }).catch(err => {
+ logError(err);
+ setAlertModalOpen(true);
+ setAlertModalException(err.message);
+ });
+ };
+
+ return (
+ <>
+ setAlertModalOpen(false)}
+ footerNode={(
+
+
+
+ )}
+ >
+
+ We're sorry but something went wrong while downloading your CSV.
+ Please refer to the error below and try again later.
+
+ {alertModalExc}
+
+
+ >
+ );
+};
+
+GroupMembersCsvDownloadTableAction.propTypes = {
+ tableInstance: PropTypes.shape({
+ itemCount: PropTypes.number,
+ state: PropTypes.shape({
+ filters: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ // Can be a string for user queries or bool for show removed toggle
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ })),
+ sortBy: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ desc: PropTypes.bool,
+ })),
+ }),
+ }),
+};
+
+GroupMembersCsvDownloadTableAction.defaultProps = {
+ tableInstance: {
+ itemCount: 0,
+ state: {},
+ },
+};
+
+export default GroupMembersCsvDownloadTableAction;
diff --git a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx
index 6a51b77fe3..c524b12b0a 100644
--- a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx
+++ b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx
@@ -14,6 +14,8 @@ import MemberRemoveAction from './bulk-actions/MemberRemoveAction';
import MemberRemoveModal from './bulk-actions/MemberRemoveModal';
import { DEFAULT_PAGE, MEMBERS_TABLE_PAGE_SIZE } from '../data';
import useRemoveMember from '../data/hooks/useRemoveMember';
+import GroupMembersCsvDownloadTableAction from './GroupMembersCsvDownloadTableAction';
+import MembersTableSwitchFilter from './MembersTableSwitchFilter';
const FilterStatus = (rest) => ;
@@ -75,6 +77,8 @@ const LearnerCreditGroupMembersTable = ({
isLoading={isLoading}
defaultColumnValues={{ Filter: TableTextFilter }}
FilterStatusComponent={FilterStatus}
+ numBreakoutFilters={2}
+ tableActions={[]}
columns={[
{
Header: 'Member Details',
@@ -85,7 +89,8 @@ const LearnerCreditGroupMembersTable = ({
Header: MemberStatusTableColumnHeader,
accessor: 'status',
Cell: MemberStatusTableCell,
- disableFilters: true,
+ Filter: MembersTableSwitchFilter,
+ filter: 'status',
},
{
Header: 'Recent action',
@@ -95,14 +100,15 @@ const LearnerCreditGroupMembersTable = ({
},
{
Header: MemberEnrollmentsTableColumnHeader,
- accessor: 'memberEnrollment',
- // TODO:
- Cell: () => ('0'),
+ accessor: 'enrollmentCount',
+ Cell: ({ row }) => row.original.enrollmentCount,
disableFilters: true,
+ disableSortBy: true,
},
]}
initialTableOptions={{
getRowId: row => row?.memberDetails.userEmail,
+ autoResetPage: true,
}}
initialState={{
pageSize: MEMBERS_TABLE_PAGE_SIZE,
diff --git a/src/components/learner-credit-management/members-tab/MembersTableSwitchFilter.jsx b/src/components/learner-credit-management/members-tab/MembersTableSwitchFilter.jsx
new file mode 100644
index 0000000000..31e29278a8
--- /dev/null
+++ b/src/components/learner-credit-management/members-tab/MembersTableSwitchFilter.jsx
@@ -0,0 +1,32 @@
+import { Form } from '@edx/paragon';
+import PropTypes from 'prop-types';
+
+const MembersTableSwitchFilter = ({ column: { filterValue, setFilter } }) => (
+ {
+ setFilter(!filterValue || false); // Set undefined to remove the filter entirely
+ }}
+ data-testid="show-removed-toggle"
+ >
+ Show removed
+
+);
+
+MembersTableSwitchFilter.propTypes = {
+ /**
+ * Specifies a column object.
+ *
+ * `setFilter`: Function to set the filter value.
+ *
+ * `filterValue`: Value for the filter input.
+ */
+ column: PropTypes.shape({
+ setFilter: PropTypes.func.isRequired,
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
+ filterValue: PropTypes.bool,
+ }).isRequired,
+};
+
+export default MembersTableSwitchFilter;
diff --git a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx
index 66964da452..7386c510d8 100644
--- a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx
+++ b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx
@@ -26,6 +26,7 @@ import {
} from '../../data/tests/constants';
import { queryClient } from '../../../test/testUtils';
import LmsApiService from '../../../../data/services/LmsApiService';
+import EnterpriseAccessApiService from '../../../../data/services/EnterpriseAccessApiService';
jest.mock('@edx/frontend-enterprise-utils', () => ({
...jest.requireActual('@edx/frontend-enterprise-utils'),
@@ -54,6 +55,11 @@ jest.mock('../../data', () => ({
jest.mock('../../../../data/services/EnterpriseAccessApiService');
jest.mock('../../../../data/services/LmsApiService');
+jest.mock('file-saver', () => ({
+ ...jest.requireActual('react-router-dom'),
+ saveAs: jest.fn(),
+}));
+
const mockStore = configureMockStore([thunk]);
const getMockStore = store => mockStore(store);
const enterpriseSlug = 'test-enterprise';
@@ -250,7 +256,7 @@ describe('', () => {
memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: jest.fn(),
@@ -308,36 +314,49 @@ describe('', () => {
memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: mockFetchEnterpriseGroupMembersTableData,
});
renderWithRouter();
- userEvent.click(screen.getByTestId('members-table-status-column-header'));
+ await userEvent.type(screen.getByText('Search by member details'), 'foobar');
await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({
- filters: [],
+ filters: [{ id: 'memberDetails', value: 'foobar' }],
pageIndex: 0,
pageSize: 10,
- sortBy: [{ desc: false, id: 'status' }],
+ sortBy: [{ desc: true, id: 'memberDetails' }],
}));
- userEvent.click(screen.getByTestId('members-table-enrollments-column-header'));
+ userEvent.click(screen.getByTestId('members-table-status-column-header'));
await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({
- filters: [],
+ filters: [{ id: 'memberDetails', value: 'foobar' }],
pageIndex: 0,
pageSize: 10,
- sortBy: [{ desc: false, id: 'memberEnrollment' }],
+ sortBy: [{ desc: false, id: 'status' }],
}));
- userEvent.type(screen.getByText('Search by member details'), 'foobar');
+ const removeToggle = screen.getByTestId('show-removed-toggle');
+ userEvent.click(removeToggle);
await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({
- filters: [{ id: 'memberDetails', value: 'foobar' }],
+ filters: [
+ { id: 'memberDetails', value: 'foobar' },
+ { id: 'status', value: true },
+ ],
pageIndex: 0,
pageSize: 10,
- sortBy: [{ desc: false, id: 'memberEnrollment' }],
+ sortBy: [{ desc: false, id: 'status' }],
}));
+
+ // TODO Sorting by enrollment count is currently not supported by the backend
+ // userEvent.click(screen.getByTestId('members-table-enrollments-column-header'));
+ // await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({
+ // filters: [],
+ // pageIndex: 0,
+ // pageSize: 10,
+ // sortBy: [{ desc: false, id: 'enrollmentCount' }],
+ // }));
});
it('remove learner flow', async () => {
const initialState = {
@@ -388,7 +407,7 @@ describe('', () => {
memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: jest.fn(),
@@ -464,13 +483,13 @@ describe('', () => {
memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
},
{
memberDetails: { userEmail: 'tammy2@test.com', userName: 'tammy 2' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: jest.fn(),
@@ -543,7 +562,7 @@ describe('', () => {
memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: jest.fn(),
@@ -612,7 +631,7 @@ describe('', () => {
memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 0,
}],
},
fetchEnterpriseGroupMembersTableData: jest.fn(),
@@ -633,7 +652,7 @@ describe('', () => {
await waitForElementToBeRemoved(() => screen.queryByText('Removing (1)'));
await waitFor(() => expect(screen.queryByText('There was an error with your request. Please try again.')).toBeInTheDocument());
});
- it('Remove toggle shows removed members', async () => {
+ it('displays members download button that makes requests to fetch member data with queries', async () => {
const initialState = {
portalConfiguration: {
...initialStoreState.portalConfiguration,
@@ -646,6 +665,7 @@ describe('', () => {
enterpriseSlug: 'test-enterprise-slug',
enterpriseAppPage: 'test-enterprise-page',
activeTabKey: 'members',
+ budgetId: mockAssignableSubsidyAccessPolicy.uuid,
});
useSubsidyAccessPolicy.mockReturnValue({
isInitialLoading: false,
@@ -660,53 +680,55 @@ describe('', () => {
budgetRedemptions: mockEmptyBudgetRedemptions,
fetchBudgetRedemptions: jest.fn(),
});
- useEnterpriseGroupLearners.mockReturnValueOnce({
+ useEnterpriseGroupLearners.mockReturnValue({
data: {
count: 1,
currentPage: 1,
next: null,
numPages: 1,
- results: [
- {
- enterpriseGroupMembershipUuid: 'cde2e374-032f-4c08-8c0d-bf3205fa7c7e',
- learnerId: 4382,
- memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
- status: 'pending',
- },
- {
- enterpriseGroupMembershipUuid: 'cde2e374-032f-4c08-8c0d-bf3205fa7c7d',
- learnerId: 4382,
- memberDetails: { userEmail: 'tammy2@example.com', userName: 'tammy 2' },
- status: 'removed',
- },
- ],
+ results: {
+ enterpriseGroupMembershipUuid: 'cde2e374-032f-4c08-8c0d-bf3205fa7c7e',
+ learnerId: 4382,
+ memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' },
+ },
},
});
- useEnterpriseGroupMembersTableData.mockReturnValue({
+ const mockFetchEnterpriseGroupMembersTableData = jest.fn();
+ const mockGroupData = {
isLoading: false,
- showRemoved: true,
enterpriseGroupMembersTableData: {
- itemCount: 2,
+ itemCount: 1,
pageCount: 1,
results: [{
- memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' },
+ memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' },
status: 'pending',
recentAction: 'Pending: April 02, 2024',
- memberEnrollments: 0,
- }, {
- memberDetails: { userEmail: 'tammy2@example.com', userName: 'tammy 2' },
- status: 'removed',
- recentAction: 'Removed: April 02, 2024',
- memberEnrollments: 0,
+ enrollmentCount: 1,
}],
},
- fetchEnterpriseGroupMembersTableData: jest.fn(),
- });
- // when we pass in showRemoved=true, we should see removed members
+ fetchEnterpriseGroupMembersTableData: mockFetchEnterpriseGroupMembersTableData,
+ };
+ useEnterpriseGroupMembersTableData.mockReturnValue(mockGroupData);
+ EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData.mockResolvedValue('a,b,c,\nd,e,f');
renderWithRouter();
+ userEvent.type(screen.getByText('Search by member details'), 'foobar');
+ userEvent.click(screen.getByTestId('members-table-enrollments-column-header'));
const removeToggle = screen.getByTestId('show-removed-toggle');
- expect(removeToggle).toHaveAttribute('checked', '');
- expect(screen.queryByText('Former member')).toBeInTheDocument();
- expect(screen.queryByText('tammy2@example.com')).toBeInTheDocument();
+ userEvent.click(removeToggle);
+
+ const downloadButton = screen.getByText('Download all (1)');
+ expect(downloadButton).toBeInTheDocument();
+ userEvent.click(downloadButton);
+ expect(EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData).toHaveBeenCalledWith(
+ mockAssignableSubsidyAccessPolicy.uuid,
+ {
+ format_csv: true,
+ traverse_pagination: true,
+ group_uuid: mockAssignableSubsidyAccessPolicy.groupAssociations[0],
+ user_query: 'foobar',
+ sort_by: 'member_details',
+ show_removed: true,
+ },
+ );
});
});
diff --git a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
index abc3042c4c..aa61bc92d0 100644
--- a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
+++ b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
@@ -695,10 +695,10 @@ describe('', () => {
const spentSection = within(screen.getByTestId('spent-section'));
expect(spentSection.getByText('No results found')).toBeInTheDocument();
expect(spentSection.getByText('Spent activity is driven by completed enrollments.', { exact: false })).toBeInTheDocument();
- const isSubsidyAccessPolicyWithAnalyicsApi = (
+ const isSubsidyAccessPolicyWithAnalyticsApi = (
budgetId === mockSubsidyAccessPolicyUUID && !isTopDownAssignmentEnabled
);
- if (budgetId === mockEnterpriseOfferId || isSubsidyAccessPolicyWithAnalyicsApi) {
+ if (budgetId === mockEnterpriseOfferId || isSubsidyAccessPolicyWithAnalyticsApi) {
// This copy is only present when the "Spent" table is backed by the
// analytics API (i.e., budget is an enterprise offer or a subsidy access
// policy with the LC2 feature flag disabled).
diff --git a/src/data/services/EnterpriseAccessApiService.js b/src/data/services/EnterpriseAccessApiService.js
index 84f4bab6ce..fe58d1078e 100644
--- a/src/data/services/EnterpriseAccessApiService.js
+++ b/src/data/services/EnterpriseAccessApiService.js
@@ -253,6 +253,12 @@ class EnterpriseAccessApiService {
const url = `${EnterpriseAccessApiService.baseUrl}/policy-allocation/${subsidyAccessPolicyUUID}/allocate/`;
return EnterpriseAccessApiService.apiClient().post(url, payload);
}
+
+ static fetchSubsidyHydratedGroupMembersData(subsidyAccessPolicyUUID, options) {
+ const queryParams = new URLSearchParams(options);
+ const subsidyHydratedGroupLearnersEndpoint = `${EnterpriseAccessApiService.baseUrl}/subsidy-access-policies/${subsidyAccessPolicyUUID}/group-members?${queryParams.toString()}`;
+ return EnterpriseAccessApiService.apiClient().get(subsidyHydratedGroupLearnersEndpoint);
+ }
}
export default EnterpriseAccessApiService;