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: translate chart hover template and legend #1299

Merged
merged 2 commits into from
Sep 19, 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
13 changes: 12 additions & 1 deletion src/components/AdvanceAnalyticsV2/charts/ChartWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,18 @@ ChartWrapper.propTypes = {
isFetching: PropTypes.bool.isRequired,
isError: PropTypes.bool.isRequired,
chartType: PropTypes.oneOf(['ScatterChart', 'LineChart', 'BarChart']).isRequired,
chartProps: PropTypes.shape({ data: PropTypes.shape({}) }).isRequired,
chartProps: PropTypes.shape({
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
xKey: PropTypes.string.isRequired,
yKey: PropTypes.string.isRequired,
colorKey: PropTypes.string.isRequired,
colorMap: PropTypes.objectOf(PropTypes.string).isRequired,
hovertemplate: PropTypes.string.isRequired,
xAxisTitle: PropTypes.string,
yAxisTitle: PropTypes.string,
markerSizeKey: PropTypes.string,
customDataKeys: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
loadingMessage: PropTypes.string.isRequired,
};

Expand Down
7 changes: 5 additions & 2 deletions src/components/AdvanceAnalyticsV2/charts/ScatterChart.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useMemo } from 'react';
import Plot from 'react-plotly.js';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../messages';

/**
* ScatterChart component renders a scatter chart using Plotly.js.
Expand All @@ -21,6 +23,7 @@ import PropTypes from 'prop-types';
const ScatterChart = ({
data, xKey, yKey, colorKey, colorMap, hovertemplate, xAxisTitle, yAxisTitle, markerSizeKey, customDataKeys,
}) => {
const intl = useIntl();
const categories = Object.keys(colorMap);

const traces = useMemo(() => categories.map(category => {
Expand All @@ -30,15 +33,15 @@ const ScatterChart = ({
y: filteredData.map(item => item[yKey]),
type: 'scatter',
mode: 'markers',
name: category,
name: messages[category] ? intl.formatMessage(messages[category]) : category,
marker: {
color: colorMap[category],
size: filteredData.map(item => item[markerSizeKey] * 0.015).map(size => (size < 5 ? size + 6 : size)),
},
customdata: customDataKeys.length ? filteredData.map(item => customDataKeys.map(key => item[key])) : [],
hovertemplate,
};
}), [data, xKey, yKey, colorKey, colorMap, hovertemplate, categories, markerSizeKey, customDataKeys]);
}), [data, xKey, yKey, colorKey, colorMap, hovertemplate, categories, markerSizeKey, customDataKeys, intl]);

const layout = {
margin: { t: 0 },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import Plot from 'react-plotly.js';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import ScatterChart from './ScatterChart';

describe('ScatterChart', () => {
Expand Down Expand Up @@ -28,8 +29,10 @@ describe('ScatterChart', () => {
};

it('renders correctly', () => {
const wrapper = shallow(
<ScatterChart {...props} />,
const wrapper = mount(
<IntlProvider locale="en">
<ScatterChart {...props} />,
</IntlProvider>,
);
const plotComponent = wrapper.find(Plot);
const traces = plotComponent.prop('data');
Expand Down
14 changes: 14 additions & 0 deletions src/components/AdvanceAnalyticsV2/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { sum } from 'lodash';
import utc from 'dayjs/plugin/utc';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
import { CHART_TYPES, CALCULATION } from './constants';
import messages from '../messages';

dayjs.extend(utc);
dayjs.extend(quarterOfYear);
Expand All @@ -14,6 +15,19 @@ const simulateURL = (activeTab, chartType) => {
return `${activeTab}/stats`;
};

/**
* Constructs a chart hover template.
*
* @param {Object} intl - Internationalization object.
* @param {Object} hoverInfo - Object containing hover information to show over chart data points.
* @returns {string} The constructed chart hover template.
*/
export function constructChartHoverTemplate(intl, hoverInfo) {
return Object.entries(hoverInfo)
.map(([key, value]) => `${intl.formatMessage(messages[key])}: ${value}`)
.join('<br>');
}

export default simulateURL;

/**
Expand Down
16 changes: 15 additions & 1 deletion src/components/AdvanceAnalyticsV2/data/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Jest test for utils.js

import { applyCalculation, applyGranularity } from './utils';
import { createIntl } from '@edx/frontend-platform/i18n';
import { applyCalculation, applyGranularity, constructChartHoverTemplate } from './utils';
import { CALCULATION, GRANULARITY } from './constants';

describe('utils', () => {
Expand Down Expand Up @@ -201,3 +202,16 @@ describe('utils', () => {
});
});
});

describe('constructChartHoverTemplate', () => {
const intl = createIntl({
locale: 'en',
messages: {},
});

it('should construct a hover template', () => {
const hoverInfo = { skill: 'value1', enrollments: 'value2' };
const result = constructChartHoverTemplate(intl, hoverInfo);
expect(result).toBe('Skill: value1<br>Enrollments: value2');
});
});
54 changes: 54 additions & 0 deletions src/components/AdvanceAnalyticsV2/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
skill: {
id: 'advance.analytics.skill.label',
defaultMessage: 'Skill',
},
enrollments: {
id: 'advance.analytics.enrollments.label',
defaultMessage: 'Enrollments',
},
completions: {
id: 'advance.analytics.completions.label',
defaultMessage: 'Completions',
},
date: {
id: 'advance.analytics.date.label',
defaultMessage: 'Date',
},
course: {
id: 'advance.analytics.course.label',
defaultMessage: 'Course',
},
subject: {
id: 'advance.analytics.subject.label',
defaultMessage: 'Subject',
},
learningHours: {
id: 'advance.analytics.learning.hours.label',
defaultMessage: 'Learning Hours',
},
'Common Skill': {
id: 'advance.analytics.common.skill.label',
defaultMessage: 'Common Skill',
},
'Specialized Skill': {
id: 'advance.analytics.specialized.skill.label',
defaultMessage: 'Specialized Skill',
},
'Hard Skill': {
id: 'advance.analytics.hard.skill.label',
defaultMessage: 'Hard Skill',
},
'Soft Skill': {
id: 'advance.analytics.soft.skill.label',
defaultMessage: 'Soft Skill',
},
Certification: {
id: 'advance.analytics.certification.label',
defaultMessage: 'Certification',
},
});

export default messages;
16 changes: 13 additions & 3 deletions src/components/AdvanceAnalyticsV2/tabs/Completions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AnalyticsTable from './AnalyticsTable';
import ChartWrapper from '../charts/ChartWrapper';
import { useEnterpriseAnalyticsData } from '../data/hooks';
import DownloadCSV from '../DownloadCSV';
import { constructChartHoverTemplate } from '../data/utils';

const Completions = ({
startDate, endDate, granularity, calculation, enterpriseId,
Expand Down Expand Up @@ -62,7 +63,10 @@ const Completions = ({
colorMap: chartColorMap,
xAxisTitle: '',
yAxisTitle: 'Number of Completions',
hovertemplate: 'Date: %{x}<br>Number of Completions: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
date: '%{x}',
completions: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.completions.tab.chart.top.courses.by.completions.loading.message',
Expand Down Expand Up @@ -110,7 +114,10 @@ const Completions = ({
defaultMessage: 'Number of Completions',
description: 'Y-axis title for the top courses by completions chart.',
}),
hovertemplate: 'Course: %{x}<br>Number of Completions: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
course: '%{x}',
completions: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.completions.tab.chart.top.10.courses.by.completions.loading.message',
Expand Down Expand Up @@ -158,7 +165,10 @@ const Completions = ({
defaultMessage: 'Number of Completions',
description: 'Y-axis title for the top subjects by completions chart.',
}),
hovertemplate: 'Subject: %{x}<br>Number of Completions: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
subject: '%{x}',
completions: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.completions.tab.chart.top.subjects.by.completions.loading.message',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
render, screen, waitFor, within,
} from '@testing-library/react';
Expand Down
16 changes: 13 additions & 3 deletions src/components/AdvanceAnalyticsV2/tabs/Engagements.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AnalyticsTable from './AnalyticsTable';
import ChartWrapper from '../charts/ChartWrapper';
import { useEnterpriseAnalyticsData } from '../data/hooks';
import DownloadCSV from '../DownloadCSV';
import { constructChartHoverTemplate } from '../data/utils';

const Engagements = ({
startDate, endDate, granularity, calculation, enterpriseId,
Expand Down Expand Up @@ -61,7 +62,10 @@ const Engagements = ({
colorMap: chartColorMap,
xAxisTitle: '',
yAxisTitle: 'Number of Learning Hours',
hovertemplate: 'Date: %{x}<br>Learning Hours: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
date: '%{x}',
learningHours: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.engagements.tab.chart.learning.hours.over.time.loading.message',
Expand Down Expand Up @@ -109,7 +113,10 @@ const Engagements = ({
defaultMessage: 'Number of Learning Hours',
description: 'Y-axis title for the top 10 courses by learning hours chart.',
}),
hovertemplate: 'Course: %{x}<br>Learning Hours: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
course: '%{x}',
learningHours: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.engagements.tab.chart.top.10.courses.by.learning.hours.loading.message',
Expand Down Expand Up @@ -157,7 +164,10 @@ const Engagements = ({
defaultMessage: 'Number of Learning Hours',
description: 'Y-axis title for the top 10 subjects by learning hours chart.',
}),
hovertemplate: 'Subject: %{x}<br>Learning Hours: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
subject: '%{x}',
learningHours: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.engagements.tab.chart.top.10.subjects.by.learning.hours.loading.message',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
render, screen, waitFor, within,
} from '@testing-library/react';
Expand Down
17 changes: 13 additions & 4 deletions src/components/AdvanceAnalyticsV2/tabs/Enrollments.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import AnalyticsTable from './AnalyticsTable';
import ChartWrapper from '../charts/ChartWrapper';
import { useEnterpriseEnrollmentsData } from '../data/hooks';
import DownloadCSVButton from '../DownloadCSVButton';
import { modifyDataToIntroduceEnrollTypeCount } from '../data/utils';
import { modifyDataToIntroduceEnrollTypeCount, constructChartHoverTemplate } from '../data/utils';

dayjs.extend(utc);

Expand Down Expand Up @@ -101,7 +101,10 @@ const Enrollments = ({
colorMap: chartColorMap,
xAxisTitle: '',
yAxisTitle: 'Number of Enrollments',
hovertemplate: 'Date: %{x}<br>Enrolls: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
date: '%{x}',
enrollments: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.enrollments.tab.chart.enrollments.over.time.loading.message',
Expand Down Expand Up @@ -141,7 +144,10 @@ const Enrollments = ({
colorMap: chartColorMap,
xAxisTitle: '',
yAxisTitle: 'Number of Enrollments',
hovertemplate: 'Course: %{x}<br>Enrolls: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
course: '%{x}',
enrollments: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.enrollments.tab.chart.top.courses.by.enrollments.loading.message',
Expand Down Expand Up @@ -181,7 +187,10 @@ const Enrollments = ({
colorMap: chartColorMap,
xAxisTitle: '',
yAxisTitle: 'Number of Enrollments',
hovertemplate: 'Subject: %{x}<br>Enrolls: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
subject: '%{x}',
enrollments: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.enrollments.tab.chart.top.subjects.by.enrollments.loading.message',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
render, screen, waitFor, within,
} from '@testing-library/react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
render, screen, waitFor, within,
} from '@testing-library/react';
Expand Down
17 changes: 14 additions & 3 deletions src/components/AdvanceAnalyticsV2/tabs/Skills.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { useEnterpriseAnalyticsData } from '../data/hooks';
import ChartWrapper from '../charts/ChartWrapper';
import DownloadCSV from '../DownloadCSV';
import { constructChartHoverTemplate } from '../data/utils';

const Skills = ({ startDate, endDate, enterpriseId }) => {
const intl = useIntl();
Expand Down Expand Up @@ -68,7 +69,11 @@ const Skills = ({ startDate, endDate, enterpriseId }) => {
}),
markerSizeKey: 'completions',
customDataKeys: ['skillName', 'skillType'],
hovertemplate: 'Skill: %{customdata[0]}<br>Enrolls: %{x}<br>Completions: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
skill: '%{customdata[0]}',
enrollments: '%{x}',
completions: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.skills.tab.chart.top.skills.loading.message',
Expand Down Expand Up @@ -102,7 +107,10 @@ const Skills = ({ startDate, endDate, enterpriseId }) => {
defaultMessage: 'Number of Enrollments',
description: 'Y-axis title for the top skills by enrollment chart.',
}),
hovertemplate: 'Skill: %{x}<br>Enrolls: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
skill: '%{x}',
enrollments: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.skills.tab.chart.top.skills.by.enrollment.loading.message',
Expand Down Expand Up @@ -136,7 +144,10 @@ const Skills = ({ startDate, endDate, enterpriseId }) => {
defaultMessage: 'Number of Completions',
description: 'Y-axis title for the top skills by completion chart.',
}),
hovertemplate: 'Skill: %{x}<br>Completions: %{y}',
hovertemplate: constructChartHoverTemplate(intl, {
skill: '%{x}',
completions: '%{y}',
}),
}}
loadingMessage={intl.formatMessage({
id: 'advance.analytics.skills.tab.chart.top.skills.by.completion.loading.message',
Expand Down