Skip to content

Commit

Permalink
Merge pull request #1315 from openedx/saleem-latif/ENT-9434
Browse files Browse the repository at this point in the history
feat: Updated completions and engagements tabs in analytics according to performance enhancement updates on the backend.
  • Loading branch information
saleem-latif authored Sep 23, 2024
2 parents aefd6bd + 3401429 commit b8cd0c7
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 69 deletions.
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],
),
'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,
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],
),
'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,
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 Completions = ({

const {
isFetching, isError, data,
} = useEnterpriseAnalyticsData({
} = useEnterpriseCompletionsData({
enterpriseCustomerUUID: enterpriseId,
key: ANALYTICS_TABS.COMPLETIONS,
startDate,
Expand All @@ -25,6 +26,48 @@ const Completions = ({
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,
}) => ({
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 @@ const Completions = ({
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 @@ const Completions = ({
chartProps={{
data: data?.completionsOverTime,
xKey: 'passedDate',
yKey: 'count',
yKey: 'completionCount',
colorKey: 'enrollType',
colorMap: chartColorMap,
xAxisTitle: '',
Expand Down Expand Up @@ -88,14 +126,9 @@ const Completions = ({
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 @@ const Completions = ({
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 @@ const Completions = ({
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 @@ const Completions = ({
chartProps={{
data: data?.topSubjectsByCompletions,
xKey: 'courseSubject',
yKey: 'count',
yKey: 'completionCount',
colorKey: 'enrollType',
colorMap: chartColorMap,
yAxisTitle: intl.formatMessage({
Expand Down
Loading

0 comments on commit b8cd0c7

Please sign in to comment.