UI: Added relative percentage change details in UI (#9354)

* UI: Added relative percentage change details in UI

* added license

* UI: updated chart styling and layout of all the DI chart as per new mock

* added space in tier chart

* updated legend style and other kpi page style

* updated target marker style in progress bar

* fixed styling for target marker

* removed unwanted code and updated opacity for hover effect

* added localization and remove use of tailwind class

* fixed failing unit test

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Shailesh Parmar 2022-12-18 17:27:10 +05:30 committed by GitHub
parent 1a00e81fe8
commit 5e81d51c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 967 additions and 378 deletions

View File

@ -0,0 +1,47 @@
/*
* Copyright 2022 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Typography } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
interface ChangeInValueIndicatorProps {
changeInValue: number;
suffix: string;
duration?: number;
}
const ChangeInValueIndicator = ({
changeInValue,
suffix,
duration,
}: ChangeInValueIndicatorProps) => {
const { t } = useTranslation();
return (
<Typography.Paragraph className="data-insight-label-text text-xs">
<Typography.Text type={changeInValue >= 0 ? 'success' : 'danger'}>
{changeInValue >= 0 ? '+' : ''}
{changeInValue.toFixed(2)}
{suffix}
</Typography.Text>{' '}
{duration
? t('label.days-change-lowercase', {
days: duration,
})
: ''}
</Typography.Paragraph>
);
};
export default ChangeInValueIndicator;

View File

@ -0,0 +1,56 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Space, Typography } from 'antd';
import { isNil } from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import ChangeInValueIndicator from './ChangeInValueIndicator';
interface CustomStatisticProps {
value: number | string;
label?: string;
changeInValue?: number;
duration?: number;
suffix?: string;
}
const CustomStatistic = ({
value,
label,
changeInValue,
duration,
suffix = '%',
}: CustomStatisticProps) => {
const { t } = useTranslation();
return (
<Space direction="vertical" size={0} style={{ margin: 0 }}>
<Typography.Text className="data-insight-label-text">
{label ?? t('label.latest')}
</Typography.Text>
<Typography.Text className="font-bold text-lg">{value}</Typography.Text>
{changeInValue && !isNil(changeInValue) ? (
<ChangeInValueIndicator
changeInValue={changeInValue}
duration={duration}
suffix={suffix}
/>
) : (
''
)}
</Space>
);
};
export default CustomStatistic;

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -42,14 +42,16 @@ import {
renderLegend,
} from '../../utils/DataInsightUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import CustomStatistic from './CustomStatistic';
import './DataInsightDetail.less';
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
selectedDays: number;
}
const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
const DailyActiveUsersChart: FC<Props> = ({ chartFilter, selectedDays }) => {
const [dailyActiveUsers, setDailyActiveUsers] = useState<DailyActiveUsers[]>(
[]
);
@ -58,7 +60,7 @@ const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
const { t } = useTranslation();
const { data, total } = useMemo(
const { data, total, relativePercentage } = useMemo(
() => getFormattedActiveUsersData(dailyActiveUsers),
[dailyActiveUsers]
);
@ -102,28 +104,43 @@ const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
</>
}>
{dailyActiveUsers.length ? (
<ResponsiveContainer debounce={1} minHeight={400}>
<LineChart data={data} margin={BAR_CHART_MARGIN}>
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
<Legend
align="left"
content={() =>
renderLegend({ payload: [] } as LegendProps, `${total}`)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
<Row gutter={16}>
<Col span={18}>
<ResponsiveContainer debounce={1} minHeight={400}>
<LineChart data={data} margin={BAR_CHART_MARGIN}>
<CartesianGrid
stroke={GRAPH_BACKGROUND_COLOR}
vertical={false}
/>
<Legend
align="left"
content={() =>
renderLegend({ payload: [] } as LegendProps, [])
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
/>
<XAxis dataKey="timestamp" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
<Line
dataKey="activeUsers"
stroke={DATA_INSIGHT_GRAPH_COLORS[3]}
type="monotone"
/>
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<CustomStatistic
changeInValue={relativePercentage}
duration={selectedDays}
label={t('label.total-active-user')}
value={total}
/>
<XAxis dataKey="timestamp" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
<Line
dataKey="activeUsers"
stroke={DATA_INSIGHT_GRAPH_COLORS[3]}
type="monotone"
/>
</LineChart>
</ResponsiveContainer>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -11,6 +11,8 @@
* limitations under the License.
*/
@import url('../../styles/variables.less');
@label-color: #6b7280;
@summary-card-bg-hover: #f0f1f3;
@ -18,6 +20,14 @@
.ant-card-head {
border-bottom: none;
}
.custom-data-insight-tooltip {
.ant-card-head {
border-bottom: 1px solid @border-gray-color;
}
}
.ant-card-body {
padding-top: 8px;
}
}
.ant-typography.data-insight-label-text {

View File

@ -13,9 +13,11 @@
import { Progress, Typography } from 'antd';
import classNames from 'classnames';
import { isNil } from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import ChangeInValueIndicator from './ChangeInValueIndicator';
interface DataInsightProgressBarProps {
width?: number;
@ -28,9 +30,13 @@ interface DataInsightProgressBarProps {
successValue?: number | string;
startValue?: number | string;
suffix?: string;
changeInValue?: number;
duration?: number;
showEndValueAsLabel?: boolean;
}
const DataInsightProgressBar = ({
showEndValueAsLabel = false,
width,
progress,
className,
@ -41,17 +47,19 @@ const DataInsightProgressBar = ({
successValue = 100,
showLabel = true,
showSuccessInfo = false,
changeInValue,
duration,
}: DataInsightProgressBarProps) => {
const { t } = useTranslation();
return (
<div className={classNames(className)} style={{ width }}>
{showLabel && (
<Typography.Text className="data-insight-label-text">
<Typography.Paragraph className="data-insight-label-text">
{label ?? t('label.latest')}
</Typography.Text>
</Typography.Paragraph>
)}
<div className="flex">
<div className={classNames('flex', { 'm-t-sm': Boolean(target) })}>
<Progress
className="data-insight-progress-bar"
format={(per) => (
@ -63,12 +71,16 @@ const DataInsightProgressBar = ({
{target && (
<span
className="data-insight-kpi-target"
style={{ width: `${target}%` }}
/>
style={{ width: `${target}%` }}>
<span className="target-text">
{target}
{suffix}
</span>
</span>
)}
<span>
{successValue}
{suffix}
{showEndValueAsLabel ? '' : suffix}
</span>
</>
)}
@ -79,6 +91,16 @@ const DataInsightProgressBar = ({
<SVGIcons className="m-l-xs" icon={Icons.SUCCESS_BADGE} />
)}
</div>
{changeInValue && !isNil(changeInValue) ? (
<ChangeInValueIndicator
changeInValue={changeInValue}
duration={duration}
suffix={suffix}
/>
) : (
''
)}
</div>
);
};

View File

@ -179,6 +179,7 @@ const DataInsightSummary: FC<Props> = ({ chartFilter, onScrollToChart }) => {
return (
<Card
bodyStyle={{ paddingTop: 0 }}
className="data-insight-card"
data-testid="summary-card"
loading={isLoading}
@ -204,7 +205,7 @@ const DataInsightSummary: FC<Props> = ({ chartFilter, onScrollToChart }) => {
<Typography.Text className="data-insight-label-text">
{summary.label}
</Typography.Text>
<Typography className="font-semibold text-2xl m--ml-0.5">
<Typography className="font-bold text-lg m--ml-0.5">
{summary.latest}
{summary.id.startsWith('Percentage') ||
summary.id.includes(DataInsightChartType.TotalEntitiesByTier)

View File

@ -49,6 +49,14 @@ jest.mock('../../utils/DataInsightUtils', () => ({
},
],
entities: ['Table', 'Topic', 'Database', 'Pipeline', 'Messaging'],
latestData: {
timestamp: '24/Oct',
Table: 0.3374,
Topic: 0.0353,
Database: 0.9774,
Pipeline: 0.4482,
Messaging: 0.3105,
},
})),
}));
@ -65,6 +73,7 @@ describe('Test DescriptionInsight Component', () => {
<DescriptionInsight
chartFilter={INITIAL_CHART_FILTER}
kpi={undefined}
selectedDays={30}
/>
);
const card = screen.getByTestId('entity-description-percentage-card');

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { isEmpty, uniqueId } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -61,9 +61,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
kpi: Kpi | undefined;
selectedDays: number;
}
const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
const DescriptionInsight: FC<Props> = ({ chartFilter, kpi, selectedDays }) => {
const [totalEntitiesDescriptionByType, setTotalEntitiesDescriptionByType] =
useState<DataInsightChartResult>();
@ -71,7 +72,14 @@ const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const { data, entities, total } = useMemo(() => {
const {
data,
entities,
total,
relativePercentage,
latestData,
isPercentageGraph,
} = useMemo(() => {
return getGraphDataByEntityType(
totalEntitiesDescriptionByType?.data ?? [],
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
@ -140,56 +148,97 @@ const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
</Typography.Text>
</>
}>
<DataInsightProgressBar
className="m-b-md"
progress={Number(total)}
target={targetValue}
width={250}
/>
{data.length ? (
<ResponsiveContainer
debounce={1}
id="description-summary-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, '%')}
/>
<Tooltip content={<CustomTooltip isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, total, activeKeys, false)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
<Row gutter={16}>
<Col span={18}>
<ResponsiveContainer
debounce={1}
id="description-summary-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, '%')
}
/>
<Tooltip content={<CustomTooltip isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, activeKeys)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<Row gutter={[8, 8]}>
<Col span={24}>
<Typography.Paragraph
className="data-insight-label-text"
style={{ marginBottom: '4px' }}>
{t('label.completed-description')}
{isPercentageGraph ? ' %' : ''}
</Typography.Paragraph>
<DataInsightProgressBar
changeInValue={relativePercentage}
className="m-b-md"
duration={selectedDays}
progress={Number(total)}
showLabel={false}
suffix={isPercentageGraph ? '%' : ''}
target={targetValue}
/>
</Col>
{entities.map((entity) => {
const progress = (latestData[entity] / Number(total)) * 100;
return (
<Col key={uniqueId()} span={24}>
<DataInsightProgressBar
showEndValueAsLabel
progress={progress}
showLabel={false}
startValue={latestData[entity].toFixed(2)}
successValue={entity}
suffix={isPercentageGraph ? '%' : ''}
/>
</Col>
);
})}
</Row>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -27,6 +27,8 @@ import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import {
CartesianGrid,
Legend,
LegendProps,
Line,
LineChart,
ResponsiveContainer,
@ -35,7 +37,12 @@ import {
YAxis,
} from 'recharts';
import { getLatestKpiResult, getListKpiResult } from '../../axiosAPIs/KpiAPI';
import { GRAPH_BACKGROUND_COLOR, ROUTES } from '../../constants/constants';
import {
DEFAULT_CHART_OPACITY,
GRAPH_BACKGROUND_COLOR,
HOVER_CHART_OPACITY,
ROUTES,
} from '../../constants/constants';
import {
BAR_CHART_MARGIN,
DATA_INSIGHT_GRAPH_COLORS,
@ -51,7 +58,12 @@ import {
ChartFilter,
UIKpiResult,
} from '../../interface/data-insight.interface';
import { CustomTooltip, getKpiGraphData } from '../../utils/DataInsightUtils';
import { updateActiveChartFilter } from '../../utils/ChartUtils';
import {
CustomTooltip,
getKpiGraphData,
renderLegend,
} from '../../utils/DataInsightUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import './DataInsightDetail.less';
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
@ -71,6 +83,8 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
const [kpiLatestResults, setKpiLatestResults] =
useState<Record<string, UIKpiResult>>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const handleAddKpi = () => history.push(ROUTES.ADD_KPI);
@ -152,6 +166,18 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
return { ...getKpiGraphData(kpiResults, kpiList), kpiTooltipRecord };
}, [kpiResults, kpiList]);
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(() => {
setKpiResults([]);
setKpiLatestResults(undefined);
@ -183,16 +209,10 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
</Space>
}>
{kpiList.length ? (
<Row>
<Row gutter={16}>
{graphData.length ? (
<>
{!isUndefined(kpiLatestResults) && !isEmpty(kpiLatestResults) && (
<Col span={5}>
<KPILatestResults kpiLatestResultsRecord={kpiLatestResults} />
</Col>
)}
<Col span={19}>
<Col span={18}>
<ResponsiveContainer debounce={1} minHeight={400}>
<LineChart data={graphData} margin={BAR_CHART_MARGIN}>
<CartesianGrid
@ -201,6 +221,18 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
/>
<XAxis dataKey="timestamp" />
<YAxis />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, activeKeys)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
<Tooltip
content={
<CustomTooltip kpiTooltipRecord={kpiTooltipRecord} />
@ -209,14 +241,30 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
{kpis.map((kpi, i) => (
<Line
dataKey={kpi}
hide={
activeKeys.length && kpi !== activeMouseHoverKey
? !activeKeys.includes(kpi)
: false
}
key={i}
stroke={DATA_INSIGHT_GRAPH_COLORS[i]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
kpi === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
{!isUndefined(kpiLatestResults) && !isEmpty(kpiLatestResults) && (
<Col span={6}>
<KPILatestResults kpiLatestResultsRecord={kpiLatestResults} />
</Col>
)}
</>
) : (
<EmptyGraphPlaceholder />

View File

@ -46,25 +46,30 @@ const KPILatestResults: FC<Props> = ({ kpiLatestResultsRecord }) => {
const isTargetMet = targetResult.targetMet;
return (
<Space className="w-full" direction="vertical" key={uniqueId()}>
<Space
className="w-full"
direction="vertical"
key={uniqueId()}
size={8}>
<Typography.Text className="data-insight-label-text">
{resultData.displayName ?? name}
</Typography.Text>
<div>
<DataInsightProgressBar
showSuccessInfo
progress={Number(currentProgress)}
showLabel={false}
startValue={isPercentage ? targetPercentValue : targetValue}
successValue={
isPercentage ? targetMetPercentValue : targetMetValue
}
suffix={suffix}
/>
<DataInsightProgressBar
showSuccessInfo
progress={Number(currentProgress)}
showLabel={false}
startValue={isPercentage ? targetPercentValue : targetValue}
successValue={
isPercentage ? targetMetPercentValue : targetMetValue
}
suffix={suffix}
/>
<Typography.Text className="data-insight-label-text">
{getKpiResultFeedback(daysLeft, Boolean(isTargetMet))}
</Typography.Text>
<Typography.Text className="data-insight-label-text">
{getKpiResultFeedback(daysLeft, Boolean(isTargetMet))}
</Typography.Text>
</div>
</Space>
);
})}

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { isEmpty, uniqueId } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -61,9 +61,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
kpi: Kpi | undefined;
selectedDays: number;
}
const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
const OwnerInsight: FC<Props> = ({ chartFilter, kpi, selectedDays }) => {
const [totalEntitiesOwnerByType, setTotalEntitiesOwnerByType] =
useState<DataInsightChartResult>();
@ -71,7 +72,14 @@ const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const { data, entities, total } = useMemo(() => {
const {
data,
entities,
total,
relativePercentage,
latestData,
isPercentageGraph,
} = useMemo(() => {
return getGraphDataByEntityType(
totalEntitiesOwnerByType?.data ?? [],
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
@ -139,53 +147,96 @@ const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
</Typography.Text>
</>
}>
<DataInsightProgressBar
className="m-b-md"
progress={Number(total)}
target={targetValue}
width={250}
/>
{data.length ? (
<ResponsiveContainer debounce={1} 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, '%')}
/>
<Tooltip content={<CustomTooltip isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, total, activeKeys, true)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
<Row gutter={16}>
<Col span={18}>
<ResponsiveContainer debounce={1} 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, '%')
}
/>
<Tooltip content={<CustomTooltip isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, activeKeys)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<Row gutter={[8, 8]}>
<Col span={24}>
<Typography.Paragraph
className="data-insight-label-text"
style={{ marginBottom: '4px' }}>
{t('label.assigned-entity', {
entity: t('label.owner'),
})}
{isPercentageGraph ? ' %' : ''}
</Typography.Paragraph>
<DataInsightProgressBar
changeInValue={relativePercentage}
className="m-b-md"
duration={selectedDays}
progress={Number(total)}
showLabel={false}
suffix={isPercentageGraph ? '%' : ''}
target={targetValue}
/>
</Col>
{entities.map((entity) => {
const progress = (latestData[entity] / Number(total)) * 100;
return (
<Col key={uniqueId()} span={24}>
<DataInsightProgressBar
showEndValueAsLabel
progress={progress}
showLabel={false}
startValue={latestData[entity].toFixed(2)}
successValue={entity}
suffix={isPercentageGraph ? '%' : ''}
/>
</Col>
);
})}
</Row>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { isEmpty, uniqueId } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -48,14 +48,17 @@ import {
renderLegend,
} from '../../utils/DataInsightUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import CustomStatistic from './CustomStatistic';
import './DataInsightDetail.less';
import DataInsightProgressBar from './DataInsightProgressBar';
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
selectedDays: number;
}
const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter, selectedDays }) => {
const [pageViewsByEntities, setPageViewsByEntities] =
useState<PageViewsByEntities[]>();
@ -63,12 +66,13 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const { data, entities, total } = useMemo(() => {
return getGraphDataByEntityType(
pageViewsByEntities,
DataInsightChartType.PageViewsByEntities
);
}, [pageViewsByEntities]);
const { data, entities, total, relativePercentage, latestData } =
useMemo(() => {
return getGraphDataByEntityType(
pageViewsByEntities,
DataInsightChartType.PageViewsByEntities
);
}, [pageViewsByEntities]);
const { t } = useTranslation();
@ -124,44 +128,79 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
</>
}>
{data.length ? (
<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, `${total}`, activeKeys)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
<Row gutter={16}>
<Col span={18}>
<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)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<Row gutter={[8, 8]}>
<Col span={24}>
<CustomStatistic
changeInValue={relativePercentage}
duration={selectedDays}
label={t('label.total-assets-view')}
value={total}
/>
</Col>
{entities.map((entity) => {
const progress = (latestData[entity] / Number(total)) * 100;
return (
<Col key={uniqueId()} span={24}>
<DataInsightProgressBar
progress={progress}
showLabel={false}
startValue={latestData[entity]}
successValue={entity}
suffix=""
/>
</Col>
);
})}
</Row>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { isEmpty, uniqueId } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -56,9 +56,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
selectedDays: number;
}
const TierInsight: FC<Props> = ({ chartFilter }) => {
const TierInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
const [totalEntitiesByTier, setTotalEntitiesByTier] =
useState<DataInsightChartResult>();
@ -66,7 +67,7 @@ const TierInsight: FC<Props> = ({ chartFilter }) => {
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const { data, tiers, total } = useMemo(() => {
const { data, tiers, total, relativePercentage, latestData } = useMemo(() => {
return getGraphDataByTierType(totalEntitiesByTier?.data ?? []);
}, [totalEntitiesByTier]);
@ -122,50 +123,89 @@ const TierInsight: FC<Props> = ({ chartFilter }) => {
</Typography.Text>
</>
}>
<DataInsightProgressBar
className="m-b-md"
progress={Number(total)}
width={250}
/>
{data.length ? (
<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 isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, total, activeKeys, false)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{tiers.map((tier) => (
<Line
dataKey={tier}
hide={
activeKeys.length && tier !== activeMouseHoverKey
? !activeKeys.includes(tier)
: false
}
key={tier}
stroke={TIER_BAR_COLOR_MAP[tier]}
strokeOpacity={
isEmpty(activeMouseHoverKey) || tier === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
<Row gutter={16}>
<Col span={18}>
<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 isPercentage />} />
<Legend
align="left"
content={(props) =>
renderLegend(props as LegendProps, activeKeys)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{tiers.map((tier) => (
<Line
dataKey={tier}
hide={
activeKeys.length && tier !== activeMouseHoverKey
? !activeKeys.includes(tier)
: false
}
key={tier}
stroke={TIER_BAR_COLOR_MAP[tier]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
tier === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<Row gutter={[8, 8]}>
<Col span={24}>
<Typography.Paragraph
className="data-insight-label-text"
style={{ marginBottom: '4px' }}>
{t('label.assigned-entity', {
entity: t('label.tier'),
})}{' '}
%
</Typography.Paragraph>
<DataInsightProgressBar
changeInValue={relativePercentage}
className="m-b-md"
duration={selectedDays}
progress={Number(total)}
showLabel={false}
/>
</Col>
{tiers.map((tiers) => {
const progress = (latestData[tiers] / Number(total)) * 100;
return (
<Col key={uniqueId()} span={24}>
<DataInsightProgressBar
showEndValueAsLabel
progress={progress}
showLabel={false}
startValue={progress.toFixed(2)}
successValue={tiers}
/>
</Col>
);
})}
</Row>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Card, Typography } from 'antd';
import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { isEmpty, uniqueId } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -50,14 +50,17 @@ import {
renderLegend,
} from '../../utils/DataInsightUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import CustomStatistic from './CustomStatistic';
import './DataInsightDetail.less';
import DataInsightProgressBar from './DataInsightProgressBar';
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
interface Props {
chartFilter: ChartFilter;
selectedDays: number;
}
const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
const TotalEntityInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
const [totalEntitiesByType, setTotalEntitiesByType] =
useState<DataInsightChartResult>();
@ -65,12 +68,13 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
const [activeKeys, setActiveKeys] = useState<string[]>([]);
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
const { data, entities, total } = useMemo(() => {
return getGraphDataByEntityType(
totalEntitiesByType?.data ?? [],
DataInsightChartType.TotalEntitiesByType
);
}, [totalEntitiesByType]);
const { data, entities, total, relativePercentage, latestData } =
useMemo(() => {
return getGraphDataByEntityType(
totalEntitiesByType?.data ?? [],
DataInsightChartType.TotalEntitiesByType
);
}, [totalEntitiesByType]);
const { t } = useTranslation();
@ -125,44 +129,81 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
</>
}>
{data.length ? (
<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, `${total}`, activeKeys)
}
layout="vertical"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
<Row gutter={16}>
<Col span={18}>
<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)
}
layout="horizontal"
verticalAlign="top"
wrapperStyle={{ left: '0px', top: '0px' }}
onClick={handleLegendClick}
onMouseEnter={handleLegendMouseEnter}
onMouseLeave={handleLegendMouseLeave}
/>
{entities.map((entity) => (
<Line
dataKey={entity}
hide={
activeKeys.length && entity !== activeMouseHoverKey
? !activeKeys.includes(entity)
: false
}
key={entity}
stroke={ENTITIES_BAR_COLO_MAP[entity]}
strokeOpacity={
isEmpty(activeMouseHoverKey) ||
entity === activeMouseHoverKey
? DEFAULT_CHART_OPACITY
: HOVER_CHART_OPACITY
}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
<Col span={6}>
<Row gutter={[8, 8]}>
<Col span={24}>
<CustomStatistic
changeInValue={relativePercentage}
duration={selectedDays}
label={t('label.total-entity', {
entity: t('label.assets'),
})}
value={total}
/>
</Col>
{entities.map((entity) => {
const progress = (latestData[entity] / Number(total)) * 100;
return (
<Col key={uniqueId()} span={24}>
<DataInsightProgressBar
progress={progress}
showLabel={false}
startValue={latestData[entity]}
successValue={entity}
suffix=""
/>
</Col>
);
})}
</Row>
</Col>
</Row>
) : (
<EmptyGraphPlaceholder />
)}

View File

@ -54,6 +54,8 @@
}
.quick-filter-dropdown-trigger-btn {
padding: 4px 8px;
.remove-field-icon {
color: @remove-icon-color;
padding-bottom: 2px;

View File

@ -23,8 +23,8 @@ import {
export const BAR_CHART_MARGIN: Margin = {
top: 20,
right: 30,
left: 20,
right: 20,
left: 10,
bottom: 5,
};

View File

@ -30,7 +30,7 @@ export const GRAPH_BACKGROUND_COLOR = '#f5f5f5';
export const GRAYED_OUT_COLOR = '#CCCCCC';
export const DEFAULT_CHART_OPACITY = 1;
export const HOVER_CHART_OPACITY = 0.5;
export const HOVER_CHART_OPACITY = 0.3;
export const SUPPORTED_FIELD_TYPES = ['string', 'markdown', 'integer'];

View File

@ -577,7 +577,14 @@
"event-type": "Event Type",
"connection-timeout-plural-optional": "Connection Timeout (s)",
"all-data-assets": "All Data Assets",
"specific-data-assets": "Specific Data Assets"
"specific-data-assets": "Specific Data Assets",
"days-change-lowercase": "{{days}}-days change",
"total-entity": "Total {{entity}}",
"assets": "Assets",
"completed-description": "Completed Description",
"assigned-entity": "Assigned {{entity}}",
"total-assets-view": "Total Assets View",
"total-active-user": "Total Active User"
},
"message": {
"service-email-required": "Service account Email is required",

View File

@ -17,6 +17,8 @@
@box-shadow-color: rgba(0, 0, 0, 0.06);
@icon-color: #515151;
@group-title-color: #76746f;
@summary-card-bg-hover: #f0f1f3;
@target-blue-color: #1890ff;
.data-insight-select-dropdown {
min-width: 136px;
@ -28,6 +30,7 @@
.data-insight-left-panel.ant-menu-root.ant-menu-inline {
background: @white;
border: 0px;
margin-top: 16px;
.ant-menu-item::after {
left: 0;
@ -87,6 +90,7 @@
// library has given height directly via style attribute , so to override need to provide !important
height: 30px !important;
}
.ant-progress-outer {
margin-right: 0;
padding-right: 0;
@ -109,12 +113,48 @@
left: 2px;
padding: 0 16px 0 8px;
font-size: 12px;
color: @text-color;
}
.data-insight-kpi-target {
height: 100%;
position: absolute;
left: 0;
border-right: 2px solid #1890ff;
border-radius: 4px;
border-right: 2px solid @target-blue-color;
.target-text {
position: relative;
right: -8px;
top: -14px;
display: flex;
justify-content: flex-end;
font-size: 10px;
}
&::after {
content: '●';
position: absolute;
top: -5px;
right: -6px;
color: @target-blue-color;
font-size: 10px;
}
}
}
.custom-data-insight-legend {
display: flex;
gap: 4px;
.custom-data-insight-legend-item {
display: flex;
align-items: center;
padding: 4px 8px;
cursor: pointer;
border-radius: 4px;
color: @text-grey-muted;
&:hover {
background: @summary-card-bg-hover;
}
}
}

View File

@ -94,6 +94,7 @@ const DataInsightPage = () => {
const [chartFilter, setChartFilter] =
useState<ChartFilter>(INITIAL_CHART_FILTER);
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
const [selectedDaysFilter, setSelectedDaysFilter] = useState(DEFAULT_DAYS);
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
@ -127,6 +128,7 @@ const DataInsightPage = () => {
};
const handleDaysChange = (days: number) => {
setSelectedDaysFilter(days);
setChartFilter((previous) => ({
...previous,
startTs: getPastDaysDateTimeMillis(days),
@ -356,19 +358,30 @@ const DataInsightPage = () => {
{activeTab === DataInsightTabs.DATA_ASSETS && (
<>
<Col span={24}>
<TotalEntityInsight chartFilter={chartFilter} />
<TotalEntityInsight
chartFilter={chartFilter}
selectedDays={selectedDaysFilter}
/>
</Col>
<Col span={24}>
<DescriptionInsight
chartFilter={chartFilter}
kpi={descriptionKpi}
selectedDays={selectedDaysFilter}
/>
</Col>
<Col span={24}>
<OwnerInsight chartFilter={chartFilter} kpi={ownerKpi} />
<OwnerInsight
chartFilter={chartFilter}
kpi={ownerKpi}
selectedDays={selectedDaysFilter}
/>
</Col>
<Col span={24}>
<TierInsight chartFilter={chartFilter} />
<TierInsight
chartFilter={chartFilter}
selectedDays={selectedDaysFilter}
/>
</Col>
</>
)}
@ -378,10 +391,16 @@ const DataInsightPage = () => {
<TopViewEntities chartFilter={chartFilter} />
</Col>
<Col span={24}>
<PageViewsByEntitiesChart chartFilter={chartFilter} />
<PageViewsByEntitiesChart
chartFilter={chartFilter}
selectedDays={selectedDaysFilter}
/>
</Col>
<Col span={24}>
<DailyActiveUsersChart chartFilter={chartFilter} />
<DailyActiveUsersChart
chartFilter={chartFilter}
selectedDays={selectedDaysFilter}
/>
</Col>
<Col span={24}>
<TopActiveUsers chartFilter={chartFilter} />

View File

@ -15,6 +15,7 @@ import { Card, Typography } from 'antd';
import { RangePickerProps } from 'antd/lib/date-picker';
import { t } from 'i18next';
import {
first,
groupBy,
isEmpty,
isInteger,
@ -52,7 +53,10 @@ import {
KpiDates,
} from '../interface/data-insight.interface';
import { pluralize } from './CommonUtils';
import { getFormattedDateFromMilliSeconds } from './TimeUtils';
import {
getDateByTimeStamp,
getFormattedDateFromMilliSeconds,
} from './TimeUtils';
const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
[
@ -62,62 +66,46 @@ const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
export const renderLegend = (
legendData: LegendProps,
latest: string | number,
activeKeys = [] as string[],
showLatestValue = true
activeKeys = [] as string[]
) => {
const { payload = [] } = legendData;
return (
<>
{showLatestValue && (
<>
<Typography.Text className="data-insight-label-text">
Latest
</Typography.Text>
<Typography
className="font-bold text-lg"
style={{ margin: '0px 0px 16px' }}>
{latest}
</Typography>
</>
)}
<ul className="mr-2">
{payload.map((entry, index) => {
const isActive =
activeKeys.length === 0 || activeKeys.includes(entry.value);
<ul className="custom-data-insight-legend">
{payload.map((entry, index) => {
const isActive =
activeKeys.length === 0 || activeKeys.includes(entry.value);
return (
<li
className="recharts-legend-item d-flex items-center m-t-xss cursor-pointer"
key={`item-${index}`}
onClick={(e) =>
legendData.onClick && legendData.onClick({ ...entry, ...e })
}
onMouseEnter={(e) =>
legendData.onMouseEnter &&
legendData.onMouseEnter({ ...entry, ...e })
}
onMouseLeave={(e) =>
legendData.onMouseLeave &&
legendData.onMouseLeave({ ...entry, ...e })
}>
<Surface className="mr-2" height={14} version="1.1" width={14}>
<rect
fill={isActive ? entry.color : GRAYED_OUT_COLOR}
height="14"
rx="2"
width="14"
/>
</Surface>
<span style={{ color: isActive ? 'inherit' : GRAYED_OUT_COLOR }}>
{entry.value}
</span>
</li>
);
})}
</ul>
</>
return (
<li
className="recharts-legend-item custom-data-insight-legend-item"
key={`item-${index}`}
onClick={(e) =>
legendData.onClick && legendData.onClick({ ...entry, ...e })
}
onMouseEnter={(e) =>
legendData.onMouseEnter &&
legendData.onMouseEnter({ ...entry, ...e })
}
onMouseLeave={(e) =>
legendData.onMouseLeave &&
legendData.onMouseLeave({ ...entry, ...e })
}>
<Surface className="m-r-xss" height={14} version="1.1" width={14}>
<rect
fill={isActive ? entry.color : GRAYED_OUT_COLOR}
height="14"
rx="2"
width="14"
/>
</Surface>
<span style={{ color: isActive ? 'inherit' : GRAYED_OUT_COLOR }}>
{entry.value}
</span>
</li>
);
})}
</ul>
);
};
@ -157,20 +145,29 @@ const getEntryFormattedValue = (
};
export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
const { active, payload = [], label, isPercentage, kpiTooltipRecord } = props;
const { active, payload = [], isPercentage, kpiTooltipRecord } = props;
if (active && payload && payload.length) {
const timestamp = getDateByTimeStamp(
payload[0].payload.timestampValue || 0,
'MMM dd, yyyy'
);
return (
<Card>
{/* this is a graph tooltip so using the explicit title here */}
<Typography.Title level={5}>{label}</Typography.Title>
<Card
className="custom-data-insight-tooltip"
title={<Typography.Title level={5}>{timestamp}</Typography.Title>}>
{payload.map((entry, index) => (
<li className="d-flex items-center" key={`item-${index}`}>
<Surface className="mr-2" height={14} version="1.1" width={14}>
<rect fill={entry.color} height="14" rx="2" width="14" />
</Surface>
<span>
{entry.dataKey} -{' '}
<li
className="d-flex items-center justify-between tw-gap-6 tw-pb-1.5 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>
{entry.dataKey}
</span>
<span className="font-medium">
{getEntryFormattedValue(
entry.value,
entry.dataKey,
@ -226,7 +223,8 @@ const getLatestCount = (latestData = {}) => {
const latestEntries = Object.entries(latestData ?? {});
for (const entry of latestEntries) {
if (entry[0] !== 'timestamp') {
// if key is 'timestamp' or 'timestampValue' skipping its count for total
if (!['timestamp', 'timestampValue'].includes(entry[0])) {
total += toNumber(entry[1]);
}
}
@ -290,6 +288,62 @@ const getLatestPercentage = (
return 0;
};
/**
*
* @param rawData raw chart data
* @param dataInsightChartType chart type
* @returns old percentage for the chart
*/
const getOldestPercentage = (
rawData: DataInsightChartResult['data'] = [],
dataInsightChartType: DataInsightChartType
) => {
let totalEntityCount = 0;
let totalEntityWithDescription = 0;
let totalEntityWithOwner = 0;
const modifiedData = rawData
.map((raw) => {
const timestamp = raw.timestamp;
if (timestamp) {
return {
...raw,
timestamp,
};
}
return;
})
.filter(Boolean);
const sortedData = sortBy(modifiedData, 'timestamp');
const groupDataByTimeStamp = groupBy(sortedData, 'timestamp');
const oldestData = first(sortedData);
if (oldestData) {
const oldestChartRecords = groupDataByTimeStamp[oldestData.timestamp];
oldestChartRecords.forEach((record) => {
totalEntityCount += record?.entityCount ?? 0;
totalEntityWithDescription += record?.completedDescription ?? 0;
totalEntityWithOwner += record?.hasOwner ?? 0;
});
switch (dataInsightChartType) {
case DataInsightChartType.PercentageOfEntitiesWithDescriptionByType:
return ((totalEntityWithDescription / totalEntityCount) * 100).toFixed(
2
);
case DataInsightChartType.PercentageOfEntitiesWithOwnerByType:
return ((totalEntityWithOwner / totalEntityCount) * 100).toFixed(2);
default:
return 0;
}
}
return 0;
};
/**
*
* @param rawData raw chart data
@ -341,6 +395,7 @@ const getGraphFilteredData = (
return {
timestamp: timestamp,
timestampValue: data.timestamp,
[data.entityType]: value,
};
}
@ -370,7 +425,20 @@ export const getGraphDataByEntityType = (
);
const graphData = prepareGraphData(timestamps, filteredData);
const latestData = last(graphData);
const latestData = last(graphData) as Record<string, number>;
const oldData = first(graphData);
const latestPercentage = toNumber(
isPercentageGraph
? getLatestPercentage(rawData, dataInsightChartType)
: getLatestCount(latestData)
);
const oldestPercentage = toNumber(
isPercentageGraph
? getOldestPercentage(rawData, dataInsightChartType)
: getLatestCount(oldData)
);
const relativePercentage = latestPercentage - oldestPercentage;
return {
data: graphData,
@ -378,6 +446,11 @@ export const getGraphDataByEntityType = (
total: isPercentageGraph
? getLatestPercentage(rawData, dataInsightChartType)
: getLatestCount(latestData),
relativePercentage: isPercentageGraph
? relativePercentage
: (relativePercentage / oldestPercentage) * 100,
latestData,
isPercentageGraph,
};
};
@ -403,6 +476,7 @@ export const getGraphDataByTierType = (rawData: TotalEntitiesByTier[]) => {
}
return {
timestampValue: data.timestamp,
timestamp: timestamp,
[tiering]: ((data?.entityCountFraction || 0) * 100).toFixed(2),
};
@ -412,12 +486,16 @@ export const getGraphDataByTierType = (rawData: TotalEntitiesByTier[]) => {
});
const graphData = prepareGraphData(timestamps, filteredData);
const latestData = last(graphData);
const latestData = getLatestCount(last(graphData));
const oldestData = getLatestCount(first(graphData));
const relativePercentage = latestData - oldestData;
return {
data: graphData,
tiers,
total: getLatestCount(latestData),
total: latestData,
relativePercentage,
latestData: last(graphData) as Record<string, number>,
};
};
@ -430,14 +508,21 @@ export const getFormattedActiveUsersData = (
) => {
const formattedData = activeUsers.map((user) => ({
...user,
timestampValue: user.timestamp,
timestamp: user.timestamp
? getFormattedDateFromMilliSeconds(user.timestamp)
: '',
}));
const latestCount = Number(last(formattedData)?.activeUsers);
const oldestCount = Number(first(formattedData)?.activeUsers);
const relativePercentage = ((latestCount - oldestCount) / oldestCount) * 100;
return {
data: formattedData,
total: last(formattedData)?.activeUsers,
total: latestCount,
relativePercentage,
};
};
@ -518,6 +603,7 @@ export const getKpiGraphData = (kpiResults: KpiResult[], kpiList: Kpi[]) => {
}
return {
timestampValue: kpiResult.timestamp,
timestamp,
[kpiFqn]:
currentKpi?.metricType === KpiTargetType.Percentage