mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 18:36:08 +00:00
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:
parent
1a00e81fe8
commit
5e81d51c4d
@ -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;
|
@ -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;
|
@ -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 />
|
||||
)}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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');
|
||||
|
@ -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 />
|
||||
)}
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
@ -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 />
|
||||
)}
|
||||
|
@ -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 />
|
||||
)}
|
||||
|
@ -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 />
|
||||
)}
|
||||
|
@ -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 />
|
||||
)}
|
||||
|
@ -54,6 +54,8 @@
|
||||
}
|
||||
|
||||
.quick-filter-dropdown-trigger-btn {
|
||||
padding: 4px 8px;
|
||||
|
||||
.remove-field-icon {
|
||||
color: @remove-icon-color;
|
||||
padding-bottom: 2px;
|
||||
|
@ -23,8 +23,8 @@ import {
|
||||
|
||||
export const BAR_CHART_MARGIN: Margin = {
|
||||
top: 20,
|
||||
right: 30,
|
||||
left: 20,
|
||||
right: 20,
|
||||
left: 10,
|
||||
bottom: 5,
|
||||
};
|
||||
|
||||
|
@ -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'];
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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} />
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user