Skip to content

Commit

Permalink
feat: integrate initial aggregates data on analytics v2 page
Browse files Browse the repository at this point in the history
  • Loading branch information
jajjibhai008 committed Sep 9, 2024
1 parent 1957eff commit 2b555db
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 21 deletions.
42 changes: 24 additions & 18 deletions src/components/AdvanceAnalyticsV2/AnalyticsV2Page.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import {
Form, Tabs, Tab,
Form, Tabs, Tab, Stack,
} from '@openedx/paragon';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
Expand All @@ -13,6 +13,7 @@ import Engagements from './tabs/Engagements';
import Completions from './tabs/Completions';
import Leaderboard from './tabs/Leaderboard';
import Skills from './tabs/Skills';
import { useEnterpriseAnalyticsAggregatesData } from './data/hooks';

const PAGE_TITLE = 'AnalyticsV2';

Expand All @@ -22,28 +23,31 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
const [calculation, setCalculation] = useState('Total');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const dataRefreshDate = '';
const intl = useIntl();

const { isFetching, isError, data } = useEnterpriseAnalyticsAggregatesData({
enterpriseCustomerUUID: enterpriseId,
startDate,
endDate,
});
return (
<>
<Helmet title={PAGE_TITLE} />
<Hero title={PAGE_TITLE} />
<div className="container-fluid w-100">
<div className="row data-refresh-msg-container mb-4">
<Stack className="container-fluid w-100" gap={4}>
<div className="row data-refresh-msg-container">
<div className="col">
<span>
<FormattedMessage
id="advance.analytics.data.refresh.msg"
defaultMessage="Data updated on {date}"
description="Data refresh message"
values={{ date: dataRefreshDate }}
values={{ date: data?.lastUpdatedAt || '' }}
/>
</span>
</div>
</div>

<div className="row filter-container mb-4">
<div className="row filter-container">
<div className="col">
<Form.Group>
<Form.Label>
Expand All @@ -55,7 +59,8 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
</Form.Label>
<Form.Control
type="date"
value={startDate}
value={startDate || data?.minEnrollmentDate}
min={data?.minEnrollmentDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</Form.Group>
Expand All @@ -71,7 +76,8 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
</Form.Label>
<Form.Control
type="date"
value={endDate}
value={endDate || data?.maxEnrollmentDate}
max={data?.maxEnrollmentDate}
onChange={(e) => setEndDate(e.target.value)}
/>
</Form.Group>
Expand Down Expand Up @@ -168,13 +174,15 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
</div>
</div>

<div className="row stats-container mb-4">
<div className="row stats-container d-flex justify-content-center">
<Stats
enrollments={0}
distinctCourses={0}
dailySessions={0}
learningHours={0}
completions={0}
enrollments={data?.enrolls || 0}
distinctCourses={data?.courses || 0}
dailySessions={data?.sessions || 0}
learningHours={data?.hours || 0}
completions={data?.completions || 0}
isFetching={isFetching}
isError={isError}
/>
</div>

Expand Down Expand Up @@ -213,8 +221,6 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
<Engagements
startDate={startDate}
endDate={endDate}
granularity={granularity}
calculation={calculation}
enterpriseId={enterpriseId}
/>
</Tab>
Expand Down Expand Up @@ -264,7 +270,7 @@ const AnalyticsV2Page = ({ enterpriseId }) => {
</Tab>
</Tabs>
</div>
</div>
</Stack>
</>
);
};
Expand Down
25 changes: 22 additions & 3 deletions src/components/AdvanceAnalyticsV2/Stats.jsx
Original file line number Diff line number Diff line change
@@ -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,
enrollments, distinctCourses, dailySessions, learningHours, completions, isFetching, isError,
}) => {
const formatter = Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });

if (isError) {
return (

Check warning on line 14 in src/components/AdvanceAnalyticsV2/Stats.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/AdvanceAnalyticsV2/Stats.jsx#L14

Added line #L14 was not covered by tests
<FormattedMessage
id="advance.analyticsStats.aggregatesNotFound.errorMesssage"
defaultMessage="No Matching Data Found"
description="Error message when no data is found."
/>
);
}
return (
<div className="container-fluid analytics-stats">
<div className={classNames('container-fluid analytics-stats stats-container', { 'is-fetching': isFetching })}>
{isFetching && (
<div className="spinner-centered">
<Spinner animation="border" />
</div>
)}
<div className="row">
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-enrollments">
Expand Down Expand Up @@ -71,6 +88,8 @@ Stats.propTypes = {
dailySessions: PropTypes.number.isRequired,
learningHours: PropTypes.number.isRequired,
completions: PropTypes.number.isRequired,
isFetching: PropTypes.bool.isRequired,
isError: PropTypes.bool.isRequired,
};

export default Stats;
3 changes: 3 additions & 0 deletions src/components/AdvanceAnalyticsV2/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
22 changes: 22 additions & 0 deletions src/components/AdvanceAnalyticsV2/data/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
};
22 changes: 22 additions & 0 deletions src/components/AdvanceAnalyticsV2/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,25 @@
z-index: 2;
}
}

.stats-container {
position: relative;
&.is-fetching::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba($white, .7);
z-index: 1;
}

.spinner-centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
}
9 changes: 9 additions & 0 deletions src/data/services/EnterpriseDataApiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down

0 comments on commit 2b555db

Please sign in to comment.