Multiple data contract tab and form and detail page improvement (#22700)

* multiple data contract tab and form and detail page improvement

* pending localization keys

* fix the quality icon and semantic listing fix

* fix minor issue

* fix the schema data not going in api and also fix some styling around add sementic button
This commit is contained in:
Ashish Gupta 2025-08-01 23:32:46 +05:30 committed by GitHub
parent a4b0833633
commit d3eb47983d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 425 additions and 344 deletions

View File

@ -1,20 +1 @@
<svg width="55" height="55" viewBox="0 0 55 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="55" height="38" rx="4" fill="#FBF2DB"/>
<path d="M50.0342 51.2431H3.96581C3.43237 51.2431 3 50.8107 3 50.2772V49.0869C3 48.5535 3.43237 48.1211 3.96581 48.1211H50.0342C50.5676 48.1211 51 48.5535 51 49.0869V50.2772C51 50.8107 50.5676 51.2431 50.0342 51.2431Z" fill="#3D4FC3"/>
<path d="M50.0341 51.2431H48.6584V48.1211H50.0341C50.5676 48.1211 50.9999 48.5535 50.9999 49.0869V50.2772C50.9999 50.8107 50.5676 51.2431 50.0341 51.2431Z" fill="#3342AA"/>
<path d="M10.4245 35.0469H6.11217C5.57874 35.0469 5.14636 35.4793 5.14636 36.0127V48.12H11.3903V36.0127C11.3903 35.4793 10.9579 35.0469 10.4245 35.0469Z" fill="#9DC6FB"/>
<path d="M10.4245 35.0469H9.24402V48.12H11.3903V36.0127C11.3903 35.4793 10.958 35.0469 10.4245 35.0469Z" fill="#80B4FB"/>
<path d="M22.9122 22.3672H18.5998C18.0664 22.3672 17.634 22.7996 17.634 23.333V48.1233H23.878V23.333C23.878 22.7996 23.4456 22.3672 22.9122 22.3672Z" fill="#AC80DD"/>
<path d="M22.9122 22.3672H21.7317V48.1233H23.878V23.333C23.878 22.7996 23.4456 22.3672 22.9122 22.3672Z" fill="#9D66D5"/>
<path d="M35.4001 29.1953H31.0878C30.5543 29.1953 30.1219 29.6277 30.1219 30.1611V48.1221H36.3659V30.1611C36.3658 29.6277 35.9334 29.1953 35.4001 29.1953Z" fill="#E490D0"/>
<path d="M35.4001 29.1953H34.2196V48.1221H36.3659V30.1611C36.3658 29.6277 35.9334 29.1953 35.4001 29.1953Z" fill="#DF73C1"/>
<path d="M47.888 17.4883H43.5757C43.0422 17.4883 42.6099 17.9207 42.6099 18.4541V48.1224H48.8538V18.4541C48.8538 17.9207 48.4213 17.4883 47.888 17.4883Z" fill="#B3E59F"/>
<path d="M47.8879 17.4883H46.7074V48.1224H48.8537V18.4541C48.8537 17.9207 48.4212 17.4883 47.8879 17.4883Z" fill="#95D6A4"/>
<path d="M33.2433 20.5556C33.1258 20.5556 33.0068 20.527 32.8964 20.4666L20.4086 13.6374C20.0576 13.4455 19.9287 13.0053 20.1207 12.6543C20.3126 12.3033 20.7526 12.1742 21.1038 12.3664L33.5916 19.1956C33.9425 19.3876 34.0715 19.8277 33.8795 20.1787C33.7479 20.4194 33.4995 20.5556 33.2433 20.5556Z" fill="#FFC250"/>
<path d="M33.2441 20.5505C33.0509 20.5505 32.8581 20.4737 32.7155 20.3216C32.4418 20.0297 32.4566 19.5713 32.7485 19.2977L45.2363 7.59044C45.5282 7.31678 45.9865 7.33169 46.2602 7.62344C46.5338 7.91538 46.519 8.37372 46.2272 8.64738L33.7393 20.3546C33.5995 20.4856 33.4216 20.5505 33.2441 20.5505Z" fill="#FFC250"/>
<path d="M8.26839 26.4089C8.08492 26.4089 7.90136 26.3397 7.76017 26.2008C7.47517 25.9201 7.47161 25.4615 7.7522 25.1764L20.2401 12.4935C20.5207 12.2083 20.9793 12.205 21.2645 12.4855C21.5495 12.7662 21.553 13.2248 21.2725 13.5099L8.78458 26.1927C8.64283 26.3368 8.45561 26.4089 8.26839 26.4089Z" fill="#FFC250"/>
<path d="M8.26833 28.8025C9.99255 28.8025 11.3903 27.4048 11.3903 25.6806C11.3903 23.9563 9.99255 22.5586 8.26833 22.5586C6.54412 22.5586 5.14636 23.9563 5.14636 25.6806C5.14636 27.4048 6.54412 28.8025 8.26833 28.8025Z" fill="#FFD064"/>
<path d="M20.7561 16.1228C22.4803 16.1228 23.8781 14.7251 23.8781 13.0009C23.8781 11.2767 22.4803 9.87891 20.7561 9.87891C19.0319 9.87891 17.6342 11.2767 17.6342 13.0009C17.6342 14.7251 19.0319 16.1228 20.7561 16.1228Z" fill="#FFD064"/>
<path d="M33.2439 22.951C34.9681 22.951 36.3659 21.5532 36.3659 19.829C36.3659 18.1048 34.9681 16.707 33.2439 16.707C31.5197 16.707 30.1219 18.1048 30.1219 19.829C30.1219 21.5532 31.5197 22.951 33.2439 22.951Z" fill="#FFD064"/>
<path d="M45.7318 11.2439C47.456 11.2439 48.8538 9.84618 48.8538 8.12197C48.8538 6.39775 47.456 5 45.7318 5C44.0076 5 42.6099 6.39775 42.6099 8.12197C42.6099 9.84618 44.0076 11.2439 45.7318 11.2439Z" fill="#FFD064"/>
</svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="linear-gradient" gradientUnits="userSpaceOnUse" x1="449.13" x2="116.43" y1="252.97" y2="252.97"><stop offset="0" stop-color="#cacfdd"/><stop offset="1" stop-color="#eef0f1"/></linearGradient><linearGradient id="linear-gradient-2" x1="425.65" x2="74.52" xlink:href="#linear-gradient" y1="284.2" y2="284.2"/><linearGradient id="linear-gradient-3" gradientUnits="userSpaceOnUse" x1="110.93" x2="165.31" y1="450.67" y2="450.67"><stop offset="0" stop-color="#a9afc4"/><stop offset="1" stop-color="#d4d8e5"/></linearGradient><linearGradient id="linear-gradient-4" x1="110.93" x2="188.22" xlink:href="#linear-gradient" y1="208.03" y2="208.03"/><g isolation="isolate"><g id="File_Document" data-name="File Document"><path d="m143 71.29a12.68 12.68 0 0 1 -12.5-10.7c-1.78-11.19 10.92-18.84 20-12.25a12.69 12.69 0 0 1 -7.49 23zm-6.58-11.64a6.69 6.69 0 1 0 10.58-6.46 6.68 6.68 0 0 0 -10.55 6.46z" fill="#5e6175"/><path d="m101.91 107.5a9 9 0 1 1 5.31-16.29c6.98 5.07 3.46 16.29-5.31 16.29zm-2.91-8.5a3 3 0 1 0 4.74-2.9 3 3 0 0 0 -4.74 2.9z" fill="#5e6175"/><path d="m429.14 55.31h19.07v6h-19.07z" fill="#5e6175" transform="matrix(.707 -.707 .707 .707 87.22 327.23)"/><path d="m413.21 36h6v19.06h-6z" fill="#5e6175"/><path d="m442.44 76.62h19.06v6h-19.06z" fill="#5e6175"/><path d="m122.43 471.52-4.24-4.25 4.24-4.24 4.25 4.24zm-11.31-11.32-4.24-4.2 4.24-4.24 4.24 4.24zm-11.31-11.31c-2.6-2.6-3.33-3.29-4.34-4.46l4.54-3.92c.89 1 1.39 1.49 4 4.14zm-10-13.89a32.5 32.5 0 0 1 -1.76-6.37l5.92-1a26.61 26.61 0 0 0 1.43 5.19zm3.79-16.8h-6v-6h6zm0-16h-6v-6h6zm0-16h-6v-6h6zm0-16h-6v-6h6z" fill="#a9afc4"/><path d="m419.85 76.71v336.83h-196.47l-49.99-47.38v-289.45z" fill="#f5f5f5"/><path d="m404.16 92.4v321.14h-180.78l-49.99-47.37v-273.77z" fill="url(#linear-gradient)" mix-blend-mode="multiply"/><path d="m422.85 416.54h-200.67l-51.79-49.09v-293.74h252.46zm-198.27-6h192.27v-330.83h-240.46v285.16z" fill="#5e6175"/><path d="m388.62 107.94v336.83h-196.47l-49.99-47.38v-289.45z" fill="#f5f5f5"/><path d="m372.93 123.63v321.13h-180.78l-49.99-47.37v-273.76z" fill="url(#linear-gradient-2)" mix-blend-mode="multiply"/><path d="m391.62 447.77h-200.62l-51.79-49.09v-293.74h252.41zm-198.27-6h192.27v-330.83h-240.46v285.16z" fill="#5e6175"/><path d="m357.39 139.17v336.83h-196.47l-49.99-47.38v-289.45z" fill="#f5f5f5"/><g fill="#dee1ec"><path d="m193.27 191.12h30.82v13.8h-30.82z"/><path d="m244.85 191.12h75.22v13.8h-75.22z"/><path d="m184.71 229.08h135.36v13.8h-135.36z"/><path d="m152.75 267.05h112.64v13.8h-112.64z"/><path d="m286.19 267.05h33.87v13.8h-33.87z"/><path d="m152.75 305.01h167.31v13.8h-167.31z"/><path d="m152.75 342.98h48.27v13.8h-48.27z"/><path d="m221.2 342.98h98.87v13.8h-98.87z"/><path d="m152.75 380.94h167.31v13.8h-167.31z"/><path d="m261.03 418.91h59.03v13.8h-59.03z"/></g><path d="m165.31 425.29v50.71h-4.13c-.39 0 3.61 3.67-50.25-47.38v-3.33z" fill="url(#linear-gradient-3)"/><path d="m160.93 479.1c-1.55 0 .8 1.81-53-49.19v-7.62h60.38v56.71h-6.6a3.21 3.21 0 0 1 -.78.1zm1.21-6.1c.27 0 .17 3.45.17-44.71h-47.37c34.23 32.44 44.48 42.21 47.2 44.71z" fill="#5e6175"/><path d="m188.22 200.26a76.72 76.72 0 0 1 -76.63 76.63h-.66v-137.72h46.86a76.5 76.5 0 0 1 30.43 61.09z" fill="url(#linear-gradient-4)" mix-blend-mode="multiply"/><path d="m360.39 479h-200.67l-51.79-49.09v-293.74h252.46zm-198.28-6h192.28v-330.83h-240.46v285.16z" fill="#5e6175"/><circle cx="111.59" cy="200.26" fill="#f5f5f5" r="61.09"/><path d="m111.59 264.35a64.09 64.09 0 1 1 64.09-64.09 64.16 64.16 0 0 1 -64.09 64.09zm0-122.18a58.09 58.09 0 1 0 58.09 58.09 58.16 58.16 0 0 0 -58.09-58.09z" fill="#5e6175"/><path d="m100.97 229.12-25.66-25.67 8.86-8.87 16.8 16.8 38.04-38.03 8.87 8.87z" fill="#e1830e"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1V13M7 1H11.6667C12.0203 1 12.3594 1.14048 12.6095 1.39052C12.8595 1.64057 13 1.97971 13 2.33333V11.6667C13 12.0203 12.8595 12.3594 12.6095 12.6095C12.3594 12.8595 12.0203 13 11.6667 13H7V1ZM7 1H2.33333C1.97971 1 1.64057 1.14048 1.39052 1.39052C1.14048 1.64057 1 1.97971 1 2.33333V11.6667C1 12.0203 1.14048 12.3594 1.39052 12.6095C1.64057 12.8595 1.97971 13 2.33333 13H7V1Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 562 B

View File

@ -22,6 +22,7 @@ import {
Typography,
} from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as ContractIcon } from '../../../assets/svg/ic-contract.svg';
@ -34,7 +35,10 @@ import {
} from '../../../constants/DataContract.constants';
import { CSMode } from '../../../enums/codemirror.enum';
import { EntityType } from '../../../enums/entity.enum';
import { DataContract } from '../../../generated/entity/data/dataContract';
import {
ContractStatus,
DataContract,
} from '../../../generated/entity/data/dataContract';
import { Table } from '../../../generated/entity/data/table';
import { createContract, updateContract } from '../../../rest/contractAPI';
import { getUpdatedContractDetails } from '../../../utils/DataContract/DataContractUtils';
@ -81,15 +85,24 @@ const AddDataContract: React.FC<{
const handleSave = useCallback(async () => {
setIsSubmitting(true);
const validSemantics = formValues.semantics?.filter(
(semantic) => !isEmpty(semantic.name) && !isEmpty(semantic.rule)
);
try {
await (contract
? updateContract(getUpdatedContractDetails(contract, formValues))
? updateContract({
...getUpdatedContractDetails(contract, formValues),
semantics: validSemantics,
})
: createContract({
...formValues,
entity: {
id: table.id,
type: EntityType.TABLE,
},
semantics: validSemantics,
status: ContractStatus.Active,
}));
showSuccessToast(t('message.data-contract-saved-successfully'));
@ -101,18 +114,20 @@ const AddDataContract: React.FC<{
}
}, [contract, formValues]);
const onNext = useCallback(
async (data: Partial<DataContract>) => {
const onFormChange = useCallback(
(data: Partial<DataContract>) => {
setFormValues((prev) => ({ ...prev, ...data }));
setActiveTab((prev) => (Number(prev) + 1).toString());
},
[activeTab, handleSave]
[setFormValues]
);
const onNext = useCallback(async () => {
setActiveTab((prev) => (Number(prev) + 1).toString());
}, [setActiveTab]);
const onPrev = useCallback(() => {
setActiveTab((prev) => (Number(prev) - 1).toString());
}, [activeTab]);
}, [setActiveTab]);
const items = useMemo(
() => [
@ -126,8 +141,9 @@ const AddDataContract: React.FC<{
key: EDataContractTab.CONTRACT_DETAIL.toString(),
children: (
<ContractDetailFormTab
initialValues={formValues}
initialValues={contract}
nextLabel={t('label.schema')}
onChange={onFormChange}
onNext={onNext}
/>
),
@ -145,8 +161,9 @@ const AddDataContract: React.FC<{
nextLabel={t('label.semantic-plural')}
prevLabel={t('label.contract-detail-plural')}
selectedSchema={
formValues.schema?.map((column) => column.name) || []
contract?.schema?.map((column) => column.name) || []
}
onChange={onFormChange}
onNext={onNext}
onPrev={onPrev}
/>
@ -162,9 +179,10 @@ const AddDataContract: React.FC<{
key: EDataContractTab.SEMANTICS.toString(),
children: (
<ContractSemanticFormTab
initialValues={formValues}
initialValues={contract}
nextLabel={t('label.quality')}
prevLabel={t('label.schema')}
onChange={onFormChange}
onNext={onNext}
onPrev={onPrev}
/>
@ -182,19 +200,17 @@ const AddDataContract: React.FC<{
<ContractQualityFormTab
prevLabel={t('label.semantic-plural')}
selectedQuality={
formValues.qualityExpectations?.map(
contract?.qualityExpectations?.map(
(quality) => quality.id ?? ''
) ?? []
}
onChange={onFormChange}
onPrev={onPrev}
onUpdate={(qualityExpectations) =>
setFormValues((prev) => ({ ...prev, qualityExpectations }))
}
/>
),
},
],
[t, onNext, onPrev]
[contract, onFormChange, onNext, onPrev]
);
const handleModeChange = useCallback((e: RadioChangeEvent) => {

View File

@ -26,9 +26,10 @@ import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
export const ContractDetailFormTab: React.FC<{
initialValues?: Partial<DataContract>;
onNext: (formData: Partial<DataContract>) => Promise<void>;
onNext: () => void;
onChange: (formData: Partial<DataContract>) => void;
nextLabel?: string;
}> = ({ initialValues, onNext, nextLabel }) => {
}> = ({ initialValues, onNext, nextLabel, onChange }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
@ -79,10 +80,6 @@ export const ContractDetailFormTab: React.FC<{
},
];
const handleSubmit = () => {
form.submit();
};
useEffect(() => {
if (initialValues) {
form.setFieldsValue({
@ -110,7 +107,7 @@ export const ContractDetailFormTab: React.FC<{
className="contract-detail-form"
form={form}
layout="vertical"
onFinish={onNext}>
onValuesChange={onChange}>
{generateFormFields(fields)}
{owners?.length > 0 && <OwnerLabel owners={owners} />}
@ -118,7 +115,7 @@ export const ContractDetailFormTab: React.FC<{
</div>
</Card>
<div className="d-flex justify-end m-t-md">
<Button htmlType="submit" type="primary" onClick={handleSubmit}>
<Button htmlType="submit" type="primary" onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
</Button>

View File

@ -22,11 +22,13 @@ import { ReactComponent as FlagIcon } from '../../../assets/svg/flag.svg';
import { ReactComponent as CheckIcon } from '../../../assets/svg/ic-check-circle.svg';
import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-trash.svg';
import { isEmpty } from 'lodash';
import { Cell, Pie, PieChart } from 'recharts';
import {
ICON_DIMENSION,
NO_DATA_PLACEHOLDER,
} from '../../../constants/constants';
import { TEST_CASE_STATUS_ICON } from '../../../constants/DataQuality.constants';
import { DEFAULT_SORT_ORDER } from '../../../constants/profiler.constant';
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { EntityType } from '../../../enums/entity.enum';
@ -46,7 +48,6 @@ import {
getContractStatusType,
getTestCaseSummaryChartItems,
} from '../../../utils/DataContract/DataContractUtils';
import { getTestCaseStatusIcon } from '../../../utils/DataQuality/DataQualityUtils';
import { getRelativeTime } from '../../../utils/date-time/DateTimeUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import { pruneEmptyChildren } from '../../../utils/TableUtils';
@ -55,6 +56,7 @@ import DescriptionV1 from '../../common/EntityDescription/DescriptionV1';
import ErrorPlaceHolderNew from '../../common/ErrorWithPlaceholder/ErrorPlaceHolderNew';
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
import RichTextEditorPreviewerNew from '../../common/RichTextEditor/RichTextEditorPreviewNew';
import { StatusType } from '../../common/StatusBadge/StatusBadge.interface';
import StatusBadgeV2 from '../../common/StatusBadge/StatusBadgeV2.component';
import Table from '../../common/Table/Table';
@ -169,6 +171,18 @@ const ContractDetail: React.FC<{
return getTestCaseSummaryChartItems(testCaseSummary);
}, [testCaseSummary]);
const getTestCaseStatusIcon = (record: TestCase) => (
<Icon
className="test-status-icon"
component={
TEST_CASE_STATUS_ICON[
(record?.testCaseResult?.testCaseStatus ??
'Queued') as keyof typeof TEST_CASE_STATUS_ICON
]
}
/>
);
const handleRunNow = () => {
if (contract?.id) {
setValidateLoading(true);
@ -183,13 +197,13 @@ const ContractDetail: React.FC<{
};
useEffect(() => {
if (contract?.id && !contract?.latestResult?.resultId) {
if (contract?.id && contract?.latestResult?.resultId) {
fetchLatestContractResults();
}
if (contract?.testSuite?.id) {
fetchTestCaseSummary();
fetchTestCases();
}
if (contract?.testSuite?.id) {
fetchTestCaseSummary();
fetchTestCases();
}
}, [contract]);
@ -226,7 +240,7 @@ const ContractDetail: React.FC<{
</Typography.Text>
<Typography.Text className="contract-time">
{t('message.created-time-ago-by', {
{t('message.modified-time-ago-by', {
time: getRelativeTime(contract.updatedAt),
by: contract.updatedBy,
})}
@ -235,7 +249,7 @@ const ContractDetail: React.FC<{
<div className="d-flex items-center gap-2 m-t-xs">
<StatusBadgeV2
externalIcon={FlagIcon}
label={t('label.active')}
label={contract.status ?? t('label.active')}
status={StatusType.Success}
/>
@ -250,16 +264,18 @@ const ContractDetail: React.FC<{
</Col>
<Col>
<div className="contract-action-container">
<div className="contract-owner-label-container">
<Typography.Text>{t('label.owner-plural')}</Typography.Text>
<OwnerLabel
avatarSize={24}
isCompactView={false}
maxVisibleOwners={5}
owners={contract.owners}
showLabel={false}
/>
</div>
{!isEmpty(contract.owners) && (
<div className="contract-owner-label-container">
<Typography.Text>{t('label.owner-plural')}</Typography.Text>
<OwnerLabel
avatarSize={24}
isCompactView={false}
maxVisibleOwners={5}
owners={contract.owners}
showLabel={false}
/>
</div>
)}
<Button
className="contract-run-now-button"
@ -431,15 +447,17 @@ const ContractDetail: React.FC<{
<Typography.Text className="card-subtitle">
{t('label.custom-integrity-rules')}
</Typography.Text>
{(contract?.semantics ?? []).map((item) => (
<div className="rule-item">
<Icon className="rule-icon" component={CheckIcon} />
<span className="rule-name">{item.name}</span>{' '}
<span className="rule-description">
{item.description}
</span>
</div>
))}
<div className="rule-item-container">
{(contract?.semantics ?? []).map((item) => (
<div className="rule-item">
<Icon className="rule-icon" component={CheckIcon} />
<span className="rule-name">{item.name}</span>{' '}
<span className="rule-description">
{item.description}
</span>
</div>
))}
</div>
</div>
</ExpandableCard>
</Col>
@ -504,21 +522,25 @@ const ContractDetail: React.FC<{
))}
</div>
<Space direction="vertical">
{testCaseResult.map((item) => (
<div
className="data-quality-item d-flex items-center"
key={item.id}>
{getTestCaseStatusIcon(item)}
<div className="data-quality-item-content">
<Typography.Text className="data-quality-item-name">
{item.name}
</Typography.Text>
<Typography.Text className="data-quality-item-description">
{item.description}
</Typography.Text>
{testCaseResult.map((item) => {
return (
<div
className="data-quality-item d-flex items-center"
key={item.id}>
{getTestCaseStatusIcon(item)}
<div className="data-quality-item-content">
<Typography.Text className="data-quality-item-name">
{item.name}
</Typography.Text>
<Typography.Text className="data-quality-item-description">
<RichTextEditorPreviewerNew
markdown={item.description ?? ''}
/>
</Typography.Text>
</div>
</div>
</div>
))}
);
})}
</Space>
</div>
)}

View File

@ -156,25 +156,31 @@
}
}
.rule-item {
display: inline-flex;
align-items: center;
gap: 4px;
.rule-item-container {
display: flex;
flex-direction: column;
gap: 8px;
.rule-icon {
font-size: 20px;
margin-right: 4px;
}
.rule-item {
display: inline-flex;
align-items: center;
gap: 4px;
.rule-name {
font-size: 14px;
font-weight: 500;
color: @grey-900;
}
.rule-icon {
font-size: 20px;
margin-right: 4px;
}
.rule-description {
font-size: 12px;
font-weight: 300;
.rule-name {
font-size: 14px;
font-weight: 500;
color: @grey-900;
}
.rule-description {
font-size: 12px;
font-weight: 300;
}
}
}

View File

@ -18,21 +18,27 @@ import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { EntityType } from '../../../enums/entity.enum';
import { Table as TableType } from '../../../generated/entity/data/table';
import { TestCase } from '../../../generated/tests/testCase';
import { TestCase, TestCaseStatus } from '../../../generated/tests/testCase';
import { EntityReference } from '../../../generated/type/entityReference';
import { usePaging } from '../../../hooks/paging/usePaging';
import { listTestCases, TestCaseType } from '../../../rest/testAPI';
import { showErrorToast } from '../../../utils/ToastUtils';
import Table from '../../common/Table/Table';
import { ColumnsType } from 'antd/lib/table';
import { toLower } from 'lodash';
import { DataContract } from '../../../generated/entity/data/dataContract';
import StatusBadge from '../../common/StatusBadge/StatusBadge.component';
import { StatusType } from '../../common/StatusBadge/StatusBadge.interface';
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
export const ContractQualityFormTab: React.FC<{
selectedQuality: string[];
onUpdate: (data: EntityReference[]) => void;
onChange: (data: Partial<DataContract>) => void;
onPrev: () => void;
prevLabel?: string;
}> = ({ selectedQuality, onUpdate, onPrev, prevLabel }) => {
const [testType, setTestType] = useState<'table' | 'column'>('table');
}> = ({ selectedQuality, onChange, onPrev, prevLabel }) => {
const [testType, setTestType] = useState<TestCaseType>(TestCaseType.table);
const [allTestCases, setAllTestCases] = useState<TestCase[]>([]);
const { data: table } = useGenericContext<TableType>();
const { pageSize, handlePagingChange } = usePaging();
@ -42,7 +48,7 @@ export const ContractQualityFormTab: React.FC<{
);
const { t } = useTranslation();
const columns = useMemo(
const columns: ColumnsType<TestCase> = useMemo(
() => [
{
title: t('label.name'),
@ -50,10 +56,20 @@ export const ContractQualityFormTab: React.FC<{
},
{
title: t('label.status'),
dataIndex: 'status',
dataIndex: 'testCaseStatus',
key: 'testCaseStatus',
render: (testCaseStatus: TestCaseStatus) => {
return (
<StatusBadge
dataTestId={`status-badge-${testCaseStatus}`}
label={testCaseStatus}
status={toLower(testCaseStatus) as StatusType}
/>
);
},
},
],
[t]
[]
);
const fetchAllTests = async () => {
@ -64,8 +80,7 @@ export const ContractQualityFormTab: React.FC<{
try {
const { data, paging } = await listTestCases({
entityFQN: table.fullyQualifiedName,
testCaseType:
testType === 'table' ? TestCaseType.table : TestCaseType.column,
testCaseType: testType,
limit: pageSize,
});
@ -80,7 +95,7 @@ export const ContractQualityFormTab: React.FC<{
useEffect(() => {
fetchAllTests();
}, []);
}, [testType]);
const handleSelection = (selectedRowKeys: string[]) => {
const qualityExpectations = selectedRowKeys.map((id) => {
@ -94,7 +109,9 @@ export const ContractQualityFormTab: React.FC<{
} as EntityReference;
});
onUpdate(qualityExpectations);
onChange({
qualityExpectations,
});
};
return (
@ -113,8 +130,12 @@ export const ContractQualityFormTab: React.FC<{
className="m-b-sm"
value={testType}
onChange={(e) => setTestType(e.target.value)}>
<Radio.Button value="table">{t('label.table')}</Radio.Button>
<Radio.Button value="column">{t('label.column')}</Radio.Button>
<Radio.Button value={TestCaseType.table}>
{t('label.table')}
</Radio.Button>
<Radio.Button value={TestCaseType.column}>
{t('label.column')}
</Radio.Button>
</Radio.Group>
<Table
columns={columns}

View File

@ -14,7 +14,7 @@ import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import { Button, Card, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { isEmpty } from 'lodash';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Key, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { NO_DATA_PLACEHOLDER } from '../../../constants/constants';
import { TABLE_COLUMNS_KEYS } from '../../../constants/TableKeys.constants';
@ -36,25 +36,34 @@ import TableTags from '../../Database/TableTags/TableTags.component';
export const ContractSchemaFormTab: React.FC<{
selectedSchema: string[];
onNext: (data: Partial<DataContract>) => void;
onNext: () => void;
onChange: (data: Partial<DataContract>) => void;
onPrev: () => void;
nextLabel?: string;
prevLabel?: string;
}> = ({ selectedSchema, onNext, onPrev, nextLabel, prevLabel }) => {
}> = ({ selectedSchema, onNext, onChange, onPrev, nextLabel, prevLabel }) => {
const { t } = useTranslation();
const { fqn } = useFqn();
const [schema, setSchema] = useState<Column[]>([]);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const [selectedKeys, setSelectedKeys] = useState<string[]>(selectedSchema);
const handleChangeTable = useCallback(
(selectedRowKeys: Key[]) => {
setSelectedKeys(selectedRowKeys as string[]);
onChange({
schema: schema.filter((column) =>
selectedRowKeys.includes(column.name)
),
});
},
[schema, onChange]
);
const fetchTableColumns = useCallback(async () => {
const response = await getTableColumnsByFQN(fqn);
setSchema(pruneEmptyChildren(response.data));
}, [fqn]);
useEffect(() => {
setSelectedKeys(selectedSchema);
}, [selectedSchema]);
useEffect(() => {
fetchTableColumns();
}, [fqn]);
@ -160,9 +169,7 @@ export const ContractSchemaFormTab: React.FC<{
rowKey="name"
rowSelection={{
selectedRowKeys: selectedKeys,
onChange: (selectedRowKeys) => {
setSelectedKeys(selectedRowKeys as string[]);
},
onChange: handleChangeTable,
}}
/>
</Card>
@ -170,15 +177,7 @@ export const ContractSchemaFormTab: React.FC<{
<Button icon={<ArrowLeftOutlined />} type="default" onClick={onPrev}>
{prevLabel ?? t('label.previous')}
</Button>
<Button
type="primary"
onClick={() =>
onNext({
schema: schema.filter((column) =>
selectedKeys.includes(column.name)
),
})
}>
<Button type="primary" onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
</Button>

View File

@ -11,22 +11,17 @@
* limitations under the License.
*/
import {
ArrowLeftOutlined,
ArrowRightOutlined,
PlusOutlined,
} from '@ant-design/icons';
import Icon, { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import { FieldErrorProps } from '@rjsf/utils';
import { Button, Col, Form, Input, Row, Switch, Typography } from 'antd';
import Card from 'antd/lib/card/Card';
import TextArea from 'antd/lib/input/TextArea';
import { useEffect, useState } from 'react';
import classNames from 'classnames';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as PlusIcon } from '../../../assets/svg/x-colored.svg';
import { EntityType } from '../../../enums/entity.enum';
import {
DataContract,
SemanticsRule,
} from '../../../generated/entity/data/dataContract';
import { DataContract } from '../../../generated/entity/data/dataContract';
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
import QueryBuilderWidget from '../../common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget';
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
@ -34,16 +29,28 @@ import { SearchOutputType } from '../../Explore/AdvanceSearchProvider/AdvanceSea
import './contract-semantic-form-tab.less';
export const ContractSemanticFormTab: React.FC<{
onNext: (data: Partial<DataContract>) => void;
onChange: (data: Partial<DataContract>) => void;
onNext: () => void;
onPrev: () => void;
initialValues?: Partial<DataContract>;
nextLabel?: string;
prevLabel?: string;
}> = ({ onNext, onPrev, nextLabel, prevLabel, initialValues }) => {
}> = ({ onChange, onNext, onPrev, nextLabel, prevLabel, initialValues }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const semanticsData = Form.useWatch('semantics', form);
const [editingKey, setEditingKey] = useState<number | null>(null);
const addFunctionRef = useRef<((defaultValue?: any) => void) | null>(null);
const handleAddSemantic = () => {
addFunctionRef.current?.({
name: '',
description: '',
rule: '',
enabled: false,
});
setEditingKey(semanticsData.length);
};
useEffect(() => {
form.setFieldsValue({
@ -58,18 +65,6 @@ export const ContractSemanticFormTab: React.FC<{
});
}, []);
const handleNext = () => {
const semantics = form.getFieldValue('semantics') as SemanticsRule[];
const validSemantics = semantics.filter((semantic) => {
return semantic.name && semantic.rule;
});
onNext({
semantics: validSemantics,
});
};
useEffect(() => {
if (initialValues?.semantics) {
form.setFieldsValue({
@ -80,162 +75,172 @@ export const ContractSemanticFormTab: React.FC<{
return (
<>
<Card className="container bg-grey p-box">
<div>
<Typography.Text className="contract-detail-form-tab-title">
{t('label.semantic-plural')}
</Typography.Text>
<Typography.Text className="contract-detail-form-tab-description">
{t('message.semantics-description')}
</Typography.Text>
<Card className="contract-semantic-form-container container bg-grey p-box">
<div className="d-flex justify-between items-center">
<div>
<Typography.Text className="contract-detail-form-tab-title">
{t('label.semantic-plural')}
</Typography.Text>
<Typography.Text className="contract-detail-form-tab-description">
{t('message.semantics-description')}
</Typography.Text>
</div>
<Button
className="add-semantic-button"
disabled={!!editingKey || !addFunctionRef.current}
icon={<Icon className="anticon" component={PlusIcon} />}
type="link"
onClick={handleAddSemantic}>
{t('label.add-entity', {
entity: t('label.semantic-plural'),
})}
</Button>
</div>
<Form form={form} layout="vertical">
<Form
form={form}
layout="vertical"
onValuesChange={(_, allValues) => {
onChange(allValues);
}}>
<Form.List name="semantics">
{(fields, { add }) => (
<>
{fields.map((field) => {
return (
<ExpandableCard
cardProps={{
className: 'm-t-md',
title: (
<div className="w-full d-flex justify-between items-center">
{editingKey === field.key ? null : (
<>
<div className="d-flex items-center gap-6">
<Switch
checked={semanticsData[field.key].enabled}
/>
<div className="d-flex flex-column">
<Typography.Text>
{semanticsData[field.key]?.name ||
t('label.untitled')}
</Typography.Text>
<Typography.Text type="secondary">
{semanticsData[field.key]?.description ||
t('label.no-description')}
</Typography.Text>
{(fields, { add }) => {
// Store the add function so it can be used outside
if (!addFunctionRef.current) {
addFunctionRef.current = add;
}
return (
<>
{fields.map((field) => {
return (
<ExpandableCard
cardProps={{
className: classNames('expandable-card m-t-md', {
'expanded-active-card': editingKey === field.key,
}),
title: (
<div className="w-full d-flex justify-between items-center">
{editingKey === field.key ? null : (
<>
<div className="d-flex items-center gap-6">
<Switch
checked={semanticsData[field.key].enabled}
/>
<div className="d-flex flex-column">
<Typography.Text>
{semanticsData[field.key]?.name ||
t('label.untitled')}
</Typography.Text>
<Typography.Text type="secondary">
{semanticsData[field.key]
?.description ||
t('label.no-description')}
</Typography.Text>
</div>
</div>
</div>
<EditIconButton
newLook
data-testid={`edit-semantic=${field.key}`}
size="small"
onClick={() => setEditingKey(field.key)}
<EditIconButton
newLook
data-testid={`edit-semantic=${field.key}`}
size="small"
onClick={() => setEditingKey(field.key)}
/>
</>
)}
</div>
),
}}
key={field.key}>
{editingKey === field.key ? (
<Row>
<Col span={24}>
<Form.Item
{...field}
label={t('label.name')}
name={[field.name, 'name']}>
<Input />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.description')}
name={[field.name, 'description']}>
<TextArea />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.enabled')}
name={[field.name, 'enabled']}>
<Switch />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.add-entity', {
entity: t('label.rule'),
})}
name={[field.name, 'rule']}>
{/* @ts-expect-error because Form.Item will provide value and onChange */}
<QueryBuilderWidget
formContext={{
entityType: EntityType.TABLE,
}}
id="rule"
label={t('label.rule')}
name={`${field.name}.rule`}
options={{
addButtonText: t('label.add-semantic'),
removeButtonText: t(
'label.remove-semantic'
),
}}
registry={{} as FieldErrorProps['registry']}
schema={{
outputType: SearchOutputType.JSONLogic,
}}
/>
</>
)}
</Form.Item>
</Col>
<Col className="d-flex justify-end" span={24}>
<Button onClick={() => setEditingKey(null)}>
{t('label.cancel')}
</Button>
<Button
className="m-l-md"
type="primary"
onClick={() => setEditingKey(null)}>
{t('label.save')}
</Button>
</Col>
</Row>
) : (
<div className="semantic-rule-editor-view-only">
{/* @ts-expect-error because Form.Item will provide value and onChange */}
<QueryBuilderWidget
readonly
formContext={{
entityType: EntityType.TABLE,
}}
registry={{} as FieldErrorProps['registry']}
schema={{
outputType: SearchOutputType.JSONLogic,
}}
value={semanticsData[field.key]?.rule ?? {}}
/>
</div>
),
}}
key={field.key}>
{editingKey === field.key ? (
<Row>
<Col span={24}>
<Form.Item
{...field}
label={t('label.name')}
name={[field.name, 'name']}>
<Input />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.description')}
name={[field.name, 'description']}>
<TextArea />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.enabled')}
name={[field.name, 'enabled']}>
<Switch />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.add-entity', {
entity: t('label.rule'),
})}
name={[field.name, 'rule']}>
{/* @ts-expect-error because Form.Item will provide value and onChange */}
<QueryBuilderWidget
formContext={{
entityType: EntityType.TABLE,
}}
id="rule"
label={t('label.rule')}
name={`${field.name}.rule`}
options={{
addButtonText: t('label.add-semantic'),
removeButtonText: t('label.remove-semantic'),
}}
registry={{} as FieldErrorProps['registry']}
schema={{
outputType: SearchOutputType.JSONLogic,
}}
/>
</Form.Item>
</Col>
<Col className="d-flex justify-end" span={24}>
<Button onClick={() => setEditingKey(null)}>
{t('label.cancel')}
</Button>
<Button
className="m-l-md"
type="primary"
onClick={() => setEditingKey(null)}>
{t('label.save')}
</Button>
</Col>
</Row>
) : (
<div className="semantic-rule-editor-view-only">
{/* @ts-expect-error because Form.Item will provide value and onChange */}
<QueryBuilderWidget
readonly
formContext={{
entityType: EntityType.TABLE,
}}
registry={{} as FieldErrorProps['registry']}
schema={{
outputType: SearchOutputType.JSONLogic,
}}
value={semanticsData[field.key]?.rule ?? {}}
/>
</div>
)}
</ExpandableCard>
);
})}
<div className="d-flex justify-between">
<Button
className="m-t-md"
disabled={!!editingKey}
icon={<PlusOutlined />}
type="primary"
onClick={() =>
add({
name: '',
description: '',
rule: '',
enabled: false,
})
}>
{t('label.add-entity', {
entity: t('label.semantic-plural'),
})}
</Button>
</div>
</>
)}
)}
</ExpandableCard>
);
})}
</>
);
}}
</Form.List>
</Form>
</Card>
@ -244,7 +249,7 @@ export const ContractSemanticFormTab: React.FC<{
<Button icon={<ArrowLeftOutlined />} onClick={onPrev}>
{prevLabel ?? t('label.previous')}
</Button>
<Button type="primary" onClick={handleNext}>
<Button type="primary" onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
</Button>

View File

@ -10,6 +10,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.add-semantic-button {
span {
font-weight: 600;
}
.anticon {
font-size: 14px;
rotate: 45deg;
}
}
.contract-semantic-form-container {
.expandable-card {
margin-top: 16px;
.ant-card-head {
background: @white !important;
border-bottom: 1px solid @grey-200 !important;
}
}
.expanded-active-card {
.ant-card-head {
display: none;
}
}
}
.semantic-rule-editor-view-only {
.ant-divider,
.group--conjunctions {

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import { AxiosError } from 'axios';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DataContractTabMode } from '../../../constants/DataContract.constants';
@ -45,27 +46,26 @@ export const ContractTab = () => {
]);
setContract(contract);
} catch {
//
setContract(undefined);
} finally {
setIsLoading(false);
}
};
const handleDelete = () => {
const handleDelete = async () => {
if (contract?.id) {
deleteContractById(contract.id)
.then(() => {
showSuccessToast(
t('message.entity-deleted-successfully', {
entity: t('label.contract'),
})
);
fetchContract();
setTabMode(DataContractTabMode.VIEW);
})
.catch((err) => {
showErrorToast(err);
});
try {
await deleteContractById(contract.id);
showSuccessToast(
t('server.entity-deleted-successfully', {
entity: t('label.contract'),
})
);
fetchContract();
setTabMode(DataContractTabMode.VIEW);
} catch (err) {
showErrorToast(err as AxiosError);
}
}
};

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "Der Minimalwert sollte kleiner als der Maximalwert sein.",
"minute": "Minute",
"missing-config-value": "Fehlender Konfigurationswert: {{field}}",
"modified-time-ago-by": "Geändert vor {{time}} von {{by}}",
"modify-hierarchy-entity-description": "Ändern Sie die Hierarchie, indem Sie die übergeordnete {{entity}} ändern.",
"most-active-users": "Zeigt die aktivsten Benutzer auf der Plattform basierend auf Seitenaufrufen.",
"most-expensive-queries-widget-description": "Top 5 der aufwändigsten Abfragen für die Datenvermögenswerte im Dienst. <0>Mehr erfahren.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "Minute",
"missing-config-value": "Missing config value: {{field}}",
"modified-time-ago-by": "Modified {{time}} ago by {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Displays the most active users on the platform based on Page Views.",
"most-expensive-queries-widget-description": "Top 5 most expensive queries for the data assets in the service. <0>learn more.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "Minuto",
"missing-config-value": "Falta el valor de configuración: {{field}}",
"modified-time-ago-by": "Modificado {{time}} atrás por {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Muestra los usuarios más activos en la plataforma basado en las vistas de página.",
"most-expensive-queries-widget-description": "Las 5 consultas más costosas para los activos de datos en el servicio. <0>saber más.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "La vleur minimum value doit être plus petite que la valeur maximum.",
"minute": "Minute",
"missing-config-value": "Valeur de configuration manquante : {{field}}",
"modified-time-ago-by": "Modifié {{time}} par {{by}}",
"modify-hierarchy-entity-description": "Modifier la hiérarchie en changeant le {{entity}} parent.",
"most-active-users": "Affiche les utilisateurs les plus actifs en fonction du nombre de consultations de pages.",
"most-expensive-queries-widget-description": "Top 5 des requêtes les plus coûteuses pour les actifs de données dans le service. <0>en savoir plus.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "O valor mínimo debe ser menor que o valor máximo.",
"minute": "Minuto",
"missing-config-value": "Falta o valor de configuración: {{field}}",
"modified-time-ago-by": "Modificado {{time}} por {{by}}",
"modify-hierarchy-entity-description": "Modifica a xerarquía cambiando o {{entity}} Pai.",
"most-active-users": "Mostra os usuarios máis activos na plataforma baseándose nas Vistas de Páxina.",
"most-expensive-queries-widget-description": "As 5 consultas máis custosas para os activos de datos no servizo. <0>saber máis.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "דקה",
"missing-config-value": "חסר ערך הגדרה: {{field}}",
"modified-time-ago-by": "עודכן {{time}} לפני {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "מציג את המשתמשים הפעילים ביותר בפלטפורמה על פי צפיות בדף.",
"most-expensive-queries-widget-description": "5 השאילתות היקרות ביותר עבור נכסי הנתונים בשירות. <0>למד עוד</0>.",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "最小値は最大値より小さくする必要があります。",
"minute": "分",
"missing-config-value": "設定値が不足しています: {{field}}",
"modified-time-ago-by": "{{time}} 前に {{by}} によって更新されました",
"modify-hierarchy-entity-description": "親{{entity}}を変更して階層を変更します。",
"most-active-users": "ページビューに基づく最もアクティブなユーザーの表示。",
"most-expensive-queries-widget-description": "サービス内のデータアセットにおける最もコストの高い上位5つのクエリ。<0>詳細を見る。</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "최소값은 최대값보다 작아야 합니다.",
"minute": "분",
"missing-config-value": "누락된 구성 값: {{field}}",
"modified-time-ago-by": "{{time}} 전에 {{by}}에 의해 수정됨",
"modify-hierarchy-entity-description": "상위 {{entity}}를 변경하여 계층 구조를 수정합니다.",
"most-active-users": "페이지 조회수를 기반으로 플랫폼에서 가장 활발한 사용자를 표시합니다.",
"most-expensive-queries-widget-description": "서비스 내에서 가장 많이 사용된 데이터 자산 상위 5개입니다. <0>자세히 알아보기.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "किमान मूल्य कमाल मूल्यापेक्षा लहान असावे.",
"minute": "मिनिट",
"missing-config-value": "कॉन्फिगरेशन व्हॅल्यू गहाळ आहे: {{field}}",
"modified-time-ago-by": "{{time}} पूर्व {{by}} द्वारे संशोधित",
"modify-hierarchy-entity-description": "पालक {{entity}} बदलून श्रेणीक्रम बदल करा.",
"most-active-users": "पृष्ठ दृश्यांवर आधारित प्लॅटफॉर्मवरील सर्वात सक्रिय वापरकर्ते दर्शवते.",
"most-expensive-queries-widget-description": "सेवेतील डेटा मालमत्तांसाठी शीर्ष 5 सर्वात महाग क्वेरी. <0>अधिक जाणून घ्या.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "Minuut",
"missing-config-value": "Ontbrekende configuratiewaarde: {{field}}",
"modified-time-ago-by": "Aangepast {{time}} geleden door {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Toont de meest actieve gebruikers op het platform op basis van paginaweergaven.",
"most-expensive-queries-widget-description": "Top 5 meest kostbare queries voor de data-assets in de service. <0>meer informatie.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "مقدار حداقل باید کوچکتر از مقدار حداکثر باشد.",
"minute": "دقیقه",
"missing-config-value": "مقدار پیکربندی گم شده: {{field}}",
"modified-time-ago-by": "{{time}} قبل از {{by}} تغییر کرد",
"modify-hierarchy-entity-description": "با تغییر والد {{entity}}، سلسله‌مراتب را ویرایش کنید.",
"most-active-users": "فعال‌ترین کاربران بر اساس بازدیدهای صفحه را نمایش می‌دهد.",
"most-expensive-queries-widget-description": "5 کوئری پرهزینه برتر برای دارایی‌های داده در سرویس. <0>بیشتر بدانید.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "O valor mínimo deve ser menor que o valor máximo.",
"minute": "Minuto",
"missing-config-value": "Valor de configuração ausente: {{field}}",
"modified-time-ago-by": "Modificado {{time}} atrás por {{by}}",
"modify-hierarchy-entity-description": "Modifique a hierarquia alterando o Pai {{entity}}.",
"most-active-users": "Exibe os usuários mais ativos na plataforma com base nas Visualizações de Página.",
"most-expensive-queries-widget-description": "Top 5 consultas mais caras para os ativos de dados no serviço. <0>saiba mais.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "Minuto",
"missing-config-value": "Valor de configuração em falta: {{field}}",
"modified-time-ago-by": "Modificado {{time}} atrás por {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Exibe os Utilizadores mais ativos na plataforma com base nas Visualizações de Página.",
"most-expensive-queries-widget-description": "Top 5 consultas mais dispendiosas para os ativos de dados no serviço. <0>saber mais.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "Минимальное значение должно быть меньше максимального значения.",
"minute": "Минута",
"missing-config-value": "Отсутствует значение конфигурации: {{field}}",
"modified-time-ago-by": "Изменено {{time}} назад {{by}}",
"modify-hierarchy-entity-description": "Измените иерархию, изменив родительский элемент {{entity}}.",
"most-active-users": "Отображает самых активных пользователей на платформе на основе просмотров страниц.",
"most-expensive-queries-widget-description": "Топ-5 самых дорогих запросов для объектов данных в сервисе. <0>узнать больше.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "The minimum value should be smaller than the maximum value.",
"minute": "Minute",
"missing-config-value": "ค่าการกำหนดค่าที่หายไป: {{field}}",
"modified-time-ago-by": "แก้ไข {{time}} ก่อนหน้า {{by}}",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Displays the most active users on the platform based on Page Views.",
"most-expensive-queries-widget-description": "5 อันดับคิวรีที่ใช้ทรัพยากรมากที่สุดสำหรับสินทรัพย์ข้อมูลในบริการ <0>เรียนรู้เพิ่มเติม</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "Minimum değer maksimum değerden küçük olmalıdır.",
"minute": "Dakika",
"missing-config-value": "Eksik yapılandırma değeri: {{field}}",
"modified-time-ago-by": "{{time}} önce {{by}} tarafından değiştirildi",
"modify-hierarchy-entity-description": "Üst {{entity}} değiştirerek hiyerarşiyi değiştirin.",
"most-active-users": "Sayfa Görüntülemelerine göre platformdaki en aktif kullanıcıları görüntüler.",
"most-expensive-queries-widget-description": "Servisteki veri varlıkları için en maliyetli ilk 5 sorgu. <0>daha fazla bilgi edinin.</0>",

View File

@ -2085,6 +2085,7 @@
"minimum-value-error": "最小值应小于最大值",
"minute": "分钟",
"missing-config-value": "缺少配置值:{{field}}",
"modified-time-ago-by": "{{time}} 前由 {{by}} 修改",
"modify-hierarchy-entity-description": "通过更改父{{entity}}来修改层级结构.",
"most-active-users": "显示平台上最活跃的用户(基于页面浏览量)",
"most-expensive-queries-widget-description": "服务中数据资产的前 5 个最昂贵查询。<0>了解更多。</0>",

View File

@ -115,8 +115,8 @@ export const getTestCaseSummaryChartItems = (testCaseSummary?: TestSummary) => {
color: GREEN_3,
chartData: [
{ name: 'success', value: success, color: GREEN_3 },
{ name: 'aborted', value: failed, color: YELLOW_2 },
{ name: 'failed', value: aborted, color: RED_3 },
{ name: 'failed', value: failed, color: RED_3 },
{ name: 'aborted', value: aborted, color: YELLOW_2 },
],
},
{

View File

@ -10,7 +10,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Icon from '@ant-design/icons';
import { isArray, isNil, isUndefined, omit, omitBy } from 'lodash';
import { ReactComponent as AccuracyIcon } from '../../assets/svg/ic-accuracy.svg';
import { ReactComponent as CompletenessIcon } from '../../assets/svg/ic-completeness.svg';
@ -21,14 +20,10 @@ import { ReactComponent as UniquenessIcon } from '../../assets/svg/ic-uniqueness
import { ReactComponent as ValidityIcon } from '../../assets/svg/ic-validity.svg';
import { ReactComponent as NoDimensionIcon } from '../../assets/svg/no-dimension-icon.svg';
import { TestCaseSearchParams } from '../../components/DataQuality/DataQuality.interface';
import { TEST_CASE_STATUS_ICON } from '../../constants/DataQuality.constants';
import { TEST_CASE_FILTERS } from '../../constants/profiler.constant';
import { Table } from '../../generated/entity/data/table';
import { DataQualityReport } from '../../generated/tests/dataQualityReport';
import {
TestCase,
TestCaseParameterValue,
} from '../../generated/tests/testCase';
import { TestCaseParameterValue } from '../../generated/tests/testCase';
import {
DataQualityDimensions,
TestDataType,
@ -327,15 +322,3 @@ export const convertSearchSourceToTable = (
...searchSource,
columns: searchSource.columns || [],
} as Table);
export const getTestCaseStatusIcon = (record: TestCase) => (
<Icon
className="test-status-icon"
component={
TEST_CASE_STATUS_ICON[
(record?.testCaseResult?.testCaseStatus ??
'Queued') as keyof typeof TEST_CASE_STATUS_ICON
]
}
/>
);