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']; information: MetricChartType['information'];
tickFormatter?: string; tickFormatter?: string;
stringValue?: boolean; stringValue?: boolean;
extra?: ReactNode;
} }
export type TestCaseAction = { export type TestCaseAction = {

View File

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

View File

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