>
);
};
diff --git a/src/components/AdvanceAnalyticsV2/Stats.jsx b/src/components/AdvanceAnalyticsV2/Stats.jsx
index 81c89ae91e..a30b55bf7d 100644
--- a/src/components/AdvanceAnalyticsV2/Stats.jsx
+++ b/src/components/AdvanceAnalyticsV2/Stats.jsx
@@ -1,14 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import {
+ Spinner,
+} from '@openedx/paragon';
+import classNames from 'classnames';
const Stats = ({
- enrollments, distinctCourses, dailySessions, learningHours, completions,
+ isFetching, isError, data,
}) => {
const formatter = Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });
-
+ if (isError) {
+ return (
+
+
+ {isFetching && (
+
+
+
+ )}
@@ -18,7 +35,7 @@ const Stats = ({
description="Title for the enrollments stat."
/>
-
{formatter.format(enrollments)}
+
{formatter.format(data?.enrolls || 0)}
@@ -28,7 +45,7 @@ const Stats = ({
description="Title for the distinct courses stat."
/>
-
{formatter.format(distinctCourses)}
+
{formatter.format(data?.courses || 0)}
@@ -38,7 +55,7 @@ const Stats = ({
description="Title for the daily sessions stat."
/>
-
{formatter.format(dailySessions)}
+
{formatter.format(data?.sessions || 0)}
@@ -48,7 +65,7 @@ const Stats = ({
description="Title for the learning hours stat."
/>
-
{formatter.format(learningHours)}
+
{formatter.format(data?.hours || 0)}
@@ -58,7 +75,7 @@ const Stats = ({
description="Title for the completions stat."
/>
-
{formatter.format(completions)}
+
{formatter.format(data?.completions || 0)}
@@ -66,11 +83,16 @@ const Stats = ({
};
Stats.propTypes = {
- enrollments: PropTypes.number.isRequired,
- distinctCourses: PropTypes.number.isRequired,
- dailySessions: PropTypes.number.isRequired,
- learningHours: PropTypes.number.isRequired,
- completions: PropTypes.number.isRequired,
+ data: PropTypes.shape({
+ enrolls: PropTypes.number,
+ courses: PropTypes.number,
+ sessions: PropTypes.number,
+ hours: PropTypes.number,
+ completions: PropTypes.number,
+ }).isRequired,
+ isFetching: PropTypes.bool.isRequired,
+ isError: PropTypes.bool.isRequired,
+
};
export default Stats;
diff --git a/src/components/AdvanceAnalyticsV2/data/constants.js b/src/components/AdvanceAnalyticsV2/data/constants.js
index 1fa630a9ca..846daefa15 100644
--- a/src/components/AdvanceAnalyticsV2/data/constants.js
+++ b/src/components/AdvanceAnalyticsV2/data/constants.js
@@ -77,6 +77,9 @@ export const advanceAnalyticsQueryKeys = {
leaderboardTable: (enterpriseUUID, requestOptions) => (
generateKey(analyticsDataTableKeys.leaderboard, enterpriseUUID, requestOptions)
),
+ aggregates: (enterpriseUUID, requestOptions) => (
+ generateKey('aggregates', enterpriseUUID, requestOptions)
+ ),
};
export const skillsColorMap = {
diff --git a/src/components/AdvanceAnalyticsV2/data/hooks.js b/src/components/AdvanceAnalyticsV2/data/hooks.js
index fd8ffdb77f..4e710e4def 100644
--- a/src/components/AdvanceAnalyticsV2/data/hooks.js
+++ b/src/components/AdvanceAnalyticsV2/data/hooks.js
@@ -47,3 +47,25 @@ export const usePaginatedData = (data) => useMemo(() => {
data: [],
};
}, [data]);
+
+export const useEnterpriseAnalyticsAggregatesData = ({
+ enterpriseCustomerUUID,
+ startDate,
+ endDate,
+ queryOptions = {},
+}) => {
+ const requestOptions = {
+ startDate, endDate,
+ };
+ return useQuery({
+ queryKey: advanceAnalyticsQueryKeys.aggregates(enterpriseCustomerUUID, requestOptions),
+ queryFn: () => EnterpriseDataApiService.fetchAdminAggregatesData(
+ enterpriseCustomerUUID,
+ 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,
+ });
+};
diff --git a/src/components/AdvanceAnalyticsV2/styles/index.scss b/src/components/AdvanceAnalyticsV2/styles/index.scss
index 21748bd525..bc64ffc5a3 100644
--- a/src/components/AdvanceAnalyticsV2/styles/index.scss
+++ b/src/components/AdvanceAnalyticsV2/styles/index.scss
@@ -2,26 +2,35 @@
font-size: 2.5rem;
}
-.analytics-chart-container {
+@mixin fetching-overlay {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba($white, 0.7);
+ z-index: 1;
+}
+
+@mixin spinner-centered {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 2;
+}
+
+.analytics-chart-container,
+.stats-container {
position: relative;
- min-height: 40vh;
&.is-fetching::before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba($white, .7);
- z-index: 1;
+ @include fetching-overlay;
}
.spinner-centered {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 2;
+ @include spinner-centered;
}
}
+
diff --git a/src/components/AdvanceAnalyticsV2/tests/Stats.test.jsx b/src/components/AdvanceAnalyticsV2/tests/Stats.test.jsx
index 340f6ab9cc..4c3b8fc03f 100644
--- a/src/components/AdvanceAnalyticsV2/tests/Stats.test.jsx
+++ b/src/components/AdvanceAnalyticsV2/tests/Stats.test.jsx
@@ -3,17 +3,18 @@ import { mount } from 'enzyme';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import Stats from '../Stats';
+const data = {
+ enrolls: 150400,
+ courses: 365,
+ sessions: 1892,
+ hours: 25349876,
+ completions: 265400,
+};
describe('Stats', () => {
it('renders the correct values for each statistic', () => {
const wrapper = mount(
-
+
,
);
diff --git a/src/data/services/EnterpriseDataApiService.js b/src/data/services/EnterpriseDataApiService.js
index a64a26ae7a..79f3d04884 100644
--- a/src/data/services/EnterpriseDataApiService.js
+++ b/src/data/services/EnterpriseDataApiService.js
@@ -167,6 +167,15 @@ class EnterpriseDataApiService {
return EnterpriseDataApiService.apiClient().get(url).then((response) => camelCaseObject(response.data));
}
+ static fetchAdminAggregatesData(enterpriseCustomerUUID, options) {
+ const baseURL = EnterpriseDataApiService.enterpriseAdminAnalyticsV2BaseUrl;
+ const enterpriseUUID = EnterpriseDataApiService.getEnterpriseUUID(enterpriseCustomerUUID);
+ const transformOptions = omitBy(snakeCaseObject(options), isFalsy);
+ const queryParams = new URLSearchParams(transformOptions);
+ const url = `${baseURL}${enterpriseUUID}?${queryParams.toString()}`;
+ return EnterpriseDataApiService.apiClient().get(url).then((response) => camelCaseObject(response.data));
+ }
+
static fetchDashboardInsights(enterpriseId) {
const enterpriseUUID = EnterpriseDataApiService.getEnterpriseUUID(enterpriseId);
const url = `${EnterpriseDataApiService.enterpriseAdminBaseUrl}insights/${enterpriseUUID}`;