mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-07 13:07:22 +00:00
* [UI] Data Insight Layout looks broken in case of large number of assets #13803 * move height to constant variable in cost file * fixed DI cypress * fixed failing unit test
This commit is contained in:
parent
210f7d2f4b
commit
cefe22247b
@ -111,7 +111,7 @@ describe('Data Insight feature', () => {
|
||||
interceptURL('GET', '/api/v1/apps?limit=*', 'apps');
|
||||
interceptURL(
|
||||
'GET',
|
||||
'/api/v1/apps/name/DataInsightsApplication?fields=owner,pipelines',
|
||||
'/api/v1/apps/name/DataInsightsApplication?*',
|
||||
'dataInsightsApplication'
|
||||
);
|
||||
interceptURL(
|
||||
@ -128,7 +128,7 @@ describe('Data Insight feature', () => {
|
||||
cy.get('[data-menu-id*="integrations.apps"]').scrollIntoView().click();
|
||||
verifyResponseStatusCode('@apps', 200);
|
||||
cy.get(
|
||||
'[data-testid="data-insights-card"] [data-testid="config-btn"]'
|
||||
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
|
||||
).click();
|
||||
verifyResponseStatusCode('@dataInsightsApplication', 200);
|
||||
cy.get('[data-testid="deploy-button"]').click();
|
||||
|
||||
@ -46,7 +46,7 @@ describe('Data Insight settings page should work properly', () => {
|
||||
'triggerPipeline'
|
||||
);
|
||||
cy.get(
|
||||
'[data-testid="data-insights-card"] [data-testid="config-btn"]'
|
||||
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
|
||||
).click();
|
||||
verifyResponseStatusCode('@getDataInsightDetails', 200);
|
||||
cy.get('[data-testid="deploy-button"]').click();
|
||||
@ -67,7 +67,7 @@ describe('Data Insight settings page should work properly', () => {
|
||||
);
|
||||
interceptURL('PATCH', '/api/v1/apps/*', 'updateApplication');
|
||||
cy.get(
|
||||
'[data-testid="data-insights-card"] [data-testid="config-btn"]'
|
||||
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
|
||||
).click();
|
||||
verifyResponseStatusCode('@getDataInsightDetails', 200);
|
||||
cy.get('[data-testid="edit-button"]').click();
|
||||
@ -93,7 +93,7 @@ describe('Data Insight settings page should work properly', () => {
|
||||
'deleteApplication'
|
||||
);
|
||||
cy.get(
|
||||
'[data-testid="data-insights-card"] [data-testid="config-btn"]'
|
||||
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
|
||||
).click();
|
||||
verifyResponseStatusCode('@getDataInsightDetails', 200);
|
||||
cy.get('[data-testid="manage-button"]').click();
|
||||
@ -101,16 +101,18 @@ describe('Data Insight settings page should work properly', () => {
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
verifyResponseStatusCode('@deleteApplication', 200);
|
||||
verifyResponseStatusCode('@getApplications', 200);
|
||||
cy.get('[data-testid="data-insights-card"]').should('not.exist');
|
||||
cy.get('[data-testid="data-insights-application-card"]').should(
|
||||
'not.exist'
|
||||
);
|
||||
});
|
||||
|
||||
it('Install application', () => {
|
||||
interceptURL('GET', '/api/v1/apps/marketplace?limit=*', 'getMarketPlace');
|
||||
interceptURL('POST', '/api/v1/apps/install', 'installApplication');
|
||||
interceptURL('POST', '/api/v1/apps', 'installApplication');
|
||||
cy.get('[data-testid="add-application"]').click();
|
||||
verifyResponseStatusCode('@getMarketPlace', 200);
|
||||
cy.get(
|
||||
'[data-testid="data-insights-card"] [data-testid="config-btn"]'
|
||||
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
|
||||
).click();
|
||||
cy.get('[data-testid="install-application"]').click();
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
@ -119,6 +121,8 @@ describe('Data Insight settings page should work properly', () => {
|
||||
cy.get('[data-testid="deploy-button"]').click();
|
||||
verifyResponseStatusCode('@installApplication', 201);
|
||||
verifyResponseStatusCode('@getApplications', 200);
|
||||
cy.get('[data-testid="data-insights-card"]').should('be.visible');
|
||||
cy.get('[data-testid="data-insights-application-card"]').should(
|
||||
'be.visible'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -39,7 +39,7 @@ const ApplicationCard = ({
|
||||
className,
|
||||
'application-card card-body-border-none'
|
||||
)}
|
||||
data-testid={`${kebabCase(title)}-card`}>
|
||||
data-testid={`${kebabCase(appName)}-card`}>
|
||||
<div className="d-flex items-center gap-3">
|
||||
<div className="application-logo">
|
||||
<AppLogo appName={appName} />
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
BAR_CHART_MARGIN,
|
||||
DATA_INSIGHT_GRAPH_COLORS,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult';
|
||||
@ -106,7 +107,7 @@ const DailyActiveUsersChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
{dailyActiveUsers.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||
<ResponsiveContainer debounce={1} height={GRAPH_HEIGHT}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
.ant-card-head {
|
||||
border-bottom: 1px solid @border-color;
|
||||
}
|
||||
.custom-data-insight-tooltip-container {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
.ant-card-body {
|
||||
padding: 16px 16px;
|
||||
|
||||
@ -39,6 +39,7 @@ jest.mock('../../utils/DataInsightUtils', () => ({
|
||||
getGraphDataByEntityType: jest
|
||||
.fn()
|
||||
.mockImplementation(() => DUMMY_GRAPH_DATA),
|
||||
sortEntityByValue: jest.fn().mockImplementation((entities) => entities),
|
||||
}));
|
||||
jest.mock('./EntitySummaryProgressBar.component', () => {
|
||||
return jest.fn().mockImplementation(({ label, entity }) => (
|
||||
@ -58,18 +59,20 @@ jest.mock('react-i18next', () => ({
|
||||
describe('Test DescriptionInsight Component', () => {
|
||||
it('Should render the graph', async () => {
|
||||
await act(async () => {
|
||||
const { container } = render(<DescriptionInsight {...mockProps} />);
|
||||
const card = screen.getByTestId('entity-description-percentage-card');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
container,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
render(<DescriptionInsight {...mockProps} />);
|
||||
});
|
||||
const card = await screen.findByTestId(
|
||||
'entity-description-percentage-card'
|
||||
);
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
card,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render the graph and progress bar even if one entity dont have values', async () => {
|
||||
@ -77,21 +80,23 @@ describe('Test DescriptionInsight Component', () => {
|
||||
() => DUMMY_GRAPH_DATA_WITH_MISSING_ENTITY
|
||||
);
|
||||
await act(async () => {
|
||||
const { container } = render(<DescriptionInsight {...mockProps} />);
|
||||
const card = screen.getByTestId('entity-description-percentage-card');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
container,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
const missingEntityValue = await screen.findByTestId('Table');
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
expect(missingEntityValue).toBeInTheDocument();
|
||||
expect(missingEntityValue.textContent).toBe('0');
|
||||
render(<DescriptionInsight {...mockProps} />);
|
||||
});
|
||||
const card = await screen.findByTestId(
|
||||
'entity-description-percentage-card'
|
||||
);
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
card,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
const missingEntityValue = await screen.findByTestId('Table');
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
expect(missingEntityValue).toBeInTheDocument();
|
||||
expect(missingEntityValue.textContent).toBe('0');
|
||||
});
|
||||
|
||||
it('Should fetch data based on dataInsightChartName props', async () => {
|
||||
|
||||
@ -11,15 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Col, Row } from 'antd';
|
||||
import { Button, Card, Col, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isEmpty, round, uniqueId } from 'lodash';
|
||||
import { includes, isEmpty, round, toLower } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
LegendProps,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import {
|
||||
BAR_CHART_MARGIN,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
TOTAL_ENTITY_CHART_COLOR,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
@ -53,9 +52,11 @@ import {
|
||||
import {
|
||||
CustomTooltip,
|
||||
getGraphDataByEntityType,
|
||||
renderLegend,
|
||||
getRandomHexColor,
|
||||
sortEntityByValue,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import './DataInsightDetail.less';
|
||||
import DataInsightProgressBar from './DataInsightProgressBar';
|
||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||
@ -79,9 +80,10 @@ const DescriptionInsight: FC<Props> = ({
|
||||
const [totalEntitiesDescriptionByType, setTotalEntitiesDescriptionByType] =
|
||||
useState<DataInsightChartResult>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||
const [searchEntityKeyWord, setSearchEntityKeyWord] = useState('');
|
||||
|
||||
const {
|
||||
data,
|
||||
@ -97,6 +99,18 @@ const DescriptionInsight: FC<Props> = ({
|
||||
);
|
||||
}, [totalEntitiesDescriptionByType]);
|
||||
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(entities, latestData);
|
||||
}, [entities, latestData]);
|
||||
|
||||
const rightSideEntityList = useMemo(
|
||||
() =>
|
||||
sortedEntitiesByValue.filter((entity) =>
|
||||
includes(toLower(entity), toLower(searchEntityKeyWord))
|
||||
),
|
||||
[sortedEntitiesByValue, searchEntityKeyWord]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const targetValue = useMemo(() => {
|
||||
@ -125,16 +139,16 @@ const DescriptionInsight: FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleLegendClick: LegendProps['onClick'] = (event) => {
|
||||
const handleLegendClick = (entity: string) => {
|
||||
setActiveKeys((prevActiveKeys) =>
|
||||
updateActiveChartFilter(event.dataKey, prevActiveKeys)
|
||||
updateActiveChartFilter(entity, prevActiveKeys)
|
||||
);
|
||||
};
|
||||
|
||||
const handleLegendMouseEnter: LegendProps['onMouseEnter'] = (event) => {
|
||||
setActiveMouseHoverKey(event.dataKey);
|
||||
const handleLegendMouseEnter = (entity: string) => {
|
||||
setActiveMouseHoverKey(entity);
|
||||
};
|
||||
const handleLegendMouseLeave: LegendProps['onMouseLeave'] = () => {
|
||||
const handleLegendMouseLeave = () => {
|
||||
setActiveMouseHoverKey('');
|
||||
};
|
||||
|
||||
@ -142,111 +156,137 @@ const DescriptionInsight: FC<Props> = ({
|
||||
fetchTotalEntitiesDescriptionByType();
|
||||
}, [chartFilter]);
|
||||
|
||||
if (isLoading || data.length === 0) {
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.description-lowercase'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
<EmptyGraphPlaceholder />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
data-testid="entity-description-percentage-card"
|
||||
id={dataInsightChartName}
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.description-lowercase'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
{data.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
id={`${dataInsightChartName}-graph`}
|
||||
minHeight={400}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis
|
||||
tickFormatter={(value: number) =>
|
||||
axisTickFormatter(value, '%')
|
||||
id={dataInsightChartName}>
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.description-lowercase'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<ResponsiveContainer
|
||||
className="m-t-lg"
|
||||
debounce={1}
|
||||
height={GRAPH_HEIGHT}
|
||||
id={`${dataInsightChartName}-graph`}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis
|
||||
tickFormatter={(value: number) => axisTickFormatter(value, '%')}
|
||||
/>
|
||||
<Tooltip
|
||||
content={<CustomTooltip isPercentage />}
|
||||
wrapperStyle={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||
<Legend
|
||||
align="left"
|
||||
content={(props) =>
|
||||
renderLegend(props as LegendProps, activeKeys)
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i] ?? getRandomHexColor()}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
layout="horizontal"
|
||||
verticalAlign="top"
|
||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||
onClick={handleLegendClick}
|
||||
onMouseEnter={handleLegendMouseEnter}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={DI_STRUCTURE.rightRowGutter}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
className="m-b-md"
|
||||
duration={selectedDays}
|
||||
label={`${t('label.completed-entity', {
|
||||
entity: t('label.description'),
|
||||
})}${isPercentageGraph ? ' %' : ''}`}
|
||||
progress={Number(total)}
|
||||
suffix={isPercentageGraph ? '%' : ''}
|
||||
target={targetValue}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={[8, 16]}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
duration={selectedDays}
|
||||
label={`${t('label.completed-entity', {
|
||||
entity: t('label.description'),
|
||||
})}${isPercentageGraph ? ' %' : ''}`}
|
||||
progress={Number(total)}
|
||||
suffix={isPercentageGraph ? '%' : ''}
|
||||
target={targetValue}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
searchValue={searchEntityKeyWord}
|
||||
onSearch={setSearchEntityKeyWord}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="chart-card-right-panel-container" span={24}>
|
||||
<Row gutter={[8, 8]}>
|
||||
{rightSideEntityList.map((entity, i) => {
|
||||
return (
|
||||
<Col
|
||||
className="entity-summary-container"
|
||||
key={entity}
|
||||
span={24}
|
||||
onClick={() => handleLegendClick(entity)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(entity)}
|
||||
onMouseLeave={handleLegendMouseLeave}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
isActive={
|
||||
activeKeys.length ? activeKeys.includes(entity) : true
|
||||
}
|
||||
label={`${round(latestData[entity] ?? 0, 2)}${
|
||||
isPercentageGraph ? '%' : ''
|
||||
}`}
|
||||
latestData={latestData}
|
||||
progress={latestData[entity]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
{activeKeys.length > 0 && (
|
||||
<Col className="flex justify-end" span={24}>
|
||||
<Button type="link" onClick={() => setActiveKeys([])}>
|
||||
{t('label.clear')}
|
||||
</Button>
|
||||
</Col>
|
||||
{entities.map((entity, i) => {
|
||||
return (
|
||||
<Col key={uniqueId()} span={24}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
label={`${round(latestData[entity] ?? 0, 2)}${
|
||||
isPercentageGraph ? '%' : ''
|
||||
}`}
|
||||
latestData={latestData}
|
||||
progress={latestData[entity]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
)}
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,11 +11,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Col, Progress, Row, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { round } from 'lodash';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { GRAYED_OUT_COLOR } from '../../constants/constants';
|
||||
|
||||
type EntitySummaryProgressBarProps = {
|
||||
pluralize?: boolean;
|
||||
isActive?: boolean;
|
||||
progress: number;
|
||||
entity: string;
|
||||
latestData: Record<string, number>;
|
||||
@ -24,6 +27,7 @@ type EntitySummaryProgressBarProps = {
|
||||
};
|
||||
|
||||
const EntitySummaryProgressBar = ({
|
||||
isActive = true,
|
||||
pluralize = true,
|
||||
entity,
|
||||
latestData,
|
||||
@ -36,16 +40,24 @@ const EntitySummaryProgressBar = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Row className="m-b-xs" data-testid="entity-summary-container">
|
||||
<Row
|
||||
className={classNames({
|
||||
'non-active-details': !isActive,
|
||||
})}
|
||||
data-testid="entity-summary-container">
|
||||
<Col
|
||||
className="d-flex justify-between items-center text-xs"
|
||||
md={12}
|
||||
sm={24}>
|
||||
<Typography.Paragraph className="m-b-0" data-testid="entity-name">
|
||||
<Typography.Paragraph
|
||||
className="m-b-0 entity-summary-name break-all"
|
||||
data-testid="entity-name">
|
||||
{pluralize ? pluralizeName(entity) : entity}
|
||||
</Typography.Paragraph>
|
||||
|
||||
<Typography.Paragraph className="m-b-0" data-testid="entity-value">
|
||||
<Typography.Paragraph
|
||||
className="m-b-0 entity-summary-value"
|
||||
data-testid="entity-value">
|
||||
{label ?? round(latestData[entity] || 0, 2)}
|
||||
</Typography.Paragraph>
|
||||
</Col>
|
||||
@ -56,7 +68,7 @@ const EntitySummaryProgressBar = ({
|
||||
percent={progress}
|
||||
showInfo={false}
|
||||
size="small"
|
||||
strokeColor={strokeColor}
|
||||
strokeColor={isActive ? strokeColor : GRAYED_OUT_COLOR}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -41,6 +41,7 @@ import {
|
||||
BAR_CHART_MARGIN,
|
||||
DATA_INSIGHT_GRAPH_COLORS,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../enums/common.enum';
|
||||
import {
|
||||
@ -214,8 +215,8 @@ const KPIChart: FC<Props> = ({
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
id="kpi-chart"
|
||||
minHeight={400}>
|
||||
height={GRAPH_HEIGHT}
|
||||
id="kpi-chart">
|
||||
<LineChart data={graphData} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
|
||||
@ -37,6 +37,7 @@ jest.mock('../../utils/DataInsightUtils', () => ({
|
||||
getGraphDataByEntityType: jest
|
||||
.fn()
|
||||
.mockImplementation(() => DUMMY_GRAPH_DATA),
|
||||
sortEntityByValue: jest.fn().mockImplementation((entities) => entities),
|
||||
}));
|
||||
jest.mock('./EntitySummaryProgressBar.component', () => {
|
||||
return jest.fn().mockImplementation(({ label, entity }) => (
|
||||
@ -59,18 +60,18 @@ jest.mock('react-i18next', () => ({
|
||||
describe('Test DescriptionInsight Component', () => {
|
||||
it('Should render the graph', async () => {
|
||||
await act(async () => {
|
||||
const { container } = render(<OwnerInsight {...mockProps} />);
|
||||
const card = screen.getByTestId('entity-summary-card-percentage');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
container,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
render(<OwnerInsight {...mockProps} />);
|
||||
});
|
||||
const card = await screen.findByTestId('entity-summary-card-percentage');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
card,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render the graph and progress bar even if one entity dont have values', async () => {
|
||||
@ -78,21 +79,21 @@ describe('Test DescriptionInsight Component', () => {
|
||||
() => DUMMY_GRAPH_DATA_WITH_MISSING_ENTITY
|
||||
);
|
||||
await act(async () => {
|
||||
const { container } = render(<OwnerInsight {...mockProps} />);
|
||||
const card = screen.getByTestId('entity-summary-card-percentage');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
container,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
const missingEntityValue = await screen.findByTestId('Table');
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
expect(missingEntityValue).toBeInTheDocument();
|
||||
expect(missingEntityValue.textContent).toBe('0');
|
||||
render(<OwnerInsight {...mockProps} />);
|
||||
});
|
||||
const card = await screen.findByTestId('entity-summary-card-percentage');
|
||||
|
||||
const graph = queryByAttribute(
|
||||
'id',
|
||||
card,
|
||||
`${mockProps.dataInsightChartName}-graph`
|
||||
);
|
||||
const missingEntityValue = await screen.findByTestId('Table');
|
||||
|
||||
expect(card).toBeInTheDocument();
|
||||
expect(graph).toBeInTheDocument();
|
||||
expect(missingEntityValue).toBeInTheDocument();
|
||||
expect(missingEntityValue.textContent).toBe('0');
|
||||
});
|
||||
|
||||
it('Should fetch data based on dataInsightChartName props', async () => {
|
||||
|
||||
@ -11,15 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Col, Row } from 'antd';
|
||||
import { Button, Card, Col, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isEmpty, round } from 'lodash';
|
||||
import { includes, isEmpty, round, toLower } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
LegendProps,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import {
|
||||
BAR_CHART_MARGIN,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
TOTAL_ENTITY_CHART_COLOR,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
@ -53,9 +52,11 @@ import {
|
||||
import {
|
||||
CustomTooltip,
|
||||
getGraphDataByEntityType,
|
||||
renderLegend,
|
||||
getRandomHexColor,
|
||||
sortEntityByValue,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import './DataInsightDetail.less';
|
||||
import DataInsightProgressBar from './DataInsightProgressBar';
|
||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||
@ -79,9 +80,10 @@ const OwnerInsight: FC<Props> = ({
|
||||
const [totalEntitiesOwnerByType, setTotalEntitiesOwnerByType] =
|
||||
useState<DataInsightChartResult>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||
const [searchEntityKeyWord, setSearchEntityKeyWord] = useState('');
|
||||
|
||||
const {
|
||||
data,
|
||||
@ -105,6 +107,18 @@ const OwnerInsight: FC<Props> = ({
|
||||
return undefined;
|
||||
}, [kpi]);
|
||||
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(entities, latestData);
|
||||
}, [entities, latestData]);
|
||||
|
||||
const rightSideEntityList = useMemo(
|
||||
() =>
|
||||
sortedEntitiesByValue.filter((entity) =>
|
||||
includes(toLower(entity), toLower(searchEntityKeyWord))
|
||||
),
|
||||
[sortedEntitiesByValue, searchEntityKeyWord]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const fetchTotalEntitiesOwnerByType = async () => {
|
||||
@ -125,15 +139,16 @@ const OwnerInsight: FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleLegendClick: LegendProps['onClick'] = (event) => {
|
||||
const handleLegendClick = (entity: string) => {
|
||||
setActiveKeys((prevActiveKeys) =>
|
||||
updateActiveChartFilter(event.dataKey, prevActiveKeys)
|
||||
updateActiveChartFilter(entity, prevActiveKeys)
|
||||
);
|
||||
};
|
||||
const handleLegendMouseEnter: LegendProps['onMouseEnter'] = (event) => {
|
||||
setActiveMouseHoverKey(event.dataKey);
|
||||
|
||||
const handleLegendMouseEnter = (entity: string) => {
|
||||
setActiveMouseHoverKey(entity);
|
||||
};
|
||||
const handleLegendMouseLeave: LegendProps['onMouseLeave'] = () => {
|
||||
const handleLegendMouseLeave = () => {
|
||||
setActiveMouseHoverKey('');
|
||||
};
|
||||
|
||||
@ -141,112 +156,139 @@ const OwnerInsight: FC<Props> = ({
|
||||
fetchTotalEntitiesOwnerByType();
|
||||
}, [chartFilter]);
|
||||
|
||||
if (isLoading || data.length === 0) {
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.owner'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
<EmptyGraphPlaceholder />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
data-testid="entity-summary-card-percentage"
|
||||
id={dataInsightChartName}
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.owner'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
{data.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
id={`${dataInsightChartName}-graph`}
|
||||
minHeight={400}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis
|
||||
tickFormatter={(value: number) =>
|
||||
axisTickFormatter(value, '%')
|
||||
loading={isLoading}>
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<PageHeader
|
||||
data={{
|
||||
header,
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.owner'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<ResponsiveContainer
|
||||
className="m-t-lg"
|
||||
debounce={1}
|
||||
height={GRAPH_HEIGHT}
|
||||
id={`${dataInsightChartName}-graph`}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis
|
||||
tickFormatter={(value: number) => axisTickFormatter(value, '%')}
|
||||
/>
|
||||
<Tooltip
|
||||
content={<CustomTooltip isPercentage />}
|
||||
wrapperStyle={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||
<Legend
|
||||
align="left"
|
||||
content={(props) =>
|
||||
renderLegend(props as LegendProps, activeKeys)
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i] ?? getRandomHexColor()}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
layout="horizontal"
|
||||
verticalAlign="top"
|
||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||
onClick={handleLegendClick}
|
||||
onMouseEnter={handleLegendMouseEnter}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
type="monotone"
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={DI_STRUCTURE.rightRowGutter}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
className="m-b-md"
|
||||
duration={selectedDays}
|
||||
label={`${t('label.assigned-entity', {
|
||||
entity: t('label.owner'),
|
||||
})}
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={[8, 16]}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
duration={selectedDays}
|
||||
label={`${t('label.assigned-entity', {
|
||||
entity: t('label.owner'),
|
||||
})}
|
||||
${isPercentageGraph ? ' %' : ''}`}
|
||||
progress={Number(total)}
|
||||
suffix={isPercentageGraph ? '%' : ''}
|
||||
target={targetValue}
|
||||
/>
|
||||
progress={Number(total)}
|
||||
suffix={isPercentageGraph ? '%' : ''}
|
||||
target={targetValue}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
searchValue={searchEntityKeyWord}
|
||||
onSearch={setSearchEntityKeyWord}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="chart-card-right-panel-container" span={24}>
|
||||
<Row gutter={[8, 8]}>
|
||||
{rightSideEntityList.map((entity, i) => {
|
||||
return (
|
||||
<Col
|
||||
className="entity-summary-container"
|
||||
key={entity}
|
||||
span={24}
|
||||
onClick={() => handleLegendClick(entity)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(entity)}
|
||||
onMouseLeave={handleLegendMouseLeave}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
isActive={
|
||||
activeKeys.length ? activeKeys.includes(entity) : true
|
||||
}
|
||||
label={`${round(latestData[entity] || 0, 2)}${
|
||||
isPercentageGraph ? '%' : ''
|
||||
}`}
|
||||
latestData={latestData}
|
||||
progress={latestData[entity]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
{activeKeys.length > 0 && (
|
||||
<Col className="flex justify-end" span={24}>
|
||||
<Button type="link" onClick={() => setActiveKeys([])}>
|
||||
{t('label.clear')}
|
||||
</Button>
|
||||
</Col>
|
||||
{entities.map((entity, i) => {
|
||||
return (
|
||||
<Col key={entity} span={24}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
label={`${round(latestData[entity] || 0, 2)}${
|
||||
isPercentageGraph ? '%' : ''
|
||||
}`}
|
||||
latestData={latestData}
|
||||
progress={latestData[entity]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
)}
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -18,8 +18,6 @@ import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
LegendProps,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import {
|
||||
BAR_CHART_MARGIN,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
TOTAL_ENTITY_CHART_COLOR,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
@ -43,11 +42,10 @@ import { DataInsightChartType } from '../../generated/dataInsight/dataInsightCha
|
||||
import { PageViewsByEntities } from '../../generated/dataInsight/type/pageViewsByEntities';
|
||||
import { ChartFilter } from '../../interface/data-insight.interface';
|
||||
import { getAggregateChartData } from '../../rest/DataInsightAPI';
|
||||
import { updateActiveChartFilter } from '../../utils/ChartUtils';
|
||||
import {
|
||||
CustomTooltip,
|
||||
getGraphDataByEntityType,
|
||||
renderLegend,
|
||||
sortEntityByValue,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import './DataInsightDetail.less';
|
||||
@ -63,7 +61,7 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
const [pageViewsByEntities, setPageViewsByEntities] =
|
||||
useState<PageViewsByEntities[]>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||
|
||||
@ -74,6 +72,9 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
DataInsightChartType.PageViewsByEntities
|
||||
);
|
||||
}, [pageViewsByEntities]);
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(entities, latestData);
|
||||
}, [entities, latestData]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -95,97 +96,89 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLegendClick: LegendProps['onClick'] = (event) => {
|
||||
setActiveKeys((prevActiveKeys) =>
|
||||
updateActiveChartFilter(event.dataKey, prevActiveKeys)
|
||||
);
|
||||
};
|
||||
|
||||
const handleLegendMouseEnter: LegendProps['onMouseEnter'] = (event) => {
|
||||
setActiveMouseHoverKey(event.dataKey);
|
||||
};
|
||||
const handleLegendMouseLeave: LegendProps['onMouseLeave'] = () => {
|
||||
setActiveMouseHoverKey('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPageViewsByEntities();
|
||||
}, [chartFilter]);
|
||||
|
||||
if (isLoading || data.length === 0) {
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.page-views-by-data-asset-plural'),
|
||||
subHeader: t('message.data-insight-page-views'),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
<EmptyGraphPlaceholder />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
data-testid="entity-page-views-card"
|
||||
id={DataInsightChartType.PageViewsByEntities}
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.page-views-by-data-asset-plural'),
|
||||
subHeader: t('message.data-insight-page-views'),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
{data.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend
|
||||
align="left"
|
||||
content={(props) =>
|
||||
renderLegend(props as LegendProps, activeKeys)
|
||||
loading={isLoading}>
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.page-views-by-data-asset-plural'),
|
||||
subHeader: t('message.data-insight-page-views'),
|
||||
}}
|
||||
/>
|
||||
<ResponsiveContainer debounce={1} height={GRAPH_HEIGHT}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
content={<CustomTooltip />}
|
||||
wrapperStyle={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
layout="horizontal"
|
||||
verticalAlign="top"
|
||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||
onClick={handleLegendClick}
|
||||
onMouseEnter={handleLegendMouseEnter}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<TotalEntityInsightSummary
|
||||
entities={entities}
|
||||
gutter={DI_STRUCTURE.rightRowGutter}
|
||||
latestData={latestData}
|
||||
relativePercentage={relativePercentage}
|
||||
selectedDays={selectedDays}
|
||||
total={total}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
)}
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<TotalEntityInsightSummary
|
||||
allowFilter
|
||||
activeKeys={activeKeys}
|
||||
entities={sortedEntitiesByValue}
|
||||
gutter={DI_STRUCTURE.rightRowGutter}
|
||||
latestData={latestData}
|
||||
relativePercentage={relativePercentage}
|
||||
selectedDays={selectedDays}
|
||||
total={total}
|
||||
onActiveKeyMouseHover={(entity) => setActiveMouseHoverKey(entity)}
|
||||
onActiveKeysUpdate={(entities) => setActiveKeys(entities)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,15 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Col, Row } from 'antd';
|
||||
import { Button, Card, Col, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isEmpty, round } from 'lodash';
|
||||
import { includes, isEmpty, round, toLower } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
LegendProps,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import {
|
||||
BAR_CHART_MARGIN,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
TOTAL_ENTITY_CHART_COLOR,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
@ -53,10 +52,11 @@ import {
|
||||
import {
|
||||
CustomTooltip,
|
||||
getGraphDataByTierType,
|
||||
renderLegend,
|
||||
sortEntityByValue,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { getEntityName } from '../../utils/EntityUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import './DataInsightDetail.less';
|
||||
import DataInsightProgressBar from './DataInsightProgressBar';
|
||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||
@ -75,10 +75,21 @@ const TierInsight: FC<Props> = ({ chartFilter, selectedDays, tierTags }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||
const [searchEntityKeyWord, setSearchEntityKeyWord] = useState('');
|
||||
|
||||
const { data, tiers, total, relativePercentage, latestData } = useMemo(() => {
|
||||
return getGraphDataByTierType(totalEntitiesByTier?.data ?? []);
|
||||
}, [totalEntitiesByTier]);
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(tiers, latestData);
|
||||
}, [tiers, latestData]);
|
||||
const rightSideEntityList = useMemo(
|
||||
() =>
|
||||
sortedEntitiesByValue.filter((entity) =>
|
||||
includes(toLower(entity), toLower(searchEntityKeyWord))
|
||||
),
|
||||
[sortedEntitiesByValue, searchEntityKeyWord]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -109,15 +120,16 @@ const TierInsight: FC<Props> = ({ chartFilter, selectedDays, tierTags }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLegendClick: LegendProps['onClick'] = (event) => {
|
||||
const handleLegendClick = (entity: string) => {
|
||||
setActiveKeys((prevActiveKeys) =>
|
||||
updateActiveChartFilter(event.dataKey, prevActiveKeys)
|
||||
updateActiveChartFilter(entity, prevActiveKeys)
|
||||
);
|
||||
};
|
||||
const handleLegendMouseEnter: LegendProps['onMouseEnter'] = (event) => {
|
||||
setActiveMouseHoverKey(event.dataKey);
|
||||
|
||||
const handleLegendMouseEnter = (entity: string) => {
|
||||
setActiveMouseHoverKey(entity);
|
||||
};
|
||||
const handleLegendMouseLeave: LegendProps['onMouseLeave'] = () => {
|
||||
const handleLegendMouseLeave = () => {
|
||||
setActiveMouseHoverKey('');
|
||||
};
|
||||
|
||||
@ -127,107 +139,132 @@ const TierInsight: FC<Props> = ({ chartFilter, selectedDays, tierTags }) => {
|
||||
}
|
||||
}, [chartFilter, tierTags]);
|
||||
|
||||
if (isLoading || data.length === 0) {
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-tier-summary'),
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.tier'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
<EmptyGraphPlaceholder />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
data-testid="entity-summary-card-percentage"
|
||||
id={DataInsightChartType.TotalEntitiesByTier}
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-tier-summary'),
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.tier'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
{data.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
id={`${DataInsightChartType.TotalEntitiesByTier}-graph`}
|
||||
minHeight={400}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis
|
||||
tickFormatter={(value) => axisTickFormatter(value, '%')}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip isPercentage isTier />} />
|
||||
<Legend
|
||||
align="left"
|
||||
content={(props) =>
|
||||
renderLegend(props as LegendProps, activeKeys)
|
||||
loading={isLoading}>
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-tier-summary'),
|
||||
subHeader: t('message.field-insight', {
|
||||
field: t('label.tier'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
height={GRAPH_HEIGHT}
|
||||
id={`${DataInsightChartType.TotalEntitiesByTier}-graph`}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis tickFormatter={(value) => axisTickFormatter(value, '%')} />
|
||||
<Tooltip
|
||||
content={<CustomTooltip isPercentage isTier />}
|
||||
wrapperStyle={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
{tiers.map((tier, i) => (
|
||||
<Line
|
||||
dataKey={tier}
|
||||
hide={
|
||||
activeKeys.length && tier !== activeMouseHoverKey
|
||||
? !activeKeys.includes(tier)
|
||||
: false
|
||||
}
|
||||
layout="horizontal"
|
||||
verticalAlign="top"
|
||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||
onClick={handleLegendClick}
|
||||
onMouseEnter={handleLegendMouseEnter}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
/>
|
||||
{tiers.map((tier, i) => (
|
||||
<Line
|
||||
dataKey={tier}
|
||||
hide={
|
||||
activeKeys.length && tier !== activeMouseHoverKey
|
||||
? !activeKeys.includes(tier)
|
||||
: false
|
||||
}
|
||||
key={tier}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
tier === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={DI_STRUCTURE.rightRowGutter}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
className="m-b-md"
|
||||
duration={selectedDays}
|
||||
label={`${t('label.assigned-entity', {
|
||||
entity: t('label.tier'),
|
||||
})} %`}
|
||||
progress={Number(total)}
|
||||
showLabel={false}
|
||||
key={tier}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) || tier === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<Row gutter={[8, 16]}>
|
||||
<Col span={24}>
|
||||
<DataInsightProgressBar
|
||||
changeInValue={relativePercentage}
|
||||
duration={selectedDays}
|
||||
label={`${t('label.assigned-entity', {
|
||||
entity: t('label.tier'),
|
||||
})} %`}
|
||||
progress={Number(total)}
|
||||
showLabel={false}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
searchValue={searchEntityKeyWord}
|
||||
onSearch={setSearchEntityKeyWord}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="chart-card-right-panel-container" span={24}>
|
||||
<Row gutter={[8, 8]}>
|
||||
{rightSideEntityList.map((tier, i) => {
|
||||
return (
|
||||
<Col
|
||||
className="entity-summary-container"
|
||||
key={tier}
|
||||
span={24}
|
||||
onClick={() => handleLegendClick(tier)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(tier)}
|
||||
onMouseLeave={handleLegendMouseLeave}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={tier}
|
||||
isActive={
|
||||
activeKeys.length ? activeKeys.includes(tier) : true
|
||||
}
|
||||
label={round(latestData[tier] || 0, 2) + '%'}
|
||||
latestData={latestData}
|
||||
pluralize={false}
|
||||
progress={latestData[tier]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
{activeKeys.length > 0 && (
|
||||
<Col className="flex justify-end" span={24}>
|
||||
<Button type="link" onClick={() => setActiveKeys([])}>
|
||||
{t('label.clear')}
|
||||
</Button>
|
||||
</Col>
|
||||
{tiers.map((tiers, i) => {
|
||||
return (
|
||||
<Col key={tiers} span={24}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={tiers}
|
||||
label={round(latestData[tiers] || 0, 2) + '%'}
|
||||
latestData={latestData}
|
||||
pluralize={false}
|
||||
progress={latestData[tiers]}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
)}
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -18,8 +18,6 @@ import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
LegendProps,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import {
|
||||
BAR_CHART_MARGIN,
|
||||
DI_STRUCTURE,
|
||||
GRAPH_HEIGHT,
|
||||
TOTAL_ENTITY_CHART_COLOR,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||
@ -45,14 +44,11 @@ import {
|
||||
} from '../../generated/dataInsight/dataInsightChartResult';
|
||||
import { ChartFilter } from '../../interface/data-insight.interface';
|
||||
import { getAggregateChartData } from '../../rest/DataInsightAPI';
|
||||
import {
|
||||
axisTickFormatter,
|
||||
updateActiveChartFilter,
|
||||
} from '../../utils/ChartUtils';
|
||||
import { axisTickFormatter } from '../../utils/ChartUtils';
|
||||
import {
|
||||
CustomTooltip,
|
||||
getGraphDataByEntityType,
|
||||
renderLegend,
|
||||
sortEntityByValue,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import './DataInsightDetail.less';
|
||||
@ -81,6 +77,9 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
}, [totalEntitiesByType]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(entities, latestData);
|
||||
}, [entities, latestData]);
|
||||
|
||||
const fetchTotalEntitiesByType = async () => {
|
||||
setIsLoading(true);
|
||||
@ -100,99 +99,92 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLegendClick: LegendProps['onClick'] = (event) => {
|
||||
setActiveKeys((prevActiveKeys) =>
|
||||
updateActiveChartFilter(event.dataKey, prevActiveKeys)
|
||||
);
|
||||
};
|
||||
const handleLegendMouseEnter: LegendProps['onMouseEnter'] = (event) => {
|
||||
setActiveMouseHoverKey(event.dataKey);
|
||||
};
|
||||
const handleLegendMouseLeave: LegendProps['onMouseLeave'] = () => {
|
||||
setActiveMouseHoverKey('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTotalEntitiesByType();
|
||||
}, [chartFilter]);
|
||||
|
||||
if (isLoading || data.length === 0) {
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-total-entity-summary'),
|
||||
subHeader: t('message.total-entity-insight'),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
<EmptyGraphPlaceholder />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="data-insight-card"
|
||||
data-testid="entity-summary-card"
|
||||
id={DataInsightChartType.TotalEntitiesByType}
|
||||
loading={isLoading}
|
||||
title={
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-total-entity-summary'),
|
||||
subHeader: t('message.total-entity-insight'),
|
||||
}}
|
||||
/>
|
||||
}>
|
||||
{data.length ? (
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<ResponsiveContainer
|
||||
debounce={1}
|
||||
id="entity-summary-chart"
|
||||
minHeight={400}>
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid
|
||||
stroke={GRAPH_BACKGROUND_COLOR}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis tickFormatter={(value) => axisTickFormatter(value)} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend
|
||||
align="left"
|
||||
content={(props) =>
|
||||
renderLegend(props as LegendProps, activeKeys)
|
||||
loading={isLoading}>
|
||||
<Row gutter={DI_STRUCTURE.rowContainerGutter}>
|
||||
<Col span={DI_STRUCTURE.leftContainerSpan}>
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.data-insight-total-entity-summary'),
|
||||
subHeader: t('message.total-entity-insight'),
|
||||
}}
|
||||
/>
|
||||
<ResponsiveContainer
|
||||
className="m-t-lg"
|
||||
debounce={1}
|
||||
height={GRAPH_HEIGHT}
|
||||
id="entity-summary-chart">
|
||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis tickFormatter={(value) => axisTickFormatter(value)} />
|
||||
<Tooltip
|
||||
content={<CustomTooltip />}
|
||||
wrapperStyle={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
layout="horizontal"
|
||||
verticalAlign="top"
|
||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||
onClick={handleLegendClick}
|
||||
onMouseEnter={handleLegendMouseEnter}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
{entities.map((entity, i) => (
|
||||
<Line
|
||||
dataKey={entity}
|
||||
hide={
|
||||
activeKeys.length && entity !== activeMouseHoverKey
|
||||
? !activeKeys.includes(entity)
|
||||
: false
|
||||
}
|
||||
key={entity}
|
||||
stroke={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
strokeOpacity={
|
||||
isEmpty(activeMouseHoverKey) ||
|
||||
entity === activeMouseHoverKey
|
||||
? DEFAULT_CHART_OPACITY
|
||||
: HOVER_CHART_OPACITY
|
||||
}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<TotalEntityInsightSummary
|
||||
entities={entities}
|
||||
gutter={DI_STRUCTURE.rightRowGutter}
|
||||
latestData={latestData}
|
||||
relativePercentage={relativePercentage}
|
||||
selectedDays={selectedDays}
|
||||
total={total}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
)}
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Col>
|
||||
<Col span={DI_STRUCTURE.rightContainerSpan}>
|
||||
<TotalEntityInsightSummary
|
||||
allowFilter
|
||||
activeKeys={activeKeys}
|
||||
entities={sortedEntitiesByValue}
|
||||
gutter={DI_STRUCTURE.rightRowGutter}
|
||||
latestData={latestData}
|
||||
relativePercentage={relativePercentage}
|
||||
selectedDays={selectedDays}
|
||||
total={total}
|
||||
onActiveKeyMouseHover={(entity) => setActiveMouseHoverKey(entity)}
|
||||
onActiveKeysUpdate={(entities) => setActiveKeys(entities)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,11 +10,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Col, Row } from 'antd';
|
||||
import { Button, Col, Row } from 'antd';
|
||||
import { Gutter } from 'antd/lib/grid/row';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { includes, toLower } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TOTAL_ENTITY_CHART_COLOR } from '../../constants/DataInsight.constants';
|
||||
import { updateActiveChartFilter } from '../../utils/ChartUtils';
|
||||
import { sortEntityByValue } from '../../utils/DataInsightUtils';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import CustomStatistic from './CustomStatistic';
|
||||
import EntitySummaryProgressBar from './EntitySummaryProgressBar.component';
|
||||
|
||||
@ -25,20 +30,51 @@ type TotalEntityInsightSummaryProps = {
|
||||
entities: string[];
|
||||
latestData: Record<string, number>;
|
||||
gutter?: Gutter | [Gutter, Gutter];
|
||||
onActiveKeysUpdate?: (entity: string[]) => void;
|
||||
onActiveKeyMouseHover?: (entity: string) => void;
|
||||
activeKeys?: string[];
|
||||
allowFilter?: boolean;
|
||||
};
|
||||
|
||||
const TotalEntityInsightSummary = ({
|
||||
total,
|
||||
allowFilter = false,
|
||||
relativePercentage,
|
||||
selectedDays,
|
||||
entities,
|
||||
latestData,
|
||||
gutter,
|
||||
activeKeys,
|
||||
onActiveKeysUpdate,
|
||||
onActiveKeyMouseHover,
|
||||
}: TotalEntityInsightSummaryProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchEntityKeyWord, setSearchEntityKeyWord] = useState('');
|
||||
|
||||
const sortedEntitiesByValue = useMemo(() => {
|
||||
return sortEntityByValue(entities, latestData);
|
||||
}, [entities, latestData]);
|
||||
|
||||
const rightSideEntityList = useMemo(
|
||||
() =>
|
||||
sortedEntitiesByValue.filter((entity) =>
|
||||
includes(toLower(entity), toLower(searchEntityKeyWord))
|
||||
),
|
||||
[sortedEntitiesByValue, searchEntityKeyWord]
|
||||
);
|
||||
|
||||
const handleLegendClick = (entity: string) => {
|
||||
onActiveKeysUpdate?.(updateActiveChartFilter(entity, activeKeys ?? []));
|
||||
};
|
||||
|
||||
const handleLegendMouseEnter = (entity: string) => {
|
||||
onActiveKeyMouseHover?.(entity);
|
||||
};
|
||||
const handleLegendMouseLeave = () => {
|
||||
onActiveKeyMouseHover?.('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Row data-testid="total-entity-insight-summary-container" gutter={gutter}>
|
||||
<Row data-testid="total-entity-insight-summary-container" gutter={[8, 16]}>
|
||||
<Col className="p-b-sm" span={24}>
|
||||
<CustomStatistic
|
||||
changeInValue={relativePercentage}
|
||||
@ -49,20 +85,56 @@ const TotalEntityInsightSummary = ({
|
||||
value={total}
|
||||
/>
|
||||
</Col>
|
||||
{entities.map((entity, i) => {
|
||||
const progress = (latestData[entity] / Number(total)) * 100;
|
||||
{allowFilter && (
|
||||
<Col span={24}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
searchValue={searchEntityKeyWord}
|
||||
onSearch={setSearchEntityKeyWord}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col
|
||||
className={classNames({
|
||||
'chart-card-right-panel-container': allowFilter,
|
||||
})}
|
||||
span={24}>
|
||||
<Row gutter={[8, 8]}>
|
||||
{rightSideEntityList.map((entity, i) => {
|
||||
const progress = (latestData[entity] / Number(total)) * 100;
|
||||
|
||||
return (
|
||||
<Col key={entity} span={24}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
latestData={latestData}
|
||||
progress={progress}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<Col
|
||||
className={classNames({
|
||||
'entity-summary-container': allowFilter,
|
||||
})}
|
||||
key={entity}
|
||||
span={24}
|
||||
onClick={() => handleLegendClick(entity)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(entity)}
|
||||
onMouseLeave={handleLegendMouseLeave}>
|
||||
<EntitySummaryProgressBar
|
||||
entity={entity}
|
||||
isActive={
|
||||
activeKeys?.length ? activeKeys.includes(entity) : true
|
||||
}
|
||||
latestData={latestData}
|
||||
progress={progress}
|
||||
strokeColor={TOTAL_ENTITY_CHART_COLOR[i]}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
{activeKeys && activeKeys.length > 0 && allowFilter && (
|
||||
<Col className="flex justify-end" span={24}>
|
||||
<Button type="link" onClick={() => onActiveKeysUpdate?.([])}>
|
||||
{t('label.clear')}
|
||||
</Button>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
@ -34,9 +34,11 @@ export const DI_STRUCTURE = {
|
||||
rowContainerGutter: 32 as RowProps['gutter'],
|
||||
leftContainerSpan: 16,
|
||||
rightContainerSpan: 8,
|
||||
rightRowGutter: [8, 16] as RowProps['gutter'],
|
||||
rightRowGutter: [8, 0] as RowProps['gutter'],
|
||||
};
|
||||
|
||||
export const GRAPH_HEIGHT = 500;
|
||||
|
||||
export const DATA_INSIGHT_GRAPH_COLORS = [
|
||||
'#E7B85D',
|
||||
'#416BB3',
|
||||
|
||||
@ -36,7 +36,7 @@ export const TEXT_GREY_MUTED = '#757575';
|
||||
export const SUCCESS_COLOR = '#008376';
|
||||
export const DE_ACTIVE_COLOR = '#6B7280';
|
||||
export const GRAPH_BACKGROUND_COLOR = '#f5f5f5';
|
||||
export const GRAYED_OUT_COLOR = '#CCCCCC';
|
||||
export const GRAYED_OUT_COLOR = '#959595';
|
||||
export const GREEN_COLOR = '#28A745';
|
||||
export const GREEN_COLOR_OPACITY_30 = '#28A74530';
|
||||
export const BORDER_COLOR = '#0000001a';
|
||||
|
||||
@ -156,3 +156,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.entity-summary-container {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: @grey-1;
|
||||
}
|
||||
.non-active-details {
|
||||
.entity-summary-name,
|
||||
.entity-summary-value {
|
||||
color: @grey-3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-right-panel-container {
|
||||
max-height: 375px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@ -164,28 +164,30 @@ export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
||||
<Card
|
||||
className="custom-data-insight-tooltip"
|
||||
title={<Typography.Title level={5}>{timestamp}</Typography.Title>}>
|
||||
{payload.map((entry, index) => (
|
||||
<li
|
||||
className="d-flex items-center justify-between gap-6 p-b-xss text-sm"
|
||||
key={`item-${index}`}>
|
||||
<span className="flex items-center text-grey-muted">
|
||||
<Surface className="mr-2" height={12} version="1.1" width={12}>
|
||||
<rect fill={entry.color} height="14" rx="2" width="14" />
|
||||
</Surface>
|
||||
{startCase(entry.dataKey as string)}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{valueFormatter
|
||||
? valueFormatter(entry.value)
|
||||
: getEntryFormattedValue(
|
||||
entry.value,
|
||||
entry.dataKey,
|
||||
kpiTooltipRecord,
|
||||
isPercentage
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
<ul className="custom-data-insight-tooltip-container">
|
||||
{payload.map((entry, index) => (
|
||||
<li
|
||||
className="d-flex items-center justify-between gap-6 p-b-xss text-sm"
|
||||
key={`item-${index}`}>
|
||||
<span className="flex items-center text-grey-muted">
|
||||
<Surface className="mr-2" height={12} version="1.1" width={12}>
|
||||
<rect fill={entry.color} height="14" rx="2" width="14" />
|
||||
</Surface>
|
||||
{startCase(entry.dataKey as string)}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{valueFormatter
|
||||
? valueFormatter(entry.value)
|
||||
: getEntryFormattedValue(
|
||||
entry.value,
|
||||
entry.dataKey,
|
||||
kpiTooltipRecord,
|
||||
isPercentage
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -679,3 +681,25 @@ export const getOptionalDataInsightTabFlag = (tab: DataInsightTabs) => {
|
||||
tab === DataInsightTabs.KPIS || tab === DataInsightTabs.DATA_ASSETS,
|
||||
};
|
||||
};
|
||||
|
||||
export const sortEntityByValue = (
|
||||
entities: string[],
|
||||
latestData: Record<string, number>
|
||||
) => {
|
||||
const entityValues = entities.map((entity) => ({
|
||||
entity,
|
||||
value: latestData[entity] ?? 0,
|
||||
}));
|
||||
|
||||
// Sort the entities based on their values in descending order
|
||||
entityValues.sort((a, b) => b.value - a.value);
|
||||
|
||||
// Extract the sorted entities without their values
|
||||
return entityValues.map((entity) => entity.entity);
|
||||
};
|
||||
|
||||
export const getRandomHexColor = () => {
|
||||
const randomColor = Math.floor(Math.random() * 16777215).toString(16);
|
||||
|
||||
return `#${randomColor}`;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user