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

feat: Updated completions and engagements tabs in analytics according to performance enhancement updates on the backend. #1315

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/components/AdvanceAnalyticsV2/data/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { advanceAnalyticsQueryKeys } from '../constants';
import EnterpriseDataApiService from '../../../../data/services/EnterpriseDataApiService';

export { default as useEnterpriseEnrollmentsData } from './useEnterpriseEnrollmentsData';
export { default as useEnterpriseEngagementData } from './useEnterpriseEngagementData';
export { default as useEnterpriseCompletionsData } from './useEnterpriseCompletionsData';

export const useEnterpriseAnalyticsData = ({
enterpriseCustomerUUID,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useQuery } from '@tanstack/react-query';
import _ from 'lodash';

import { useMemo } from 'react';
import { ANALYTICS_TABS, generateKey } from '../constants';
import { applyGranularity, applyCalculation } from '../utils';
import EnterpriseDataApiService from '../../../../data/services/EnterpriseDataApiService';

/**
Applies data transformations to the response data.

Apply transformations to the response data based on the granularity and calculation. Data transformation is applied
only on the items with the allowed enrollment types.

* @param {object} response - The response data returned from the API.
* @param {OpUnitType} granularity - Granularity of the data. e.g. `day`, `week`, `month`, `quarter`, `year`.
* @param {String} calculation - Calculation to apply on the data. e.g.
* `total`, `running_total`, `moving_average_3_periods`, `moving_average_7_periods`.
* @param {Array} allowedEnrollTypes - Allowed enrollment types to consider. e.g. [`certificate`, `audit`].
*/
const applyDataTransformations = (response, granularity, calculation, allowedEnrollTypes = ['certificate', 'audit']) => {
const modifiedResponse = _.cloneDeep(response);
if (modifiedResponse?.data?.completionsOverTime) {
let completionsOverTime = [];
for (let i = 0; i < allowedEnrollTypes.length; i++) {
const data = applyGranularity(
modifiedResponse.data.completionsOverTime.filter(
completion => completion.enrollType === allowedEnrollTypes[i],

Check warning on line 28 in src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseCompletionsData.js

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseCompletionsData.js#L28

Added line #L28 was not covered by tests
),
'passedDate',
'completionCount',
granularity,
);
completionsOverTime = completionsOverTime.concat(
applyCalculation(data, 'completionCount', calculation),
);
}

modifiedResponse.data.completionsOverTime = completionsOverTime;
}

return modifiedResponse;
};

/**
Fetches enterprise completion data.

* @param {String} enterpriseCustomerUUID - UUID of the enterprise customer.
* @param {Date} startDate - Start date for the data.
* @param {Date} endDate - End date for the data.
* @param {OpUnitType} granularity - Granularity of the data. e.g. `day`, `week`, `month`, `quarter`, `year`.
* @param {String} calculation - Calculation to apply on the data. e.g.
* `total`, `running_total`, `moving_average_3_periods`, `moving_average_7_periods`.
* @param {object} queryOptions - Additional options for the query.
*/
const useEnterpriseCompletionsData = ({
enterpriseCustomerUUID,
startDate,
endDate,
granularity = undefined,
calculation = undefined,

Check warning on line 61 in src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseCompletionsData.js

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseCompletionsData.js#L60-L61

Added lines #L60 - L61 were not covered by tests
queryOptions = {},
}) => {
const requestOptions = { startDate, endDate };
const response = useQuery({
queryKey: generateKey('completions', enterpriseCustomerUUID, requestOptions),
queryFn: () => EnterpriseDataApiService.fetchAdminAnalyticsData(
enterpriseCustomerUUID,
ANALYTICS_TABS.COMPLETIONS,
requestOptions,
),
staleTime: 0.5 * (1000 * 60 * 60), // 30 minutes. The time in milliseconds after data is considered stale.
cacheTime: 0.75 * (1000 * 60 * 60), // 45 minutes. Cache data will be garbage collected after this duration.
keepPreviousData: true,
...queryOptions,
});

return useMemo(() => applyDataTransformations(
response,
granularity,
calculation,
), [response, granularity, calculation]);
};

export default useEnterpriseCompletionsData;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useQuery } from '@tanstack/react-query';
import _ from 'lodash';

import { useMemo } from 'react';
import { ANALYTICS_TABS, generateKey } from '../constants';
import { applyGranularity, applyCalculation } from '../utils';
import EnterpriseDataApiService from '../../../../data/services/EnterpriseDataApiService';

