-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: display ai analytics section on LPR page
- Loading branch information
1 parent
6a47c98
commit 71c016b
Showing
17 changed files
with
2,628 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { logError } from '@edx/frontend-platform/logging'; | ||
import LmsApiService from '../../../data/services/LmsApiService'; | ||
|
||
const useAIAnalyticsSummary = (enterpriseId, insights) => { | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [analyticsSummaryData, setAnalyticsSummaryData] = useState(null); | ||
|
||
useEffect(() => { | ||
const fetchData = async () => { | ||
try { | ||
setIsLoading(true); | ||
const response = await LmsApiService.generateAIAnalyticsSummary(enterpriseId, insights); | ||
setAnalyticsSummaryData(response.data); | ||
} catch (error) { | ||
logError(error); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
if (enterpriseId && insights) { | ||
fetchData(); | ||
} else { | ||
setIsLoading(false); | ||
} | ||
}, [enterpriseId, insights]); | ||
|
||
return { | ||
isLoading, | ||
data: analyticsSummaryData, | ||
}; | ||
}; | ||
|
||
export default useAIAnalyticsSummary; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import React, { useState } from 'react'; | ||
import { connect } from 'react-redux'; | ||
import PropTypes from 'prop-types'; | ||
import { | ||
Button, Card, Stack, Badge, | ||
} from '@edx/paragon'; | ||
import { FormattedMessage } from '@edx/frontend-platform/i18n'; | ||
import { AutoFixHigh, Groups } from '@edx/paragon/icons'; | ||
import useAIAnalyticsSummary from '../AIAnalyticsSummary/data/hooks'; | ||
|
||
const AnalyticsDetailCard = ({ onClose, data }) => ( | ||
<Card className="mt-3 mb-4"> | ||
<Card.Section> | ||
<Badge variant="light" className="mb-3 font-weight-semibold"> | ||
Beta | ||
</Badge> | ||
<Stack gap={1} direction="horizontal"> | ||
<p className="card-text text-justify small">{data || 'No insights found..'}</p> | ||
<Button variant="link" className="mb-4 ml-3" onClick={onClose}> | ||
<span className="small font-weight-bold text-gray-800">Dismiss</span> | ||
</Button> | ||
</Stack> | ||
<label className="x-small" htmlFor="poweredBylabel">Powered by OpenAI</label> | ||
</Card.Section> | ||
</Card> | ||
); | ||
|
||
AnalyticsDetailCard.propTypes = { | ||
onClose: PropTypes.func.isRequired, | ||
data: PropTypes.string, | ||
}; | ||
|
||
const AIAnalyticsSummary = ({ enterpriseId, insights }) => { | ||
const [showSummarizeCard, setShowSummarizeCard] = useState(false); | ||
const [showTrackProgressCard, setShowTrackProgressCard] = useState(false); | ||
|
||
const analyticsSummary = useAIAnalyticsSummary(enterpriseId, insights); | ||
|
||
const toggleSummarizeCard = () => { | ||
setShowSummarizeCard(!showSummarizeCard); | ||
setShowTrackProgressCard(false); | ||
}; | ||
|
||
const toggleTrackProgressCard = () => { | ||
setShowTrackProgressCard(!showTrackProgressCard); | ||
setShowSummarizeCard(false); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Stack gap={3} direction="horizontal"> | ||
<Button | ||
variant="outline-primary" | ||
className="d-sm-inline" | ||
onClick={toggleSummarizeCard} | ||
data-testid="summarize-analytics" | ||
> | ||
<> | ||
<AutoFixHigh className="mr-2" /> | ||
<FormattedMessage id="adminPortal.summarizeAnalytics" defaultMessage="Summarize Analytics" /> | ||
</> | ||
</Button> | ||
<Button | ||
variant="outline-primary" | ||
className="d-sm-inline" | ||
onClick={toggleTrackProgressCard} | ||
data-testid="track-progress" | ||
> | ||
<> | ||
<Groups className="mr-2" /> | ||
<FormattedMessage id="adminPortal.trackProgress" defaultMessage="Track Progress" /> | ||
</> | ||
</Button> | ||
</Stack> | ||
{showSummarizeCard && ( | ||
<AnalyticsDetailCard | ||
data={analyticsSummary?.data?.learner_engagement} | ||
onClose={() => setShowSummarizeCard(false)} | ||
/> | ||
)} | ||
{showTrackProgressCard && ( | ||
<AnalyticsDetailCard | ||
data={analyticsSummary?.data?.learner_progress} | ||
onClose={() => setShowTrackProgressCard(false)} | ||
/> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
const mapStateToProps = state => ({ | ||
insights: state.dashboardInsights.insights, | ||
}); | ||
|
||
AIAnalyticsSummary.propTypes = { | ||
enterpriseId: PropTypes.string.isRequired, | ||
insights: PropTypes.objectOf(PropTypes.shape), | ||
}; | ||
|
||
export default connect(mapStateToProps)(AIAnalyticsSummary); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import React from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { Provider } from 'react-redux'; | ||
import renderer from 'react-test-renderer'; | ||
import configureMockStore from 'redux-mock-store'; | ||
import { MemoryRouter } from 'react-router-dom'; | ||
import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
import thunk from 'redux-thunk'; | ||
import AIAnalyticsSummary from './AIAnalyticsSummary'; | ||
|
||
const mockedInsights = { | ||
learner_progress: { | ||
enterprise_customer_uuid: 'aac56d39-f38d-4510-8ef9-085cab048ea9', | ||
enterprise_customer_name: 'Microsoft Corporation', | ||
active_subscription_plan: true, | ||
assigned_licenses: 0, | ||
activated_licenses: 0, | ||
assigned_licenses_percentage: 0.0, | ||
activated_licenses_percentage: 0.0, | ||
active_enrollments: 1026, | ||
at_risk_enrollment_less_than_one_hour: 26, | ||
at_risk_enrollment_end_date_soon: 15, | ||
at_risk_enrollment_dormant: 918, | ||
created_at: '2023-10-02T03:24:17Z', | ||
}, | ||
learner_engagement: { | ||
enterprise_customer_uuid: 'aac56d39-f38d-4510-8ef9-085cab048ea9', | ||
enterprise_customer_name: 'Microsoft Corporation', | ||
enrolls: 49, | ||
enrolls_prior: 45, | ||
passed: 2, | ||
passed_prior: 0, | ||
engage: 67, | ||
engage_prior: 50, | ||
hours: 62, | ||
hours_prior: 49, | ||
contract_end_date: '2022-06-13T00:00:00Z', | ||
active_contract: false, | ||
created_at: '2023-10-02T03:24:40Z', | ||
}, | ||
}; | ||
const mockStore = configureMockStore([thunk]); | ||
const store = mockStore({ | ||
portalConfiguration: { | ||
enterpriseId: 'test-enterprise-id', | ||
}, | ||
dashboardInsights: mockedInsights, | ||
}); | ||
|
||
const AIAnalyticsSummaryWrapper = props => ( | ||
<MemoryRouter> | ||
<Provider store={store}> | ||
<IntlProvider locale="en"> | ||
<AIAnalyticsSummary | ||
enterpriseId="test-enterprise-id" | ||
insights={mockedInsights} | ||
{...props} | ||
/>, | ||
</IntlProvider> | ||
</Provider> | ||
</MemoryRouter> | ||
); | ||
|
||
describe('<AIAnalyticsSummary />', () => { | ||
it('should render action buttons correctly', () => { | ||
const tree = renderer | ||
.create(( | ||
<AIAnalyticsSummaryWrapper | ||
insights={mockedInsights} | ||
/> | ||
)) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
it('should display AnalyticsDetailCard when Summarize Analytics button is clicked', () => { | ||
const wrapper = mount(<AIAnalyticsSummaryWrapper insights={mockedInsights} />); | ||
wrapper.find('[data-testid="summarize-analytics"]').first().simulate('click'); | ||
|
||
const tree = renderer | ||
.create(<AIAnalyticsSummaryWrapper insights={mockedInsights} />) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import React from 'react'; | ||
import { Skeleton, Stack } from '@edx/paragon'; | ||
|
||
const AIAnalyticsSummarySkeleton = () => ( | ||
<Stack direction="horizontal" gap={2}> | ||
<Skeleton height={40} width={250} /> | ||
<Skeleton height={40} width={250} /> | ||
</Stack> | ||
); | ||
|
||
export default AIAnalyticsSummarySkeleton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.