Skip to content

Commit 89592af

Browse files
Merge pull request #1723 from openedx/maham/ENT-11176
[ENT-11176] Download filtered CSV data in analytics
2 parents 7dbd320 + 389627a commit 89592af

File tree

5 files changed

+201
-8
lines changed

5 files changed

+201
-8
lines changed

src/components/AdvanceAnalyticsV2.0/DownloadCSVButton.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const DownloadCSVButton = ({
1717
useEffect(() => {
1818
if (jsonData.length > 0) {
1919
setButtonState('default');
20+
} else {
21+
setButtonState('disabled');
2022
}
2123
}, [jsonData]);
2224

src/components/AdvanceAnalyticsV2.0/data/hooks.test.jsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,38 @@ describe('useEnterpriseAnalyticsData', () => {
249249
},
250250
);
251251
});
252+
it('includes groupUUID in API request when provided', async () => {
253+
const startDate = '2021-01-01';
254+
const endDate = '2021-12-31';
255+
const groupUUID = 'group-1234';
256+
257+
const requestOptions = {
258+
startDate,
259+
endDate,
260+
groupUUID,
261+
};
262+
263+
const { result } = renderHook(
264+
() => useEnterpriseAnalyticsData({
265+
enterpriseCustomerUUID: TEST_ENTERPRISE_ID,
266+
key: 'completions',
267+
startDate,
268+
endDate,
269+
groupUUID,
270+
}),
271+
{ wrapper },
272+
);
273+
274+
await waitFor(() => {
275+
expect(result.current.isFetching).toBe(false);
276+
});
277+
278+
expect(EnterpriseDataApiService.fetchAdminAnalyticsData).toHaveBeenCalledWith(
279+
TEST_ENTERPRISE_ID,
280+
'completions',
281+
requestOptions,
282+
);
283+
});
252284
});
253285

