feat(ui): Enhance Profiler components with additional props and improve layout using Grid

This commit is contained in:
Shailesh Parmar 2025-09-24 13:04:08 +05:30
parent b188cc56b3
commit 5a6443cfc3
4 changed files with 242 additions and 200 deletions

View File

@ -81,6 +81,7 @@ export interface ProfilerLatestValueProps {
information: MetricChartType['information'];
tickFormatter?: string;
stringValue?: boolean;
extra?: ReactNode;
}
export type TestCaseAction = {

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Box, Divider, Stack, Typography, useTheme } from '@mui/material';
import { Box, Divider, Grid, Typography, useTheme } from '@mui/material';
import { isUndefined } from 'lodash';
import { getStatisticsDisplayValue } from '../../../../utils/CommonUtils';
import '../ProfilerDashboard/profiler-dashboard.less';
@ -21,6 +21,7 @@ const ProfilerLatestValue = ({
information,
tickFormatter,
stringValue = false,
extra,
}: ProfilerLatestValueProps) => {
const theme = useTheme();
@ -37,65 +38,71 @@ const ProfilerLatestValue = ({
};
return (
<Stack
<Grid
container
alignItems="center"
data-testid="data-summary-container"
direction="row"
spacing={20}
sx={{
width: '100%',
backgroundColor: theme.palette.grey[50],
borderRadius: '10px',
p: '16px 30px',
}}>
{information.map((info) => (
<Box key={info.title}>
<Typography
className="break-all"
data-testid="title"
sx={{
color: theme.palette.grey[700],
fontSize: theme.typography.pxToRem(11),
fontWeight: theme.typography.fontWeightBold,
borderLeft: `4px solid ${info.color}`,
paddingLeft: '8px',
lineHeight: '12px',
mb: 1,
}}>
{info.title}
</Typography>
<Typography
className="break-all"
data-testid="value"
sx={{
color: theme.palette.grey[900],
fontSize: theme.typography.pxToRem(17),
fontWeight: 700,
}}>
{getLatestValue(info.latestValue)}
</Typography>
{info.extra && (
<>
<Divider
sx={{
my: 2,
borderStyle: 'dashed',
borderColor: theme.palette.allShades.gray[300],
}}
/>
<Typography
className="break-all"
data-testid="extra"
sx={{
color: theme.palette.grey[900],
fontSize: theme.typography.pxToRem(11),
}}>
{info.extra}
</Typography>
</>
)}
</Box>
))}
</Stack>
<Grid display="flex" gap={20} size="grow">
{information.map((info) => (
<Box key={info.title}>
<Typography
className="break-all"
data-testid="title"
sx={{
color: theme.palette.grey[700],
fontSize: theme.typography.pxToRem(11),
fontWeight: theme.typography.fontWeightBold,
borderLeft: `4px solid ${info.color}`,
paddingLeft: '8px',
lineHeight: '12px',
mb: 1,
}}>
{info.title}
</Typography>
<Typography
className="break-all"
data-testid="value"
sx={{
color: theme.palette.grey[900],
fontSize: theme.typography.pxToRem(17),
fontWeight: 700,
}}>
{getLatestValue(info.latestValue)}
</Typography>
{info.extra && (
<>
<Divider
sx={{
my: 2,
borderStyle: 'dashed',
borderColor: theme.palette.allShades.gray[300],
}}
/>
<Typography
className="break-all"
data-testid="extra"
sx={{
color: theme.palette.grey[900],
fontSize: theme.typography.pxToRem(11),
}}>
{info.extra}
</Typography>
</>
)}
</Box>
))}
</Grid>
{extra && (
<Grid display="flex" justifyContent="flex-end" size={1}>
{extra}
</Grid>
)}
</Grid>
);
};

View File

@ -10,33 +10,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import MoreVertIcon from '@mui/icons-material/MoreVert';
import {
Button,
Card,
Col,
Dropdown,
Form,
Modal,
Row,
Typography,
} from 'antd';
Box,
Grid,
IconButton,
Menu,
MenuItem,
Stack,
useTheme,
} from '@mui/material';
import { Form, Modal } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty, isUndefined, last, omit, toPairs } from 'lodash';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Area,
CartesianGrid,
Legend,
ComposedChart,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { ReactComponent as IconDropdown } from '../../../../../assets/svg/menu.svg';
import { GRAPH_BACKGROUND_COLOR } from '../../../../../constants/constants';
import { PAGE_HEADERS } from '../../../../../constants/PageHeaders.constant';
import { EntityType } from '../../../../../enums/entity.enum';
import { CustomMetric } from '../../../../../generated/entity/data/table';
import { Operation } from '../../../../../generated/entity/policies/policy';
@ -48,11 +47,7 @@ import {
axisTickFormatter,
tooltipFormatter,
} from '../../../../../utils/ChartUtils';
import { entityChartColor } from '../../../../../utils/CommonUtils';
import {
CustomTooltip,
getRandomHexColor,
} from '../../../../../utils/DataInsightUtils';
import { CustomDQTooltip } from '../../../../../utils/DataQuality/DataQualityUtils';
import { formatDateTimeLong } from '../../../../../utils/date-time/DateTimeUtils';
import { getPrioritizedEditPermission } from '../../../../../utils/PermissionsUtils';
import {
@ -62,8 +57,7 @@ import {
import DeleteWidgetModal from '../../../../common/DeleteWidget/DeleteWidgetModal';
import ErrorPlaceHolder from '../../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import CustomMetricForm from '../../../../DataQuality/CustomMetricForm/CustomMetricForm.component';
import PageHeader from '../../../../PageHeader/PageHeader.component';
import ProfilerLatestValue from '../../ProfilerLatestValue/ProfilerLatestValue';
import ProfilerStateWrapper from '../../ProfilerStateWrapper/ProfilerStateWrapper.component';
import { useTableProfiler } from '../TableProfilerProvider';
import './custom-metric-graphs.style.less';
import {
@ -76,6 +70,7 @@ const CustomMetricGraphs = ({
isLoading,
customMetrics,
}: CustomMetricGraphsProps) => {
const theme = useTheme();
const { t } = useTranslation();
const [form] = Form.useForm<CustomMetric>();
const {
@ -92,6 +87,9 @@ const CustomMetricGraphs = ({
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [isActionLoading, setIsActionLoading] = useState(false);
const [anchorEl, setAnchorEl] = useState<Record<string, HTMLElement | null>>(
{}
);
const items = useMemo(
() => [
@ -168,6 +166,7 @@ const CustomMetricGraphs = ({
setSelectedMetrics(
customMetrics?.find((metric) => metric.name === metricName)
);
setAnchorEl((prev) => ({ ...prev, [metricName]: null }));
switch (key) {
case MenuOptions.EDIT:
setIsEditModalVisible(true);
@ -182,119 +181,157 @@ const CustomMetricGraphs = ({
}
};
const handleMenuOpen = (
event: React.MouseEvent<HTMLElement>,
metricName: string
) => {
setAnchorEl((prev) => ({ ...prev, [metricName]: event.currentTarget }));
};
const handleMenuClose = (metricName: string) => {
setAnchorEl((prev) => ({ ...prev, [metricName]: null }));
};
return (
<Row data-testid="custom-metric-graph-container" gutter={[16, 16]}>
{!isEmpty(customMetrics) && (
<Col span={24}>
<PageHeader data={PAGE_HEADERS.CUSTOM_METRICS} />
</Col>
)}
{toPairs(customMetricsGraphData).map(([key, metric], i) => {
<Stack data-testid="custom-metric-graph-container" spacing="30px">
{toPairs(customMetricsGraphData).map(([key, metric]) => {
const metricDetails = customMetrics?.find(
(metric) => metric.name === key
);
const color = entityChartColor(i) ?? getRandomHexColor();
return isUndefined(metricDetails) ? null : (
<Col key={key} span={24}>
<Card
className="shadow-none global-border-radius custom-metric-card"
<Box key={key}>
<ProfilerStateWrapper
data-testid={`${key}-custom-metrics`}
extra={
editPermission || deletePermission ? (
<Dropdown
menu={{
items,
onClick: (info) => handleMenuClick(info.key, key),
}}
placement="bottomLeft"
trigger={['click']}>
<Button
className="flex-center"
data-testid={`${key}-custom-metrics-menu`}
icon={<IconDropdown className="self-center" />}
size="small"
/>
</Dropdown>
) : null
}
loading={isLoading}
title={<Typography.Title level={5}>{key}</Typography.Title>}>
<Row gutter={[16, 16]}>
<Col span={4}>
<ProfilerLatestValue
information={[
{
latestValue: last(metric)?.[key] ?? '--',
title: '',
dataKey: key,
color,
},
]}
/>
</Col>
<Col span={20}>
{isEmpty(metric) ? (
<Row
align="middle"
className="h-full w-full"
justify="center">
<Col>
<ErrorPlaceHolder className="mt-0-important" />
</Col>
</Row>
) : (
<ResponsiveContainer
className="custom-legend"
debounce={200}
id={`${key}-graph`}
minHeight={300}>
<LineChart
className="w-full"
data={metric}
margin={{ left: 16 }}>
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} />
<XAxis
dataKey="formattedTimestamp"
padding={{ left: 16, right: 16 }}
tick={{ fontSize: 12 }}
/>
isLoading={isLoading}
profilerLatestValueProps={{
information: [
{
latestValue: last(metric)?.[key] ?? '--',
title: t('label.count'),
dataKey: key,
color: theme.palette.allShades.blue[500],
},
],
extra:
editPermission || deletePermission ? (
<>
<IconButton
data-testid={`${key}-custom-metrics-menu`}
size="small"
sx={{
color: theme.palette.grey[800],
}}
onClick={(e) => handleMenuOpen(e, key)}>
<MoreVertIcon fontSize="small" />
</IconButton>
<Menu
anchorEl={anchorEl[key]}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
open={Boolean(anchorEl[key])}
sx={{
'.MuiPaper-root': {
width: 'max-content',
},
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
onClose={() => handleMenuClose(key)}>
{items.map((item) => (
<MenuItem
disabled={item.disabled}
key={item.key}
onClick={() => handleMenuClick(item.key, key)}>
{item.label}
</MenuItem>
))}
</Menu>
</>
) : undefined,
}}
title={key}>
<Box>
{isEmpty(metric) ? (
<Grid
alignItems="middle"
className="h-full w-full"
display="flex"
justifyContent="center">
<ErrorPlaceHolder className="mt-0-important" />
</Grid>
) : (
<ResponsiveContainer
className="custom-legend"
debounce={200}
id={`${key}-graph`}
minHeight={300}>
<ComposedChart
className="w-full"
data={metric}
margin={{ left: 16 }}>
<CartesianGrid
stroke={GRAPH_BACKGROUND_COLOR}
strokeDasharray="3 3"
vertical={false}
/>
<XAxis
axisLine={{
stroke: theme.palette.grey[200],
}}
dataKey="formattedTimestamp"
padding={{ left: 16, right: 16 }}
tick={{ fontSize: 12 }}
tickLine={false}
/>
<YAxis
domain={['min', 'max']}
padding={{ top: 16, bottom: 16 }}
tick={{ fontSize: 12 }}
tickFormatter={(props) => axisTickFormatter(props)}
type="number"
/>
<YAxis
axisLine={false}
domain={['min', 'max']}
padding={{ top: 16, bottom: 16 }}
tick={{ fontSize: 12 }}
tickFormatter={(props) => axisTickFormatter(props)}
tickLine={false}
type="number"
/>
<Tooltip
content={
<CustomTooltip
dateTimeFormatter={formatDateTimeLong}
timeStampKey="timestamp"
valueFormatter={(value) =>
tooltipFormatter(value)
}
/>
}
/>
<Tooltip
content={
<CustomDQTooltip
dateTimeFormatter={formatDateTimeLong}
timeStampKey="timestamp"
valueFormatter={(value) => tooltipFormatter(value)}
/>
}
cursor={{
stroke: theme.palette.grey[200],
strokeDasharray: '3 3',
}}
/>
<Line
dataKey={key}
name={key}
stroke={color}
type="monotone"
/>
<Legend />
</LineChart>
</ResponsiveContainer>
)}
</Col>
</Row>
</Card>
</Col>
<Line
dataKey={key}
name={key}
stroke={theme.palette.allShades.blue[500]}
type="monotone"
/>
<Area
dataKey={key}
fill={theme.palette.allShades.blue[50]}
name={key}
stroke={theme.palette.allShades.blue[500]}
type="monotone"
/>
</ComposedChart>
</ResponsiveContainer>
)}
</Box>
</ProfilerStateWrapper>
</Box>
);
})}
<DeleteWidgetModal
@ -329,7 +366,7 @@ const CustomMetricGraphs = ({
/>
</Modal>
)}
</Row>
</Stack>
);
};

View File

@ -209,29 +209,26 @@ const TableProfilerChart = ({
/>
</Box>
{showSystemMetrics && (
<Box>
<ProfilerStateWrapper
dataTestId="operation-metrics"
isLoading={isSystemProfilerLoading}
profilerLatestValueProps={{
information: operationMetrics.information,
}}
title={t('label.volume-change')}>
<CustomBarChart
chartCollection={operationMetrics}
name="operationMetrics"
noDataPlaceholderText={noProfilerMessage}
/>
</ProfilerStateWrapper>
</Box>
<ProfilerStateWrapper
dataTestId="operation-metrics"
isLoading={isSystemProfilerLoading}
profilerLatestValueProps={{
information: operationMetrics.information,
}}
title={t('label.volume-change')}>
<CustomBarChart
chartCollection={operationMetrics}
name="operationMetrics"
noDataPlaceholderText={noProfilerMessage}
/>
</ProfilerStateWrapper>
)}
<Box>
<CustomMetricGraphs
customMetrics={customMetrics}
customMetricsGraphData={tableCustomMetricsProfiling}
isLoading={isTableProfilerLoading || isSummaryLoading}
/>
</Box>
<CustomMetricGraphs
customMetrics={customMetrics}
customMetricsGraphData={tableCustomMetricsProfiling}
isLoading={isTableProfilerLoading || isSummaryLoading}
/>
</Stack>
</Stack>
);