mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-28 10:56:02 +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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -42,14 +42,16 @@ import {
|
|||||||
renderLegend,
|
renderLegend,
|
||||||
} from '../../utils/DataInsightUtils';
|
} from '../../utils/DataInsightUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import CustomStatistic from './CustomStatistic';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
|
const DailyActiveUsersChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||||
const [dailyActiveUsers, setDailyActiveUsers] = useState<DailyActiveUsers[]>(
|
const [dailyActiveUsers, setDailyActiveUsers] = useState<DailyActiveUsers[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -58,7 +60,7 @@ const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { data, total } = useMemo(
|
const { data, total, relativePercentage } = useMemo(
|
||||||
() => getFormattedActiveUsersData(dailyActiveUsers),
|
() => getFormattedActiveUsersData(dailyActiveUsers),
|
||||||
[dailyActiveUsers]
|
[dailyActiveUsers]
|
||||||
);
|
);
|
||||||
@ -102,28 +104,43 @@ const DailyActiveUsersChart: FC<Props> = ({ chartFilter }) => {
|
|||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
{dailyActiveUsers.length ? (
|
{dailyActiveUsers.length ? (
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<Row gutter={16}>
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
<Col span={18}>
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<Legend
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
align="left"
|
<CartesianGrid
|
||||||
content={() =>
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
renderLegend({ payload: [] } as LegendProps, `${total}`)
|
vertical={false}
|
||||||
}
|
/>
|
||||||
layout="vertical"
|
<Legend
|
||||||
verticalAlign="top"
|
align="left"
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
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" />
|
</Col>
|
||||||
<YAxis />
|
</Row>
|
||||||
<Tooltip content={<CustomTooltip />} />
|
|
||||||
<Line
|
|
||||||
dataKey="activeUsers"
|
|
||||||
stroke={DATA_INSIGHT_GRAPH_COLORS[3]}
|
|
||||||
type="monotone"
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
) : (
|
) : (
|
||||||
<EmptyGraphPlaceholder />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import url('../../styles/variables.less');
|
||||||
|
|
||||||
@label-color: #6b7280;
|
@label-color: #6b7280;
|
||||||
@summary-card-bg-hover: #f0f1f3;
|
@summary-card-bg-hover: #f0f1f3;
|
||||||
|
|
||||||
@ -18,6 +20,14 @@
|
|||||||
.ant-card-head {
|
.ant-card-head {
|
||||||
border-bottom: none;
|
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 {
|
.ant-typography.data-insight-label-text {
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
|
|
||||||
import { Progress, Typography } from 'antd';
|
import { Progress, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { isNil } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
|
import ChangeInValueIndicator from './ChangeInValueIndicator';
|
||||||
|
|
||||||
interface DataInsightProgressBarProps {
|
interface DataInsightProgressBarProps {
|
||||||
width?: number;
|
width?: number;
|
||||||
@ -28,9 +30,13 @@ interface DataInsightProgressBarProps {
|
|||||||
successValue?: number | string;
|
successValue?: number | string;
|
||||||
startValue?: number | string;
|
startValue?: number | string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
|
changeInValue?: number;
|
||||||
|
duration?: number;
|
||||||
|
showEndValueAsLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataInsightProgressBar = ({
|
const DataInsightProgressBar = ({
|
||||||
|
showEndValueAsLabel = false,
|
||||||
width,
|
width,
|
||||||
progress,
|
progress,
|
||||||
className,
|
className,
|
||||||
@ -41,17 +47,19 @@ const DataInsightProgressBar = ({
|
|||||||
successValue = 100,
|
successValue = 100,
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
showSuccessInfo = false,
|
showSuccessInfo = false,
|
||||||
|
changeInValue,
|
||||||
|
duration,
|
||||||
}: DataInsightProgressBarProps) => {
|
}: DataInsightProgressBarProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(className)} style={{ width }}>
|
<div className={classNames(className)} style={{ width }}>
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
<Typography.Text className="data-insight-label-text">
|
<Typography.Paragraph className="data-insight-label-text">
|
||||||
{label ?? t('label.latest')}
|
{label ?? t('label.latest')}
|
||||||
</Typography.Text>
|
</Typography.Paragraph>
|
||||||
)}
|
)}
|
||||||
<div className="flex">
|
<div className={classNames('flex', { 'm-t-sm': Boolean(target) })}>
|
||||||
<Progress
|
<Progress
|
||||||
className="data-insight-progress-bar"
|
className="data-insight-progress-bar"
|
||||||
format={(per) => (
|
format={(per) => (
|
||||||
@ -63,12 +71,16 @@ const DataInsightProgressBar = ({
|
|||||||
{target && (
|
{target && (
|
||||||
<span
|
<span
|
||||||
className="data-insight-kpi-target"
|
className="data-insight-kpi-target"
|
||||||
style={{ width: `${target}%` }}
|
style={{ width: `${target}%` }}>
|
||||||
/>
|
<span className="target-text">
|
||||||
|
{target}
|
||||||
|
{suffix}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
{successValue}
|
{successValue}
|
||||||
{suffix}
|
{showEndValueAsLabel ? '' : suffix}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -79,6 +91,16 @@ const DataInsightProgressBar = ({
|
|||||||
<SVGIcons className="m-l-xs" icon={Icons.SUCCESS_BADGE} />
|
<SVGIcons className="m-l-xs" icon={Icons.SUCCESS_BADGE} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{changeInValue && !isNil(changeInValue) ? (
|
||||||
|
<ChangeInValueIndicator
|
||||||
|
changeInValue={changeInValue}
|
||||||
|
duration={duration}
|
||||||
|
suffix={suffix}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -179,6 +179,7 @@ const DataInsightSummary: FC<Props> = ({ chartFilter, onScrollToChart }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
bodyStyle={{ paddingTop: 0 }}
|
||||||
className="data-insight-card"
|
className="data-insight-card"
|
||||||
data-testid="summary-card"
|
data-testid="summary-card"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@ -204,7 +205,7 @@ const DataInsightSummary: FC<Props> = ({ chartFilter, onScrollToChart }) => {
|
|||||||
<Typography.Text className="data-insight-label-text">
|
<Typography.Text className="data-insight-label-text">
|
||||||
{summary.label}
|
{summary.label}
|
||||||
</Typography.Text>
|
</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.latest}
|
||||||
{summary.id.startsWith('Percentage') ||
|
{summary.id.startsWith('Percentage') ||
|
||||||
summary.id.includes(DataInsightChartType.TotalEntitiesByTier)
|
summary.id.includes(DataInsightChartType.TotalEntitiesByTier)
|
||||||
|
@ -49,6 +49,14 @@ jest.mock('../../utils/DataInsightUtils', () => ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
entities: ['Table', 'Topic', 'Database', 'Pipeline', 'Messaging'],
|
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
|
<DescriptionInsight
|
||||||
chartFilter={INITIAL_CHART_FILTER}
|
chartFilter={INITIAL_CHART_FILTER}
|
||||||
kpi={undefined}
|
kpi={undefined}
|
||||||
|
selectedDays={30}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const card = screen.getByTestId('entity-description-percentage-card');
|
const card = screen.getByTestId('entity-description-percentage-card');
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniqueId } from 'lodash';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -61,9 +61,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
|||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
kpi: Kpi | undefined;
|
kpi: Kpi | undefined;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
const DescriptionInsight: FC<Props> = ({ chartFilter, kpi, selectedDays }) => {
|
||||||
const [totalEntitiesDescriptionByType, setTotalEntitiesDescriptionByType] =
|
const [totalEntitiesDescriptionByType, setTotalEntitiesDescriptionByType] =
|
||||||
useState<DataInsightChartResult>();
|
useState<DataInsightChartResult>();
|
||||||
|
|
||||||
@ -71,7 +72,14 @@ const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
|||||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const { data, entities, total } = useMemo(() => {
|
const {
|
||||||
|
data,
|
||||||
|
entities,
|
||||||
|
total,
|
||||||
|
relativePercentage,
|
||||||
|
latestData,
|
||||||
|
isPercentageGraph,
|
||||||
|
} = useMemo(() => {
|
||||||
return getGraphDataByEntityType(
|
return getGraphDataByEntityType(
|
||||||
totalEntitiesDescriptionByType?.data ?? [],
|
totalEntitiesDescriptionByType?.data ?? [],
|
||||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
||||||
@ -140,56 +148,97 @@ const DescriptionInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
<DataInsightProgressBar
|
|
||||||
className="m-b-md"
|
|
||||||
progress={Number(total)}
|
|
||||||
target={targetValue}
|
|
||||||
width={250}
|
|
||||||
/>
|
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
<ResponsiveContainer
|
<Row gutter={16}>
|
||||||
debounce={1}
|
<Col span={18}>
|
||||||
id="description-summary-graph"
|
<ResponsiveContainer
|
||||||
minHeight={400}>
|
debounce={1}
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
id="description-summary-graph"
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
minHeight={400}>
|
||||||
<XAxis dataKey="timestamp" />
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
<YAxis
|
<CartesianGrid
|
||||||
tickFormatter={(value: number) => axisTickFormatter(value, '%')}
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
/>
|
vertical={false}
|
||||||
<Tooltip content={<CustomTooltip isPercentage />} />
|
/>
|
||||||
<Legend
|
<XAxis dataKey="timestamp" />
|
||||||
align="left"
|
<YAxis
|
||||||
content={(props) =>
|
tickFormatter={(value: number) =>
|
||||||
renderLegend(props as LegendProps, total, activeKeys, false)
|
axisTickFormatter(value, '%')
|
||||||
}
|
}
|
||||||
layout="vertical"
|
/>
|
||||||
verticalAlign="top"
|
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
<Legend
|
||||||
onClick={handleLegendClick}
|
align="left"
|
||||||
onMouseEnter={handleLegendMouseEnter}
|
content={(props) =>
|
||||||
onMouseLeave={handleLegendMouseLeave}
|
renderLegend(props as LegendProps, activeKeys)
|
||||||
/>
|
}
|
||||||
{entities.map((entity) => (
|
layout="horizontal"
|
||||||
<Line
|
verticalAlign="top"
|
||||||
dataKey={entity}
|
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||||
hide={
|
onClick={handleLegendClick}
|
||||||
activeKeys.length && entity !== activeMouseHoverKey
|
onMouseEnter={handleLegendMouseEnter}
|
||||||
? !activeKeys.includes(entity)
|
onMouseLeave={handleLegendMouseLeave}
|
||||||
: false
|
/>
|
||||||
}
|
{entities.map((entity) => (
|
||||||
key={entity}
|
<Line
|
||||||
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
dataKey={entity}
|
||||||
strokeOpacity={
|
hide={
|
||||||
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
|
activeKeys.length && entity !== activeMouseHoverKey
|
||||||
? DEFAULT_CHART_OPACITY
|
? !activeKeys.includes(entity)
|
||||||
: HOVER_CHART_OPACITY
|
: false
|
||||||
}
|
}
|
||||||
type="monotone"
|
key={entity}
|
||||||
/>
|
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
||||||
))}
|
strokeOpacity={
|
||||||
</LineChart>
|
isEmpty(activeMouseHoverKey) ||
|
||||||
</ResponsiveContainer>
|
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 />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -27,6 +27,8 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
|
Legend,
|
||||||
|
LegendProps,
|
||||||
Line,
|
Line,
|
||||||
LineChart,
|
LineChart,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@ -35,7 +37,12 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { getLatestKpiResult, getListKpiResult } from '../../axiosAPIs/KpiAPI';
|
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 {
|
import {
|
||||||
BAR_CHART_MARGIN,
|
BAR_CHART_MARGIN,
|
||||||
DATA_INSIGHT_GRAPH_COLORS,
|
DATA_INSIGHT_GRAPH_COLORS,
|
||||||
@ -51,7 +58,12 @@ import {
|
|||||||
ChartFilter,
|
ChartFilter,
|
||||||
UIKpiResult,
|
UIKpiResult,
|
||||||
} from '../../interface/data-insight.interface';
|
} 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 { showErrorToast } from '../../utils/ToastUtils';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||||
@ -71,6 +83,8 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
|
|||||||
const [kpiLatestResults, setKpiLatestResults] =
|
const [kpiLatestResults, setKpiLatestResults] =
|
||||||
useState<Record<string, UIKpiResult>>();
|
useState<Record<string, UIKpiResult>>();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const handleAddKpi = () => history.push(ROUTES.ADD_KPI);
|
const handleAddKpi = () => history.push(ROUTES.ADD_KPI);
|
||||||
|
|
||||||
@ -152,6 +166,18 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
|
|||||||
return { ...getKpiGraphData(kpiResults, kpiList), kpiTooltipRecord };
|
return { ...getKpiGraphData(kpiResults, kpiList), kpiTooltipRecord };
|
||||||
}, [kpiResults, kpiList]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
setKpiResults([]);
|
setKpiResults([]);
|
||||||
setKpiLatestResults(undefined);
|
setKpiLatestResults(undefined);
|
||||||
@ -183,16 +209,10 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
|
|||||||
</Space>
|
</Space>
|
||||||
}>
|
}>
|
||||||
{kpiList.length ? (
|
{kpiList.length ? (
|
||||||
<Row>
|
<Row gutter={16}>
|
||||||
{graphData.length ? (
|
{graphData.length ? (
|
||||||
<>
|
<>
|
||||||
{!isUndefined(kpiLatestResults) && !isEmpty(kpiLatestResults) && (
|
<Col span={18}>
|
||||||
<Col span={5}>
|
|
||||||
<KPILatestResults kpiLatestResultsRecord={kpiLatestResults} />
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col span={19}>
|
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<LineChart data={graphData} margin={BAR_CHART_MARGIN}>
|
<LineChart data={graphData} margin={BAR_CHART_MARGIN}>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
@ -201,6 +221,18 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
|
|||||||
/>
|
/>
|
||||||
<XAxis dataKey="timestamp" />
|
<XAxis dataKey="timestamp" />
|
||||||
<YAxis />
|
<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
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
<CustomTooltip kpiTooltipRecord={kpiTooltipRecord} />
|
<CustomTooltip kpiTooltipRecord={kpiTooltipRecord} />
|
||||||
@ -209,14 +241,30 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
|
|||||||
{kpis.map((kpi, i) => (
|
{kpis.map((kpi, i) => (
|
||||||
<Line
|
<Line
|
||||||
dataKey={kpi}
|
dataKey={kpi}
|
||||||
|
hide={
|
||||||
|
activeKeys.length && kpi !== activeMouseHoverKey
|
||||||
|
? !activeKeys.includes(kpi)
|
||||||
|
: false
|
||||||
|
}
|
||||||
key={i}
|
key={i}
|
||||||
stroke={DATA_INSIGHT_GRAPH_COLORS[i]}
|
stroke={DATA_INSIGHT_GRAPH_COLORS[i]}
|
||||||
|
strokeOpacity={
|
||||||
|
isEmpty(activeMouseHoverKey) ||
|
||||||
|
kpi === activeMouseHoverKey
|
||||||
|
? DEFAULT_CHART_OPACITY
|
||||||
|
: HOVER_CHART_OPACITY
|
||||||
|
}
|
||||||
type="monotone"
|
type="monotone"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Col>
|
</Col>
|
||||||
|
{!isUndefined(kpiLatestResults) && !isEmpty(kpiLatestResults) && (
|
||||||
|
<Col span={6}>
|
||||||
|
<KPILatestResults kpiLatestResultsRecord={kpiLatestResults} />
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<EmptyGraphPlaceholder />
|
<EmptyGraphPlaceholder />
|
||||||
|
@ -46,25 +46,30 @@ const KPILatestResults: FC<Props> = ({ kpiLatestResultsRecord }) => {
|
|||||||
const isTargetMet = targetResult.targetMet;
|
const isTargetMet = targetResult.targetMet;
|
||||||
|
|
||||||
return (
|
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">
|
<Typography.Text className="data-insight-label-text">
|
||||||
{resultData.displayName ?? name}
|
{resultData.displayName ?? name}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
<div>
|
||||||
|
<DataInsightProgressBar
|
||||||
|
showSuccessInfo
|
||||||
|
progress={Number(currentProgress)}
|
||||||
|
showLabel={false}
|
||||||
|
startValue={isPercentage ? targetPercentValue : targetValue}
|
||||||
|
successValue={
|
||||||
|
isPercentage ? targetMetPercentValue : targetMetValue
|
||||||
|
}
|
||||||
|
suffix={suffix}
|
||||||
|
/>
|
||||||
|
|
||||||
<DataInsightProgressBar
|
<Typography.Text className="data-insight-label-text">
|
||||||
showSuccessInfo
|
{getKpiResultFeedback(daysLeft, Boolean(isTargetMet))}
|
||||||
progress={Number(currentProgress)}
|
</Typography.Text>
|
||||||
showLabel={false}
|
</div>
|
||||||
startValue={isPercentage ? targetPercentValue : targetValue}
|
|
||||||
successValue={
|
|
||||||
isPercentage ? targetMetPercentValue : targetMetValue
|
|
||||||
}
|
|
||||||
suffix={suffix}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Typography.Text className="data-insight-label-text">
|
|
||||||
{getKpiResultFeedback(daysLeft, Boolean(isTargetMet))}
|
|
||||||
</Typography.Text>
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniqueId } from 'lodash';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -61,9 +61,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
|||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
kpi: Kpi | undefined;
|
kpi: Kpi | undefined;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
const OwnerInsight: FC<Props> = ({ chartFilter, kpi, selectedDays }) => {
|
||||||
const [totalEntitiesOwnerByType, setTotalEntitiesOwnerByType] =
|
const [totalEntitiesOwnerByType, setTotalEntitiesOwnerByType] =
|
||||||
useState<DataInsightChartResult>();
|
useState<DataInsightChartResult>();
|
||||||
|
|
||||||
@ -71,7 +72,14 @@ const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
|||||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const { data, entities, total } = useMemo(() => {
|
const {
|
||||||
|
data,
|
||||||
|
entities,
|
||||||
|
total,
|
||||||
|
relativePercentage,
|
||||||
|
latestData,
|
||||||
|
isPercentageGraph,
|
||||||
|
} = useMemo(() => {
|
||||||
return getGraphDataByEntityType(
|
return getGraphDataByEntityType(
|
||||||
totalEntitiesOwnerByType?.data ?? [],
|
totalEntitiesOwnerByType?.data ?? [],
|
||||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
||||||
@ -139,53 +147,96 @@ const OwnerInsight: FC<Props> = ({ chartFilter, kpi }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
<DataInsightProgressBar
|
|
||||||
className="m-b-md"
|
|
||||||
progress={Number(total)}
|
|
||||||
target={targetValue}
|
|
||||||
width={250}
|
|
||||||
/>
|
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<Row gutter={16}>
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
<Col span={18}>
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<XAxis dataKey="timestamp" />
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
<YAxis
|
<CartesianGrid
|
||||||
tickFormatter={(value: number) => axisTickFormatter(value, '%')}
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
/>
|
vertical={false}
|
||||||
<Tooltip content={<CustomTooltip isPercentage />} />
|
/>
|
||||||
<Legend
|
<XAxis dataKey="timestamp" />
|
||||||
align="left"
|
<YAxis
|
||||||
content={(props) =>
|
tickFormatter={(value: number) =>
|
||||||
renderLegend(props as LegendProps, total, activeKeys, true)
|
axisTickFormatter(value, '%')
|
||||||
}
|
}
|
||||||
layout="vertical"
|
/>
|
||||||
verticalAlign="top"
|
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
<Legend
|
||||||
onClick={handleLegendClick}
|
align="left"
|
||||||
onMouseEnter={handleLegendMouseEnter}
|
content={(props) =>
|
||||||
onMouseLeave={handleLegendMouseLeave}
|
renderLegend(props as LegendProps, activeKeys)
|
||||||
/>
|
}
|
||||||
{entities.map((entity) => (
|
layout="horizontal"
|
||||||
<Line
|
verticalAlign="top"
|
||||||
dataKey={entity}
|
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||||
hide={
|
onClick={handleLegendClick}
|
||||||
activeKeys.length && entity !== activeMouseHoverKey
|
onMouseEnter={handleLegendMouseEnter}
|
||||||
? !activeKeys.includes(entity)
|
onMouseLeave={handleLegendMouseLeave}
|
||||||
: false
|
/>
|
||||||
}
|
{entities.map((entity) => (
|
||||||
key={entity}
|
<Line
|
||||||
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
dataKey={entity}
|
||||||
strokeOpacity={
|
hide={
|
||||||
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
|
activeKeys.length && entity !== activeMouseHoverKey
|
||||||
? DEFAULT_CHART_OPACITY
|
? !activeKeys.includes(entity)
|
||||||
: HOVER_CHART_OPACITY
|
: false
|
||||||
}
|
}
|
||||||
type="monotone"
|
key={entity}
|
||||||
/>
|
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
||||||
))}
|
strokeOpacity={
|
||||||
</LineChart>
|
isEmpty(activeMouseHoverKey) ||
|
||||||
</ResponsiveContainer>
|
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 />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniqueId } from 'lodash';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -48,14 +48,17 @@ import {
|
|||||||
renderLegend,
|
renderLegend,
|
||||||
} from '../../utils/DataInsightUtils';
|
} from '../../utils/DataInsightUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import CustomStatistic from './CustomStatistic';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
|
import DataInsightProgressBar from './DataInsightProgressBar';
|
||||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
|
const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||||
const [pageViewsByEntities, setPageViewsByEntities] =
|
const [pageViewsByEntities, setPageViewsByEntities] =
|
||||||
useState<PageViewsByEntities[]>();
|
useState<PageViewsByEntities[]>();
|
||||||
|
|
||||||
@ -63,12 +66,13 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
|
|||||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const { data, entities, total } = useMemo(() => {
|
const { data, entities, total, relativePercentage, latestData } =
|
||||||
return getGraphDataByEntityType(
|
useMemo(() => {
|
||||||
pageViewsByEntities,
|
return getGraphDataByEntityType(
|
||||||
DataInsightChartType.PageViewsByEntities
|
pageViewsByEntities,
|
||||||
);
|
DataInsightChartType.PageViewsByEntities
|
||||||
}, [pageViewsByEntities]);
|
);
|
||||||
|
}, [pageViewsByEntities]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -124,44 +128,79 @@ const PageViewsByEntitiesChart: FC<Props> = ({ chartFilter }) => {
|
|||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<Row gutter={16}>
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
<Col span={18}>
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<XAxis dataKey="timestamp" />
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
<YAxis />
|
<CartesianGrid
|
||||||
<Tooltip content={<CustomTooltip />} />
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
<Legend
|
vertical={false}
|
||||||
align="left"
|
/>
|
||||||
content={(props) =>
|
<XAxis dataKey="timestamp" />
|
||||||
renderLegend(props as LegendProps, `${total}`, activeKeys)
|
<YAxis />
|
||||||
}
|
<Tooltip content={<CustomTooltip />} />
|
||||||
layout="vertical"
|
<Legend
|
||||||
verticalAlign="top"
|
align="left"
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
content={(props) =>
|
||||||
onClick={handleLegendClick}
|
renderLegend(props as LegendProps, activeKeys)
|
||||||
onMouseEnter={handleLegendMouseEnter}
|
}
|
||||||
onMouseLeave={handleLegendMouseLeave}
|
layout="horizontal"
|
||||||
/>
|
verticalAlign="top"
|
||||||
{entities.map((entity) => (
|
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||||
<Line
|
onClick={handleLegendClick}
|
||||||
dataKey={entity}
|
onMouseEnter={handleLegendMouseEnter}
|
||||||
hide={
|
onMouseLeave={handleLegendMouseLeave}
|
||||||
activeKeys.length && entity !== activeMouseHoverKey
|
/>
|
||||||
? !activeKeys.includes(entity)
|
{entities.map((entity) => (
|
||||||
: false
|
<Line
|
||||||
}
|
dataKey={entity}
|
||||||
key={entity}
|
hide={
|
||||||
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
activeKeys.length && entity !== activeMouseHoverKey
|
||||||
strokeOpacity={
|
? !activeKeys.includes(entity)
|
||||||
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
|
: false
|
||||||
? DEFAULT_CHART_OPACITY
|
}
|
||||||
: HOVER_CHART_OPACITY
|
key={entity}
|
||||||
}
|
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
||||||
type="monotone"
|
strokeOpacity={
|
||||||
/>
|
isEmpty(activeMouseHoverKey) ||
|
||||||
))}
|
entity === activeMouseHoverKey
|
||||||
</LineChart>
|
? DEFAULT_CHART_OPACITY
|
||||||
</ResponsiveContainer>
|
: 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 />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniqueId } from 'lodash';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -56,9 +56,10 @@ import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TierInsight: FC<Props> = ({ chartFilter }) => {
|
const TierInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||||
const [totalEntitiesByTier, setTotalEntitiesByTier] =
|
const [totalEntitiesByTier, setTotalEntitiesByTier] =
|
||||||
useState<DataInsightChartResult>();
|
useState<DataInsightChartResult>();
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ const TierInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const { data, tiers, total } = useMemo(() => {
|
const { data, tiers, total, relativePercentage, latestData } = useMemo(() => {
|
||||||
return getGraphDataByTierType(totalEntitiesByTier?.data ?? []);
|
return getGraphDataByTierType(totalEntitiesByTier?.data ?? []);
|
||||||
}, [totalEntitiesByTier]);
|
}, [totalEntitiesByTier]);
|
||||||
|
|
||||||
@ -122,50 +123,89 @@ const TierInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
<DataInsightProgressBar
|
|
||||||
className="m-b-md"
|
|
||||||
progress={Number(total)}
|
|
||||||
width={250}
|
|
||||||
/>
|
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<Row gutter={16}>
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
<Col span={18}>
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<XAxis dataKey="timestamp" />
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
<YAxis />
|
<CartesianGrid
|
||||||
<Tooltip content={<CustomTooltip isPercentage />} />
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
<Legend
|
vertical={false}
|
||||||
align="left"
|
/>
|
||||||
content={(props) =>
|
<XAxis dataKey="timestamp" />
|
||||||
renderLegend(props as LegendProps, total, activeKeys, false)
|
<YAxis />
|
||||||
}
|
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||||
layout="vertical"
|
<Legend
|
||||||
verticalAlign="top"
|
align="left"
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
content={(props) =>
|
||||||
onClick={handleLegendClick}
|
renderLegend(props as LegendProps, activeKeys)
|
||||||
onMouseEnter={handleLegendMouseEnter}
|
}
|
||||||
onMouseLeave={handleLegendMouseLeave}
|
layout="horizontal"
|
||||||
/>
|
verticalAlign="top"
|
||||||
{tiers.map((tier) => (
|
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||||
<Line
|
onClick={handleLegendClick}
|
||||||
dataKey={tier}
|
onMouseEnter={handleLegendMouseEnter}
|
||||||
hide={
|
onMouseLeave={handleLegendMouseLeave}
|
||||||
activeKeys.length && tier !== activeMouseHoverKey
|
/>
|
||||||
? !activeKeys.includes(tier)
|
{tiers.map((tier) => (
|
||||||
: false
|
<Line
|
||||||
}
|
dataKey={tier}
|
||||||
key={tier}
|
hide={
|
||||||
stroke={TIER_BAR_COLOR_MAP[tier]}
|
activeKeys.length && tier !== activeMouseHoverKey
|
||||||
strokeOpacity={
|
? !activeKeys.includes(tier)
|
||||||
isEmpty(activeMouseHoverKey) || tier === activeMouseHoverKey
|
: false
|
||||||
? DEFAULT_CHART_OPACITY
|
}
|
||||||
: HOVER_CHART_OPACITY
|
key={tier}
|
||||||
}
|
stroke={TIER_BAR_COLOR_MAP[tier]}
|
||||||
type="monotone"
|
strokeOpacity={
|
||||||
/>
|
isEmpty(activeMouseHoverKey) ||
|
||||||
))}
|
tier === activeMouseHoverKey
|
||||||
</LineChart>
|
? DEFAULT_CHART_OPACITY
|
||||||
</ResponsiveContainer>
|
: 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 />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Col, Row, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniqueId } from 'lodash';
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -50,14 +50,17 @@ import {
|
|||||||
renderLegend,
|
renderLegend,
|
||||||
} from '../../utils/DataInsightUtils';
|
} from '../../utils/DataInsightUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import CustomStatistic from './CustomStatistic';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
|
import DataInsightProgressBar from './DataInsightProgressBar';
|
||||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chartFilter: ChartFilter;
|
chartFilter: ChartFilter;
|
||||||
|
selectedDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
|
const TotalEntityInsight: FC<Props> = ({ chartFilter, selectedDays }) => {
|
||||||
const [totalEntitiesByType, setTotalEntitiesByType] =
|
const [totalEntitiesByType, setTotalEntitiesByType] =
|
||||||
useState<DataInsightChartResult>();
|
useState<DataInsightChartResult>();
|
||||||
|
|
||||||
@ -65,12 +68,13 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
const [activeMouseHoverKey, setActiveMouseHoverKey] = useState('');
|
||||||
|
|
||||||
const { data, entities, total } = useMemo(() => {
|
const { data, entities, total, relativePercentage, latestData } =
|
||||||
return getGraphDataByEntityType(
|
useMemo(() => {
|
||||||
totalEntitiesByType?.data ?? [],
|
return getGraphDataByEntityType(
|
||||||
DataInsightChartType.TotalEntitiesByType
|
totalEntitiesByType?.data ?? [],
|
||||||
);
|
DataInsightChartType.TotalEntitiesByType
|
||||||
}, [totalEntitiesByType]);
|
);
|
||||||
|
}, [totalEntitiesByType]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -125,44 +129,81 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
<ResponsiveContainer debounce={1} minHeight={400}>
|
<Row gutter={16}>
|
||||||
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
<Col span={18}>
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} vertical={false} />
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
<XAxis dataKey="timestamp" />
|
<LineChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
<YAxis />
|
<CartesianGrid
|
||||||
<Tooltip content={<CustomTooltip />} />
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
<Legend
|
vertical={false}
|
||||||
align="left"
|
/>
|
||||||
content={(props) =>
|
<XAxis dataKey="timestamp" />
|
||||||
renderLegend(props as LegendProps, `${total}`, activeKeys)
|
<YAxis />
|
||||||
}
|
<Tooltip content={<CustomTooltip />} />
|
||||||
layout="vertical"
|
<Legend
|
||||||
verticalAlign="top"
|
align="left"
|
||||||
wrapperStyle={{ left: '0px', top: '0px' }}
|
content={(props) =>
|
||||||
onClick={handleLegendClick}
|
renderLegend(props as LegendProps, activeKeys)
|
||||||
onMouseEnter={handleLegendMouseEnter}
|
}
|
||||||
onMouseLeave={handleLegendMouseLeave}
|
layout="horizontal"
|
||||||
/>
|
verticalAlign="top"
|
||||||
{entities.map((entity) => (
|
wrapperStyle={{ left: '0px', top: '0px' }}
|
||||||
<Line
|
onClick={handleLegendClick}
|
||||||
dataKey={entity}
|
onMouseEnter={handleLegendMouseEnter}
|
||||||
hide={
|
onMouseLeave={handleLegendMouseLeave}
|
||||||
activeKeys.length && entity !== activeMouseHoverKey
|
/>
|
||||||
? !activeKeys.includes(entity)
|
{entities.map((entity) => (
|
||||||
: false
|
<Line
|
||||||
}
|
dataKey={entity}
|
||||||
key={entity}
|
hide={
|
||||||
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
activeKeys.length && entity !== activeMouseHoverKey
|
||||||
strokeOpacity={
|
? !activeKeys.includes(entity)
|
||||||
isEmpty(activeMouseHoverKey) || entity === activeMouseHoverKey
|
: false
|
||||||
? DEFAULT_CHART_OPACITY
|
}
|
||||||
: HOVER_CHART_OPACITY
|
key={entity}
|
||||||
}
|
stroke={ENTITIES_BAR_COLO_MAP[entity]}
|
||||||
type="monotone"
|
strokeOpacity={
|
||||||
/>
|
isEmpty(activeMouseHoverKey) ||
|
||||||
))}
|
entity === activeMouseHoverKey
|
||||||
</LineChart>
|
? DEFAULT_CHART_OPACITY
|
||||||
</ResponsiveContainer>
|
: 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 />
|
<EmptyGraphPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
@ -54,6 +54,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-filter-dropdown-trigger-btn {
|
.quick-filter-dropdown-trigger-btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
.remove-field-icon {
|
.remove-field-icon {
|
||||||
color: @remove-icon-color;
|
color: @remove-icon-color;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
|
@ -23,8 +23,8 @@ import {
|
|||||||
|
|
||||||
export const BAR_CHART_MARGIN: Margin = {
|
export const BAR_CHART_MARGIN: Margin = {
|
||||||
top: 20,
|
top: 20,
|
||||||
right: 30,
|
right: 20,
|
||||||
left: 20,
|
left: 10,
|
||||||
bottom: 5,
|
bottom: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export const GRAPH_BACKGROUND_COLOR = '#f5f5f5';
|
|||||||
export const GRAYED_OUT_COLOR = '#CCCCCC';
|
export const GRAYED_OUT_COLOR = '#CCCCCC';
|
||||||
|
|
||||||
export const DEFAULT_CHART_OPACITY = 1;
|
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'];
|
export const SUPPORTED_FIELD_TYPES = ['string', 'markdown', 'integer'];
|
||||||
|
|
||||||
|
@ -577,7 +577,14 @@
|
|||||||
"event-type": "Event Type",
|
"event-type": "Event Type",
|
||||||
"connection-timeout-plural-optional": "Connection Timeout (s)",
|
"connection-timeout-plural-optional": "Connection Timeout (s)",
|
||||||
"all-data-assets": "All Data Assets",
|
"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": {
|
"message": {
|
||||||
"service-email-required": "Service account Email is required",
|
"service-email-required": "Service account Email is required",
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
@box-shadow-color: rgba(0, 0, 0, 0.06);
|
@box-shadow-color: rgba(0, 0, 0, 0.06);
|
||||||
@icon-color: #515151;
|
@icon-color: #515151;
|
||||||
@group-title-color: #76746f;
|
@group-title-color: #76746f;
|
||||||
|
@summary-card-bg-hover: #f0f1f3;
|
||||||
|
@target-blue-color: #1890ff;
|
||||||
|
|
||||||
.data-insight-select-dropdown {
|
.data-insight-select-dropdown {
|
||||||
min-width: 136px;
|
min-width: 136px;
|
||||||
@ -28,6 +30,7 @@
|
|||||||
.data-insight-left-panel.ant-menu-root.ant-menu-inline {
|
.data-insight-left-panel.ant-menu-root.ant-menu-inline {
|
||||||
background: @white;
|
background: @white;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
.ant-menu-item::after {
|
.ant-menu-item::after {
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -87,6 +90,7 @@
|
|||||||
// library has given height directly via style attribute , so to override need to provide !important
|
// library has given height directly via style attribute , so to override need to provide !important
|
||||||
height: 30px !important;
|
height: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-progress-outer {
|
.ant-progress-outer {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
@ -109,12 +113,48 @@
|
|||||||
left: 2px;
|
left: 2px;
|
||||||
padding: 0 16px 0 8px;
|
padding: 0 16px 0 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
color: @text-color;
|
||||||
}
|
}
|
||||||
.data-insight-kpi-target {
|
.data-insight-kpi-target {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-right: 2px solid #1890ff;
|
border-right: 2px solid @target-blue-color;
|
||||||
border-radius: 4px;
|
|
||||||
|
.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] =
|
const [chartFilter, setChartFilter] =
|
||||||
useState<ChartFilter>(INITIAL_CHART_FILTER);
|
useState<ChartFilter>(INITIAL_CHART_FILTER);
|
||||||
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
||||||
|
const [selectedDaysFilter, setSelectedDaysFilter] = useState(DEFAULT_DAYS);
|
||||||
|
|
||||||
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
|
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
|
||||||
|
|
||||||
@ -127,6 +128,7 @@ const DataInsightPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDaysChange = (days: number) => {
|
const handleDaysChange = (days: number) => {
|
||||||
|
setSelectedDaysFilter(days);
|
||||||
setChartFilter((previous) => ({
|
setChartFilter((previous) => ({
|
||||||
...previous,
|
...previous,
|
||||||
startTs: getPastDaysDateTimeMillis(days),
|
startTs: getPastDaysDateTimeMillis(days),
|
||||||
@ -356,19 +358,30 @@ const DataInsightPage = () => {
|
|||||||
{activeTab === DataInsightTabs.DATA_ASSETS && (
|
{activeTab === DataInsightTabs.DATA_ASSETS && (
|
||||||
<>
|
<>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<TotalEntityInsight chartFilter={chartFilter} />
|
<TotalEntityInsight
|
||||||
|
chartFilter={chartFilter}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<DescriptionInsight
|
<DescriptionInsight
|
||||||
chartFilter={chartFilter}
|
chartFilter={chartFilter}
|
||||||
kpi={descriptionKpi}
|
kpi={descriptionKpi}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<OwnerInsight chartFilter={chartFilter} kpi={ownerKpi} />
|
<OwnerInsight
|
||||||
|
chartFilter={chartFilter}
|
||||||
|
kpi={ownerKpi}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<TierInsight chartFilter={chartFilter} />
|
<TierInsight
|
||||||
|
chartFilter={chartFilter}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -378,10 +391,16 @@ const DataInsightPage = () => {
|
|||||||
<TopViewEntities chartFilter={chartFilter} />
|
<TopViewEntities chartFilter={chartFilter} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<PageViewsByEntitiesChart chartFilter={chartFilter} />
|
<PageViewsByEntitiesChart
|
||||||
|
chartFilter={chartFilter}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<DailyActiveUsersChart chartFilter={chartFilter} />
|
<DailyActiveUsersChart
|
||||||
|
chartFilter={chartFilter}
|
||||||
|
selectedDays={selectedDaysFilter}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<TopActiveUsers chartFilter={chartFilter} />
|
<TopActiveUsers chartFilter={chartFilter} />
|
||||||
|
@ -15,6 +15,7 @@ import { Card, Typography } from 'antd';
|
|||||||
import { RangePickerProps } from 'antd/lib/date-picker';
|
import { RangePickerProps } from 'antd/lib/date-picker';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import {
|
import {
|
||||||
|
first,
|
||||||
groupBy,
|
groupBy,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
isInteger,
|
isInteger,
|
||||||
@ -52,7 +53,10 @@ import {
|
|||||||
KpiDates,
|
KpiDates,
|
||||||
} from '../interface/data-insight.interface';
|
} from '../interface/data-insight.interface';
|
||||||
import { pluralize } from './CommonUtils';
|
import { pluralize } from './CommonUtils';
|
||||||
import { getFormattedDateFromMilliSeconds } from './TimeUtils';
|
import {
|
||||||
|
getDateByTimeStamp,
|
||||||
|
getFormattedDateFromMilliSeconds,
|
||||||
|
} from './TimeUtils';
|
||||||
|
|
||||||
const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
|
const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
|
||||||
[
|
[
|
||||||
@ -62,62 +66,46 @@ const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
|
|||||||
|
|
||||||
export const renderLegend = (
|
export const renderLegend = (
|
||||||
legendData: LegendProps,
|
legendData: LegendProps,
|
||||||
latest: string | number,
|
activeKeys = [] as string[]
|
||||||
activeKeys = [] as string[],
|
|
||||||
showLatestValue = true
|
|
||||||
) => {
|
) => {
|
||||||
const { payload = [] } = legendData;
|
const { payload = [] } = legendData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ul className="custom-data-insight-legend">
|
||||||
{showLatestValue && (
|
{payload.map((entry, index) => {
|
||||||
<>
|
const isActive =
|
||||||
<Typography.Text className="data-insight-label-text">
|
activeKeys.length === 0 || activeKeys.includes(entry.value);
|
||||||
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);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className="recharts-legend-item d-flex items-center m-t-xss cursor-pointer"
|
className="recharts-legend-item custom-data-insight-legend-item"
|
||||||
key={`item-${index}`}
|
key={`item-${index}`}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
legendData.onClick && legendData.onClick({ ...entry, ...e })
|
legendData.onClick && legendData.onClick({ ...entry, ...e })
|
||||||
}
|
}
|
||||||
onMouseEnter={(e) =>
|
onMouseEnter={(e) =>
|
||||||
legendData.onMouseEnter &&
|
legendData.onMouseEnter &&
|
||||||
legendData.onMouseEnter({ ...entry, ...e })
|
legendData.onMouseEnter({ ...entry, ...e })
|
||||||
}
|
}
|
||||||
onMouseLeave={(e) =>
|
onMouseLeave={(e) =>
|
||||||
legendData.onMouseLeave &&
|
legendData.onMouseLeave &&
|
||||||
legendData.onMouseLeave({ ...entry, ...e })
|
legendData.onMouseLeave({ ...entry, ...e })
|
||||||
}>
|
}>
|
||||||
<Surface className="mr-2" height={14} version="1.1" width={14}>
|
<Surface className="m-r-xss" height={14} version="1.1" width={14}>
|
||||||
<rect
|
<rect
|
||||||
fill={isActive ? entry.color : GRAYED_OUT_COLOR}
|
fill={isActive ? entry.color : GRAYED_OUT_COLOR}
|
||||||
height="14"
|
height="14"
|
||||||
rx="2"
|
rx="2"
|
||||||
width="14"
|
width="14"
|
||||||
/>
|
/>
|
||||||
</Surface>
|
</Surface>
|
||||||
<span style={{ color: isActive ? 'inherit' : GRAYED_OUT_COLOR }}>
|
<span style={{ color: isActive ? 'inherit' : GRAYED_OUT_COLOR }}>
|
||||||
{entry.value}
|
{entry.value}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -157,20 +145,29 @@ const getEntryFormattedValue = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
||||||
const { active, payload = [], label, isPercentage, kpiTooltipRecord } = props;
|
const { active, payload = [], isPercentage, kpiTooltipRecord } = props;
|
||||||
|
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
|
const timestamp = getDateByTimeStamp(
|
||||||
|
payload[0].payload.timestampValue || 0,
|
||||||
|
'MMM dd, yyyy'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card
|
||||||
{/* this is a graph tooltip so using the explicit title here */}
|
className="custom-data-insight-tooltip"
|
||||||
<Typography.Title level={5}>{label}</Typography.Title>
|
title={<Typography.Title level={5}>{timestamp}</Typography.Title>}>
|
||||||
{payload.map((entry, index) => (
|
{payload.map((entry, index) => (
|
||||||
<li className="d-flex items-center" key={`item-${index}`}>
|
<li
|
||||||
<Surface className="mr-2" height={14} version="1.1" width={14}>
|
className="d-flex items-center justify-between tw-gap-6 tw-pb-1.5 text-sm"
|
||||||
<rect fill={entry.color} height="14" rx="2" width="14" />
|
key={`item-${index}`}>
|
||||||
</Surface>
|
<span className="flex items-center text-grey-muted">
|
||||||
<span>
|
<Surface className="mr-2" height={12} version="1.1" width={12}>
|
||||||
{entry.dataKey} -{' '}
|
<rect fill={entry.color} height="14" rx="2" width="14" />
|
||||||
|
</Surface>
|
||||||
|
{entry.dataKey}
|
||||||
|
</span>
|
||||||
|
<span className="font-medium">
|
||||||
{getEntryFormattedValue(
|
{getEntryFormattedValue(
|
||||||
entry.value,
|
entry.value,
|
||||||
entry.dataKey,
|
entry.dataKey,
|
||||||
@ -226,7 +223,8 @@ const getLatestCount = (latestData = {}) => {
|
|||||||
const latestEntries = Object.entries(latestData ?? {});
|
const latestEntries = Object.entries(latestData ?? {});
|
||||||
|
|
||||||
for (const entry of latestEntries) {
|
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]);
|
total += toNumber(entry[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,6 +288,62 @@ const getLatestPercentage = (
|
|||||||
return 0;
|
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
|
* @param rawData raw chart data
|
||||||
@ -341,6 +395,7 @@ const getGraphFilteredData = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
|
timestampValue: data.timestamp,
|
||||||
[data.entityType]: value,
|
[data.entityType]: value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -370,7 +425,20 @@ export const getGraphDataByEntityType = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const graphData = prepareGraphData(timestamps, filteredData);
|
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 {
|
return {
|
||||||
data: graphData,
|
data: graphData,
|
||||||
@ -378,6 +446,11 @@ export const getGraphDataByEntityType = (
|
|||||||
total: isPercentageGraph
|
total: isPercentageGraph
|
||||||
? getLatestPercentage(rawData, dataInsightChartType)
|
? getLatestPercentage(rawData, dataInsightChartType)
|
||||||
: getLatestCount(latestData),
|
: getLatestCount(latestData),
|
||||||
|
relativePercentage: isPercentageGraph
|
||||||
|
? relativePercentage
|
||||||
|
: (relativePercentage / oldestPercentage) * 100,
|
||||||
|
latestData,
|
||||||
|
isPercentageGraph,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -403,6 +476,7 @@ export const getGraphDataByTierType = (rawData: TotalEntitiesByTier[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
timestampValue: data.timestamp,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
[tiering]: ((data?.entityCountFraction || 0) * 100).toFixed(2),
|
[tiering]: ((data?.entityCountFraction || 0) * 100).toFixed(2),
|
||||||
};
|
};
|
||||||
@ -412,12 +486,16 @@ export const getGraphDataByTierType = (rawData: TotalEntitiesByTier[]) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const graphData = prepareGraphData(timestamps, filteredData);
|
const graphData = prepareGraphData(timestamps, filteredData);
|
||||||
const latestData = last(graphData);
|
const latestData = getLatestCount(last(graphData));
|
||||||
|
const oldestData = getLatestCount(first(graphData));
|
||||||
|
const relativePercentage = latestData - oldestData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: graphData,
|
data: graphData,
|
||||||
tiers,
|
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) => ({
|
const formattedData = activeUsers.map((user) => ({
|
||||||
...user,
|
...user,
|
||||||
|
timestampValue: user.timestamp,
|
||||||
timestamp: user.timestamp
|
timestamp: user.timestamp
|
||||||
? getFormattedDateFromMilliSeconds(user.timestamp)
|
? getFormattedDateFromMilliSeconds(user.timestamp)
|
||||||
: '',
|
: '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const latestCount = Number(last(formattedData)?.activeUsers);
|
||||||
|
const oldestCount = Number(first(formattedData)?.activeUsers);
|
||||||
|
|
||||||
|
const relativePercentage = ((latestCount - oldestCount) / oldestCount) * 100;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: formattedData,
|
data: formattedData,
|
||||||
total: last(formattedData)?.activeUsers,
|
total: latestCount,
|
||||||
|
relativePercentage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -518,6 +603,7 @@ export const getKpiGraphData = (kpiResults: KpiResult[], kpiList: Kpi[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
timestampValue: kpiResult.timestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
[kpiFqn]:
|
[kpiFqn]:
|
||||||
currentKpi?.metricType === KpiTargetType.Percentage
|
currentKpi?.metricType === KpiTargetType.Percentage
|
||||||
|
Loading…
x
Reference in New Issue
Block a user