254286
describe('useEnterpriseAnalyticsAggregatesData', () => {

src/components/AdvanceAnalyticsV2.0/data/hooks/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ export const useEnterpriseAnalyticsData = ({
3535
calculation,
3636
page: currentPage,
3737
pageSize,
38-
groupUUID,
3938
};
4039

4140
if (courseType && courseType !== COURSE_TYPES.ALL_COURSE_TYPES) {
@@ -50,6 +49,10 @@ export const useEnterpriseAnalyticsData = ({
5049
requestOptions.budgetUUID = budgetUUID;
5150
}
5251

52+
if (groupUUID) {
53+
requestOptions.groupUUID = groupUUID;
54+
}
55+
5356
return useQuery({
5457
queryKey: advanceAnalyticsQueryKeys[key](enterpriseCustomerUUID, requestOptions),
5558
queryFn: () => EnterpriseDataApiService.fetchAdminAnalyticsData(

src/components/AdvanceAnalyticsV2.0/tables/AnalyticsTable.jsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
44
import { DataTable, Icon } from '@openedx/paragon';
55
import { Link } from 'react-router-dom';
66
import { Download } from '@openedx/paragon/icons';
7-
import { analyticsDataTableKeys } from '../data/constants';
7+
import { analyticsDataTableKeys, COURSE_TYPES, ALL_COURSES } from '../data/constants';
88

99
import { useEnterpriseAnalyticsData, usePaginatedData } from '../data/hooks';
1010
import EnterpriseDataApiService from '../../../data/services/EnterpriseDataApiService';
@@ -44,14 +44,31 @@ const AnalyticsTable = ({
4444
course,
4545
});
4646

47+
const csvDownloadOptions = {
48+
start_date: startDate,
49+
end_date: endDate,
50+
};
51+
52+
if (courseType && courseType !== COURSE_TYPES.ALL_COURSE_TYPES) {
53+
csvDownloadOptions.course_type = courseType;
54+
}
55+
56+
if (course?.value && course?.value !== ALL_COURSES.value) {
57+
csvDownloadOptions.course_key = course.value;
58+
}
59+
60+
if (budgetUUID) {
61+
csvDownloadOptions.budget_uuid = budgetUUID;
62+
}
63+
64+
if (groupUUID) {
65+
csvDownloadOptions.group_uuid = groupUUID;
66+
}
67+
4768
const CSVDownloadURL = EnterpriseDataApiService.getAnalyticsCSVDownloadURL(
4869
analyticsDataTableKeys[name],
4970
enterpriseId,
50-
{
51-
start_date: startDate,
52-
end_date: endDate,
53-
groupUUID,
54-
},
71+
csvDownloadOptions,
5572
);
5673

5774
const fetchData = useCallback(
@@ -73,7 +90,7 @@ const AnalyticsTable = ({
7390
<Link
7491
to={CSVDownloadURL}
7592
target="_blank"
76-
className="btn btn-sm btn-primary rounded-0"
93+
className={`btn btn-sm btn-primary rounded-0 ${!data?.results?.length ? 'disabled' : ''}`}
7794
onClick={() => trackCsvDownloadClick(entityId)}
7895
>
7996
<Icon src={Download} className="me-2" />
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import React from 'react';
3+
import { BrowserRouter as Router } from 'react-router-dom';
4+
import { render, screen, fireEvent } from '@testing-library/react';
5+
import { IntlProvider } from '@edx/frontend-platform/i18n';
6+
import { QueryClientProvider } from '@tanstack/react-query';
7+
import AnalyticsTable from './AnalyticsTable';
8+
import { useEnterpriseAnalyticsData, usePaginatedData } from '../data/hooks';
9+
import EnterpriseDataApiService from '../../../data/services/EnterpriseDataApiService';
10+
import { queryClient } from '../../test/testUtils';
11+
import { analyticsDataTableKeys } from '../data/constants';
12+
import '@testing-library/jest-dom';
13+
14+
// Mock hooks
15+
jest.mock('../data/hooks', () => ({
16+
useEnterpriseAnalyticsData: jest.fn(),
17+
usePaginatedData: jest.fn(),
18+
}));
19+
20+
// Mock service
21+
jest.mock('../../../data/services/EnterpriseDataApiService', () => ({
22+
getAnalyticsCSVDownloadURL: jest.fn(() => '/mock.csv'),
23+
}));
24+
25+
const defaultProps = {
26+
name: 'leaderboardTable',
27+
tableColumns: [],
28+
tableTitle: 'Leaderboard Table',
29+
entityId: 'entity-1',
30+
enterpriseId: 'enterprise-1',
31+
startDate: '2023-01-01',
32+
endDate: '2023-02-01',
33+
trackCsvDownloadClick: jest.fn(),
34+
};
35+
36+
function renderWithIntl(ui) {
37+
return render(
38+
<Router>
39+
<QueryClientProvider client={queryClient()}>
40+
<IntlProvider locale="en">
41+
{ui}
42+
</IntlProvider>
43+
</QueryClientProvider>
44+
</Router>,
45+
);
46+
}
47+
48+
describe('AnalyticsTable', () => {
49+
beforeEach(() => {
50+
jest.clearAllMocks();
51+
usePaginatedData.mockReturnValue({
52+
data: [],
53+
itemCount: 0,
54+
pageCount: 1,
55+
});
56+
});
57+
58+
test('disables CSV button when data.results is empty', () => {
59+
useEnterpriseAnalyticsData.mockReturnValue({
60+
data: { results: [] },
61+
isFetching: false,
62+
});
63+
64+
renderWithIntl(<AnalyticsTable {...defaultProps} />);
65+
66+
const button = screen.getByRole('link', { name: /download/i });
67+
68+
expect(button).toHaveClass('disabled');
69+
});
70+
71+
test('enables CSV button when data.results has items', () => {
72+
useEnterpriseAnalyticsData.mockReturnValue({
73+
data: { results: [{ id: 1 }] },
74+
isFetching: false,
75+
});
76+
77+
renderWithIntl(<AnalyticsTable {...defaultProps} />);
78+
79+
const button = screen.getByRole('link', { name: /download/i });
80+
81+
expect(button).not.toHaveClass('disabled');
82+
});
83+
84+
test('CSV download URL is generated', () => {
85+
useEnterpriseAnalyticsData.mockReturnValue({
86+
data: { results: [] },
87+
isFetching: false,
88+
});
89+
90+
renderWithIntl(<AnalyticsTable {...defaultProps} />);
91+
92+
expect(EnterpriseDataApiService.getAnalyticsCSVDownloadURL).toHaveBeenCalled();
93+
});
94+
95+
test('CSV download URL includes correct csvDownloadOptions', () => {
96+
useEnterpriseAnalyticsData.mockReturnValue({
97+
data: { results: [{ id: 1 }] },
98+
isFetching: false,
99+
});
100+
101+
const props = {
102+
...defaultProps,
103+
courseType: 'special',
104+
course: { value: 'course-123', label: 'Course 123' },
105+
budgetUUID: 'budget-xyz',
106+
groupUUID: 'group-abc',
107+
};
108+
109+
renderWithIntl(<AnalyticsTable {...props} />);
110+
111+
expect(EnterpriseDataApiService.getAnalyticsCSVDownloadURL).toHaveBeenCalledWith(
112+
analyticsDataTableKeys[props.name],
113+
props.enterpriseId,
114+
{
115+
start_date: props.startDate,
116+
end_date: props.endDate,
117+
course_type: props.courseType,
118+
course_key: props.course.value,
119+
budget_uuid: props.budgetUUID,
120+
group_uuid: props.groupUUID,
121+
},
122+
);
123+
});
124+
125+
test('clicking CSV button triggers trackCsvDownloadClick', () => {
126+
useEnterpriseAnalyticsData.mockReturnValue({
127+
data: { results: [{ id: 1 }] },
128+
isFetching: false,
129+
});
130+
131+
renderWithIntl(<AnalyticsTable {...defaultProps} />);
132+
133+
const link = screen.getByRole('link', { name: /download/i });
134+
135+
fireEvent.click(link);
136+
137+
expect(defaultProps.trackCsvDownloadClick).toHaveBeenCalledWith('entity-1');
138+
});
139+
});

0 commit comments

Comments
 (0)