Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public SearchListFilter(Include include) {
private static final String FIELD_OWNERS_ID = "owners.id";
private static final String FIELD_CREATED_BY = "createdBy";
private static final String FIELD_DOMAINS_FQN = "domains.fullyQualifiedName";
private static final String FIELD_DATA_PRODUCTS_FQN = "dataProducts.fullyQualifiedName";
private static final String FIELD_SERVICE_NAME = "service.name";
private static final String FIELD_TEST_CASE_STATUS = "testCaseResult.testCaseStatus";
private static final String FIELD_TEST_PLATFORMS = "testPlatforms";
Expand Down Expand Up @@ -217,6 +218,7 @@ private String getTestCaseCondition() {
String tier = getQueryParam("tier");
String serviceName = getQueryParam("serviceName");
String dataQualityDimension = getQueryParam("dataQualityDimension");
String dataProductFqn = getQueryParam("dataProductFqn");
String followedBy = getQueryParam("followedBy");
String columnName = getQueryParam("columnName");

Expand Down Expand Up @@ -277,6 +279,13 @@ private String getTestCaseCondition() {
conditions.add(
getDataQualityDimensionCondition(dataQualityDimension, "dataQualityDimension"));

if (dataProductFqn != null) {
conditions.add(
String.format(
"{\"term\": {\"%s\": \"%s\"}}",
FIELD_DATA_PRODUCTS_FQN, escapeDoubleQuotes(dataProductFqn)));
}

if (followedBy != null) {
conditions.add(
String.format("{\"term\": {\"%s\": \"%s\"}}", FIELD_FOLLOWERS_KEYWORD, followedBy));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ private void setParentRelationships(Map<String, Object> doc, TestCase testCase)
&& linkedTable.getCertification() != null) {
doc.put("certification", linkedTable.getCertification());
}

if (nullOrEmpty(testCase.getDataProducts())
&& linkedTable != null
&& !nullOrEmpty(linkedTable.getDataProducts())) {
doc.put(
Entity.FIELD_DATA_PRODUCTS, getEntitiesWithDisplayName(linkedTable.getDataProducts()));
}
}

private EntityInterface denormalizeTestSuiteParents(Map<String, Object> doc, TestCase testCase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ static Table addTestSuiteParentEntityRelations(
EntityReference testSuiteRef, Map<String, Object> doc) {
if (testSuiteRef.getType().equals(Entity.TABLE)) {
try {
Table table = Entity.getEntity(testSuiteRef, "domains,certification", Include.ALL);
Table table =
Entity.getEntity(testSuiteRef, "domains,certification,dataProducts", Include.ALL);
doc.put("table", table.getEntityReference());
doc.put("database", table.getDatabase());
doc.put("databaseSchema", table.getDatabaseSchema());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type TestCaseSearchParams = {
tags?: string;
serviceName?: string;
dataQualityDimension?: string;
dataProductFqn?: string;
};

export type DataQualityPageParams = TestCaseSearchParams & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Typography } from '@openmetadata/ui-core-components';
import { Divider, Skeleton, Space, Tooltip } from 'antd';
import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch';
import { first, isUndefined, last } from 'lodash';
import { first, isEmpty, isUndefined, last } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
Expand All @@ -39,6 +39,7 @@ import {
transitionIncident,
updateTestCaseIncidentById,
} from '../../../../rest/incidentManagerAPI';
import { updateTestCaseById } from '../../../../rest/testAPI';
import {
getColumnNameFromEntityLink,
getEntityName,
Expand All @@ -53,6 +54,7 @@ import { getTaskDetailPath as getNewTaskDetailPath } from '../../../../utils/Tas
import { showErrorToast } from '../../../../utils/ToastUtils';
import { useRequiredParams } from '../../../../utils/useRequiredParams';
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import { DomainLabel } from '../../../common/DomainLabel/DomainLabel.component';
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
import { ProfilerTabPath } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface';
import Severity from '../Severity/Severity.component';
Expand All @@ -71,7 +73,11 @@ const IncidentManagerPageHeader = ({
const [testCaseStatusData, setTestCaseStatusData] =
useState<TestCaseResolutionStatus>();
const [isLoading, setIsLoading] = useState(true);
const { testCase: testCaseData, testCasePermission } = useTestCaseStore();
const {
testCase: testCaseData,
testCasePermission,
setTestCase,
} = useTestCaseStore();

const { dimensionKey } = useRequiredParams<{
fqn: string;
Expand Down Expand Up @@ -223,6 +229,30 @@ const IncidentManagerPageHeader = ({
}
}, [testCaseData]);

const handleDomainUpdate = async (
selectedDomain: EntityReference | EntityReference[]
) => {
if (!testCaseData) {
return;
}

const domains = Array.isArray(selectedDomain)
? selectedDomain
: isEmpty(selectedDomain)
? []
: [selectedDomain];

const patch = compare(testCaseData, { ...testCaseData, domains });
if (patch.length && testCaseData.id) {
try {
const updated = await updateTestCaseById(testCaseData.id, patch);
setTestCase(updated);
} catch (error) {
showErrorToast(error as AxiosError);
}
}
};

const { hasEditStatusPermission, hasEditOwnerPermission } = useMemo(() => {
return isVersionPage
? {
Expand Down Expand Up @@ -358,6 +388,19 @@ const IncidentManagerPageHeader = ({
owners={testCaseData?.owners ?? ownerRef}
onUpdate={onOwnerUpdate}
/>
<Divider className="self-center m-x-sm" type="vertical" />
<DomainLabel
headerLayout
showDashPlaceholder
domains={testCaseData?.domains}
entityFqn={testCaseData?.fullyQualifiedName ?? ''}
entityId={testCaseData?.id ?? ''}
entityType={EntityType.TEST_CASE}
hasPermission={!isVersionPage && Boolean(testCasePermission?.EditAll)}
multiple={false}
textClassName="render-domain-lebel-style"
onUpdate={handleDomainUpdate}
/>
{!isVersionPage && statusDetails}
{tableFqn && (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useParams } from 'react-router-dom';
import { ReactComponent as StarIcon } from '../../../../assets/svg/ic-suggestions.svg';
import { EntityField } from '../../../../constants/Feeds.constants';
import { TagSource } from '../../../../generated/api/domains/createDataProduct';
import { DataProduct } from '../../../../generated/entity/domains/dataProduct';
import { Operation } from '../../../../generated/entity/policies/policy';
import {
ChangeDescription,
Expand Down Expand Up @@ -57,6 +58,7 @@ import DescriptionV1 from '../../../common/EntityDescription/DescriptionV1';
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
import TestSummary from '../../../Database/Profiler/TestSummary/TestSummary';
import SchemaEditor from '../../../Database/SchemaEditor/SchemaEditor';
import DataProductsContainer from '../../../DataProducts/DataProductsContainer/DataProductsContainer.component';
import TagsContainerV2 from '../../../Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from '../../../Tag/TagsViewer/TagsViewer.interface';
import EditTestCaseModal from '../../AddDataQualityTest/EditTestCaseModal';
Expand Down Expand Up @@ -204,6 +206,37 @@ const TestCaseResultTab = () => {
}
};

const handleDataProductsSave = useCallback(
async (dataProducts: DataProduct[]) => {
if (!testCaseData) {
return;
}

const updatedDataProducts = dataProducts.map((dp) => ({
id: dp.id ?? '',
type: 'dataProduct',
name: dp.name,
fullyQualifiedName: dp.fullyQualifiedName,
displayName: dp.displayName,
}));

const patch = compare(testCaseData, {
...testCaseData,
dataProducts: updatedDataProducts,
});

if (patch.length) {
try {
const res = await updateTestCaseById(testCaseData.id ?? '', patch);
setTestCase(res);
} catch (error) {
showErrorToast(error as AxiosError);
}
}
},
[testCaseData, setTestCase]
);

const handleDescriptionChange = useCallback(
async (description: string) => {
if (testCaseData) {
Expand Down Expand Up @@ -589,6 +622,16 @@ const TestCaseResultTab = () => {
onSelectionChange={handleTagSelection}
/>
</div>
<div className="tw:w-full">
<DataProductsContainer
multiple
newLook
activeDomains={testCaseData?.domains ?? []}
dataProducts={testCaseData?.dataProducts ?? []}
hasPermission={!isVersionPage && (hasEditPermission ?? false)}
onSave={handleDataProductsSave}
/>
</div>
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ jest.mock('../../../Database/SchemaEditor/SchemaEditor', () => {
jest.mock('../../../Database/Profiler/TestSummary/TestSummary', () => {
return jest.fn().mockImplementation(() => <div>TestSummary</div>);
});
jest.mock(
'../../../DataProducts/DataProductsContainer/DataProductsContainer.component',
() => {
return jest.fn().mockImplementation(() => <div>DataProductsContainer</div>);
}
);
jest.mock('../../AddDataQualityTest/EditTestCaseModal', () => {
return jest.fn().mockImplementation(({ onUpdate, testCase, onCancel }) => (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ export const TestCases = () => {
const [tagOptions, setTagOptions] = useState<DefaultOptionType[]>([]);
const [tierOptions, setTierOptions] = useState<DefaultOptionType[]>([]);
const [serviceOptions, setServiceOptions] = useState<DefaultOptionType[]>([]);
const [dataProductOptions, setDataProductOptions] = useState<
DefaultOptionType[]
>([]);
const [sortOptions, setSortOptions] =
useState<ListTestCaseParamsBySearch>(DEFAULT_SORT_ORDER);

Expand Down Expand Up @@ -393,6 +396,44 @@ export const TestCases = () => {
}
};

const fetchDataProductOptions = async (search = WILD_CARD_CHAR) => {
setIsOptionsLoading(true);
try {
const response = await searchQuery({
query: search === WILD_CARD_CHAR ? search : `*${search}*`,
pageNumber: 1,
pageSize: PAGE_SIZE_BASE,
searchIndex: SearchIndex.DATA_PRODUCT,
fetchSource: true,
includeFields: ['name', 'fullyQualifiedName', 'displayName'],
});

const options = response.hits.hits.map((hit) => {
return {
label: (
<Space
data-testid={hit._source.fullyQualifiedName}
direction="vertical"
size={0}>
<Typography.Text className="text-xs text-grey-muted">
{hit._source.fullyQualifiedName}
</Typography.Text>
<Typography.Text className="text-sm">
{getEntityName(hit._source)}
</Typography.Text>
</Space>
),
value: hit._source.fullyQualifiedName,
};
});
setDataProductOptions(options);
} catch {
setDataProductOptions([]);
} finally {
setIsOptionsLoading(false);
}
};

const getInitialOptions = (key: string, isLengthCheck = false) => {
switch (key) {
case TEST_CASE_FILTERS.tier:
Expand All @@ -412,6 +453,12 @@ export const TestCases = () => {

break;

case TEST_CASE_FILTERS.dataProduct:
(isEmpty(dataProductOptions) || !isLengthCheck) &&
fetchDataProductOptions();

break;

default:
break;
}
Expand Down Expand Up @@ -512,6 +559,11 @@ export const TestCases = () => {
[fetchServiceOptions]
);

const debounceFetchDataProductOptions = useCallback(
debounce(fetchDataProductOptions, 1000),
[fetchDataProductOptions]
);

const getTestCases = () => {
if (!isEmpty(params) || !isEmpty(selectedFilter)) {
const updatedValue = uniq([...selectedFilter, ...Object.keys(params)]);
Expand Down Expand Up @@ -723,6 +775,23 @@ export const TestCases = () => {
/>
</Form.Item>
)}
{selectedFilter.includes(TEST_CASE_FILTERS.dataProduct) && (
<Form.Item
className="m-0 w-80"
label={t('label.data-product-plural')}
name="dataProductFqn">
<Select
allowClear
showSearch
data-testid="data-product-select-filter"
getPopupContainer={getPopupContainer}
loading={isOptionsLoading}
options={dataProductOptions}
placeholder={t('label.data-product-plural')}
onSearch={debounceFetchDataProductOptions}
/>
</Form.Item>
)}
</Space>
</Form>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export const TEST_CASE_FILTERS: Record<string, keyof TestCaseSearchParams> = {
tags: 'tags',
service: 'serviceName',
dimension: 'dataQualityDimension',
dataProduct: 'dataProductFqn',
};

export const TEST_CASE_FILTERS_LABELS: Record<
Expand All @@ -404,6 +405,7 @@ export const TEST_CASE_FILTERS_LABELS: Record<
tags: t('label.tag-plural'),
service: t('label.service'),
dimension: t('label.dimension'),
dataProduct: t('label.data-product-plural'),
};

export const TEST_CASE_PLATFORM_OPTION = values(TestPlatform).map((value) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ describe('TestCaseClassBase', () => {
'owners',
'incidentId',
'tags',
'dataProducts',
'domains',
'inspectionQuery',
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ class TestCaseClassBase {
TabSpecificField.OWNERS,
TabSpecificField.INCIDENT_ID,
TabSpecificField.TAGS,
TabSpecificField.DATA_PRODUCTS,
TabSpecificField.DOMAINS,
'inspectionQuery',
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export type ListTestCaseParamsBySearch = ListTestCaseParams & {
serviceName?: string;
dataQualityDimension?: string;
followedBy?: string;
dataProductFqn?: string;
};

export type ListTestDefinitionsParams = ListParams & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class DataProductClassBase {
EntityTabs.INPUT_OUTPUT_PORTS,
EntityTabs.ASSETS,
EntityTabs.CONTRACT,
EntityTabs.DATA_OBSERVABILITY,
EntityTabs.CUSTOM_PROPERTIES,
].map((tab: EntityTabs) => ({
id: tab,
Expand Down
Loading
Loading