/**
Applies data transformations to the response data.

Apply transformations to the response data based on the granularity and calculation. Data transformation is applied
only on the items with the allowed enrollment types.

* @param {object} response - The response data returned from the API.
* @param {OpUnitType} granularity - Granularity of the data. e.g. `day`, `week`, `month`, `quarter`, `year`.
* @param {String} calculation - Calculation to apply on the data. e.g.
* `total`, `running_total`, `moving_average_3_periods`, `moving_average_7_periods`.
* @param {Array} allowedEnrollTypes - Allowed enrollment types to consider. e.g. [`certificate`, `audit`].
*/
const applyDataTransformations = (response, granularity, calculation, allowedEnrollTypes = ['certificate', 'audit']) => {
const modifiedResponse = _.cloneDeep(response);
if (modifiedResponse?.data?.engagementOverTime) {
let engagementOverTime = [];
for (let i = 0; i < allowedEnrollTypes.length; i++) {
const data = applyGranularity(
modifiedResponse.data.engagementOverTime.filter(
engagement => engagement.enrollType === allowedEnrollTypes[i],

Check warning on line 28 in src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseEngagementData.js

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseEngagementData.js#L28

Added line #L28 was not covered by tests
),
'activityDate',
'learningTimeHours',
granularity,
);
engagementOverTime = engagementOverTime.concat(
applyCalculation(data, 'learningTimeHours', calculation),
);
}

modifiedResponse.data.engagementOverTime = engagementOverTime;
}

return modifiedResponse;
};

/**
Fetches enterprise engagement data.

* @param {String} enterpriseCustomerUUID - UUID of the enterprise customer.
* @param {Date} startDate - Start date for the data.
* @param {Date} endDate - End date for the data.
* @param {OpUnitType} granularity - Granularity of the data. e.g. `day`, `week`, `month`, `quarter`, `year`.
* @param {String} calculation - Calculation to apply on the data. e.g.
* `total`, `running_total`, `moving_average_3_periods`, `moving_average_7_periods`.
* @param {object} queryOptions - Additional options for the query.
*/
const useEnterpriseEngagementData = ({
enterpriseCustomerUUID,
startDate,
endDate,
granularity = undefined,
calculation = undefined,

Check warning on line 61 in src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseEngagementData.js

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/data/hooks/useEnterpriseEngagementData.js#L60-L61

Added lines #L60 - L61 were not covered by tests
queryOptions = {},
}) => {
const requestOptions = { startDate, endDate };
const response = useQuery({
queryKey: generateKey('engagements', enterpriseCustomerUUID, requestOptions),
queryFn: () => EnterpriseDataApiService.fetchAdminAnalyticsData(
enterpriseCustomerUUID,
ANALYTICS_TABS.ENGAGEMENTS,
requestOptions,
),
staleTime: 0.5 * (1000 * 60 * 60), // 30 minutes. The time in milliseconds after data is considered stale.
cacheTime: 0.75 * (1000 * 60 * 60), // 45 minutes. Cache data will be garbage collected after this duration.
keepPreviousData: true,
...queryOptions,
});

return useMemo(() => applyDataTransformations(
response,
granularity,
calculation,
), [response, granularity, calculation]);
};

export default useEnterpriseEngagementData;
94 changes: 61 additions & 33 deletions src/components/AdvanceAnalyticsV2/tabs/Completions.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import Header from '../Header';
import { ANALYTICS_TABS, CHART_TYPES, chartColorMap } from '../data/constants';
import { ANALYTICS_TABS, chartColorMap } from '../data/constants';
import AnalyticsTable from './AnalyticsTable';
import ChartWrapper from '../charts/ChartWrapper';
import { useEnterpriseAnalyticsData } from '../data/hooks';
import DownloadCSV from '../DownloadCSV';
import { constructChartHoverTemplate } from '../data/utils';
import { useEnterpriseCompletionsData } from '../data/hooks';
import DownloadCSVButton from '../DownloadCSVButton';
import { constructChartHoverTemplate, modifyDataToIntroduceEnrollTypeCount } from '../data/utils';

