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,16 +38,16 @@ 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',
}}> }}>
<Grid display="flex" gap={20} size="grow">
{information.map((info) => ( {information.map((info) => (
<Box key={info.title}> <Box key={info.title}>
<Typography <Typography
@ -95,7 +96,13 @@ const ProfilerLatestValue = ({
)} )}
</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,
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] ?? '--', latestValue: last(metric)?.[key] ?? '--',
title: '', title: t('label.count'),
dataKey: key, dataKey: key,
color, color: theme.palette.allShades.blue[500],
}, },
]} ],
/> extra:
</Col> editPermission || deletePermission ? (
<Col span={20}> <>
<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) ? ( {isEmpty(metric) ? (
<Row <Grid
align="middle" alignItems="middle"
className="h-full w-full" className="h-full w-full"
justify="center"> display="flex"
<Col> justifyContent="center">
<ErrorPlaceHolder className="mt-0-important" /> <ErrorPlaceHolder className="mt-0-important" />
</Col> </Grid>
</Row>
) : ( ) : (
<ResponsiveContainer <ResponsiveContainer
className="custom-legend" className="custom-legend"
debounce={200} debounce={200}
id={`${key}-graph`} id={`${key}-graph`}
minHeight={300}> minHeight={300}>
<LineChart <ComposedChart
className="w-full" className="w-full"
data={metric} data={metric}
margin={{ left: 16 }}> margin={{ left: 16 }}>
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} /> <CartesianGrid
stroke={GRAPH_BACKGROUND_COLOR}
strokeDasharray="3 3"
vertical={false}
/>
<XAxis <XAxis
axisLine={{
stroke: theme.palette.grey[200],
}}
dataKey="formattedTimestamp" dataKey="formattedTimestamp"
padding={{ left: 16, right: 16 }} padding={{ left: 16, right: 16 }}
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
tickLine={false}
/> />
<YAxis <YAxis
axisLine={false}
domain={['min', 'max']} domain={['min', 'max']}
padding={{ top: 16, bottom: 16 }} padding={{ top: 16, bottom: 16 }}
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
tickFormatter={(props) => axisTickFormatter(props)} tickFormatter={(props) => axisTickFormatter(props)}
tickLine={false}
type="number" 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]}
name={key}
stroke={theme.palette.allShades.blue[500]}
type="monotone"
/>
</ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>
)} )}
</Col> </Box>
</Row> </ProfilerStateWrapper>
</Card> </Box>
</Col>
); );
})} })}
<DeleteWidgetModal <DeleteWidgetModal
@ -329,7 +366,7 @@ const CustomMetricGraphs = ({
/> />
</Modal> </Modal>
)} )}
</Row> </Stack>
); );
}; };

View File

@ -209,7 +209,6 @@ const TableProfilerChart = ({
/> />
</Box> </Box>
{showSystemMetrics && ( {showSystemMetrics && (
<Box>
<ProfilerStateWrapper <ProfilerStateWrapper
dataTestId="operation-metrics" dataTestId="operation-metrics"
isLoading={isSystemProfilerLoading} isLoading={isSystemProfilerLoading}
@ -223,15 +222,13 @@ const TableProfilerChart = ({
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>
); );