diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts
index 4f1d20884f7..bd11cd1479e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts
@@ -41,6 +41,7 @@ export interface ProfilerDetailsCardProps {
curveType?: CurveType;
isLoading?: boolean;
noDataPlaceholderText?: ReactNode;
+ children?: ReactNode;
}
export enum TableProfilerTab {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.test.tsx
index f02ee55e8d3..4af75975414 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.test.tsx
@@ -13,6 +13,7 @@
import { queryByAttribute, render, screen } from '@testing-library/react';
import React from 'react';
+import '../../../../test/unit/mocks/recharts.mock';
import { ProfilerDetailsCardProps } from '../ProfilerDashboard/profilerDashboard.interface';
import ProfilerDetailsCard from './ProfilerDetailsCard';
@@ -39,6 +40,11 @@ jest.mock('../../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () =>
jest.mock('../../../../utils/DataInsightUtils', () => ({
CustomTooltip: jest.fn(() =>
CustomTooltip
),
}));
+jest.mock('../../../../constants/profiler.constant', () => {
+ return {
+ PROFILER_CHART_DATA_SIZE: 500,
+ };
+});
// Improve mock data to be minimal
const mockProps: ProfilerDetailsCardProps = {
@@ -49,6 +55,11 @@ const mockProps: ProfilerDetailsCardProps = {
name: 'rowCount',
};
+const mockData = Array.from({ length: 501 }, (_, index) => ({
+ name: `test ${index}`,
+ value: index,
+}));
+
describe('ProfilerDetailsCard Test', () => {
it('Component should render', async () => {
const { container } = render();
@@ -59,6 +70,21 @@ describe('ProfilerDetailsCard Test', () => {
expect(
queryByAttribute('id', container, `${mockProps.name}_graph`)
).toBeInTheDocument();
+ expect(screen.queryByText('Brush')).not.toBeInTheDocument();
+ });
+
+ it('Component should render brush when data length is greater than PROFILER_CHART_DATA_SIZE', async () => {
+ render(
+
+ );
+
+ expect(screen.getByText('Brush')).toBeInTheDocument();
});
it('No data should be rendered', async () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.tsx
index 87af9db26a2..cf47cc5ee48 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDetailsCard/ProfilerDetailsCard.tsx
@@ -12,8 +12,9 @@
*/
import { Card, Col, Row, Typography } from 'antd';
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import {
+ Brush,
CartesianGrid,
Legend,
LegendProps,
@@ -25,6 +26,7 @@ import {
YAxis,
} from 'recharts';
import { GRAPH_BACKGROUND_COLOR } from '../../../../constants/constants';
+import { PROFILER_CHART_DATA_SIZE } from '../../../../constants/profiler.constant';
import {
axisTickFormatter,
tooltipFormatter,
@@ -48,6 +50,12 @@ const ProfilerDetailsCard: React.FC = ({
}: ProfilerDetailsCardProps) => {
const { data, information } = chartCollection;
const [activeKeys, setActiveKeys] = useState([]);
+ const { showBrush, endIndex } = useMemo(() => {
+ return {
+ showBrush: data.length > PROFILER_CHART_DATA_SIZE,
+ endIndex: PROFILER_CHART_DATA_SIZE,
+ };
+ }, [data]);
const handleClick: LegendProps['onClick'] = (event) => {
setActiveKeys((prevActiveKeys) =>
@@ -122,6 +130,15 @@ const ProfilerDetailsCard: React.FC = ({
/>
))}
+ {showBrush && (
+
+ )}
) : (
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.component.tsx
new file mode 100644
index 00000000000..ce25e9ca7bb
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.component.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 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 { Card, Col, Row, Typography } from 'antd';
+import React from 'react';
+import ProfilerLatestValue from '../ProfilerLatestValue/ProfilerLatestValue';
+import { ProfilerStateWrapperProps } from './ProfilerStateWrapper.interface';
+
+const ProfilerStateWrapper = ({
+ isLoading,
+ children,
+ title,
+ profilerLatestValueProps,
+ dataTestId,
+}: ProfilerStateWrapperProps) => {
+ return (
+
+
+
+ {title}
+
+
+
+
+ {children}
+
+
+ );
+};
+
+export default ProfilerStateWrapper;
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.interface.ts
new file mode 100644
index 00000000000..0dec155592f
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.interface.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025 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 { ProfilerLatestValueProps } from '../ProfilerDashboard/profilerDashboard.interface';
+
+export interface ProfilerStateWrapperProps {
+ isLoading: boolean;
+ children: React.ReactNode;
+ title: string;
+ profilerLatestValueProps: ProfilerLatestValueProps;
+ dataTestId?: string;
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.test.tsx
new file mode 100644
index 00000000000..ca60aae479d
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerStateWrapper/ProfilerStateWrapper.test.tsx
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2025 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 { render, screen } from '@testing-library/react';
+import React from 'react';
+import ProfilerStateWrapper from './ProfilerStateWrapper.component';
+
+// Mock ProfilerLatestValue component
+jest.mock('../ProfilerLatestValue/ProfilerLatestValue', () => {
+ return jest.fn(() => (
+ ProfilerLatestValue
+ ));
+});
+
+describe('ProfilerStateWrapper', () => {
+ const mockProfilerLatestValueProps = {
+ information: [
+ {
+ title: 'Test Label',
+ dataKey: 'testKey',
+ color: '#000000',
+ latestValue: '100',
+ },
+ ],
+ };
+
+ const defaultProps = {
+ isLoading: false,
+ title: 'Test Title',
+ profilerLatestValueProps: mockProfilerLatestValueProps,
+ children: Test Content
,
+ };
+
+ it('renders without crashing', () => {
+ render();
+
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
+ expect(screen.getByText('Test Content')).toBeInTheDocument();
+ });
+
+ it('shows loading state when isLoading is true', () => {
+ render();
+
+ const card = screen.getByTestId('profiler-details-card-container');
+
+ expect(card).toHaveClass('ant-card-loading');
+ });
+
+ it('renders with custom data-testid', () => {
+ const customTestId = 'custom-test-id';
+ render(
+
+ );
+
+ expect(screen.getByTestId(customTestId)).toBeInTheDocument();
+ });
+
+ it('renders with default data-testid when not provided', () => {
+ render();
+
+ expect(
+ screen.getByTestId('profiler-details-card-container')
+ ).toBeInTheDocument();
+ });
+
+ it('renders ProfilerLatestValue with correct props', () => {
+ render();
+
+ const profilerLatestValue = screen.getByTestId('profiler-latest-value');
+
+ expect(profilerLatestValue).toBeInTheDocument();
+ });
+
+ it('renders children content correctly', () => {
+ const { title, profilerLatestValueProps, isLoading } = defaultProps;
+
+ render(
+
+
+
Custom Header
+
Custom Paragraph
+
+
+ );
+
+ expect(screen.getByText('Custom Header')).toBeInTheDocument();
+ expect(screen.getByText('Custom Paragraph')).toBeInTheDocument();
+ });
+});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx
index eb59c5a754f..7c840f9f7ab 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx
@@ -12,21 +12,12 @@
*/
import { DownOutlined } from '@ant-design/icons';
-import {
- Button,
- Card,
- Col,
- Dropdown,
- Row,
- Space,
- Tooltip,
- Typography,
-} from 'antd';
+import { Button, Col, Dropdown, Row, Space, Tooltip } from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { isEqual, pick } from 'lodash';
import { DateRangeObject } from 'Models';
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { ReactComponent as SettingIcon } from '../../../../../assets/svg/ic-settings-primery.svg';
@@ -64,7 +55,7 @@ import CustomBarChart from '../../../../Visualisations/Chart/CustomBarChart';
import OperationDateBarChart from '../../../../Visualisations/Chart/OperationDateBarChart';
import { MetricChartType } from '../../ProfilerDashboard/profilerDashboard.interface';
import ProfilerDetailsCard from '../../ProfilerDetailsCard/ProfilerDetailsCard';
-import ProfilerLatestValue from '../../ProfilerLatestValue/ProfilerLatestValue';
+import ProfilerStateWrapper from '../../ProfilerStateWrapper/ProfilerStateWrapper.component';
import CustomMetricGraphs from '../CustomMetricGraphs/CustomMetricGraphs.component';
import NoProfilerBanner from '../NoProfilerBanner/NoProfilerBanner.component';
import { TableProfilerChartProps } from '../TableProfiler.interface';
@@ -104,7 +95,8 @@ const TableProfilerChart = ({
);
const [operationDateMetrics, setOperationDateMetrics] =
useState(INITIAL_OPERATION_METRIC_VALUE);
- const [isLoading, setIsLoading] = useState(true);
+ const [isTableProfilerLoading, setIsTableProfilerLoading] = useState(true);
+ const [isSystemProfilerLoading, setIsSystemProfilerLoading] = useState(true);
const [profileMetrics, setProfileMetrics] = useState([]);
const profilerDocsLink =
documentationLinksClassBase.getDocsURLS()
@@ -163,53 +155,95 @@ const TableProfilerChart = ({
}
};
- const fetchTableProfiler = async (
- fqn: string,
- dateRangeObj: DateRangeObject
- ) => {
- try {
- const { data } = await getTableProfilesList(fqn, dateRangeObj);
- const rowMetricsData = calculateRowCountMetrics(data, rowCountMetrics);
- setRowCountMetrics(rowMetricsData);
- setProfileMetrics(data);
- } catch (error) {
- showErrorToast(error as AxiosError);
- }
- };
- const fetchSystemProfiler = async (
- fqn: string,
- dateRangeObj: DateRangeObject
- ) => {
- try {
- const { data } = await getSystemProfileList(fqn, dateRangeObj);
- const { operationMetrics: metricsData, operationDateMetrics } =
- calculateSystemMetrics(data, operationMetrics);
+ const fetchTableProfiler = useCallback(
+ async (fqn: string, dateRangeObj: DateRangeObject) => {
+ setIsTableProfilerLoading(true);
+ try {
+ const { data } = await getTableProfilesList(fqn, dateRangeObj);
+ const rowMetricsData = calculateRowCountMetrics(data, rowCountMetrics);
+ setRowCountMetrics(rowMetricsData);
+ setProfileMetrics(data);
+ } catch (error) {
+ showErrorToast(error as AxiosError);
+ } finally {
+ setIsTableProfilerLoading(false);
+ }
+ },
+ [rowCountMetrics, profileMetrics]
+ );
+ const fetchSystemProfiler = useCallback(
+ async (fqn: string, dateRangeObj: DateRangeObject) => {
+ setIsSystemProfilerLoading(true);
+ try {
+ const { data } = await getSystemProfileList(fqn, dateRangeObj);
+ const { operationMetrics: metricsData, operationDateMetrics } =
+ calculateSystemMetrics(data, operationMetrics);
- setOperationDateMetrics(operationDateMetrics);
- setOperationMetrics(metricsData);
- } catch (error) {
- showErrorToast(error as AxiosError);
- }
- };
+ setOperationDateMetrics(operationDateMetrics);
+ setOperationMetrics(metricsData);
+ } catch (error) {
+ showErrorToast(error as AxiosError);
+ } finally {
+ setIsSystemProfilerLoading(false);
+ }
+ },
+ [operationMetrics, operationDateMetrics]
+ );
- const fetchProfilerData = async (
- fqn: string,
- dateRangeObj: DateRangeObject
- ) => {
- const dateRange = pick(dateRangeObj, ['startTs', 'endTs']);
- setIsLoading(true);
- await fetchTableProfiler(fqn, dateRange);
- await fetchSystemProfiler(fqn, dateRange);
- setIsLoading(false);
- };
+ const fetchProfilerData = useCallback(
+ (fqn: string, dateRangeObj: DateRangeObject) => {
+ const dateRange = pick(dateRangeObj, ['startTs', 'endTs']);
+ fetchTableProfiler(fqn, dateRange);
+ fetchSystemProfiler(fqn, dateRange);
+ },
+ [fetchSystemProfiler, fetchTableProfiler]
+ );
useEffect(() => {
if (datasetFQN || entityFqn) {
fetchProfilerData(datasetFQN || entityFqn, dateRangeObject);
} else {
- setIsLoading(false);
+ setIsTableProfilerLoading(false);
+ setIsSystemProfilerLoading(false);
}
- }, [datasetFQN, dateRangeObject]);
+ }, [datasetFQN, dateRangeObject, entityFqn]);
+
+ const operationDateMetricsCard = useMemo(() => {
+ return (
+
+
+
+ );
+ }, [isSystemProfilerLoading, operationDateMetrics, noProfilerMessage]);
+
+ const operationMetricsCard = useMemo(() => {
+ return (
+
+
+
+ );
+ }, [isSystemProfilerLoading, operationMetrics, noProfilerMessage]);
return (
@@ -290,68 +324,19 @@ const TableProfilerChart = ({
-
-
-
-
-
- {t('label.table-update-plural')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('label.volume-change')}
-
-
-
-
-
-
-
-
-
-
-
+ {operationDateMetricsCard}
+ {operationMetricsCard}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.test.tsx
index c9cb42490e6..59ca49cff81 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.test.tsx
@@ -42,6 +42,11 @@ jest.mock('../../../utils/DataInsightUtils', () => {
});
});
+const mockData = Array.from({ length: 501 }, (_, index) => ({
+ name: `test ${index}`,
+ value: index,
+}));
+
jest.mock('../../../utils/date-time/DateTimeUtils', () => ({
formatDateTimeLong: jest.fn(),
}));
@@ -51,6 +56,10 @@ jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => ({
default: jest.fn().mockReturnValue(ErrorPlaceHolder
),
}));
+jest.mock('../../../constants/profiler.constant', () => ({
+ PROFILER_CHART_DATA_SIZE: 500,
+}));
+
jest.mock('../../../utils/ChartUtils', () => ({
axisTickFormatter: jest.fn(),
tooltipFormatter: jest.fn(),
@@ -70,6 +79,21 @@ describe('CustomBarChart component test', () => {
expect(XAxis).toBeInTheDocument();
expect(YAxis).toBeInTheDocument();
expect(noData).not.toBeInTheDocument();
+ expect(screen.queryByText('Brush')).not.toBeInTheDocument();
+ });
+
+ it('Component should render brush when data length is greater than PROFILER_CHART_DATA_SIZE', async () => {
+ render(
+
+ );
+
+ expect(screen.getByText('Brush')).toBeInTheDocument();
});
it('If there is no data, placeholder should be visible', async () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.tsx
index 59574b9339a..24829887f09 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/CustomBarChart.tsx
@@ -12,10 +12,11 @@
*/
import { Col, Row } from 'antd';
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import {
Bar,
BarChart,
+ Brush,
CartesianGrid,
Legend,
LegendProps,
@@ -25,6 +26,7 @@ import {
YAxis,
} from 'recharts';
import { GRAPH_BACKGROUND_COLOR } from '../../../constants/constants';
+import { PROFILER_CHART_DATA_SIZE } from '../../../constants/profiler.constant';
import {
axisTickFormatter,
tooltipFormatter,
@@ -44,6 +46,13 @@ const CustomBarChart = ({
const { data, information } = chartCollection;
const [activeKeys, setActiveKeys] = useState([]);
+ const { showBrush, endIndex } = useMemo(() => {
+ return {
+ showBrush: data.length > PROFILER_CHART_DATA_SIZE,
+ endIndex: PROFILER_CHART_DATA_SIZE,
+ };
+ }, [data.length]);
+
if (data.length === 0) {
return (
@@ -104,6 +113,15 @@ const CustomBarChart = ({
/>
))}
+ {showBrush && (
+
+ )}
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.test.tsx
index ed6f90aa030..91b08b456ae 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.test.tsx
@@ -42,6 +42,11 @@ jest.mock('../../../utils/DataInsightUtils', () => {
});
});
+const mockData = Array.from({ length: 501 }, (_, index) => ({
+ name: `test ${index}`,
+ value: index,
+}));
+
jest.mock('../../../utils/ChartUtils', () => ({
tooltipFormatter: jest.fn(),
updateActiveChartFilter: jest.fn(),
@@ -50,6 +55,9 @@ jest.mock('../../../utils/ChartUtils', () => ({
jest.mock('../../../utils/date-time/DateTimeUtils', () => ({
formatDateTimeLong: jest.fn(),
}));
+jest.mock('../../../constants/profiler.constant', () => ({
+ PROFILER_CHART_DATA_SIZE: 500,
+}));
jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => ({
__esModule: true,
@@ -67,8 +75,23 @@ describe('OperationDateBarChart component test', () => {
expect(container).toBeInTheDocument();
expect(XAxis).toBeInTheDocument();
- expect(YAxis).not.toBeInTheDocument();
+ expect(YAxis).toBeInTheDocument();
expect(noData).not.toBeInTheDocument();
+ expect(screen.queryByText('Brush')).not.toBeInTheDocument();
+ });
+
+ it('Component should render brush when data length is greater than PROFILER_CHART_DATA_SIZE', async () => {
+ render(
+
+ );
+
+ expect(screen.getByText('Brush')).toBeInTheDocument();
});
it('If there is no data, placeholder should be visible', async () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.tsx
index 084287da84f..e8ad7985197 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Visualisations/Chart/OperationDateBarChart.tsx
@@ -12,9 +12,10 @@
*/
import { Col, Row } from 'antd';
-import React, { useState } from 'react';
+import React, { Fragment, useMemo, useState } from 'react';
import {
Bar,
+ Brush,
CartesianGrid,
ComposedChart,
Legend,
@@ -23,8 +24,10 @@ import {
Scatter,
Tooltip,
XAxis,
+ YAxis,
} from 'recharts';
import { GRAPH_BACKGROUND_COLOR } from '../../../constants/constants';
+import { PROFILER_CHART_DATA_SIZE } from '../../../constants/profiler.constant';
import {
tooltipFormatter,
updateActiveChartFilter,
@@ -42,6 +45,13 @@ const OperationDateBarChart = ({
const { data, information } = chartCollection;
const [activeKeys, setActiveKeys] = useState([]);
+ const { showBrush, endIndex } = useMemo(() => {
+ return {
+ showBrush: data.length > PROFILER_CHART_DATA_SIZE,
+ endIndex: PROFILER_CHART_DATA_SIZE,
+ };
+ }, [data.length]);
+
const handleClick: LegendProps['onClick'] = (event) => {
setActiveKeys((prevActiveKeys) =>
updateActiveChartFilter(event.dataKey, prevActiveKeys)
@@ -73,6 +83,14 @@ const OperationDateBarChart = ({
padding={{ left: 16, right: 16 }}
tick={{ fontSize: 12 }}
/>
+ ''}
+ tickLine={false}
+ />
{information.map((info) => (
-
- ))}
- {information.map((info) => (
-
+
+
+
+
))}
+ {showBrush && (
+
+ )}
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts
index bbc852d5030..09e49736760 100644
--- a/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts
@@ -64,6 +64,7 @@ export const PROFILER_METRIC = [
'histogram',
'customMetricsProfile',
];
+export const PROFILER_CHART_DATA_SIZE = 500;
export const PROFILER_FILTER_RANGE: DateFilterType = {
yesterday: {
diff --git a/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/recharts.mock.js b/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/recharts.mock.js
index 7156fe87ee6..e98255ec40a 100644
--- a/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/recharts.mock.js
+++ b/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/recharts.mock.js
@@ -14,6 +14,8 @@
import React from 'react';
jest.mock('recharts', () => ({
Bar: jest.fn().mockImplementation(() => Bar
),
+ Line: jest.fn().mockImplementation(() => Line
),
+ Brush: jest.fn().mockImplementation(() => Brush
),
Area: jest.fn().mockImplementation(() => Area
),
Scatter: jest.fn().mockImplementation(() => Scatter
),
CartesianGrid: jest.fn().mockImplementation(() => CartesianGrid
),
@@ -27,6 +29,9 @@ jest.mock('recharts', () => ({
AreaChart: jest
.fn()
.mockImplementation(({ children }) => {children}
),
+ LineChart: jest
+ .fn()
+ .mockImplementation(({ children }) => {children}
),
ComposedChart: jest
.fn()
.mockImplementation(({ children }) => {children}
),
@@ -35,9 +40,9 @@ jest.mock('recharts', () => ({
.mockImplementation(({ children, ...rest }) => (
{children}
)),
- ResponsiveContainer: jest
- .fn()
- .mockImplementation(({ children }) => (
- {children}
- )),
+ ResponsiveContainer: jest.fn().mockImplementation(({ children, ...rest }) => (
+
+ {children}
+
+ )),
}));
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableProfilerUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TableProfilerUtils.ts
index b02b18c09ee..0353d0c86d0 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/TableProfilerUtils.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableProfilerUtils.ts
@@ -11,7 +11,7 @@
* limitations under the License.
*/
-import { findLast, isUndefined, last, sortBy } from 'lodash';
+import { isUndefined, last, sortBy } from 'lodash';
import { MetricChartType } from '../components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface';
import { SystemProfile } from '../generated/api/data/createTableProfile';
import { Table, TableProfile } from '../generated/entity/data/table';
@@ -30,10 +30,11 @@ export const calculateRowCountMetrics = (
profiler: TableProfile[],
currentMetrics: MetricChartType
): MetricChartType => {
- const updateProfilerData = sortBy(profiler, 'timestamp');
const rowCountMetricData: MetricChartType['data'] = [];
- updateProfilerData.forEach((data) => {
+ // reverse the profiler data to show the latest data at the top
+ for (let i = profiler.length - 1; i >= 0; i--) {
+ const data = profiler[i];
const timestamp = customFormatDateTime(
data.timestamp,
DATE_TIME_12_HOUR_FORMAT
@@ -44,7 +45,8 @@ export const calculateRowCountMetrics = (
timestamp: data.timestamp,
rowCount: data.rowCount,
});
- });
+ }
+
const countMetricInfo = currentMetrics.information.map((item) => ({
...item,
latestValue:
@@ -59,16 +61,23 @@ export const calculateSystemMetrics = (
currentMetrics: MetricChartType,
stackId?: string
) => {
- const updateProfilerData = sortBy(profiler, 'timestamp');
const operationMetrics: MetricChartType['data'] = [];
const operationDateMetrics: MetricChartType['data'] = [];
+ const latestOperations = new Map();
- updateProfilerData.forEach((data) => {
+ // reverse the profiler data to show the latest data at the top
+ for (let i = profiler.length - 1; i >= 0; i--) {
+ const data = profiler[i];
const timestamp = customFormatDateTime(
data.timestamp,
DATE_TIME_12_HOUR_FORMAT
);
+ // Store latest operation if not already stored
+ if (data.operation) {
+ latestOperations.set(data.operation, data);
+ }
+
operationMetrics.push({
name: timestamp,
timestamp: Number(data.timestamp),
@@ -80,33 +89,24 @@ export const calculateSystemMetrics = (
data: data.rowsAffected,
[data.operation ?? 'value']: 5,
});
- });
- const operationMetricsInfo = currentMetrics.information.map((item) => {
- const operation = findLast(
- updateProfilerData,
- (value) => value.operation === item.dataKey
- );
+ }
- return {
- ...item,
- stackId: stackId,
- latestValue: operation?.rowsAffected,
- };
- });
- const operationDateMetricsInfo = currentMetrics.information.map((item) => {
- const operation = findLast(
- updateProfilerData,
- (value) => value.operation === item.dataKey
- );
+ const operationMetricsInfo = currentMetrics.information.map((item) => ({
+ ...item,
+ stackId,
+ latestValue: latestOperations.get(item.dataKey)?.rowsAffected,
+ }));
- return {
- ...item,
- stackId: stackId,
- latestValue: operation?.timestamp
- ? customFormatDateTime(operation?.timestamp, DATE_TIME_12_HOUR_FORMAT)
- : '--',
- };
- });
+ const operationDateMetricsInfo = currentMetrics.information.map((item) => ({
+ ...item,
+ stackId,
+ latestValue: latestOperations.get(item.dataKey)?.timestamp
+ ? customFormatDateTime(
+ latestOperations.get(item.dataKey)?.timestamp,
+ DATE_TIME_12_HOUR_FORMAT
+ )
+ : '--',
+ }));
return {
operationMetrics: {