mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-10 16:25:37 +00:00
Fix: Implement Data Quality Dashboards (Incident Manager + Data Quality) (#19231)
* Fix: Implement Data Quality Dashboards (Incident Manager + Data Quality) * added icon for test case status * added filters in the api * added filters for dq * added filter of table/column * added test coverage * address the comments
This commit is contained in:
parent
fd2575d244
commit
2c06bcf327
@ -46,6 +46,11 @@ export type TestCaseSearchParams = {
|
|||||||
dataQualityDimension?: string;
|
dataQualityDimension?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DataQualityPageParams = TestCaseSearchParams & {
|
||||||
|
owner?: string;
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface IncidentTypeAreaChartWidgetProps {
|
export interface IncidentTypeAreaChartWidgetProps {
|
||||||
title: string;
|
title: string;
|
||||||
incidentStatusType: TestCaseResolutionStatusTypes;
|
incidentStatusType: TestCaseResolutionStatusTypes;
|
||||||
|
@ -11,56 +11,110 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Col, Row } from 'antd';
|
import { Col, Row } from 'antd';
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReactComponent as TestCaseAbortedIcon } from '../../../assets/svg/aborted-status.svg';
|
||||||
|
import { ReactComponent as TestCaseIcon } from '../../../assets/svg/all-activity-v2.svg';
|
||||||
|
import { ReactComponent as TestCaseFailedIcon } from '../../../assets/svg/failed-status.svg';
|
||||||
|
import { ReactComponent as DataAssetsCoverageIcon } from '../../../assets/svg/ic-data-assets-coverage.svg';
|
||||||
|
import { ReactComponent as HealthCheckIcon } from '../../../assets/svg/ic-green-heart-border.svg';
|
||||||
|
import { ReactComponent as TestCaseSuccessIcon } from '../../../assets/svg/success-colored.svg';
|
||||||
import { SummaryCard } from '../../../components/common/SummaryCard/SummaryCard.component';
|
import { SummaryCard } from '../../../components/common/SummaryCard/SummaryCard.component';
|
||||||
|
import { PRIMARY_COLOR } from '../../../constants/Color.constants';
|
||||||
import { SummaryPanelProps } from './SummaryPanel.interface';
|
import { SummaryPanelProps } from './SummaryPanel.interface';
|
||||||
|
|
||||||
export const SummaryPanel: FC<SummaryPanelProps> = ({
|
export const SummaryPanel: FC<SummaryPanelProps> = ({
|
||||||
testSummary: summary,
|
testSummary: summary,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
showAdditionalSummary = false,
|
||||||
}: SummaryPanelProps) => {
|
}: SummaryPanelProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const spanValue = useMemo(
|
||||||
|
() => (showAdditionalSummary ? 8 : 6),
|
||||||
|
[showAdditionalSummary]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row wrap gutter={[16, 16]}>
|
<Row wrap gutter={[16, 16]}>
|
||||||
<Col span={6}>
|
<Col span={spanValue}>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-primary"
|
||||||
className="h-full"
|
className="h-full"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showProgressBar={false}
|
showProgressBar={false}
|
||||||
title={t('label.total-entity', { entity: t('label.test-plural') })}
|
title={t('label.total-entity', { entity: t('label.test-plural') })}
|
||||||
|
titleIcon={
|
||||||
|
<TestCaseIcon color={PRIMARY_COLOR} height={16} width={16} />
|
||||||
|
}
|
||||||
total={summary?.total ?? 0}
|
total={summary?.total ?? 0}
|
||||||
value={summary?.total ?? 0}
|
value={summary?.total ?? 0}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={spanValue}>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-success"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
title={t('label.success')}
|
title={t('label.success')}
|
||||||
|
titleIcon={<TestCaseSuccessIcon height={16} width={16} />}
|
||||||
total={summary?.total ?? 0}
|
total={summary?.total ?? 0}
|
||||||
type="success"
|
type="success"
|
||||||
value={summary?.success ?? 0}
|
value={summary?.success ?? 0}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={spanValue}>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-aborted"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
title={t('label.aborted')}
|
title={t('label.aborted')}
|
||||||
|
titleIcon={<TestCaseAbortedIcon height={16} width={16} />}
|
||||||
total={summary?.total ?? 0}
|
total={summary?.total ?? 0}
|
||||||
type="aborted"
|
type="aborted"
|
||||||
value={summary?.aborted ?? 0}
|
value={summary?.aborted ?? 0}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={spanValue}>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-failed"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
title={t('label.failed')}
|
title={t('label.failed')}
|
||||||
|
titleIcon={<TestCaseFailedIcon height={16} width={16} />}
|
||||||
total={summary?.total ?? 0}
|
total={summary?.total ?? 0}
|
||||||
type="failed"
|
type="failed"
|
||||||
value={summary?.failed ?? 0}
|
value={summary?.failed ?? 0}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
{showAdditionalSummary && (
|
||||||
|
<>
|
||||||
|
<Col span={spanValue}>
|
||||||
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-success"
|
||||||
|
isLoading={isLoading}
|
||||||
|
title={t('label.healthy-data-asset-plural')}
|
||||||
|
titleIcon={<HealthCheckIcon height={16} width={16} />}
|
||||||
|
total={summary?.totalDQEntities ?? 0}
|
||||||
|
type="success"
|
||||||
|
value={summary?.healthy ?? 0}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={spanValue}>
|
||||||
|
<SummaryCard
|
||||||
|
inverseLabel
|
||||||
|
cardBackgroundClass="bg-primary"
|
||||||
|
isLoading={isLoading}
|
||||||
|
title={t('label.data-asset-plural-coverage')}
|
||||||
|
titleIcon={<DataAssetsCoverageIcon height={16} width={16} />}
|
||||||
|
total={summary?.totalEntityCount ?? 0}
|
||||||
|
type="acknowledged"
|
||||||
|
value={summary?.totalDQEntities ?? 0}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -15,4 +15,5 @@ import { TestSummary } from '../../../generated/tests/testSuite';
|
|||||||
export interface SummaryPanelProps {
|
export interface SummaryPanelProps {
|
||||||
testSummary: TestSummary;
|
testSummary: TestSummary;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
showAdditionalSummary?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,14 @@ describe('SummaryPanel component', () => {
|
|||||||
expect(summaryCards).toHaveLength(4);
|
expect(summaryCards).toHaveLength(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Show additional summary card if showAdditionalSummary is true', async () => {
|
||||||
|
render(<SummaryPanel showAdditionalSummary testSummary={mockSummary} />);
|
||||||
|
|
||||||
|
const summaryCards = await screen.findAllByText('SummaryCard.component');
|
||||||
|
|
||||||
|
expect(summaryCards).toHaveLength(6);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not call getTestCaseExecutionSummary API, if testSummary data is provided', async () => {
|
it('should not call getTestCaseExecutionSummary API, if testSummary data is provided', async () => {
|
||||||
const mockGetTestCaseExecutionSummary =
|
const mockGetTestCaseExecutionSummary =
|
||||||
getTestCaseExecutionSummary as jest.Mock;
|
getTestCaseExecutionSummary as jest.Mock;
|
||||||
|
@ -111,8 +111,8 @@ export const TestCases = () => {
|
|||||||
|
|
||||||
return params as TestCaseSearchParams;
|
return params as TestCaseSearchParams;
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
const { searchValue = '' } = params;
|
|
||||||
|
|
||||||
|
const { searchValue = '' } = params;
|
||||||
const [testCase, setTestCase] = useState<TestCase[]>([]);
|
const [testCase, setTestCase] = useState<TestCase[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const [selectedFilter, setSelectedFilter] = useState<string[]>([
|
const [selectedFilter, setSelectedFilter] = useState<string[]>([
|
||||||
@ -416,7 +416,6 @@ export const TestCases = () => {
|
|||||||
key: filter,
|
key: filter,
|
||||||
label: startCase(name),
|
label: startCase(name),
|
||||||
value: filter,
|
value: filter,
|
||||||
onClick: handleMenuClick,
|
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -497,6 +496,7 @@ export const TestCases = () => {
|
|||||||
menu={{
|
menu={{
|
||||||
items: filterMenu,
|
items: filterMenu,
|
||||||
selectedKeys: selectedFilter,
|
selectedKeys: selectedFilter,
|
||||||
|
onClick: handleMenuClick,
|
||||||
}}
|
}}
|
||||||
trigger={['click']}>
|
trigger={['click']}>
|
||||||
<Button
|
<Button
|
||||||
@ -641,6 +641,7 @@ export const TestCases = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<SummaryPanel
|
<SummaryPanel
|
||||||
|
showAdditionalSummary
|
||||||
isLoading={isTestCaseSummaryLoading}
|
isLoading={isTestCaseSummaryLoading}
|
||||||
testSummary={testCaseSummary}
|
testSummary={testCaseSummary}
|
||||||
/>
|
/>
|
||||||
|
@ -321,6 +321,7 @@ export const TestSuites = () => {
|
|||||||
|
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<SummaryPanel
|
<SummaryPanel
|
||||||
|
showAdditionalSummary
|
||||||
isLoading={isTestCaseSummaryLoading}
|
isLoading={isTestCaseSummaryLoading}
|
||||||
testSummary={testCaseSummary}
|
testSummary={testCaseSummary}
|
||||||
/>
|
/>
|
||||||
|
@ -27,6 +27,9 @@ export const SummaryCard = ({
|
|||||||
showProgressBar = true,
|
showProgressBar = true,
|
||||||
className,
|
className,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
inverseLabel,
|
||||||
|
titleIcon,
|
||||||
|
cardBackgroundClass,
|
||||||
}: SummaryCardProps) => {
|
}: SummaryCardProps) => {
|
||||||
const percent = useMemo(() => {
|
const percent = useMemo(() => {
|
||||||
if (isNumber(value)) {
|
if (isNumber(value)) {
|
||||||
@ -48,14 +51,19 @@ export const SummaryCard = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space
|
<Space
|
||||||
className={classNames('summary-card', className)}
|
className={classNames('summary-card', cardBackgroundClass, className)}
|
||||||
data-testid="summary-card-container">
|
data-testid="summary-card-container">
|
||||||
<div>
|
<div
|
||||||
<Typography.Paragraph
|
className={classNames({ 'inverse-label': inverseLabel })}
|
||||||
className="summary-card-title"
|
data-testid="summary-card-label">
|
||||||
data-testid="summary-card-title">
|
<Space align="center" size={8}>
|
||||||
{title}
|
{titleIcon}
|
||||||
</Typography.Paragraph>
|
<Typography.Paragraph
|
||||||
|
className="summary-card-title"
|
||||||
|
data-testid="summary-card-title">
|
||||||
|
{title}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</Space>
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
className="summary-card-description"
|
className="summary-card-description"
|
||||||
data-testid="summary-card-description">
|
data-testid="summary-card-description">
|
||||||
|
@ -18,4 +18,11 @@ export interface SummaryCardProps {
|
|||||||
showProgressBar?: boolean;
|
showProgressBar?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
inverseLabel?: boolean;
|
||||||
|
titleIcon?: React.ReactElement;
|
||||||
|
cardBackgroundClass?:
|
||||||
|
| 'bg-success'
|
||||||
|
| 'bg-failed'
|
||||||
|
| 'bg-aborted'
|
||||||
|
| 'bg-primary';
|
||||||
}
|
}
|
||||||
|
@ -67,4 +67,31 @@ describe('SummaryCard component', () => {
|
|||||||
expect(title.textContent).toStrictEqual('summary title');
|
expect(title.textContent).toStrictEqual('summary title');
|
||||||
expect(description.textContent).toStrictEqual('description');
|
expect(description.textContent).toStrictEqual('description');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("label should be inverse, if 'inverseLabel' is true", async () => {
|
||||||
|
render(<SummaryCard {...mockProps} inverseLabel />);
|
||||||
|
|
||||||
|
const label = await screen.findByTestId('summary-card-label');
|
||||||
|
|
||||||
|
expect(label).toHaveClass('inverse-label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render title icon, if 'titleIcon' is provided", async () => {
|
||||||
|
render(
|
||||||
|
<SummaryCard
|
||||||
|
{...mockProps}
|
||||||
|
titleIcon={<span data-testid="title-icon">icon</span>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByTestId('title-icon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render card background based on 'cardBackgroundClass'", async () => {
|
||||||
|
render(<SummaryCard {...mockProps} cardBackgroundClass="bg-success" />);
|
||||||
|
|
||||||
|
const container = await screen.findByTestId('summary-card-container');
|
||||||
|
|
||||||
|
expect(container).toHaveClass('bg-success');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,11 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inverse-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-card-description {
|
.summary-card-description {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -37,6 +42,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.bg-success {
|
||||||
|
background-color: @green-8;
|
||||||
|
}
|
||||||
|
&.bg-failed {
|
||||||
|
background-color: @red-8;
|
||||||
|
}
|
||||||
|
&.bg-aborted {
|
||||||
|
background-color: @yellow-8;
|
||||||
|
}
|
||||||
|
&.bg-primary {
|
||||||
|
background-color: @blue-8;
|
||||||
|
}
|
||||||
|
|
||||||
.new.ant-progress-circle {
|
.new.ant-progress-circle {
|
||||||
.ant-progress-circle-path {
|
.ant-progress-circle-path {
|
||||||
stroke: @blue-3 !important;
|
stroke: @blue-3 !important;
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EntityType } from '../../enums/entity.enum';
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
import { TestSummary } from '../../generated/tests/testCase';
|
import { TestCaseStatus, TestSummary } from '../../generated/tests/testCase';
|
||||||
|
import { TestCaseType } from '../../rest/testAPI';
|
||||||
|
|
||||||
export enum DataQualityPageTabs {
|
export enum DataQualityPageTabs {
|
||||||
TEST_SUITES = 'test-suites',
|
TEST_SUITES = 'test-suites',
|
||||||
@ -35,4 +36,9 @@ export type DataQualityDashboardChartFilters = {
|
|||||||
endTs?: number;
|
endTs?: number;
|
||||||
entityFQN?: string;
|
entityFQN?: string;
|
||||||
entityType?: EntityType;
|
entityType?: EntityType;
|
||||||
|
serviceName?: string;
|
||||||
|
testPlatforms?: string[];
|
||||||
|
dataQualityDimension?: string;
|
||||||
|
testCaseStatus?: TestCaseStatus;
|
||||||
|
testCaseType?: TestCaseType;
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,11 @@
|
|||||||
/* eslint-disable i18next/no-literal-string */
|
/* eslint-disable i18next/no-literal-string */
|
||||||
import { act, render, screen } from '@testing-library/react';
|
import { act, render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { fetchTestCaseSummary } from '../../rest/dataQualityDashboardAPI';
|
import {
|
||||||
|
fetchEntityCoveredWithDQ,
|
||||||
|
fetchTestCaseSummary,
|
||||||
|
fetchTotalEntityCount,
|
||||||
|
} from '../../rest/dataQualityDashboardAPI';
|
||||||
import { DataQualityPageTabs } from './DataQualityPage.interface';
|
import { DataQualityPageTabs } from './DataQualityPage.interface';
|
||||||
import DataQualityProvider, {
|
import DataQualityProvider, {
|
||||||
useDataQualityProvider,
|
useDataQualityProvider,
|
||||||
@ -30,6 +34,10 @@ const mockPermissionsData = {
|
|||||||
const mockUseParam = { tab: DataQualityPageTabs.TABLES } as {
|
const mockUseParam = { tab: DataQualityPageTabs.TABLES } as {
|
||||||
tab?: DataQualityPageTabs;
|
tab?: DataQualityPageTabs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockLocation = {
|
||||||
|
search: '',
|
||||||
|
};
|
||||||
jest.mock('../../context/PermissionProvider/PermissionProvider', () => ({
|
jest.mock('../../context/PermissionProvider/PermissionProvider', () => ({
|
||||||
usePermissionProvider: () => mockPermissionsData,
|
usePermissionProvider: () => mockPermissionsData,
|
||||||
}));
|
}));
|
||||||
@ -38,11 +46,43 @@ jest.mock('react-router-dom', () => {
|
|||||||
useParams: jest.fn().mockImplementation(() => mockUseParam),
|
useParams: jest.fn().mockImplementation(() => mockUseParam),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
jest.mock('../../hooks/useCustomLocation/useCustomLocation', () => {
|
||||||
|
return jest.fn().mockImplementation(() => mockLocation);
|
||||||
|
});
|
||||||
jest.mock('../../rest/dataQualityDashboardAPI', () => ({
|
jest.mock('../../rest/dataQualityDashboardAPI', () => ({
|
||||||
fetchTestCaseSummary: jest.fn().mockImplementation(() => {
|
fetchTestCaseSummary: jest.fn().mockImplementation(() => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve({ data: [] });
|
resolve({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
document_count: '4',
|
||||||
|
'testCaseResult.testCaseStatus': 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document_count: '3',
|
||||||
|
'testCaseResult.testCaseStatus': 'failed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document_count: '1',
|
||||||
|
'testCaseResult.testCaseStatus': 'aborted',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}, 2000); // Simulate a delay
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
fetchEntityCoveredWithDQ: jest.fn().mockImplementation(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ data: [{ originEntityFQN: '1' }] });
|
||||||
|
}, 2000); // Simulate a delay
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
fetchTotalEntityCount: jest.fn().mockImplementation(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ data: [{ fullyQualifiedName: '29' }] });
|
||||||
}, 2000); // Simulate a delay
|
}, 2000); // Simulate a delay
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@ -87,7 +127,41 @@ describe('DataQualityProvider', () => {
|
|||||||
expect(await screen.findByText('tables component')).toBeInTheDocument();
|
expect(await screen.findByText('tables component')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call fetchTestCaseSummary', async () => {
|
it('should call fetchTestCaseSummary, fetchEntityCoveredWithDQ & fetchTotalEntityCount', async () => {
|
||||||
|
expect(await screen.findByText('tables component')).toBeInTheDocument();
|
||||||
expect(fetchTestCaseSummary).toHaveBeenCalledTimes(1);
|
expect(fetchTestCaseSummary).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fetchEntityCoveredWithDQ).toHaveBeenCalledTimes(2);
|
||||||
|
expect(fetchTotalEntityCount).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call fetchTestCaseSummary, fetchEntityCoveredWithDQ & fetchTotalEntityCount based on prams change', async () => {
|
||||||
|
mockLocation.search =
|
||||||
|
'?testCaseType=table&testCaseStatus=Success&tier=Tier.Tier1';
|
||||||
|
|
||||||
|
expect(await screen.findByText('tables component')).toBeInTheDocument();
|
||||||
|
expect(fetchTestCaseSummary).toHaveBeenCalledWith({
|
||||||
|
entityFQN: undefined,
|
||||||
|
ownerFqn: undefined,
|
||||||
|
testCaseStatus: 'Success',
|
||||||
|
testCaseType: 'table',
|
||||||
|
tier: ['Tier.Tier1'],
|
||||||
|
});
|
||||||
|
expect(fetchEntityCoveredWithDQ).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
entityFQN: undefined,
|
||||||
|
ownerFqn: undefined,
|
||||||
|
testCaseStatus: 'Success',
|
||||||
|
testCaseType: 'table',
|
||||||
|
tier: ['Tier.Tier1'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(fetchTotalEntityCount).toHaveBeenCalledWith({
|
||||||
|
entityFQN: undefined,
|
||||||
|
ownerFqn: undefined,
|
||||||
|
testCaseStatus: 'Success',
|
||||||
|
testCaseType: 'table',
|
||||||
|
tier: ['Tier.Tier1'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import QueryString from 'qs';
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
useContext,
|
useContext,
|
||||||
@ -19,10 +21,16 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { DataQualityPageParams } from '../../components/DataQuality/DataQuality.interface';
|
||||||
import { INITIAL_TEST_SUMMARY } from '../../constants/TestSuite.constant';
|
import { INITIAL_TEST_SUMMARY } from '../../constants/TestSuite.constant';
|
||||||
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
||||||
import { TestSummary } from '../../generated/tests/testCase';
|
import { TestSummary } from '../../generated/tests/testCase';
|
||||||
import { fetchTestCaseSummary } from '../../rest/dataQualityDashboardAPI';
|
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
||||||
|
import {
|
||||||
|
fetchEntityCoveredWithDQ,
|
||||||
|
fetchTestCaseSummary,
|
||||||
|
fetchTotalEntityCount,
|
||||||
|
} from '../../rest/dataQualityDashboardAPI';
|
||||||
import { transformToTestCaseStatusObject } from '../../utils/DataQuality/DataQualityUtils';
|
import { transformToTestCaseStatusObject } from '../../utils/DataQuality/DataQualityUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
import {
|
import {
|
||||||
@ -36,6 +44,17 @@ export const DataQualityContext = createContext<DataQualityContextInterface>(
|
|||||||
|
|
||||||
const DataQualityProvider = ({ children }: { children: React.ReactNode }) => {
|
const DataQualityProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
const { tab: activeTab } = useParams<{ tab: DataQualityPageTabs }>();
|
const { tab: activeTab } = useParams<{ tab: DataQualityPageTabs }>();
|
||||||
|
const location = useCustomLocation();
|
||||||
|
const params = useMemo(() => {
|
||||||
|
const search = location.search;
|
||||||
|
|
||||||
|
const params = QueryString.parse(
|
||||||
|
search.startsWith('?') ? search.substring(1) : search
|
||||||
|
);
|
||||||
|
|
||||||
|
return params as DataQualityPageParams;
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
const [testCaseSummary, setTestCaseSummary] =
|
const [testCaseSummary, setTestCaseSummary] =
|
||||||
useState<TestSummary>(INITIAL_TEST_SUMMARY);
|
useState<TestSummary>(INITIAL_TEST_SUMMARY);
|
||||||
const [isTestCaseSummaryLoading, setIsTestCaseSummaryLoading] =
|
const [isTestCaseSummaryLoading, setIsTestCaseSummaryLoading] =
|
||||||
@ -52,25 +71,65 @@ const DataQualityProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
}, [testCaseSummary, isTestCaseSummaryLoading, activeTab]);
|
}, [testCaseSummary, isTestCaseSummaryLoading, activeTab]);
|
||||||
|
|
||||||
const fetchTestSummary = async () => {
|
const fetchTestSummary = async (params?: DataQualityPageParams) => {
|
||||||
|
const filters = {
|
||||||
|
...pick(params, [
|
||||||
|
'tags',
|
||||||
|
'serviceName',
|
||||||
|
'testPlatforms',
|
||||||
|
'dataQualityDimension',
|
||||||
|
'testCaseStatus',
|
||||||
|
'testCaseType',
|
||||||
|
]),
|
||||||
|
ownerFqn: params?.owner ? JSON.parse(params.owner)?.name : undefined,
|
||||||
|
tier: params?.tier ? [params.tier] : undefined,
|
||||||
|
entityFQN: params?.tableFqn,
|
||||||
|
};
|
||||||
|
|
||||||
setIsTestCaseSummaryLoading(true);
|
setIsTestCaseSummaryLoading(true);
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchTestCaseSummary();
|
const { data } = await fetchTestCaseSummary(filters);
|
||||||
|
const { data: unhealthyData } = await fetchEntityCoveredWithDQ(
|
||||||
|
filters,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const { data: totalDQCoverage } = await fetchEntityCoveredWithDQ(
|
||||||
|
filters,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: entityCount } = await fetchTotalEntityCount(filters);
|
||||||
|
|
||||||
|
const unhealthy = parseInt(unhealthyData[0].originEntityFQN);
|
||||||
|
const total = parseInt(totalDQCoverage[0].originEntityFQN);
|
||||||
|
let totalEntityCount = parseInt(entityCount[0].fullyQualifiedName);
|
||||||
|
|
||||||
|
if (total > totalEntityCount) {
|
||||||
|
totalEntityCount = total;
|
||||||
|
}
|
||||||
|
|
||||||
const updatedData = transformToTestCaseStatusObject(data);
|
const updatedData = transformToTestCaseStatusObject(data);
|
||||||
setTestCaseSummary(updatedData);
|
setTestCaseSummary({
|
||||||
|
...updatedData,
|
||||||
|
unhealthy,
|
||||||
|
healthy: total - unhealthy,
|
||||||
|
totalDQEntities: total,
|
||||||
|
totalEntityCount,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error as AxiosError);
|
showErrorToast(error as AxiosError);
|
||||||
} finally {
|
} finally {
|
||||||
setIsTestCaseSummaryLoading(false);
|
setIsTestCaseSummaryLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) {
|
if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) {
|
||||||
fetchTestSummary();
|
fetchTestSummary(params);
|
||||||
} else {
|
} else {
|
||||||
setIsTestCaseSummaryLoading(false);
|
setIsTestCaseSummaryLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [params]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataQualityContext.Provider value={dataQualityContextValue}>
|
<DataQualityContext.Provider value={dataQualityContextValue}>
|
||||||
|
@ -16,6 +16,7 @@ import { EntityType } from '../enums/entity.enum';
|
|||||||
import { TestCaseStatus } from '../generated/tests/testCase';
|
import { TestCaseStatus } from '../generated/tests/testCase';
|
||||||
import { TestCaseResolutionStatusTypes } from '../generated/tests/testCaseResolutionStatus';
|
import { TestCaseResolutionStatusTypes } from '../generated/tests/testCaseResolutionStatus';
|
||||||
import {
|
import {
|
||||||
|
buildDataQualityDashboardFilters,
|
||||||
buildMustEsFilterForOwner,
|
buildMustEsFilterForOwner,
|
||||||
buildMustEsFilterForTags,
|
buildMustEsFilterForTags,
|
||||||
} from '../utils/DataQuality/DataQualityUtils';
|
} from '../utils/DataQuality/DataQualityUtils';
|
||||||
@ -37,21 +38,27 @@ jest.mock('./testAPI', () => ({
|
|||||||
jest.mock('../utils/DataQuality/DataQualityUtils', () => ({
|
jest.mock('../utils/DataQuality/DataQualityUtils', () => ({
|
||||||
buildMustEsFilterForOwner: jest.fn(),
|
buildMustEsFilterForOwner: jest.fn(),
|
||||||
buildMustEsFilterForTags: jest.fn(),
|
buildMustEsFilterForTags: jest.fn(),
|
||||||
|
buildDataQualityDashboardFilters: jest.fn().mockReturnValue([]),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('dataQualityDashboardAPI', () => {
|
describe('dataQualityDashboardAPI', () => {
|
||||||
describe('fetchTotalEntityCount', () => {
|
describe('fetchTotalEntityCount', () => {
|
||||||
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
||||||
const filters = { ownerFqn: 'owner1' };
|
const filters = { ownerFqn: 'owner1' };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue({
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
term: {
|
{
|
||||||
'owners.fullyQualifiedName': 'owner1',
|
term: {
|
||||||
|
'owners.fullyQualifiedName': 'owner1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
]);
|
||||||
|
|
||||||
await fetchTotalEntityCount(filters);
|
await fetchTotalEntityCount(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForOwner).toHaveBeenCalledWith('owner1');
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
|
filters: { ownerFqn: 'owner1' },
|
||||||
|
isTableApi: true,
|
||||||
|
});
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
query: {
|
query: {
|
||||||
@ -73,6 +80,16 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when tags are provided', async () => {
|
it('should call getDataQualityReport with correct query when tags are provided', async () => {
|
||||||
const filters = { tags: ['tag1', 'tag2'] };
|
const filters = { tags: ['tag1', 'tag2'] };
|
||||||
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ term: { 'tags.tagFQN': 'tag1' } },
|
||||||
|
{ term: { 'tags.tagFQN': 'tag2' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await fetchTotalEntityCount(filters);
|
await fetchTotalEntityCount(filters);
|
||||||
|
|
||||||
@ -100,6 +117,16 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when tier is provided', async () => {
|
it('should call getDataQualityReport with correct query when tier is provided', async () => {
|
||||||
const filters = { tier: ['tier1', 'tier2'] };
|
const filters = { tier: ['tier1', 'tier2'] };
|
||||||
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ term: { 'tier.tagFQN': 'tier1' } },
|
||||||
|
{ term: { 'tier.tagFQN': 'tier2' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await fetchTotalEntityCount(filters);
|
await fetchTotalEntityCount(filters);
|
||||||
|
|
||||||
@ -127,15 +154,30 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
||||||
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue({
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
term: {
|
{
|
||||||
'owners.fullyQualifiedName': 'owner1',
|
term: {
|
||||||
|
'owners.fullyQualifiedName': 'owner1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
bool: {
|
||||||
|
should: [{ term: { 'tags.tagFQN': 'tag1' } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [{ term: { 'tier.tagFQN': 'tier1' } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await fetchTotalEntityCount(filters);
|
await fetchTotalEntityCount(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForOwner).toHaveBeenCalledWith('owner1');
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
|
filters: { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] },
|
||||||
|
isTableApi: true,
|
||||||
|
});
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
query: {
|
query: {
|
||||||
@ -350,6 +392,9 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
func: fetchEntityCoveredWithDQ,
|
func: fetchEntityCoveredWithDQ,
|
||||||
index: 'testCase',
|
index: 'testCase',
|
||||||
aggregationQuery: `bucketName=entityWithTests:aggType=cardinality:field=originEntityFQN`,
|
aggregationQuery: `bucketName=entityWithTests:aggType=cardinality:field=originEntityFQN`,
|
||||||
|
params: {
|
||||||
|
unhealthy: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
functionName: 'fetchTestCaseSummaryByDimension',
|
functionName: 'fetchTestCaseSummaryByDimension',
|
||||||
@ -364,15 +409,16 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
describe(`${testData.functionName}`, () => {
|
describe(`${testData.functionName}`, () => {
|
||||||
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
||||||
const filters = { ownerFqn: testCaseData.filters.ownerFqn };
|
const filters = { ownerFqn: testCaseData.filters.ownerFqn };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue(
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
testCaseData.ownerExpectedQuery
|
testCaseData.ownerExpectedQuery,
|
||||||
);
|
]);
|
||||||
|
|
||||||
await testData.func(filters);
|
await testData.func(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForOwner).toHaveBeenCalledWith(
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
testCaseData.filters.ownerFqn
|
filters,
|
||||||
);
|
...testData.params,
|
||||||
|
});
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: testCaseData.test1.q,
|
q: testCaseData.test1.q,
|
||||||
index: testData.index,
|
index: testData.index,
|
||||||
@ -382,15 +428,16 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when tags are provided', async () => {
|
it('should call getDataQualityReport with correct query when tags are provided', async () => {
|
||||||
const filters = { tags: testCaseData.filters.tags };
|
const filters = { tags: testCaseData.filters.tags };
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue(
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
testCaseData.test2.expected
|
testCaseData.test2.expected,
|
||||||
);
|
]);
|
||||||
|
|
||||||
await testData.func(filters);
|
await testData.func(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForTags).toHaveBeenCalledWith(
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
testCaseData.filters.tags
|
filters,
|
||||||
);
|
...testData.params,
|
||||||
|
});
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: testCaseData.test2.q,
|
q: testCaseData.test2.q,
|
||||||
index: testData.index,
|
index: testData.index,
|
||||||
@ -400,15 +447,16 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when tier is provided', async () => {
|
it('should call getDataQualityReport with correct query when tier is provided', async () => {
|
||||||
const filters = { tier: testCaseData.filters.tier };
|
const filters = { tier: testCaseData.filters.tier };
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue(
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
testCaseData.test3.expected
|
testCaseData.test3.expected,
|
||||||
);
|
]);
|
||||||
|
|
||||||
await testData.func(filters);
|
await testData.func(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForTags).toHaveBeenCalledWith(
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
testCaseData.filters.tier
|
filters,
|
||||||
);
|
...testData.params,
|
||||||
|
});
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: testCaseData.test3.q,
|
q: testCaseData.test3.q,
|
||||||
index: testData.index,
|
index: testData.index,
|
||||||
@ -418,22 +466,19 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
|
|
||||||
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
||||||
const filters = testCaseData.filters;
|
const filters = testCaseData.filters;
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue(
|
|
||||||
testCaseData.ownerExpectedQuery
|
(buildDataQualityDashboardFilters as jest.Mock).mockReturnValueOnce([
|
||||||
);
|
testCaseData.ownerExpectedQuery,
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue(
|
testCaseData.test4.expected,
|
||||||
testCaseData.test4.expected
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
await testData.func(filters);
|
await testData.func(filters);
|
||||||
|
|
||||||
expect(buildMustEsFilterForOwner).toHaveBeenCalledWith(
|
expect(buildDataQualityDashboardFilters).toHaveBeenCalledWith({
|
||||||
testCaseData.filters.ownerFqn
|
filters,
|
||||||
);
|
...testData.params,
|
||||||
expect(buildMustEsFilterForTags).toHaveBeenCalledWith([
|
});
|
||||||
...testCaseData.filters.tags,
|
|
||||||
...testCaseData.filters.tier,
|
|
||||||
]);
|
|
||||||
expect(getDataQualityReport).toHaveBeenCalledWith({
|
expect(getDataQualityReport).toHaveBeenCalledWith({
|
||||||
q: testCaseData.test4.q,
|
q: testCaseData.test4.q,
|
||||||
index: testData.index,
|
index: testData.index,
|
||||||
@ -486,7 +531,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
||||||
const status = TestCaseResolutionStatusTypes.Assigned;
|
const status = TestCaseResolutionStatusTypes.Assigned;
|
||||||
const filters = { ownerFqn: 'owner1' };
|
const filters = { ownerFqn: 'owner1' };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce({
|
||||||
term: {
|
term: {
|
||||||
'owners.fullyQualifiedName': 'owner1',
|
'owners.fullyQualifiedName': 'owner1',
|
||||||
},
|
},
|
||||||
@ -527,7 +572,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when tags and tier are provided', async () => {
|
it('should call getDataQualityReport with correct query when tags and tier are provided', async () => {
|
||||||
const status = TestCaseResolutionStatusTypes.New;
|
const status = TestCaseResolutionStatusTypes.New;
|
||||||
const filters = { tags: ['tag1'], tier: ['tier1'] };
|
const filters = { tags: ['tag1'], tier: ['tier1'] };
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
@ -587,12 +632,12 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
||||||
const status = TestCaseResolutionStatusTypes.Resolved;
|
const status = TestCaseResolutionStatusTypes.Resolved;
|
||||||
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce({
|
||||||
term: {
|
term: {
|
||||||
'owners.fullyQualifiedName': 'owner1',
|
'owners.fullyQualifiedName': 'owner1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
@ -732,7 +777,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
startTs: 1729073964962,
|
startTs: 1729073964962,
|
||||||
endTs: 1729678764965,
|
endTs: 1729678764965,
|
||||||
};
|
};
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue(
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce(
|
||||||
testCaseData.ownerExpectedQuery
|
testCaseData.ownerExpectedQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -788,7 +833,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
startTs: 1729073964962,
|
startTs: 1729073964962,
|
||||||
endTs: 1729678764965,
|
endTs: 1729678764965,
|
||||||
};
|
};
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
@ -863,10 +908,10 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
startTs: 1729073964962,
|
startTs: 1729073964962,
|
||||||
endTs: 1729678764965,
|
endTs: 1729678764965,
|
||||||
};
|
};
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue(
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce(
|
||||||
testCaseData.ownerExpectedQuery
|
testCaseData.ownerExpectedQuery
|
||||||
);
|
);
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
@ -1011,7 +1056,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
it('should call getDataQualityReport with correct query when ownerFqn is provided', async () => {
|
||||||
const status = TestCaseStatus.Failed;
|
const status = TestCaseStatus.Failed;
|
||||||
const filters = { ownerFqn: testCaseData.filters.ownerFqn };
|
const filters = { ownerFqn: testCaseData.filters.ownerFqn };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue(
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce(
|
||||||
testCaseData.ownerExpectedQuery
|
testCaseData.ownerExpectedQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1053,7 +1098,7 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when tags and tier are provided', async () => {
|
it('should call getDataQualityReport with correct query when tags and tier are provided', async () => {
|
||||||
const status = TestCaseStatus.Aborted;
|
const status = TestCaseStatus.Aborted;
|
||||||
const filters = { tags: ['tag1'], tier: ['tier1'] };
|
const filters = { tags: ['tag1'], tier: ['tier1'] };
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
@ -1113,12 +1158,12 @@ describe('dataQualityDashboardAPI', () => {
|
|||||||
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
it('should call getDataQualityReport with correct query when all filters are provided', async () => {
|
||||||
const status = TestCaseStatus.Failed;
|
const status = TestCaseStatus.Failed;
|
||||||
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
const filters = { ownerFqn: 'owner1', tags: ['tag1'], tier: ['tier1'] };
|
||||||
(buildMustEsFilterForOwner as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForOwner as jest.Mock).mockReturnValueOnce({
|
||||||
term: {
|
term: {
|
||||||
'owners.fullyQualifiedName': 'owner1',
|
'owners.fullyQualifiedName': 'owner1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
(buildMustEsFilterForTags as jest.Mock).mockReturnValue({
|
(buildMustEsFilterForTags as jest.Mock).mockReturnValueOnce({
|
||||||
nested: {
|
nested: {
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
query: {
|
query: {
|
||||||
|
@ -16,6 +16,7 @@ import { TestCaseStatus } from '../generated/tests/testCase';
|
|||||||
import { TestCaseResolutionStatusTypes } from '../generated/tests/testCaseResolutionStatus';
|
import { TestCaseResolutionStatusTypes } from '../generated/tests/testCaseResolutionStatus';
|
||||||
import { DataQualityDashboardChartFilters } from '../pages/DataQuality/DataQualityPage.interface';
|
import { DataQualityDashboardChartFilters } from '../pages/DataQuality/DataQualityPage.interface';
|
||||||
import {
|
import {
|
||||||
|
buildDataQualityDashboardFilters,
|
||||||
buildMustEsFilterForOwner,
|
buildMustEsFilterForOwner,
|
||||||
buildMustEsFilterForTags,
|
buildMustEsFilterForTags,
|
||||||
} from '../utils/DataQuality/DataQualityUtils';
|
} from '../utils/DataQuality/DataQualityUtils';
|
||||||
@ -25,27 +26,7 @@ export const fetchEntityCoveredWithDQ = (
|
|||||||
filters?: DataQualityDashboardChartFilters,
|
filters?: DataQualityDashboardChartFilters,
|
||||||
unhealthy = false
|
unhealthy = false
|
||||||
) => {
|
) => {
|
||||||
const mustFilter = [];
|
const mustFilter = buildDataQualityDashboardFilters({ filters, unhealthy });
|
||||||
if (unhealthy) {
|
|
||||||
mustFilter.push({
|
|
||||||
terms: {
|
|
||||||
'testCaseStatus.keyword': ['Failed', 'Aborted'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.ownerFqn) {
|
|
||||||
mustFilter.push(buildMustEsFilterForOwner(filters.ownerFqn));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.tags || filters?.tier) {
|
|
||||||
mustFilter.push(
|
|
||||||
buildMustEsFilterForTags([
|
|
||||||
...(filters?.tags ?? []),
|
|
||||||
...(filters?.tier ?? []),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDataQualityReport({
|
return getDataQualityReport({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
@ -63,35 +44,10 @@ export const fetchEntityCoveredWithDQ = (
|
|||||||
export const fetchTotalEntityCount = (
|
export const fetchTotalEntityCount = (
|
||||||
filters?: DataQualityDashboardChartFilters
|
filters?: DataQualityDashboardChartFilters
|
||||||
) => {
|
) => {
|
||||||
const mustFilter = [];
|
const mustFilter = buildDataQualityDashboardFilters({
|
||||||
|
filters,
|
||||||
if (filters?.ownerFqn) {
|
isTableApi: true,
|
||||||
mustFilter.push(buildMustEsFilterForOwner(filters.ownerFqn));
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.tags) {
|
|
||||||
mustFilter.push({
|
|
||||||
bool: {
|
|
||||||
should: filters.tags.map((tag) => ({
|
|
||||||
term: {
|
|
||||||
'tags.tagFQN': tag,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.tier) {
|
|
||||||
mustFilter.push({
|
|
||||||
bool: {
|
|
||||||
should: filters.tier.map((tag) => ({
|
|
||||||
term: {
|
|
||||||
'tier.tagFQN': tag,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDataQualityReport({
|
return getDataQualityReport({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
@ -109,26 +65,7 @@ export const fetchTotalEntityCount = (
|
|||||||
export const fetchTestCaseSummary = (
|
export const fetchTestCaseSummary = (
|
||||||
filters?: DataQualityDashboardChartFilters
|
filters?: DataQualityDashboardChartFilters
|
||||||
) => {
|
) => {
|
||||||
const mustFilter = [];
|
const mustFilter = buildDataQualityDashboardFilters({ filters });
|
||||||
if (filters?.ownerFqn) {
|
|
||||||
mustFilter.push(buildMustEsFilterForOwner(filters.ownerFqn));
|
|
||||||
}
|
|
||||||
if (filters?.tags || filters?.tier) {
|
|
||||||
mustFilter.push(
|
|
||||||
buildMustEsFilterForTags([
|
|
||||||
...(filters?.tags ?? []),
|
|
||||||
...(filters?.tier ?? []),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.entityFQN) {
|
|
||||||
mustFilter.push({
|
|
||||||
term: {
|
|
||||||
entityFQN: filters.entityFQN,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDataQualityReport({
|
return getDataQualityReport({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
@ -147,18 +84,7 @@ export const fetchTestCaseSummary = (
|
|||||||
export const fetchTestCaseSummaryByDimension = (
|
export const fetchTestCaseSummaryByDimension = (
|
||||||
filters?: DataQualityDashboardChartFilters
|
filters?: DataQualityDashboardChartFilters
|
||||||
) => {
|
) => {
|
||||||
const mustFilter = [];
|
const mustFilter = buildDataQualityDashboardFilters({ filters });
|
||||||
if (filters?.ownerFqn) {
|
|
||||||
mustFilter.push(buildMustEsFilterForOwner(filters.ownerFqn));
|
|
||||||
}
|
|
||||||
if (filters?.tags || filters?.tier) {
|
|
||||||
mustFilter.push(
|
|
||||||
buildMustEsFilterForTags([
|
|
||||||
...(filters?.tags ?? []),
|
|
||||||
...(filters?.tier ?? []),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDataQualityReport({
|
return getDataQualityReport({
|
||||||
q: JSON.stringify({
|
q: JSON.stringify({
|
||||||
|
@ -32,7 +32,8 @@ import {
|
|||||||
TestDataType,
|
TestDataType,
|
||||||
TestDefinition,
|
TestDefinition,
|
||||||
} from '../../generated/tests/testDefinition';
|
} from '../../generated/tests/testDefinition';
|
||||||
import { ListTestCaseParamsBySearch } from '../../rest/testAPI';
|
import { DataQualityDashboardChartFilters } from '../../pages/DataQuality/DataQualityPage.interface';
|
||||||
|
import { ListTestCaseParamsBySearch, TestCaseType } from '../../rest/testAPI';
|
||||||
import { generateEntityLink } from '../TableUtils';
|
import { generateEntityLink } from '../TableUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,6 +219,115 @@ export const buildMustEsFilterForOwner = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildDataQualityDashboardFilters = (data: {
|
||||||
|
filters?: DataQualityDashboardChartFilters;
|
||||||
|
unhealthy?: boolean;
|
||||||
|
isTableApi?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { filters, unhealthy = false, isTableApi = false } = data;
|
||||||
|
const mustFilter = [];
|
||||||
|
|
||||||
|
if (unhealthy) {
|
||||||
|
mustFilter.push({
|
||||||
|
terms: {
|
||||||
|
'testCaseStatus.keyword': ['Failed', 'Aborted'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.ownerFqn) {
|
||||||
|
mustFilter.push(buildMustEsFilterForOwner(filters.ownerFqn));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.tags && isTableApi) {
|
||||||
|
mustFilter.push({
|
||||||
|
bool: {
|
||||||
|
should: filters.tags.map((tag) => ({
|
||||||
|
term: {
|
||||||
|
'tags.tagFQN': tag,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.tier && isTableApi) {
|
||||||
|
mustFilter.push({
|
||||||
|
bool: {
|
||||||
|
should: filters.tier.map((tag) => ({
|
||||||
|
term: {
|
||||||
|
'tier.tagFQN': tag,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((filters?.tags || filters?.tier) && !isTableApi) {
|
||||||
|
mustFilter.push(
|
||||||
|
buildMustEsFilterForTags([
|
||||||
|
...(filters?.tags ?? []),
|
||||||
|
...(filters?.tier ?? []),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.entityFQN) {
|
||||||
|
mustFilter.push({
|
||||||
|
term: {
|
||||||
|
[isTableApi ? 'fullyQualifiedName.keyword' : 'entityFQN']:
|
||||||
|
filters.entityFQN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.serviceName) {
|
||||||
|
mustFilter.push({
|
||||||
|
term: {
|
||||||
|
'service.name.keyword': filters.serviceName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.testPlatforms) {
|
||||||
|
mustFilter.push({
|
||||||
|
terms: {
|
||||||
|
testPlatforms: filters.testPlatforms,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.dataQualityDimension) {
|
||||||
|
mustFilter.push({
|
||||||
|
term: {
|
||||||
|
dataQualityDimension: filters.dataQualityDimension,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.testCaseStatus) {
|
||||||
|
mustFilter.push({
|
||||||
|
term: {
|
||||||
|
'testCaseResult.testCaseStatus': filters.testCaseStatus,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.testCaseType) {
|
||||||
|
if (filters.testCaseType === TestCaseType.table) {
|
||||||
|
mustFilter.push({
|
||||||
|
bool: { must_not: [{ regexp: { entityLink: '.*::columns::.*' } }] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.testCaseType === TestCaseType.column) {
|
||||||
|
mustFilter.push({ regexp: { entityLink: '.*::columns::.*' } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mustFilter;
|
||||||
|
};
|
||||||
|
|
||||||
export const getDimensionIcon = (dimension: DataQualityDimensions) => {
|
export const getDimensionIcon = (dimension: DataQualityDimensions) => {
|
||||||
switch (dimension) {
|
switch (dimension) {
|
||||||
case DataQualityDimensions.Accuracy:
|
case DataQualityDimensions.Accuracy:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user