mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-28 10:25:20 +00:00
fix: improve application robustness and UI consistency
- Set default value for plugins in AppRouter to prevent potential errors. - Add conditional rendering for ProfilerLatestValue to avoid rendering issues when props are undefined. - Make profilerLatestValueProps optional in ProfilerStateWrapper interface for better flexibility. - Refactor ColumnProfileTable to remove unused imports and optimize rendering logic. - Replace Button with Typography for better styling in ColumnProfileTable. - Update SingleColumnProfile to use Stack for layout consistency and include ColumnSummary when available. - Enhance CardinalityDistributionChart and DataDistributionHistogram with new styling and layout using MUI components. - Introduce DataPill styled component for consistent data display. - Update color constants for improved visual consistency across charts. - Modify data insight tooltip to conditionally display date in header for better clarity.
This commit is contained in:
parent
f062908e56
commit
7ef9508f0a
@ -43,7 +43,7 @@ const AppRouter = () => {
|
|||||||
isApplicationLoading,
|
isApplicationLoading,
|
||||||
isAuthenticating,
|
isAuthenticating,
|
||||||
} = useApplicationStore();
|
} = useApplicationStore();
|
||||||
const { plugins } = useApplicationsProvider();
|
const { plugins = [] } = useApplicationsProvider();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
@ -57,7 +57,9 @@ const ProfilerStateWrapper = ({
|
|||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
}}>
|
}}>
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<ProfilerLatestValue {...profilerLatestValueProps} />
|
{profilerLatestValueProps && (
|
||||||
|
<ProfilerLatestValue {...profilerLatestValueProps} />
|
||||||
|
)}
|
||||||
<Box flexGrow={1}>{children}</Box>
|
<Box flexGrow={1}>{children}</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -16,6 +16,6 @@ export interface ProfilerStateWrapperProps {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
profilerLatestValueProps: ProfilerLatestValueProps;
|
profilerLatestValueProps?: ProfilerLatestValueProps;
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Grid, Stack, Typography, useTheme } from '@mui/material';
|
import { Grid, Stack, Typography, useTheme } from '@mui/material';
|
||||||
import { Button, Col, Row } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import classNames from 'classnames';
|
import { isEmpty, round } from 'lodash';
|
||||||
import {
|
|
||||||
filter,
|
|
||||||
find,
|
|
||||||
groupBy,
|
|
||||||
isEmpty,
|
|
||||||
isUndefined,
|
|
||||||
map,
|
|
||||||
round,
|
|
||||||
toLower,
|
|
||||||
} from 'lodash';
|
|
||||||
import Qs from 'qs';
|
import Qs from 'qs';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -38,10 +27,6 @@ import {
|
|||||||
Table as TableType,
|
Table as TableType,
|
||||||
} from '../../../../../generated/entity/data/table';
|
} from '../../../../../generated/entity/data/table';
|
||||||
import { Operation } from '../../../../../generated/entity/policies/policy';
|
import { Operation } from '../../../../../generated/entity/policies/policy';
|
||||||
import {
|
|
||||||
TestCase,
|
|
||||||
TestCaseStatus,
|
|
||||||
} from '../../../../../generated/tests/testCase';
|
|
||||||
import { usePaging } from '../../../../../hooks/paging/usePaging';
|
import { usePaging } from '../../../../../hooks/paging/usePaging';
|
||||||
import useCustomLocation from '../../../../../hooks/useCustomLocation/useCustomLocation';
|
import useCustomLocation from '../../../../../hooks/useCustomLocation/useCustomLocation';
|
||||||
import { useFqn } from '../../../../../hooks/useFqn';
|
import { useFqn } from '../../../../../hooks/useFqn';
|
||||||
@ -49,26 +34,21 @@ import {
|
|||||||
getTableColumnsByFQN,
|
getTableColumnsByFQN,
|
||||||
searchTableColumnsByFQN,
|
searchTableColumnsByFQN,
|
||||||
} from '../../../../../rest/tableAPI';
|
} from '../../../../../rest/tableAPI';
|
||||||
import { getListTestCaseBySearch } from '../../../../../rest/testAPI';
|
|
||||||
import {
|
import {
|
||||||
formatNumberWithComma,
|
formatNumberWithComma,
|
||||||
getTableFQNFromColumnFQN,
|
getTableFQNFromColumnFQN,
|
||||||
} from '../../../../../utils/CommonUtils';
|
} from '../../../../../utils/CommonUtils';
|
||||||
import { getEntityName } from '../../../../../utils/EntityUtils';
|
import { getEntityName } from '../../../../../utils/EntityUtils';
|
||||||
import {
|
import {
|
||||||
generateEntityLink,
|
|
||||||
getTableExpandableConfig,
|
getTableExpandableConfig,
|
||||||
pruneEmptyChildren,
|
pruneEmptyChildren,
|
||||||
} from '../../../../../utils/TableUtils';
|
} from '../../../../../utils/TableUtils';
|
||||||
import ErrorPlaceHolder from '../../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||||
import FilterTablePlaceHolder from '../../../../common/ErrorWithPlaceholder/FilterTablePlaceHolder';
|
import FilterTablePlaceHolder from '../../../../common/ErrorWithPlaceholder/FilterTablePlaceHolder';
|
||||||
import { PagingHandlerParams } from '../../../../common/NextPrevious/NextPrevious.interface';
|
import { PagingHandlerParams } from '../../../../common/NextPrevious/NextPrevious.interface';
|
||||||
import { SummaryCard } from '../../../../common/SummaryCard/SummaryCard.component';
|
|
||||||
import { SummaryCardProps } from '../../../../common/SummaryCard/SummaryCard.interface';
|
|
||||||
import SummaryCardV1 from '../../../../common/SummaryCard/SummaryCardV1';
|
import SummaryCardV1 from '../../../../common/SummaryCard/SummaryCardV1';
|
||||||
import Table from '../../../../common/Table/Table';
|
import Table from '../../../../common/Table/Table';
|
||||||
import { TableProfilerTab } from '../../ProfilerDashboard/profilerDashboard.interface';
|
import { TableProfilerTab } from '../../ProfilerDashboard/profilerDashboard.interface';
|
||||||
import ColumnSummary from '../ColumnSummary';
|
|
||||||
import NoProfilerBanner from '../NoProfilerBanner/NoProfilerBanner.component';
|
import NoProfilerBanner from '../NoProfilerBanner/NoProfilerBanner.component';
|
||||||
import SingleColumnProfile from '../SingleColumnProfile';
|
import SingleColumnProfile from '../SingleColumnProfile';
|
||||||
import { ModifiedColumn } from '../TableProfiler.interface';
|
import { ModifiedColumn } from '../TableProfiler.interface';
|
||||||
@ -95,8 +75,6 @@ const ColumnProfileTable = () => {
|
|||||||
const isLoading = isTestsLoading || isProfilerDataLoading;
|
const isLoading = isTestsLoading || isProfilerDataLoading;
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
const [data, setData] = useState<ModifiedColumn[]>([]);
|
const [data, setData] = useState<ModifiedColumn[]>([]);
|
||||||
const [isTestCaseLoading, setIsTestCaseLoading] = useState(false);
|
|
||||||
const [columnTestCases, setColumnTestCases] = useState<TestCase[]>([]);
|
|
||||||
const [isColumnsLoading, setIsColumnsLoading] = useState(false);
|
const [isColumnsLoading, setIsColumnsLoading] = useState(false);
|
||||||
const {
|
const {
|
||||||
currentPage,
|
currentPage,
|
||||||
@ -138,14 +116,20 @@ const ColumnProfileTable = () => {
|
|||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Typography
|
||||||
className="break-word p-0"
|
className="break-word p-0"
|
||||||
type="link"
|
sx={{
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontSize: theme.typography.pxToRem(14),
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': { textDecoration: 'underline' },
|
||||||
|
}}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateActiveColumnFqn(record.fullyQualifiedName || '')
|
updateActiveColumnFqn(record.fullyQualifiedName || '')
|
||||||
}>
|
}>
|
||||||
{getEntityName(record)}
|
{getEntityName(record)}
|
||||||
</Button>
|
</Typography>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
sorter: (col1, col2) => col1.name.localeCompare(col2.name),
|
sorter: (col1, col2) => col1.name.localeCompare(col2.name),
|
||||||
@ -157,7 +141,11 @@ const ColumnProfileTable = () => {
|
|||||||
width: 150,
|
width: 150,
|
||||||
render: (dataTypeDisplay: string) => {
|
render: (dataTypeDisplay: string) => {
|
||||||
return (
|
return (
|
||||||
<Typography className="break-word">
|
<Typography
|
||||||
|
className="break-word"
|
||||||
|
sx={{
|
||||||
|
fontSize: theme.typography.pxToRem(14),
|
||||||
|
}}>
|
||||||
{dataTypeDisplay || 'N/A'}
|
{dataTypeDisplay || 'N/A'}
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
@ -286,51 +274,11 @@ const ColumnProfileTable = () => {
|
|||||||
];
|
];
|
||||||
}, [testCaseSummary]);
|
}, [testCaseSummary]);
|
||||||
|
|
||||||
const selectedColumn = useMemo(() => {
|
|
||||||
return find(
|
|
||||||
data,
|
|
||||||
(column: Column) => column.fullyQualifiedName === activeColumnFqn
|
|
||||||
);
|
|
||||||
}, [data, activeColumnFqn]);
|
|
||||||
|
|
||||||
const selectedColumnTestsObj = useMemo(() => {
|
|
||||||
const temp = filter(
|
|
||||||
columnTestCases,
|
|
||||||
(test: TestCase) => !isUndefined(test.testCaseResult)
|
|
||||||
);
|
|
||||||
|
|
||||||
const statusDict = {
|
|
||||||
[TestCaseStatus.Success]: [],
|
|
||||||
[TestCaseStatus.Aborted]: [],
|
|
||||||
[TestCaseStatus.Failed]: [],
|
|
||||||
...groupBy(temp, 'testCaseResult.testCaseStatus'),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { statusDict, totalTests: temp.length };
|
|
||||||
}, [columnTestCases]);
|
|
||||||
|
|
||||||
const handleSearchAction = (searchText: string) => {
|
const handleSearchAction = (searchText: string) => {
|
||||||
setSearchText(searchText);
|
setSearchText(searchText);
|
||||||
handlePageChange(1);
|
handlePageChange(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchColumnTestCase = async (activeColumnFqn: string) => {
|
|
||||||
setIsTestCaseLoading(true);
|
|
||||||
try {
|
|
||||||
const { data } = await getListTestCaseBySearch({
|
|
||||||
fields: TabSpecificField.TEST_CASE_RESULT,
|
|
||||||
entityLink: generateEntityLink(activeColumnFqn),
|
|
||||||
limit: PAGE_SIZE_LARGE,
|
|
||||||
});
|
|
||||||
|
|
||||||
setColumnTestCases(data);
|
|
||||||
} catch {
|
|
||||||
setColumnTestCases([]);
|
|
||||||
} finally {
|
|
||||||
setIsTestCaseLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchTableColumnWithProfiler = useCallback(
|
const fetchTableColumnWithProfiler = useCallback(
|
||||||
async (page: number, searchText: string) => {
|
async (page: number, searchText: string) => {
|
||||||
if (!tableFqn) {
|
if (!tableFqn) {
|
||||||
@ -382,14 +330,6 @@ const ColumnProfileTable = () => {
|
|||||||
}
|
}
|
||||||
}, [tableFqn, currentPage, searchText, pageSize]);
|
}, [tableFqn, currentPage, searchText, pageSize]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (activeColumnFqn) {
|
|
||||||
fetchColumnTestCase(activeColumnFqn);
|
|
||||||
} else {
|
|
||||||
setColumnTestCases([]);
|
|
||||||
}
|
|
||||||
}, [activeColumnFqn]);
|
|
||||||
|
|
||||||
const pagingProps = useMemo(() => {
|
const pagingProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
currentPage: currentPage,
|
currentPage: currentPage,
|
||||||
@ -424,77 +364,43 @@ const ColumnProfileTable = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack data-testid="column-profile-table-container" spacing="30px">
|
<Stack data-testid="column-profile-table-container" spacing="30px">
|
||||||
{!isLoading && !isProfilingEnabled && <NoProfilerBanner />}
|
{!isLoading && !isProfilingEnabled && <NoProfilerBanner />}
|
||||||
<Col span={24}>
|
|
||||||
<Grid container spacing={5}>
|
|
||||||
{overallSummary?.map((summary) => (
|
|
||||||
<Grid key={summary.title} size="grow">
|
|
||||||
<SummaryCardV1
|
|
||||||
extra={summary.extra}
|
|
||||||
icon={summary.icon}
|
|
||||||
isLoading={isLoading}
|
|
||||||
title={summary.title}
|
|
||||||
value={summary.value}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{!isUndefined(selectedColumn) && (
|
|
||||||
<Col span={10}>
|
|
||||||
<ColumnSummary column={selectedColumn} />
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col span={selectedColumn ? 14 : 24}>
|
<Grid container spacing={5}>
|
||||||
<Row
|
{overallSummary?.map((summary) => (
|
||||||
wrap
|
<Grid key={summary.title} size="grow">
|
||||||
className={classNames(
|
<SummaryCardV1
|
||||||
activeColumnFqn ? 'justify-start' : 'justify-between'
|
extra={summary.extra}
|
||||||
)}
|
icon={summary.icon}
|
||||||
gutter={[16, 16]}>
|
isLoading={isLoading}
|
||||||
{!isEmpty(activeColumnFqn) &&
|
title={summary.title}
|
||||||
map(selectedColumnTestsObj.statusDict, (data, key) => (
|
value={summary.value}
|
||||||
<Col key={key}>
|
/>
|
||||||
<SummaryCard
|
</Grid>
|
||||||
showProgressBar
|
))}
|
||||||
isLoading={isTestCaseLoading}
|
</Grid>
|
||||||
title={key}
|
|
||||||
total={selectedColumnTestsObj.totalTests}
|
|
||||||
type={toLower(key) as SummaryCardProps['type']}
|
|
||||||
value={data.length}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
{isEmpty(activeColumnFqn) ? (
|
{isEmpty(activeColumnFqn) ? (
|
||||||
<Col span={24}>
|
<Table
|
||||||
<Table
|
columns={tableColumn}
|
||||||
columns={tableColumn}
|
customPaginationProps={pagingProps}
|
||||||
customPaginationProps={pagingProps}
|
dataSource={data}
|
||||||
dataSource={data}
|
expandable={getTableExpandableConfig<Column>()}
|
||||||
expandable={getTableExpandableConfig<Column>()}
|
loading={isColumnsLoading || isLoading}
|
||||||
loading={isColumnsLoading || isLoading}
|
locale={{
|
||||||
locale={{
|
emptyText: <FilterTablePlaceHolder />,
|
||||||
emptyText: <FilterTablePlaceHolder />,
|
}}
|
||||||
}}
|
pagination={false}
|
||||||
pagination={false}
|
rowKey="name"
|
||||||
rowKey="name"
|
scroll={{ x: true }}
|
||||||
scroll={{ x: true }}
|
searchProps={searchProps}
|
||||||
searchProps={searchProps}
|
size="small"
|
||||||
size="small"
|
/>
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
) : (
|
) : (
|
||||||
<Col span={24}>
|
<SingleColumnProfile
|
||||||
<SingleColumnProfile
|
activeColumnFqn={activeColumnFqn}
|
||||||
activeColumnFqn={activeColumnFqn}
|
dateRangeObject={dateRangeObject}
|
||||||
dateRangeObject={dateRangeObject}
|
tableDetails={tableDetailsWithColumns}
|
||||||
tableDetails={tableDetailsWithColumns}
|
/>
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
* 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 { Card, Col, Row, Typography } from 'antd';
|
import { Stack } from '@mui/material';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { first, isString, last, pick } from 'lodash';
|
import { find, first, isString, last, pick } from 'lodash';
|
||||||
import { DateRangeObject } from 'Models';
|
import { DateRangeObject } from 'Models';
|
||||||
import { FC, useEffect, useMemo, useState } from 'react';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -20,7 +20,10 @@ import {
|
|||||||
DEFAULT_RANGE_DATA,
|
DEFAULT_RANGE_DATA,
|
||||||
INITIAL_COLUMN_METRICS_VALUE,
|
INITIAL_COLUMN_METRICS_VALUE,
|
||||||
} from '../../../../constants/profiler.constant';
|
} from '../../../../constants/profiler.constant';
|
||||||
import { ColumnProfile } from '../../../../generated/entity/data/container';
|
import {
|
||||||
|
Column,
|
||||||
|
ColumnProfile,
|
||||||
|
} from '../../../../generated/entity/data/container';
|
||||||
import { Table } from '../../../../generated/entity/data/table';
|
import { Table } from '../../../../generated/entity/data/table';
|
||||||
import { getColumnProfilerList } from '../../../../rest/tableAPI';
|
import { getColumnProfilerList } from '../../../../rest/tableAPI';
|
||||||
import { Transi18next } from '../../../../utils/CommonUtils';
|
import { Transi18next } from '../../../../utils/CommonUtils';
|
||||||
@ -35,6 +38,8 @@ import { showErrorToast } from '../../../../utils/ToastUtils';
|
|||||||
import CardinalityDistributionChart from '../../../Visualisations/Chart/CardinalityDistributionChart.component';
|
import CardinalityDistributionChart from '../../../Visualisations/Chart/CardinalityDistributionChart.component';
|
||||||
import DataDistributionHistogram from '../../../Visualisations/Chart/DataDistributionHistogram.component';
|
import DataDistributionHistogram from '../../../Visualisations/Chart/DataDistributionHistogram.component';
|
||||||
import ProfilerDetailsCard from '../ProfilerDetailsCard/ProfilerDetailsCard';
|
import ProfilerDetailsCard from '../ProfilerDetailsCard/ProfilerDetailsCard';
|
||||||
|
import ProfilerStateWrapper from '../ProfilerStateWrapper/ProfilerStateWrapper.component';
|
||||||
|
import ColumnSummary from './ColumnSummary';
|
||||||
import CustomMetricGraphs from './CustomMetricGraphs/CustomMetricGraphs.component';
|
import CustomMetricGraphs from './CustomMetricGraphs/CustomMetricGraphs.component';
|
||||||
import { useTableProfiler } from './TableProfilerProvider';
|
import { useTableProfiler } from './TableProfilerProvider';
|
||||||
|
|
||||||
@ -63,6 +68,13 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedColumn = useMemo(() => {
|
||||||
|
return find(
|
||||||
|
tableDetails?.columns ?? [],
|
||||||
|
(column: Column) => column.fullyQualifiedName === activeColumnFqn
|
||||||
|
);
|
||||||
|
}, [tableDetails, activeColumnFqn]);
|
||||||
|
|
||||||
const customMetrics = useMemo(
|
const customMetrics = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getColumnCustomMetric(
|
getColumnCustomMetric(
|
||||||
@ -151,115 +163,85 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
|
|||||||
}, [activeColumnFqn, dateRangeObject]);
|
}, [activeColumnFqn, dateRangeObject]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Stack
|
||||||
className="m-b-lg"
|
className="m-b-lg"
|
||||||
data-testid="profiler-tab-container"
|
data-testid="profiler-tab-container"
|
||||||
gutter={[16, 16]}>
|
spacing="30px">
|
||||||
<Col span={24}>
|
{selectedColumn && <ColumnSummary column={selectedColumn} />}
|
||||||
<ProfilerDetailsCard
|
<ProfilerDetailsCard
|
||||||
chartCollection={columnMetric.countMetrics}
|
chartCollection={columnMetric.countMetrics}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
name="count"
|
name="count"
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
title={t('label.data-count-plural')}
|
title={t('label.data-count-plural')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<ProfilerDetailsCard
|
||||||
<Col span={24}>
|
chartCollection={columnMetric.proportionMetrics}
|
||||||
<ProfilerDetailsCard
|
isLoading={isLoading}
|
||||||
chartCollection={columnMetric.proportionMetrics}
|
name="proportion"
|
||||||
isLoading={isLoading}
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
name="proportion"
|
tickFormatter="%"
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
title={t('label.data-proportion-plural')}
|
||||||
tickFormatter="%"
|
/>
|
||||||
title={t('label.data-proportion-plural')}
|
<ProfilerDetailsCard
|
||||||
/>
|
chartCollection={columnMetric.mathMetrics}
|
||||||
</Col>
|
isLoading={isLoading}
|
||||||
<Col span={24}>
|
name="math"
|
||||||
<ProfilerDetailsCard
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
chartCollection={columnMetric.mathMetrics}
|
showYAxisCategory={isMinMaxStringData}
|
||||||
isLoading={isLoading}
|
// only min/max category can be string
|
||||||
name="math"
|
title={t('label.data-range')}
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
/>
|
||||||
showYAxisCategory={isMinMaxStringData}
|
<ProfilerDetailsCard
|
||||||
// only min/max category can be string
|
chartCollection={columnMetric.sumMetrics}
|
||||||
title={t('label.data-range')}
|
chartType="area"
|
||||||
/>
|
isLoading={isLoading}
|
||||||
</Col>
|
name="sum"
|
||||||
<Col span={24}>
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
<ProfilerDetailsCard
|
title={t('label.data-aggregate')}
|
||||||
chartCollection={columnMetric.sumMetrics}
|
/>
|
||||||
isLoading={isLoading}
|
<ProfilerDetailsCard
|
||||||
name="sum"
|
chartCollection={columnMetric.quartileMetrics}
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
isLoading={isLoading}
|
||||||
title={t('label.data-aggregate')}
|
name="quartile"
|
||||||
/>
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
</Col>
|
title={t('label.data-quartile-plural')}
|
||||||
<Col span={24}>
|
/>
|
||||||
<ProfilerDetailsCard
|
|
||||||
chartCollection={columnMetric.quartileMetrics}
|
|
||||||
isLoading={isLoading}
|
|
||||||
name="quartile"
|
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
|
||||||
title={t('label.data-quartile-plural')}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
{firstDay?.histogram || currentDay?.histogram ? (
|
{firstDay?.histogram || currentDay?.histogram ? (
|
||||||
<Col span={24}>
|
<ProfilerStateWrapper
|
||||||
<Card
|
dataTestId="histogram-metrics"
|
||||||
className="shadow-none global-border-radius"
|
isLoading={isLoading}
|
||||||
data-testid="histogram-metrics"
|
title={t('label.data-distribution')}>
|
||||||
loading={isLoading}>
|
<DataDistributionHistogram
|
||||||
<Row gutter={[16, 16]}>
|
data={{
|
||||||
<Col span={24}>
|
firstDayData: firstDay,
|
||||||
<Typography.Title
|
currentDayData: currentDay,
|
||||||
data-testid="data-distribution-title"
|
}}
|
||||||
level={5}>
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
{t('label.data-distribution')}
|
/>
|
||||||
</Typography.Title>
|
</ProfilerStateWrapper>
|
||||||
</Col>
|
|
||||||
<Col span={24}>
|
|
||||||
<DataDistributionHistogram
|
|
||||||
data={{ firstDayData: firstDay, currentDayData: currentDay }}
|
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
) : null}
|
) : null}
|
||||||
{firstDay?.cardinalityDistribution ||
|
{firstDay?.cardinalityDistribution ||
|
||||||
currentDay?.cardinalityDistribution ? (
|
currentDay?.cardinalityDistribution ? (
|
||||||
<Col span={24}>
|
<ProfilerStateWrapper
|
||||||
<Card
|
dataTestId="cardinality-distribution-metrics"
|
||||||
className="shadow-none global-border-radius"
|
isLoading={isLoading}
|
||||||
data-testid="cardinality-distribution-metrics"
|
title={t('label.cardinality')}>
|
||||||
loading={isLoading}>
|
<CardinalityDistributionChart
|
||||||
<Row gutter={[16, 16]}>
|
data={{
|
||||||
<Col span={24}>
|
firstDayData: firstDay,
|
||||||
<Typography.Title
|
currentDayData: currentDay,
|
||||||
data-testid="cardinality-distribution-title"
|
}}
|
||||||
level={5}>
|
noDataPlaceholderText={noProfilerMessage}
|
||||||
{t('label.cardinality')}
|
/>
|
||||||
</Typography.Title>
|
</ProfilerStateWrapper>
|
||||||
</Col>
|
|
||||||
<Col span={24}>
|
|
||||||
<CardinalityDistributionChart
|
|
||||||
data={{ firstDayData: firstDay, currentDayData: currentDay }}
|
|
||||||
noDataPlaceholderText={noProfilerMessage}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
) : null}
|
) : null}
|
||||||
<Col span={24}>
|
<CustomMetricGraphs
|
||||||
<CustomMetricGraphs
|
customMetrics={customMetrics}
|
||||||
customMetrics={customMetrics}
|
customMetricsGraphData={columnCustomMetrics}
|
||||||
customMetricsGraphData={columnCustomMetrics}
|
isLoading={isLoading || isProfilerDataLoading}
|
||||||
isLoading={isLoading || isProfilerDataLoading}
|
/>
|
||||||
/>
|
</Stack>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Col, Row, Tag } from 'antd';
|
import { Box, Card, Divider, Typography, useTheme } from '@mui/material';
|
||||||
import { isUndefined, map } from 'lodash';
|
import { isUndefined } from 'lodash';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
@ -30,6 +30,7 @@ import { GRAPH_BACKGROUND_COLOR } from '../../../constants/constants';
|
|||||||
import { ColumnProfile } from '../../../generated/entity/data/table';
|
import { ColumnProfile } from '../../../generated/entity/data/table';
|
||||||
import { axisTickFormatter, tooltipFormatter } from '../../../utils/ChartUtils';
|
import { axisTickFormatter, tooltipFormatter } from '../../../utils/ChartUtils';
|
||||||
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
||||||
|
import { DataPill } from '../../common/DataPill/DataPill.styled';
|
||||||
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||||
|
|
||||||
export interface CardinalityDistributionChartProps {
|
export interface CardinalityDistributionChartProps {
|
||||||
@ -44,6 +45,7 @@ const CardinalityDistributionChart = ({
|
|||||||
data,
|
data,
|
||||||
noDataPlaceholderText,
|
noDataPlaceholderText,
|
||||||
}: CardinalityDistributionChartProps) => {
|
}: CardinalityDistributionChartProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showSingleGraph =
|
const showSingleGraph =
|
||||||
isUndefined(data.firstDayData?.cardinalityDistribution) ||
|
isUndefined(data.firstDayData?.cardinalityDistribution) ||
|
||||||
@ -54,11 +56,16 @@ const CardinalityDistributionChart = ({
|
|||||||
isUndefined(data.currentDayData?.cardinalityDistribution)
|
isUndefined(data.currentDayData?.cardinalityDistribution)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Row align="middle" className="h-full w-full" justify="center">
|
<Box
|
||||||
<Col>
|
sx={{
|
||||||
<ErrorPlaceHolder placeholderText={noDataPlaceholderText} />
|
display: 'flex',
|
||||||
</Col>
|
alignItems: 'center',
|
||||||
</Row>
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
}}>
|
||||||
|
<ErrorPlaceHolder placeholderText={noDataPlaceholderText} />
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,16 +77,60 @@ const CardinalityDistributionChart = ({
|
|||||||
const data = payload[0].payload;
|
const data = payload[0].payload;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card
|
||||||
<p className="font-semibold text-sm mb-1">{`${t('label.category')}: ${
|
sx={{
|
||||||
data.name
|
p: '10px',
|
||||||
}`}</p>
|
bgcolor: theme.palette.allShades.white,
|
||||||
<p className="text-sm mb-1">{`${t('label.count')}: ${tooltipFormatter(
|
}}>
|
||||||
data.count
|
<Typography
|
||||||
)}`}</p>
|
sx={{
|
||||||
<p className="text-sm">{`${t('label.percentage')}: ${
|
color: theme.palette.allShades.gray[900],
|
||||||
data.percentage
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
}%`}</p>
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
}}>
|
||||||
|
{data.name}
|
||||||
|
</Typography>
|
||||||
|
<Divider
|
||||||
|
sx={{
|
||||||
|
my: 2,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
borderColor: theme.palette.allShades.gray[300],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box className="d-flex items-center justify-between gap-6 p-b-xss text-sm">
|
||||||
|
<Typography
|
||||||
|
sx={(theme) => ({
|
||||||
|
color: theme.palette.allShades.gray[700],
|
||||||
|
fontSize: theme.typography.pxToRem(11),
|
||||||
|
})}>
|
||||||
|
{t('label.count')}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={(theme) => ({
|
||||||
|
color: theme.palette.allShades.gray[900],
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
fontSize: theme.typography.pxToRem(11),
|
||||||
|
})}>
|
||||||
|
{tooltipFormatter(data.count)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box className="d-flex items-center justify-between gap-6 p-b-xss text-sm">
|
||||||
|
<Typography
|
||||||
|
sx={(theme) => ({
|
||||||
|
color: theme.palette.allShades.gray[700],
|
||||||
|
fontSize: theme.typography.pxToRem(11),
|
||||||
|
})}>
|
||||||
|
{t('label.percentage')}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={(theme) => ({
|
||||||
|
color: theme.palette.allShades.gray[900],
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
fontSize: theme.typography.pxToRem(11),
|
||||||
|
})}>
|
||||||
|
{`${data.percentage}%`}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -87,9 +138,19 @@ const CardinalityDistributionChart = ({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataEntries = Object.entries(data).filter(
|
||||||
|
([, columnProfile]) => !isUndefined(columnProfile?.cardinalityDistribution)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="w-full" data-testid="chart-container">
|
<Box
|
||||||
{map(data, (columnProfile, key) => {
|
data-testid="chart-container"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
gap: 0,
|
||||||
|
}}>
|
||||||
|
{dataEntries.map(([key, columnProfile], index) => {
|
||||||
if (
|
if (
|
||||||
isUndefined(columnProfile) ||
|
isUndefined(columnProfile) ||
|
||||||
isUndefined(columnProfile?.cardinalityDistribution)
|
isUndefined(columnProfile?.cardinalityDistribution)
|
||||||
@ -108,62 +169,96 @@ const CardinalityDistributionChart = ({
|
|||||||
|
|
||||||
const graphDate = customFormatDateTime(
|
const graphDate = customFormatDateTime(
|
||||||
columnProfile?.timestamp || 0,
|
columnProfile?.timestamp || 0,
|
||||||
'MMM dd'
|
'MMM dd, yyyy'
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col key={key} span={showSingleGraph ? 24 : 12}>
|
<Box
|
||||||
<Row gutter={[8, 8]}>
|
key={key}
|
||||||
<Col
|
sx={{
|
||||||
data-testid="date"
|
flex: showSingleGraph ? '1 1 100%' : '1 1 50%',
|
||||||
offset={showSingleGraph ? 1 : 2}
|
minWidth: 0,
|
||||||
span={24}>
|
display: 'flex',
|
||||||
{graphDate}
|
flexDirection: 'column',
|
||||||
</Col>
|
px: showSingleGraph ? 4 : 6,
|
||||||
<Col offset={showSingleGraph ? 1 : 2} span={24}>
|
py: 2,
|
||||||
<Tag data-testid="cardinality-tag">{`${t('label.total-entity', {
|
borderRight:
|
||||||
|
!showSingleGraph && index === 0
|
||||||
|
? `1px solid ${theme.palette.grey[200]}`
|
||||||
|
: 'none',
|
||||||
|
}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
mb: 5,
|
||||||
|
}}>
|
||||||
|
<DataPill data-testid="date">{graphDate}</DataPill>
|
||||||
|
<DataPill data-testid="cardinality-tag">
|
||||||
|
{`${t('label.total-entity', {
|
||||||
entity: t('label.category-plural'),
|
entity: t('label.category-plural'),
|
||||||
})}: ${cardinalityData.categories?.length || 0}`}</Tag>
|
})}: ${cardinalityData.categories?.length || 0}`}
|
||||||
</Col>
|
</DataPill>
|
||||||
<Col span={24}>
|
</Box>
|
||||||
<ResponsiveContainer
|
<Box sx={{ flex: 1, minHeight: 350 }}>
|
||||||
debounce={200}
|
<ResponsiveContainer
|
||||||
id={`${key}-cardinality`}
|
debounce={200}
|
||||||
minHeight={300}>
|
id={`${key}-cardinality`}
|
||||||
<BarChart
|
minHeight={300}>
|
||||||
className="w-full"
|
<BarChart
|
||||||
data={graphData}
|
className="w-full"
|
||||||
layout="vertical"
|
data={graphData}
|
||||||
margin={{ left: 16 }}>
|
layout="vertical"
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} />
|
margin={{ left: 16 }}>
|
||||||
<XAxis
|
<CartesianGrid
|
||||||
padding={{ left: 16, right: 16 }}
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
tick={{ fontSize: 12 }}
|
strokeDasharray="3 3"
|
||||||
tickFormatter={(props) => axisTickFormatter(props, '%')}
|
vertical={false}
|
||||||
type="number"
|
/>
|
||||||
/>
|
<XAxis
|
||||||
<YAxis
|
axisLine={{
|
||||||
allowDataOverflow
|
stroke: theme.palette.grey[200],
|
||||||
dataKey="name"
|
}}
|
||||||
padding={{ top: 16, bottom: 16 }}
|
padding={{ left: 16, right: 16 }}
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
tickFormatter={(value: string) =>
|
tickFormatter={(props) => axisTickFormatter(props, '%')}
|
||||||
value?.length > 15 ? `${value.slice(0, 15)}...` : value
|
tickLine={false}
|
||||||
}
|
type="number"
|
||||||
type="category"
|
/>
|
||||||
width={120}
|
<YAxis
|
||||||
/>
|
allowDataOverflow
|
||||||
<Legend />
|
axisLine={false}
|
||||||
<Tooltip content={renderTooltip} />
|
dataKey="name"
|
||||||
<Bar dataKey="percentage" fill={CHART_BLUE_1} />
|
padding={{ top: 16, bottom: 16 }}
|
||||||
</BarChart>
|
tick={{ fontSize: 12 }}
|
||||||
</ResponsiveContainer>
|
tickFormatter={(value: string) =>
|
||||||
</Col>
|
value?.length > 15 ? `${value.slice(0, 15)}...` : value
|
||||||
</Row>
|
}
|
||||||
</Col>
|
tickLine={false}
|
||||||
|
type="category"
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
<Legend />
|
||||||
|
<Tooltip
|
||||||
|
content={renderTooltip}
|
||||||
|
cursor={{
|
||||||
|
stroke: theme.palette.grey[200],
|
||||||
|
strokeDasharray: '3 3',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="percentage"
|
||||||
|
fill={CHART_BLUE_1}
|
||||||
|
radius={[0, 8, 8, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Row>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Col, Row, Tag } from 'antd';
|
import { Box, useTheme } from '@mui/material';
|
||||||
import { isUndefined, map } from 'lodash';
|
import { isUndefined } from 'lodash';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
@ -29,7 +29,9 @@ import { GRAPH_BACKGROUND_COLOR } from '../../../constants/constants';
|
|||||||
import { DEFAULT_HISTOGRAM_DATA } from '../../../constants/profiler.constant';
|
import { DEFAULT_HISTOGRAM_DATA } from '../../../constants/profiler.constant';
|
||||||
import { HistogramClass } from '../../../generated/entity/data/table';
|
import { HistogramClass } from '../../../generated/entity/data/table';
|
||||||
import { axisTickFormatter, tooltipFormatter } from '../../../utils/ChartUtils';
|
import { axisTickFormatter, tooltipFormatter } from '../../../utils/ChartUtils';
|
||||||
|
import { CustomDQTooltip } from '../../../utils/DataQuality/DataQualityUtils';
|
||||||
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
||||||
|
import { DataPill } from '../../common/DataPill/DataPill.styled';
|
||||||
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||||
import { DataDistributionHistogramProps } from './Chart.interface';
|
import { DataDistributionHistogramProps } from './Chart.interface';
|
||||||
|
|
||||||
@ -37,7 +39,9 @@ const DataDistributionHistogram = ({
|
|||||||
data,
|
data,
|
||||||
noDataPlaceholderText,
|
noDataPlaceholderText,
|
||||||
}: DataDistributionHistogramProps) => {
|
}: DataDistributionHistogramProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const showSingleGraph =
|
const showSingleGraph =
|
||||||
isUndefined(data.firstDayData?.histogram) ||
|
isUndefined(data.firstDayData?.histogram) ||
|
||||||
isUndefined(data.currentDayData?.histogram);
|
isUndefined(data.currentDayData?.histogram);
|
||||||
@ -47,21 +51,32 @@ const DataDistributionHistogram = ({
|
|||||||
isUndefined(data.currentDayData?.histogram)
|
isUndefined(data.currentDayData?.histogram)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Row align="middle" className="h-full w-full" justify="center">
|
<Box
|
||||||
<Col>
|
sx={{
|
||||||
<ErrorPlaceHolder placeholderText={noDataPlaceholderText} />
|
display: 'flex',
|
||||||
</Col>
|
alignItems: 'center',
|
||||||
</Row>
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
}}>
|
||||||
|
<ErrorPlaceHolder placeholderText={noDataPlaceholderText} />
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const dataEntries = Object.entries(data).filter(
|
||||||
<Row className="w-full" data-testid="chart-container">
|
([, columnProfile]) => !isUndefined(columnProfile?.histogram)
|
||||||
{map(data, (columnProfile, key) => {
|
);
|
||||||
if (isUndefined(columnProfile?.histogram)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
data-testid="chart-container"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
gap: 0,
|
||||||
|
}}>
|
||||||
|
{dataEntries.map(([key, columnProfile], index) => {
|
||||||
const histogramData =
|
const histogramData =
|
||||||
(columnProfile?.histogram as HistogramClass) ||
|
(columnProfile?.histogram as HistogramClass) ||
|
||||||
DEFAULT_HISTOGRAM_DATA;
|
DEFAULT_HISTOGRAM_DATA;
|
||||||
@ -73,59 +88,105 @@ const DataDistributionHistogram = ({
|
|||||||
|
|
||||||
const graphDate = customFormatDateTime(
|
const graphDate = customFormatDateTime(
|
||||||
columnProfile?.timestamp || 0,
|
columnProfile?.timestamp || 0,
|
||||||
'MMM dd'
|
'MMM dd, yyyy'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const skewColorTheme = columnProfile?.nonParametricSkew
|
||||||
|
? columnProfile?.nonParametricSkew > 0
|
||||||
|
? theme.palette.allShades.success
|
||||||
|
: theme.palette.allShades.error
|
||||||
|
: theme.palette.allShades.info;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col key={key} span={showSingleGraph ? 24 : 12}>
|
<Box
|
||||||
<Row gutter={[8, 8]}>
|
key={key}
|
||||||
<Col
|
sx={{
|
||||||
data-testid="date"
|
flex: showSingleGraph ? '1 1 100%' : '1 1 50%',
|
||||||
offset={showSingleGraph ? 1 : 2}
|
minWidth: 0,
|
||||||
span={24}>
|
display: 'flex',
|
||||||
{graphDate}
|
flexDirection: 'column',
|
||||||
</Col>
|
px: showSingleGraph ? 4 : 3,
|
||||||
<Col offset={showSingleGraph ? 1 : 2} span={24}>
|
py: 2,
|
||||||
<Tag data-testid="skew-tag">{`${t('label.skew')}: ${
|
borderRight:
|
||||||
|
!showSingleGraph && index === 0
|
||||||
|
? `1px solid ${theme.palette.grey[200]}`
|
||||||
|
: 'none',
|
||||||
|
}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
mb: 5,
|
||||||
|
}}>
|
||||||
|
<DataPill>{graphDate}</DataPill>
|
||||||
|
<DataPill
|
||||||
|
sx={{
|
||||||
|
backgroundColor: skewColorTheme[100],
|
||||||
|
color: skewColorTheme[900],
|
||||||
|
}}>
|
||||||
|
{`${t('label.skew')}: ${
|
||||||
columnProfile?.nonParametricSkew || '--'
|
columnProfile?.nonParametricSkew || '--'
|
||||||
}`}</Tag>
|
}`}
|
||||||
</Col>
|
</DataPill>
|
||||||
<Col span={24}>
|
</Box>
|
||||||
<ResponsiveContainer
|
<Box sx={{ flex: 1, minHeight: 350 }}>
|
||||||
debounce={200}
|
<ResponsiveContainer
|
||||||
id={`${key}-histogram`}
|
debounce={200}
|
||||||
minHeight={300}>
|
height="100%"
|
||||||
<BarChart
|
id={`${key}-histogram`}
|
||||||
className="w-full"
|
width="100%">
|
||||||
data={graphData}
|
<BarChart
|
||||||
margin={{ left: 16 }}>
|
data={graphData}
|
||||||
<CartesianGrid stroke={GRAPH_BACKGROUND_COLOR} />
|
margin={{ top: 10, right: 10, bottom: 10, left: 10 }}>
|
||||||
<XAxis
|
<CartesianGrid
|
||||||
dataKey="name"
|
stroke={GRAPH_BACKGROUND_COLOR}
|
||||||
padding={{ left: 16, right: 16 }}
|
strokeDasharray="3 3"
|
||||||
tick={{ fontSize: 12 }}
|
vertical={false}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<XAxis
|
||||||
allowDataOverflow
|
axisLine={{
|
||||||
padding={{ top: 16, bottom: 16 }}
|
stroke: theme.palette.grey[200],
|
||||||
tick={{ fontSize: 12 }}
|
}}
|
||||||
tickFormatter={(props) => axisTickFormatter(props)}
|
dataKey="name"
|
||||||
/>
|
padding={{ left: 16, right: 16 }}
|
||||||
<Legend />
|
tick={{ fontSize: 12 }}
|
||||||
<Tooltip
|
tickLine={false}
|
||||||
formatter={(value: number | string) =>
|
/>
|
||||||
tooltipFormatter(value)
|
<YAxis
|
||||||
}
|
allowDataOverflow
|
||||||
/>
|
axisLine={false}
|
||||||
<Bar dataKey="frequency" fill={CHART_BLUE_1} />
|
padding={{ top: 16, bottom: 16 }}
|
||||||
</BarChart>
|
tick={{ fontSize: 12 }}
|
||||||
</ResponsiveContainer>
|
tickFormatter={(props) => axisTickFormatter(props)}
|
||||||
</Col>
|
tickLine={false}
|
||||||
</Row>
|
/>
|
||||||
</Col>
|
<Legend />
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<CustomDQTooltip
|
||||||
|
displayDateInHeader={false}
|
||||||
|
timeStampKey="name"
|
||||||
|
valueFormatter={(value) => tooltipFormatter(value)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
cursor={{
|
||||||
|
stroke: theme.palette.grey[200],
|
||||||
|
strokeDasharray: '3 3',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="frequency"
|
||||||
|
fill={CHART_BLUE_1}
|
||||||
|
radius={[8, 8, 0, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Row>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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 { Box, styled } from '@mui/material';
|
||||||
|
|
||||||
|
export const DataPill = styled(Box)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.grey[100],
|
||||||
|
color: theme.palette.grey[900],
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '6px 12px',
|
||||||
|
fontSize: theme.typography.pxToRem(14),
|
||||||
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
|
display: 'inline-block',
|
||||||
|
}));
|
@ -34,7 +34,7 @@ export const BLUE_2 = '#3ca2f4';
|
|||||||
export const BLUE_500 = '#2E90FA';
|
export const BLUE_500 = '#2E90FA';
|
||||||
export const BLUE_800 = '#1849A9';
|
export const BLUE_800 = '#1849A9';
|
||||||
export const BLUE_50 = '#EFF8FF';
|
export const BLUE_50 = '#EFF8FF';
|
||||||
export const CHART_BLUE_1 = '#1890FF';
|
export const CHART_BLUE_1 = '#4689FF';
|
||||||
export const RIPTIDE = '#76E9C6';
|
export const RIPTIDE = '#76E9C6';
|
||||||
export const MY_SIN = '#FEB019';
|
export const MY_SIN = '#FEB019';
|
||||||
export const SAN_MARINO = '#416BB3';
|
export const SAN_MARINO = '#416BB3';
|
||||||
|
@ -123,28 +123,28 @@ export const INITIAL_COUNT_METRIC_VALUE = {
|
|||||||
entity: t('label.distinct'),
|
entity: t('label.distinct'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'distinctCount',
|
dataKey: 'distinctCount',
|
||||||
color: '#1890FF',
|
color: '#467DDC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.entity-count', {
|
title: t('label.entity-count', {
|
||||||
entity: t('label.null'),
|
entity: t('label.null'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'nullCount',
|
dataKey: 'nullCount',
|
||||||
color: '#7147E8',
|
color: '#3488B5',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.entity-count', {
|
title: t('label.entity-count', {
|
||||||
entity: t('label.unique'),
|
entity: t('label.unique'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'uniqueCount',
|
dataKey: 'uniqueCount',
|
||||||
color: '#008376',
|
color: '#685997',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.entity-count', {
|
title: t('label.entity-count', {
|
||||||
entity: t('label.value-plural'),
|
entity: t('label.value-plural'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'valuesCount',
|
dataKey: 'valuesCount',
|
||||||
color: '#B02AAC',
|
color: '#464A52',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: [],
|
data: [],
|
||||||
@ -157,21 +157,21 @@ export const INITIAL_PROPORTION_METRIC_VALUE = {
|
|||||||
entity: t('label.distinct'),
|
entity: t('label.distinct'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'distinctProportion',
|
dataKey: 'distinctProportion',
|
||||||
color: '#1890FF',
|
color: '#6B97E3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.entity-proportion', {
|
title: t('label.entity-proportion', {
|
||||||
entity: t('label.null'),
|
entity: t('label.null'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'nullProportion',
|
dataKey: 'nullProportion',
|
||||||
color: '#7147E8',
|
color: '#867AAC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.entity-proportion', {
|
title: t('label.entity-proportion', {
|
||||||
entity: t('label.unique'),
|
entity: t('label.unique'),
|
||||||
}),
|
}),
|
||||||
dataKey: 'uniqueProportion',
|
dataKey: 'uniqueProportion',
|
||||||
color: '#008376',
|
color: '#6B6E75',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: [],
|
data: [],
|
||||||
@ -182,17 +182,17 @@ export const INITIAL_MATH_METRIC_VALUE = {
|
|||||||
{
|
{
|
||||||
title: t('label.max'),
|
title: t('label.max'),
|
||||||
dataKey: 'max',
|
dataKey: 'max',
|
||||||
color: '#1890FF',
|
color: '#6B97E3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.mean'),
|
title: t('label.mean'),
|
||||||
dataKey: 'mean',
|
dataKey: 'mean',
|
||||||
color: '#7147E8',
|
color: '#6B6E75',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.min'),
|
title: t('label.min'),
|
||||||
dataKey: 'min',
|
dataKey: 'min',
|
||||||
color: '#008376',
|
color: '#867AAC',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: [],
|
data: [],
|
||||||
@ -203,7 +203,8 @@ export const INITIAL_SUM_METRIC_VALUE = {
|
|||||||
{
|
{
|
||||||
title: t('label.sum'),
|
title: t('label.sum'),
|
||||||
dataKey: 'sum',
|
dataKey: 'sum',
|
||||||
color: '#1890FF',
|
color: BLUE_500,
|
||||||
|
fill: BLUE_50,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: [],
|
data: [],
|
||||||
@ -213,22 +214,22 @@ export const INITIAL_QUARTILE_METRIC_VALUE = {
|
|||||||
{
|
{
|
||||||
title: t('label.first-quartile'),
|
title: t('label.first-quartile'),
|
||||||
dataKey: 'firstQuartile',
|
dataKey: 'firstQuartile',
|
||||||
color: '#1890FF',
|
color: '#467DDC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.median'),
|
title: t('label.median'),
|
||||||
dataKey: 'median',
|
dataKey: 'median',
|
||||||
color: '#7147E8',
|
color: '#3488B5',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.inter-quartile-range'),
|
title: t('label.inter-quartile-range'),
|
||||||
dataKey: 'interQuartileRange',
|
dataKey: 'interQuartileRange',
|
||||||
color: '#008376',
|
color: '#685997',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.third-quartile'),
|
title: t('label.third-quartile'),
|
||||||
dataKey: 'thirdQuartile',
|
dataKey: 'thirdQuartile',
|
||||||
color: '#B02AAC',
|
color: '#464A52',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: [],
|
data: [],
|
||||||
|
@ -41,6 +41,7 @@ export interface ChartFilter {
|
|||||||
export interface DataInsightChartTooltipProps extends TooltipProps<any, any> {
|
export interface DataInsightChartTooltipProps extends TooltipProps<any, any> {
|
||||||
cardStyles?: React.CSSProperties;
|
cardStyles?: React.CSSProperties;
|
||||||
customValueKey?: string;
|
customValueKey?: string;
|
||||||
|
displayDateInHeader?: boolean;
|
||||||
dateTimeFormatter?: (date?: number, format?: string) => string;
|
dateTimeFormatter?: (date?: number, format?: string) => string;
|
||||||
isPercentage?: boolean;
|
isPercentage?: boolean;
|
||||||
isTier?: boolean;
|
isTier?: boolean;
|
||||||
|
@ -369,11 +369,15 @@ export const CustomDQTooltip = (props: DataInsightChartTooltipProps) => {
|
|||||||
timeStampKey = 'timestampValue',
|
timeStampKey = 'timestampValue',
|
||||||
transformLabel = true,
|
transformLabel = true,
|
||||||
valueFormatter,
|
valueFormatter,
|
||||||
|
displayDateInHeader = true,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
// we need to check if the xAxis is a date or not.
|
// we need to check if the xAxis is a date or not.
|
||||||
const timestamp = dateTimeFormatter(payload[0].payload[timeStampKey] || 0);
|
const timestamp = displayDateInHeader
|
||||||
|
? dateTimeFormatter(payload[0].payload[timeStampKey] || 0)
|
||||||
|
: payload[0].payload[timeStampKey];
|
||||||
|
|
||||||
const payloadValue = uniqBy(payload, 'dataKey');
|
const payloadValue = uniqBy(payload, 'dataKey');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user