const Completions = ({
startDate, endDate, granularity, calculation, enterpriseId,
Expand All @@ -16,7 +17,7 @@

const {
isFetching, isError, data,
} = useEnterpriseAnalyticsData({
} = useEnterpriseCompletionsData({
enterpriseCustomerUUID: enterpriseId,
key: ANALYTICS_TABS.COMPLETIONS,
startDate,
Expand All @@ -25,6 +26,48 @@
calculation,
});

const completionsOverTimeForCSV = useMemo(() => {
const completionsOverTime = modifyDataToIntroduceEnrollTypeCount(
data?.completionsOverTime,
'passedDate',
'completionCount',
);
return completionsOverTime.map(({ activityDate, certificate, audit }) => ({
activity_date: dayjs.utc(activityDate).toISOString().split('T')[0],
certificate,
audit,
}));
}, [data]);

const topCoursesByCompletionForCSV = useMemo(() => {
const topCoursesByCompletions = modifyDataToIntroduceEnrollTypeCount(
data?.topCoursesByCompletions,
'courseKey',
'completionCount',
);
return topCoursesByCompletions.map(({
courseKey, courseTitle, certificate, audit,
}) => ({

Check warning on line 50 in src/components/AdvanceAnalyticsV2/tabs/Completions.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/tabs/Completions.jsx#L50

Added line #L50 was not covered by tests
course_key: courseKey,
course_title: courseTitle,
certificate,
audit,
}));
}, [data]);

const topSubjectsByCompletionsForCSV = useMemo(() => {
const topSubjectsByCompletions = modifyDataToIntroduceEnrollTypeCount(
data?.topSubjectsByCompletions,
'courseSubject',
'completionCount',
);
return topSubjectsByCompletions.map(({ courseSubject, certificate, audit }) => ({
course_subject: courseSubject,
certificate,
audit,
}));
}, [data]);

return (
<div className="tab-completions mt-4">
<div className="completions-over-time-chart-container mb-4">
Expand All @@ -40,14 +83,9 @@
description: 'Subtitle for the completions over time chart.',
})}
DownloadCSVComponent={(
<DownloadCSV
enterpriseId={enterpriseId}
startDate={startDate}
endDate={endDate}
activeTab={ANALYTICS_TABS.COMPLETIONS}
granularity={granularity}
calculation={calculation}
chartType={CHART_TYPES.COMPLETIONS_OVER_TIME}
<DownloadCSVButton
jsonData={completionsOverTimeForCSV}
csvFileName={`Completions over time - ${startDate} - ${endDate} (${granularity} ${calculation})`}
/>
)}
/>
Expand All @@ -58,7 +96,7 @@
chartProps={{
data: data?.completionsOverTime,
xKey: 'passedDate',
yKey: 'count',
yKey: 'completionCount',
colorKey: 'enrollType',
colorMap: chartColorMap,
xAxisTitle: '',
Expand Down Expand Up @@ -88,14 +126,9 @@
description: 'Subtitle for the top 10 courses by completions chart.',
})}
DownloadCSVComponent={(
<DownloadCSV
enterpriseId={enterpriseId}
startDate={startDate}
endDate={endDate}
activeTab={ANALYTICS_TABS.COMPLETIONS}
granularity={granularity}
calculation={calculation}
chartType={CHART_TYPES.TOP_COURSES_BY_COMPLETIONS}
<DownloadCSVButton
jsonData={topCoursesByCompletionForCSV}
csvFileName={`Top Courses by Completion - ${startDate} - ${endDate} (${granularity} ${calculation})`}
/>
)}
/>
Expand All @@ -106,7 +139,7 @@
chartProps={{
data: data?.topCoursesByCompletions,
xKey: 'courseKey',
yKey: 'count',
yKey: 'completionCount',
colorKey: 'enrollType',
colorMap: chartColorMap,
yAxisTitle: intl.formatMessage({
Expand Down Expand Up @@ -139,14 +172,9 @@
description: 'Subtitle for the top 10 subjects by completion chart.',
})}
DownloadCSVComponent={(
<DownloadCSV
enterpriseId={enterpriseId}
startDate={startDate}
endDate={endDate}
activeTab={ANALYTICS_TABS.COMPLETIONS}
granularity={granularity}
calculation={calculation}
chartType={CHART_TYPES.TOP_SUBJECTS_BY_COMPLETIONS}
<DownloadCSVButton
jsonData={topSubjectsByCompletionsForCSV}
csvFileName={`Top Subjects by Completion - ${startDate} - ${endDate} (${granularity} ${calculation})`}
/>
)}
/>
Expand All @@ -157,7 +185,7 @@
chartProps={{
data: data?.topSubjectsByCompletions,
xKey: 'courseSubject',
yKey: 'count',
yKey: 'completionCount',
colorKey: 'enrollType',
colorMap: chartColorMap,
yAxisTitle: intl.formatMessage({
Expand Down
Loading