Fix(UI):Contract form design changes (#22712)

* fixed contract form design changes

* semantic-form-design-fix

* localization fix

* fix the alert contract staus, redirect to tab from failed contract badge, hide the quality chart if all the values are 0, and icon fix around quality and semantic

* added button to remove a semantic in the card and minor fix

* tag styling new look

* fixed checkbox and buttons

* added yaml page and fix layout issue

* fix the semantic rule component styling and enable first semantic in edit mode when coming to edit or new cntract

* fix the owner not seeing while edit in modal and fix the default rule not visible

* fix the edit button styling

* remove the important from less and optimize tagChip newLook code

* fix the file casing

---------

Co-authored-by: Ashish Gupta <ashish@getcollate.io>
Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
This commit is contained in:
Dhruv Parmar 2025-08-04 16:36:55 +05:30 committed by GitHub
parent 5e41039b97
commit adb0bacffb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 1230 additions and 545 deletions

View File

@ -94,6 +94,7 @@
"https-browserify": "^1.0.0",
"i18next": "^21.10.0",
"i18next-browser-languagedetector": "^6.1.6",
"js-yaml": "^4.1.0",
"jwt-decode": "^3.1.2",
"katex": "^0.16.21",
"lodash": "^4.17.21",
@ -163,6 +164,7 @@
"@types/dagre": "^0.7.47",
"@types/diff": "^5.0.2",
"@types/jest": "^26.0.23",
"@types/js-yaml": "^4.0.9",
"@types/katex": "^0.16.7",
"@types/lodash": "^4.14.167",
"@types/luxon": "^3.0.1",

View File

@ -0,0 +1,6 @@
<svg viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.91406 15.3386V6.17192C2.91406 2.83858 3.7474 2.00525 7.08073 2.00525H12.9141C16.2474 2.00525 17.0807 2.83858 17.0807 6.17192V14.5052C17.0807 14.6219 17.0807 14.7386 17.0724 14.8552" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.28906 12.8386H17.0807V15.7553C17.0807 17.3636 15.7724 18.672 14.1641 18.672H5.83073C4.2224 18.672 2.91406 17.3636 2.91406 15.7553V15.2136C2.91406 13.9053 3.98073 12.8386 5.28906 12.8386Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66406 6.172H13.3307" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66406 9.08862H10.8307" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 903 B

View File

@ -0,0 +1,5 @@
<svg viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.74219 7.83862C6.55885 8.24696 7.25885 8.86362 7.76719 9.63029C8.05885 10.0636 8.05885 10.622 7.76719 11.0553C7.25885 11.8136 6.55885 12.4303 5.74219 12.8386" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.8359 12.8386H14.1693" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.4974 18.6719H12.4974C16.6641 18.6719 18.3307 17.0052 18.3307 12.8386V7.83858C18.3307 3.67192 16.6641 2.00525 12.4974 2.00525H7.4974C3.33073 2.00525 1.66406 3.67192 1.66406 7.83858V12.8386C1.66406 17.0052 3.33073 18.6719 7.4974 18.6719Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 804 B

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.78105 2.39815C9.24259 2.13675 9.78912 2.06941 10.3003 2.21094C10.527 2.27369 10.7207 2.38301 10.9032 2.50648C11.0766 2.62375 11.2755 2.779 11.506 2.95894L11.5396 2.9852C11.7701 3.16514 11.9691 3.32039 12.1249 3.46013C12.289 3.60726 12.4421 3.7686 12.558 3.97325C12.8194 4.4348 12.8867 4.98129 12.7452 5.49249C12.6825 5.71915 12.5731 5.91284 12.4497 6.0954C12.3324 6.26879 12.1771 6.4677 11.9971 6.69822L7.26199 12.7644C7.24999 12.7798 7.23819 12.7949 7.22652 12.8099C7.00425 13.0949 6.83619 13.3104 6.62812 13.4874C6.45203 13.6372 6.25719 13.7634 6.04853 13.863C5.80202 13.9806 5.53667 14.0459 5.18568 14.1325C5.16728 14.137 5.14864 14.1416 5.12976 14.1463L3.71321 14.496C3.61511 14.5202 3.50313 14.548 3.40435 14.5631C3.29423 14.58 3.12371 14.5954 2.93538 14.5351C2.70898 14.4626 2.51581 14.3118 2.39051 14.1098C2.28629 13.9417 2.25977 13.7726 2.24945 13.6616C2.2402 13.5621 2.23987 13.4468 2.23959 13.3457C2.23957 13.3394 2.23955 13.3332 2.23953 13.3271L2.23495 11.8866C2.23489 11.8672 2.23482 11.848 2.23475 11.829C2.23345 11.4676 2.23247 11.1942 2.28672 10.9266C2.33264 10.7 2.40783 10.4803 2.51039 10.2732C2.63157 10.0284 2.79981 9.81302 3.02236 9.52815C3.03403 9.51322 3.04585 9.49808 3.05782 9.48275L7.79299 3.41652C7.97292 3.18599 8.12819 2.98708 8.26792 2.83122C8.41505 2.66712 8.57639 2.51405 8.78105 2.39815ZM9.94452 3.49593C9.77412 3.44876 9.59199 3.4712 9.43812 3.55834C9.41199 3.57314 9.36239 3.60785 9.26072 3.72128C9.15499 3.83919 9.02765 4.00169 8.83092 4.25374L10.933 5.89458C11.1298 5.64254 11.2565 5.47959 11.3452 5.34842C11.4306 5.22222 11.4522 5.16567 11.4602 5.13673C11.5074 4.96633 11.4849 4.78417 11.3978 4.63032C11.383 4.60419 11.3483 4.55458 11.2349 4.45288C11.1169 4.34717 10.9545 4.21987 10.7024 4.02312C10.4503 3.82638 10.2874 3.69964 10.1563 3.61093C10.0301 3.52558 9.97345 3.50394 9.94452 3.49593ZM10.1126 6.94562L8.01052 5.30478L4.10886 10.3032C3.83569 10.6531 3.75865 10.757 3.70533 10.8647C3.65405 10.9683 3.61645 11.0781 3.59349 11.1914C3.56962 11.3092 3.56687 11.4384 3.56828 11.8824L3.57233 13.1574L4.81019 12.8518C5.24119 12.7454 5.36591 12.7114 5.47439 12.6596C5.57873 12.6098 5.67614 12.5467 5.76419 12.4718C5.85574 12.3939 5.93777 12.294 6.21093 11.944L10.1126 6.94562ZM7.99845 14.0003C7.99845 13.6321 8.29692 13.3336 8.66512 13.3336H13.3318C13.7 13.3336 13.9985 13.6321 13.9985 14.0003C13.9985 14.3685 13.7 14.667 13.3318 14.667H8.66512C8.29692 14.667 7.99845 14.3685 7.99845 14.0003Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 15.8661L7.5 10.8661L12.5 5.86609" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -11,16 +11,7 @@
* limitations under the License.
*/
import Icon from '@ant-design/icons';
import {
Button,
Col,
Divider,
Row,
Space,
Tag,
Tooltip,
Typography,
} from 'antd';
import { Button, Col, Divider, Row, Space, Tooltip, Typography } from 'antd';
import ButtonGroup from 'antd/lib/button/button-group';
import { AxiosError } from 'axios';
import classNames from 'classnames';
@ -436,16 +427,25 @@ export const DataAssetsHeader = ({
const dataContractLatestResultButton = useMemo(() => {
if (dataContract?.latestResult?.status === ContractExecutionStatus.Failed) {
return (
<Tag
<Button
className={classNames(
`data-contract-latest-result-button
${dataContract?.latestResult?.status}`
)}>
{getDataContractStatusIcon(dataContract?.latestResult?.status)}
)}
icon={getDataContractStatusIcon(dataContract?.latestResult?.status)}
onClick={() => {
navigate(
getEntityDetailsPath(
entityType,
dataAsset?.fullyQualifiedName ?? '',
EntityTabs.CONTRACT
)
);
}}>
{t('label.entity-failed', {
entity: t('label.contract'),
})}
</Tag>
</Button>
);
}

View File

@ -129,24 +129,29 @@
}
}
.data-contract-latest-result-button {
font-size: 14px;
&.Failed {
color: @red-14;
font-weight: 600;
border: 1px solid @red-19;
background-color: @red-9;
border-radius: 12px;
.ant-btn-group.spaced {
.ant-btn.data-contract-latest-result-button {
font-size: 14px;
padding: 6px 12px;
display: flex;
align-items: center;
gap: 4px;
border-radius: 12px !important;
font-weight: 600;
box-shadow: 0px 2px 2px -1px @grey-35, 0px 4px 6px -2px @grey-35,
0px 12px 16px -4px @grey-35;
svg {
font-size: 26px;
fill: transparent;
&.Failed {
color: @red-14;
border: 1px solid @red-19;
background-color: @red-9;
svg {
font-size: 26px;
fill: transparent;
}
&:hover {
border: 1px solid @red-19;
background-color: @red-2;
}
}
}
}

View File

@ -11,16 +11,7 @@
* limitations under the License.
*/
import { CodeOutlined, EditOutlined } from '@ant-design/icons';
import {
Button,
Card,
Divider,
Radio,
RadioChangeEvent,
Tabs,
Typography,
} from 'antd';
import { Button, Card, RadioChangeEvent, Tabs, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
@ -112,7 +103,7 @@ const AddDataContract: React.FC<{
} finally {
setIsSubmitting(false);
}
}, [contract, formValues]);
}, [contract, formValues, table.id]);
const onFormChange = useCallback(
(data: Partial<DataContract>) => {
@ -220,27 +211,13 @@ const AddDataContract: React.FC<{
const cardTitle = useMemo(() => {
return (
<div className="add-contract-card-header d-flex items-center justify-between">
<div className="d-flex item-center justify-between flex-1">
<div>
<Typography.Text className="add-contract-card-title">
{t('label.add-contract-detail-plural')}
</Typography.Text>
<Typography.Paragraph className="add-contract-card-description">
{t('message.add-contract-detail-description')}
</Typography.Paragraph>
</div>
<div className="d-flex items-center">
<Radio.Group
optionType="button"
options={[
{ label: <CodeOutlined />, value: DataContractMode.YAML },
{ label: <EditOutlined />, value: DataContractMode.UI },
]}
value={mode}
onChange={handleModeChange}
/>
<Divider type="vertical" />
</div>
<div>
<Typography.Text className="add-contract-card-title">
{t('label.add-contract-detail-plural')}
</Typography.Text>
<Typography.Paragraph className="add-contract-card-description">
{t('message.add-contract-detail-description')}
</Typography.Paragraph>
</div>
<div>
<Button type="default" onClick={onCancel}>

View File

@ -28,11 +28,54 @@
overflow: initial;
}
}
.contract-prev-button,
.contract-next-button {
display: flex;
align-items: center;
justify-content: center;
padding: @size-xs @size-sm;
border: 1px solid @grey-300;
border-radius: 8px;
gap: 4px;
font-weight: @font-semibold;
box-shadow: 0px 1px 2px 0px @grey-27;
box-shadow: 0px -2px 0px 0px @grey-27 inset;
box-shadow: 0px 0px 0px 1px @grey-27 inset;
}
.contract-prev-button {
color: @grey-700;
svg {
stroke: @grey-400;
}
}
.ant-tabs-left-content {
height: 100%;
}
.ant-checkbox-checked {
.ant-checkbox-inner {
background-color: @primary-1;
border: 1px solid @primary-color;
&::after {
border-color: @primary-color;
}
}
}
.ant-checkbox-indeterminate {
.ant-checkbox-inner {
background-color: @primary-1;
border: 1px solid @primary-color;
&::after {
width: 7px;
height: 2px;
border-radius: 8px;
border: 1px solid @primary-color;
}
}
}
.ant-tabs-nav {
width: 180px;
height: initial;
@ -104,6 +147,10 @@
min-height: 500px;
border-radius: 0 8px 8px 8px;
}
.schema-table-name {
color: @grey-600;
font-weight: @font-medium;
}
// Contract detail Forms
.contract-detail-form-tab-title {
@ -147,23 +194,6 @@
& > div:first-child {
background: transparent;
padding: 0 0 20px 0;
.ant-typography {
margin: 0;
&.ant-typography-title {
font-size: 18px;
font-weight: 600;
color: #111827;
margin-bottom: 4px !important;
}
&.ant-typography-paragraph {
font-size: 14px;
color: #6b7280;
margin: 0 !important;
}
}
}
// Form styling within tabs
@ -173,28 +203,44 @@
.ant-form-item-label {
padding-bottom: 8px;
label {
font-size: 14px;
font-weight: 500;
color: #374151;
color: @grey-700;
&.ant-form-item-required::before {
color: #ef4444;
}
}
}
.ant-form-item-control-input-content {
input[type='text'] {
padding: 8px 14px;
color: @grey-700;
border: 1px solid @grey-300;
border-radius: 8px;
box-shadow: 0 1px 2px 0px @grey-27;
}
}
.ant-form-item-control {
.ant-input,
.ant-select-selector,
.ant-mentions {
border-radius: 6px;
border: 1px solid #d1d5db;
border-radius: 8px;
border: 1px solid @grey-300;
padding: 4px 12px;
color: @grey-700;
height: 40px;
box-shadow: 0 1px 2px 0px @grey-27;
&:focus,
&:hover {
border-color: #3b82f6;
border-color: @primary-color;
}
.ant-select-selection-search {
input[type='search'] {
height: 40px;
}
}
}
}
@ -227,3 +273,26 @@
padding-left: 20px;
}
}
.semantic-rule-editor-view-only {
input[type='text'] {
padding: 8px 14px;
color: @grey-700;
box-shadow: 0px 1px 2px 0px @grey-27;
border: 1px solid @grey-300;
}
.ant-select-selector,
.ant-mentions {
border: 1px solid @grey-300;
padding: 4px 12px !important;
color: @grey-700;
height: 40px !important;
box-shadow: 0 1px 2px 0px @grey-27;
&:focus,
&:hover {
border-color: @primary-color;
}
}
}

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ArrowRightOutlined, PlusOutlined } from '@ant-design/icons';
import { PlusOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Card, Form, Typography } from 'antd';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@ -61,6 +61,7 @@ export const ContractDetailFormTab: React.FC<{
type: FieldTypes.USER_TEAM_SELECT,
required: false,
props: {
owner: initialValues?.owners,
hasPermission: true,
children: (
<Button
@ -115,9 +116,13 @@ export const ContractDetailFormTab: React.FC<{
</div>
</Card>
<div className="d-flex justify-end m-t-md">
<Button htmlType="submit" type="primary" onClick={onNext}>
<Button
className="contract-next-button"
htmlType="submit"
type="primary"
onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
<RightOutlined height={15} width={8} />
</Button>
</div>
</>

View File

@ -12,30 +12,44 @@
*/
import Icon, { PlayCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Loading } from '@melloware/react-logviewer';
import { Button, Card, Col, Row, Space, Tag, Typography } from 'antd';
import {
Button,
Card,
Col,
Divider,
RadioChangeEvent,
Row,
Space,
Tag,
Typography,
} from 'antd';
import { AxiosError } from 'axios';
import React, { useEffect, useMemo, useState } from 'react';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg';
import { Cell, Pie, PieChart } from 'recharts';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new-thick.svg';
import { ReactComponent as EmptyContractIcon } from '../../../assets/svg/empty-contract.svg';
import { ReactComponent as FailIcon } from '../../../assets/svg/fail-badge.svg';
import { ReactComponent as FlagIcon } from '../../../assets/svg/flag.svg';
import { ReactComponent as CheckIcon } from '../../../assets/svg/ic-check-circle.svg';
import { ReactComponent as FailIcon } from '../../../assets/svg/ic-fail.svg';
import { ReactComponent as CheckIcon } from '../../../assets/svg/ic-successful.svg';
import { ReactComponent as DefaultIcon } from '../../../assets/svg/ic-task.svg';
import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-trash.svg';
import { isEmpty } from 'lodash';
import { Cell, Pie, PieChart } from 'recharts';
import {
ICON_DIMENSION,
ICON_DIMENSION_USER_PAGE,
NO_DATA_PLACEHOLDER,
} from '../../../constants/constants';
import { DataContractMode } from '../../../constants/DataContract.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';
import { DataContract } from '../../../generated/entity/data/dataContract';
import { DataContractResult } from '../../../generated/entity/datacontract/dataContractResult';
import {
ContractExecutionStatus,
DataContractResult,
} from '../../../generated/entity/datacontract/dataContractResult';
import { TestCase, TestSummary } from '../../../generated/tests/testCase';
import {
getContractResultByResultId,
@ -46,6 +60,7 @@ import {
getTestCaseExecutionSummary,
} from '../../../rest/testAPI';
import {
downloadContractYamlFile,
getConstraintStatus,
getContractStatusType,
getTestCaseSummaryChartItems,
@ -63,6 +78,8 @@ import RichTextEditorPreviewerNew from '../../common/RichTextEditor/RichTextEdit
import { StatusType } from '../../common/StatusBadge/StatusBadge.interface';
import StatusBadgeV2 from '../../common/StatusBadge/StatusBadgeV2.component';
import Table from '../../common/Table/Table';
import ContractViewSwitchTab from '../ContractViewSwitchTab/ContractViewSwitchTab.component';
import ContractYaml from '../ContractYaml/ContractYaml.component';
import './contract-detail.less';
const ContractDetail: React.FC<{
@ -75,9 +92,10 @@ const ContractDetail: React.FC<{
const [isLoading, setIsLoading] = useState(false);
const [isTestCaseLoading, setIsTestCaseLoading] = useState(false);
const [latestContractResults, setLatestContractResults] =
useState<DataContractResult | null>(null);
useState<DataContractResult>();
const [testCaseSummary, setTestCaseSummary] = useState<TestSummary>();
const [testCaseResult, setTestCaseResult] = useState<TestCase[]>([]);
const [mode, setMode] = useState<DataContractMode>(DataContractMode.UI);
const fetchLatestContractResults = async () => {
try {
@ -176,9 +194,29 @@ const ContractDetail: React.FC<{
return getConstraintStatus(latestContractResults);
}, [latestContractResults]);
const testCaseSummaryChartItems = useMemo(() => {
return getTestCaseSummaryChartItems(testCaseSummary);
}, [testCaseSummary]);
const { showTestCaseSummaryChart, testCaseSummaryChartItems } =
useMemo(() => {
return {
showTestCaseSummaryChart: Boolean(
testCaseSummary?.total ??
testCaseSummary?.success ??
testCaseSummary?.failed ??
testCaseSummary?.aborted
),
testCaseSummaryChartItems:
getTestCaseSummaryChartItems(testCaseSummary),
};
}, [testCaseSummary]);
const showContractStatusAlert = useMemo(() => {
const { result, contractExecutionStatus } = latestContractResults ?? {};
return (
result &&
(contractExecutionStatus === ContractExecutionStatus.Failed ||
contractExecutionStatus === ContractExecutionStatus.Aborted)
);
}, [latestContractResults]);
const getSemanticIconPerLastExecution = (semanticName: string) => {
if (!latestContractResults) {
@ -208,6 +246,14 @@ const ContractDetail: React.FC<{
/>
);
const handleExportContract = useCallback(() => {
if (!contract) {
return;
}
downloadContractYamlFile(contract);
}, [contract]);
const handleRunNow = () => {
if (contract?.id) {
setValidateLoading(true);
@ -221,6 +267,116 @@ const ContractDetail: React.FC<{
}
};
const handleModeChange = useCallback((e: RadioChangeEvent) => {
setMode(e.target.value);
}, []);
const renderDataContractHeader = useMemo(() => {
if (!contract) {
return null;
}
return (
<Row align="middle" justify="space-between">
<Col flex="auto">
<Typography.Text className="contract-title">
{getEntityName(contract)}
</Typography.Text>
<Typography.Text className="contract-time">
{t('message.modified-time-ago-by', {
time: getRelativeTime(contract.updatedAt),
by: contract.updatedBy,
})}
</Typography.Text>
<div className="contract-status-badge-container">
<StatusBadgeV2
externalIcon={FlagIcon}
label={contract.status ?? t('label.active')}
status={StatusType.Success}
/>
<StatusBadgeV2
className="contract-version-badge"
label={t('label.version-number', {
version: contract.version,
})}
status={StatusType.Version}
/>
</div>
</Col>
<Col>
<div className="contract-action-container">
{!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>
)}
<ContractViewSwitchTab
handleModeChange={handleModeChange}
mode={mode}
/>
<Divider className="contract-divider" type="vertical" />
<Button
className="contract-run-now-button"
icon={<PlayCircleOutlined />}
loading={validateLoading}
size="middle"
onClick={handleRunNow}>
{t('label.run-now')}
</Button>
<Button
className="contract-export-button"
data-testid="export-contract-button"
onClick={handleExportContract}>
{t('label.export')}
</Button>
<Button
danger
className="delete-button"
icon={<DeleteIcon />}
size="small"
onClick={onDelete}
/>
<Button
className="contract-edit-button"
icon={
<EditIcon
className="anticon"
style={{ ...ICON_DIMENSION_USER_PAGE }}
/>
}
type="primary"
onClick={onEdit}>
{t('label.edit')}
</Button>
</div>
</Col>
</Row>
);
}, [
contract,
mode,
onDelete,
onEdit,
handleRunNow,
handleModeChange,
validateLoading,
]);
useEffect(() => {
if (contract?.id && contract?.latestResult?.resultId) {
fetchLatestContractResults();
@ -255,147 +411,17 @@ const ContractDetail: React.FC<{
}
return (
<>
{/* Header Section */}
<Card className="contract-header-container" style={{ marginBottom: 16 }}>
<Row align="middle" justify="space-between">
<Col flex="auto">
<Typography.Text className="contract-title">
{getEntityName(contract)}
</Typography.Text>
<Typography.Text className="contract-time">
{t('message.modified-time-ago-by', {
time: getRelativeTime(contract.updatedAt),
by: contract.updatedBy,
})}
</Typography.Text>
<div className="d-flex items-center gap-2 m-t-xs">
<StatusBadgeV2
externalIcon={FlagIcon}
label={contract.status ?? t('label.active')}
status={StatusType.Success}
/>
<StatusBadgeV2
className="contract-version-badge"
label={t('label.version-number', {
version: contract.version,
})}
status={StatusType.Version}
/>
</div>
</Col>
<Col>
<div className="contract-action-container">
{!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"
icon={<PlayCircleOutlined />}
loading={validateLoading}
size="middle"
onClick={handleRunNow}>
{t('label.run-now')}
</Button>
<Button
danger
className="delete-button"
icon={<DeleteIcon />}
size="small"
onClick={onDelete}
/>
<Button
icon={
<EditIcon className="anticon" style={{ ...ICON_DIMENSION }} />
}
size="small"
type="primary"
onClick={onEdit}>
{t('label.edit')}
</Button>
</div>
</Col>
</Row>
</Card>
<Row className="contract-detail-container" gutter={[16, 0]}>
{/* Left Column */}
<Col span={12}>
<Row gutter={[16, 16]}>
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.entity-detail-plural', {
entity: t('label.contract'),
})}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.expected-schema-structure-of-this-asset')}
</Typography.Text>
</div>
),
}}>
<div className="expandable-card-contract-body">
<DescriptionV1
description={contract.description}
entityType={EntityType.DATA_CONTRACT}
showCommentsIcon={false}
showSuggestions={false}
/>
</div>
</ExpandableCard>
</Col>
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.schema')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.expected-schema-structure-of-this-asset')}
</Typography.Text>
</div>
),
}}>
<Table
columns={schemaColumns}
dataSource={schemaDetail}
pagination={false}
rowKey="name"
size="small"
/>
</ExpandableCard>
</Col>
</Row>
</Col>
{/* Right Column */}
<Col span={12}>
{/* Contract Status Card */}
<Row gutter={[16, 16]}>
{contract?.latestResult?.resultId && (
<Card
className="contract-header-container"
style={{ marginBottom: 16 }}
title={renderDataContractHeader}>
{mode === DataContractMode.YAML ? (
<ContractYaml contract={contract} />
) : (
<Row className="contract-detail-container" gutter={[16, 0]}>
{/* Left Column */}
<Col span={12}>
<Row gutter={[16, 16]}>
<Col span={24}>
<ExpandableCard
cardProps={{
@ -403,109 +429,27 @@ const ContractDetail: React.FC<{
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.contract-status')}
{t('label.entity-detail-plural', {
entity: t('label.contract'),
})}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.contract-status-description')}
</Typography.Text>
</div>
),
}}>
{isLoading ? (
<Loading />
) : (
<>
{latestContractResults?.result && (
<AlertBar
defafultExpand
className="h-full m-b-md"
message={latestContractResults.result}
type="error"
/>
)}
{constraintStatus.map((item) => (
<div
className="contract-status-card-item d-flex justify-between items-center"
key={item.label}>
<div className="d-flex items-center">
<Icon
className="contract-status-card-icon"
component={item.icon}
data-testid={`${item.label}-icon`}
/>
<div className="d-flex flex-column m-l-md">
<Typography.Text className="contract-status-card-label">
{item.label}
</Typography.Text>
<div>
<Typography.Text className="contract-status-card-desc">
{item.desc}
</Typography.Text>
<Typography.Text className="contract-status-card-time">
{item.time}
</Typography.Text>
</div>
</div>
</div>
<StatusBadgeV2
label={item.status}
status={getContractStatusType(item.status)}
/>
</div>
))}
</>
)}
</ExpandableCard>
</Col>
)}
{/* Semantics Card */}
{contract?.semantics && contract?.semantics.length > 0 && (
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.semantic-plural')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.semantics-description')}
{t('message.expected-schema-structure-of-this-asset')}
</Typography.Text>
</div>
),
}}>
<div className="expandable-card-contract-body">
<Typography.Text className="card-subtitle">
{t('label.custom-integrity-rules')}
</Typography.Text>
<div className="rule-item-container">
{(contract?.semantics ?? []).map((item) => (
<div className="rule-item">
<Icon
className="rule-icon"
component={getSemanticIconPerLastExecution(
item.name
)}
/>
<span className="rule-name">{item.name}</span>{' '}
<span className="rule-description">
{item.description}
</span>
</div>
))}
</div>
<DescriptionV1
description={contract.description}
entityType={EntityType.DATA_CONTRACT}
showCommentsIcon={false}
showSuggestions={false}
/>
</div>
</ExpandableCard>
</Col>
)}
{/* Quality Card */}
{contract?.testSuite?.id && (
<Col span={24}>
<ExpandableCard
cardProps={{
@ -513,86 +457,235 @@ const ContractDetail: React.FC<{
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.quality')}
{t('label.schema')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.data-quality-test-contract-title')}
{t('message.expected-schema-structure-of-this-asset')}
</Typography.Text>
</div>
),
}}>
<div className="expandable-card-contract-body">
{isTestCaseLoading ? (
<Table
columns={schemaColumns}
dataSource={schemaDetail}
pagination={false}
rowKey="name"
size="small"
/>
</ExpandableCard>
</Col>
</Row>
</Col>
{/* Right Column */}
<Col span={12}>
{/* Contract Status Card */}
<Row gutter={[16, 16]}>
{contract?.latestResult?.resultId && (
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.contract-status')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.contract-status-description')}
</Typography.Text>
</div>
),
}}>
{isLoading ? (
<Loading />
) : (
<div className="data-quality-card-container">
<div className="data-quality-chart-container">
{testCaseSummaryChartItems.map((item) => (
<div
className="data-quality-chart-item"
key={item.label}>
<Typography.Text className="chart-label">
{item.label}
</Typography.Text>
<>
{showContractStatusAlert && (
<AlertBar
defafultExpand
className="h-full m-b-md"
message={latestContractResults?.result ?? ''}
type="error"
/>
)}
<PieChart height={120} width={120}>
<Pie
cx="50%"
cy="50%"
data={item.chartData}
dataKey="value"
innerRadius={40}
outerRadius={50}>
{item.chartData.map((entry, index) => (
<Cell
fill={entry.color}
key={`cell-${index}`}
/>
))}
</Pie>
<text
className="chart-center-text"
dominantBaseline="middle"
textAnchor="middle"
x="50%"
y="50%">
{item.value}
</text>
</PieChart>
</div>
))}
</div>
<Space direction="vertical">
{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}
{constraintStatus.map((item) => (
<div
className="contract-status-card-item d-flex justify-between items-center"
key={item.label}>
<div className="d-flex items-center">
<Icon
className="contract-status-card-icon"
component={item.icon}
data-testid={`${item.label}-icon`}
/>
<div className="d-flex flex-column m-l-md">
<Typography.Text className="contract-status-card-label">
{item.label}
</Typography.Text>
<div>
<Typography.Text className="contract-status-card-desc">
{item.desc}
</Typography.Text>
<Typography.Text className="data-quality-item-description">
<RichTextEditorPreviewerNew
markdown={item.description ?? ''}
/>
<Typography.Text className="contract-status-card-time">
{item.time}
</Typography.Text>
</div>
</div>
);
})}
</Space>
</div>
</div>
<StatusBadgeV2
label={item.status}
status={getContractStatusType(item.status)}
/>
</div>
))}
</>
)}
</div>
</ExpandableCard>
</Col>
)}
</Row>
</Col>
</Row>
</>
</ExpandableCard>
</Col>
)}
{/* Semantics Card */}
{contract?.semantics && contract?.semantics.length > 0 && (
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.semantic-plural')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.semantics-description')}
</Typography.Text>
</div>
),
}}>
<div className="expandable-card-contract-body">
<Typography.Text className="card-subtitle">
{t('label.custom-integrity-rules')}
</Typography.Text>
<div className="rule-item-container">
{(contract?.semantics ?? []).map((item) => (
<div className="rule-item">
<Icon
className={classNames('rule-icon', {
'rule-icon-default': !latestContractResults,
})}
component={getSemanticIconPerLastExecution(
item.name
)}
/>
<span className="rule-name">{item.name}</span>{' '}
<span className="rule-description">
{item.description}
</span>
</div>
))}
</div>
</div>
</ExpandableCard>
</Col>
)}
{/* Quality Card */}
{contract?.testSuite?.id && (
<Col span={24}>
<ExpandableCard
cardProps={{
className: 'expandable-card-contract',
title: (
<div className="contract-card-title-container">
<Typography.Text className="contract-card-title">
{t('label.quality')}
</Typography.Text>
<Typography.Text className="contract-card-description">
{t('message.data-quality-test-contract-title')}
</Typography.Text>
</div>
),
}}>
<div className="expandable-card-contract-body">
{isTestCaseLoading ? (
<Loading />
) : (
<div className="data-quality-card-container">
{showTestCaseSummaryChart && (
<div className="data-quality-chart-container">
{testCaseSummaryChartItems.map((item) => (
<div
className="data-quality-chart-item"
key={item.label}>
<Typography.Text className="chart-label">
{item.label}
</Typography.Text>
<PieChart height={120} width={120}>
<Pie
cx="50%"
cy="50%"
data={item.chartData}
dataKey="value"
innerRadius={40}
outerRadius={50}>
{item.chartData.map((entry, index) => (
<Cell
fill={entry.color}
key={`cell-${index}`}
/>
))}
</Pie>
<text
className="chart-center-text"
dominantBaseline="middle"
textAnchor="middle"
x="50%"
y="50%">
{item.value}
</text>
</PieChart>
</div>
))}
</div>
)}
<Space direction="vertical">
{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>
);
})}
</Space>
</div>
)}
</div>
</ExpandableCard>
</Col>
)}
</Row>
</Col>
</Row>
)}
</Card>
);
};

View File

@ -63,6 +63,43 @@
border-color: @primary-color;
}
}
.ant-btn.delete-button {
background-color: transparent;
border-color: @error-color;
svg {
width: 20px;
color: @error-color;
}
&:hover {
background-color: @red-9;
}
}
.ant-btn.contract-edit-button {
font-weight: @font-semibold;
span {
margin-left: 6px;
}
}
}
.contract-status-badge-container {
margin-top: 4px;
display: flex;
align-items: center;
gap: 8px;
.status-badge {
padding: 2px 12px;
.status-badge-label {
font-size: 14px;
font-weight: 500;
}
}
}
}
@ -85,10 +122,6 @@
}
}
.schema-description {
gap: 8px !important;
}
.expandable-card-contract {
background: @grey-50;
@ -114,22 +147,13 @@
}
}
.delete-button {
background: transparent !important;
border-color: @error-color !important;
svg {
width: 20px;
color: @error-color;
}
}
.contract-status-card-item {
width: 100%;
padding: 12px 16px;
border-radius: 8px;
border: 1px solid @border-color;
margin-bottom: 12px;
background: @white;
.contract-status-card-icon {
font-size: 20px;
@ -167,8 +191,13 @@
gap: 4px;
.rule-icon {
font-size: 20px;
font-size: 32px;
margin-right: 4px;
color: transparent;
&.rule-icon-default {
font-size: 22px;
}
}
.rule-name {
@ -199,6 +228,7 @@
border: 1px solid @border-color-8;
border-radius: @card-radius;
padding: @padding-md;
background: @white;
box-shadow: @button-box-shadow-default;
.data-quality-chart-item {
@ -252,3 +282,11 @@
text-wrap: auto;
border: none;
}
.ant-tag.custom-tag.ant-tag-purple {
background-color: @purple-6;
color: @purple-5;
}
.ant-tag.custom-tag.ant-tag-blue {
background-color: @blue-33;
color: @blue-34;
}

View File

@ -11,13 +11,14 @@
* limitations under the License.
*/
import Icon, { ArrowLeftOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons';
import { Button, Card, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import { toLower } from 'lodash';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as LeftOutlined } from '../../../assets/svg/left-arrow.svg';
import { ReactComponent as PlusIcon } from '../../../assets/svg/x-colored.svg';
import { DEFAULT_SORT_ORDER } from '../../../constants/profiler.constant';
import { EntityType, TabSpecificField } from '../../../enums/entity.enum';
@ -36,7 +37,7 @@ import { TEST_LEVEL_OPTIONS } from '../../../utils/DataQuality/DataQualityUtils'
import { generateEntityLink } from '../../../utils/TableUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface';
import { SelectionCard } from '../../common/SelectionCardGroup/SelectionCardGroup';
import SelectionCardGroup from '../../common/SelectionCardGroup/SelectionCardGroup';
import StatusBadge from '../../common/StatusBadge/StatusBadge.component';
import { StatusType } from '../../common/StatusBadge/StatusBadge.interface';
import Table from '../../common/Table/Table';
@ -203,7 +204,7 @@ export const ContractQualityFormTab: React.FC<{
</div>
<Button
className="add-test-case-button"
className="contract-export-button"
data-testid="add-test-button"
icon={<Icon className="anticon" component={PlusIcon} />}
onClick={handleOpenTestCaseDrawer}>
@ -214,16 +215,11 @@ export const ContractQualityFormTab: React.FC<{
</div>
<div className="contract-form-content-container ">
<div className="w-full selection-card-group">
{TEST_LEVEL_OPTIONS.map((option) => (
<SelectionCard
isSelected={testType === option.value}
key={option.value}
option={option}
onClick={() => setTestType(option.value as TestCaseType)}
/>
))}
</div>
<SelectionCardGroup
options={TEST_LEVEL_OPTIONS}
value={testType}
onChange={(value) => setTestType(value as TestCaseType)}
/>
<Table
columns={columns}
customPaginationProps={paginationProps}
@ -242,7 +238,11 @@ export const ContractQualityFormTab: React.FC<{
</div>
<div className="d-flex justify-between m-t-md">
<Button icon={<ArrowLeftOutlined />} type="default" onClick={onPrev}>
<Button
className="contract-prev-button"
icon={<LeftOutlined height={22} width={20} />}
type="default"
onClick={onPrev}>
{prevLabel ?? t('label.previous')}
</Button>
</div>

View File

@ -14,13 +14,7 @@
@import (reference) url('../../../styles/variables.less');
.contract-quality-form-tab-container {
.add-test-case-button {
font-weight: 600;
color: @grey-700;
border: 1px solid @grey-300 !important;
box-shadow: 0px 1px 2px rgba(10, 13, 18, 0.05),
inset 0px -2px 0px rgba(10, 13, 18, 0.05);
.contract-export-button {
.anticon {
transform: rotate(45deg);
}

View File

@ -10,12 +10,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import { Button, Card, Tag, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { isEmpty } from 'lodash';
import { isEmpty, pick } from 'lodash';
import { Key, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as LeftOutlined } from '../../../assets/svg/left-arrow.svg';
import { ReactComponent as RightOutlined } from '../../../assets/svg/right-arrow.svg';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import {
NO_DATA_PLACEHOLDER,
@ -213,6 +214,7 @@ export const ContractSchemaFormTab: React.FC<{
render: (tags: TagLabel[], record: Column, index: number) => (
<TableTags<Column>
isReadOnly
newLook
entityFqn={tableFqn}
entityType={EntityType.TABLE}
handleTagSelection={() => Promise.resolve()}
@ -228,19 +230,37 @@ export const ContractSchemaFormTab: React.FC<{
title: t('label.glossary-term-plural'),
dataIndex: TABLE_COLUMNS_KEYS.TAGS,
key: TABLE_COLUMNS_KEYS.GLOSSARY,
render: (tags: TagLabel[], record: Column, index: number) => (
<TableTags<Column>
isReadOnly
entityFqn={tableFqn}
entityType={EntityType.TABLE}
handleTagSelection={() => Promise.resolve()}
hasTagEditAccess={false}
index={index}
record={record}
tags={tags}
type={TagSource.Glossary}
/>
),
render: (tags: TagLabel[], record: Column, index: number) => {
// To remove Source from the tag so that we can have consistant tag icon
const newTags = tags.map((tag) => {
return {
tagFQN: tag.tagFQN,
...pick(
tag,
'description',
'displayName',
'labelType',
'name',
'style'
),
} as TagLabel;
});
return (
<TableTags<Column>
isReadOnly
newLook
entityFqn={tableFqn}
entityType={EntityType.TABLE}
handleTagSelection={() => Promise.resolve()}
hasTagEditAccess={false}
index={index}
record={record}
tags={newTags}
type={TagSource.Glossary}
/>
);
},
},
{
title: t('label.constraint-plural'),
@ -249,7 +269,7 @@ export const ContractSchemaFormTab: React.FC<{
render: renderConstraint,
},
],
[]
[tableFqn]
);
useEffect(() => {
@ -281,12 +301,19 @@ export const ContractSchemaFormTab: React.FC<{
/>
</Card>
<div className="d-flex justify-between m-t-md">
<Button icon={<ArrowLeftOutlined />} type="default" onClick={onPrev}>
<Button
className="contract-prev-button"
icon={<LeftOutlined height={22} width={20} />}
type="default"
onClick={onPrev}>
{prevLabel ?? t('label.previous')}
</Button>
<Button type="primary" onClick={onNext}>
<Button
className="contract-next-button"
type="primary"
onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
<RightOutlined height={15} width={8} />
</Button>
</div>
</>

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import Icon, { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons';
import { Actions } from '@react-awesome-query-builder/antd';
import { FieldErrorProps } from '@rjsf/utils';
import { Button, Col, Form, Input, Row, Switch, Typography } from 'antd';
@ -21,6 +21,9 @@ import classNames from 'classnames';
import { isNull } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-trash.svg';
import { ReactComponent as LeftOutlined } from '../../../assets/svg/left-arrow.svg';
import { ReactComponent as RightOutlined } from '../../../assets/svg/right-arrow.svg';
import { ReactComponent as PlusIcon } from '../../../assets/svg/x-colored.svg';
import { EntityType } from '../../../enums/entity.enum';
import { DataContract } from '../../../generated/entity/data/dataContract';
@ -40,7 +43,10 @@ export const ContractSemanticFormTab: React.FC<{
}> = ({ onChange, onNext, onPrev, nextLabel, prevLabel, initialValues }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const semanticsData = Form.useWatch('semantics', form);
const semanticsFormData: DataContract['semantics'] = Form.useWatch(
'semantics',
form
);
const [editingKey, setEditingKey] = useState<number | null>(null);
const [queryBuilderAddRule, setQueryBuilderAddRule] = useState<Actions>();
const addFunctionRef = useRef<((defaultValue?: any) => void) | null>(null);
@ -56,32 +62,47 @@ export const ContractSemanticFormTab: React.FC<{
rule: '',
enabled: true,
});
setEditingKey(semanticsData.length);
setEditingKey(semanticsFormData?.length ?? 0);
};
const handleDeleteSemantic = useCallback(
(key: number) => {
const filteredValue = semanticsFormData?.filter(
(_, index) => index !== key
);
form.setFieldsValue({
semantics: filteredValue,
});
onChange({
semantics: filteredValue,
});
},
[semanticsFormData]
);
const handleAddNewRule = useCallback(() => {
queryBuilderAddRule?.addRule([]);
}, [queryBuilderAddRule]);
useEffect(() => {
form.setFieldsValue({
semantics: [
{
name: '',
description: '',
enabled: true,
rule: '',
},
],
});
}, []);
useEffect(() => {
if (initialValues?.semantics) {
form.setFieldsValue({
semantics: initialValues.semantics,
});
} else {
form.setFieldsValue({
semantics: [
{
name: '',
description: '',
enabled: true,
rule: '',
},
],
});
}
setEditingKey(0);
}, [initialValues]);
return (
@ -110,6 +131,7 @@ export const ContractSemanticFormTab: React.FC<{
</div>
<Form
className="new-form-style"
form={form}
layout="vertical"
onValuesChange={(_, allValues) => {
@ -134,34 +156,50 @@ export const ContractSemanticFormTab: React.FC<{
title: (
<div className="w-full d-flex justify-between items-center">
{editingKey === field.key ? null : (
<>
<div className="semantic-form-item-title-container">
<div className="d-flex items-center gap-6">
<Form.Item
{...field}
className="enable-form-item"
name={[field.name, 'enabled']}
valuePropName="checked">
<Switch />
</Form.Item>
<div className="d-flex flex-column">
<Typography.Text>
{semanticsData[field.key]?.name ||
<Typography.Text className="semantic-form-item-title">
{semanticsFormData?.[field.key]?.name ||
t('label.untitled')}
</Typography.Text>
<Typography.Text type="secondary">
{semanticsData[field.key]
<Typography.Text
ellipsis
className="semantic-form-item-description">
{semanticsFormData?.[field.key]
?.description ||
t('label.no-description')}
</Typography.Text>
</div>
</div>
<EditIconButton
newLook
data-testid={`edit-semantic=${field.key}`}
size="small"
onClick={() => setEditingKey(field.key)}
/>
</>
<div className="d-flex items-center gap-2">
<EditIconButton
newLook
className="edit-expand-button"
data-testid={`edit-semantic-${field.key}`}
size="middle"
onClick={() => setEditingKey(field.key)}
/>
<Button
danger
className="delete-expand-button"
icon={<DeleteIcon />}
size="middle"
onClick={() => {
handleDeleteSemantic(field.key);
}}
/>
</div>
</div>
)}
</div>
),
@ -188,13 +226,20 @@ export const ContractSemanticFormTab: React.FC<{
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
{...field}
label={t('label.enabled')}
name={[field.name, 'enabled']}
valuePropName="checked">
<Switch />
</Form.Item>
<div className="d-flex gap-2 items-center m-b-md">
<Form.Item
{...field}
className="m-b-0"
name={[field.name, 'enabled']}
valuePropName="checked">
<Switch />
</Form.Item>
<Typography.Paragraph className="font-medium m-0">
{t('label.enable-entity', {
entity: t('label.semantic-plural'),
})}
</Typography.Paragraph>
</div>
</Col>
<Col span={24}>
<Form.Item
@ -211,12 +256,6 @@ export const ContractSemanticFormTab: React.FC<{
getQueryActions={handleAddQueryBuilderRule}
id="rule"
name={`${field.name}.rule`}
options={{
addButtonText: t('label.add-semantic'),
removeButtonText: t(
'label.remove-semantic'
),
}}
registry={{} as FieldErrorProps['registry']}
schema={{
outputType: SearchOutputType.JSONLogic,
@ -263,7 +302,7 @@ export const ContractSemanticFormTab: React.FC<{
schema={{
outputType: SearchOutputType.JSONLogic,
}}
value={semanticsData[field.key]?.rule ?? {}}
value={semanticsFormData?.[field.key]?.rule ?? {}}
/>
</div>
)}
@ -278,12 +317,18 @@ export const ContractSemanticFormTab: React.FC<{
</Card>
<div className="d-flex justify-between m-t-md">
<Button icon={<ArrowLeftOutlined />} onClick={onPrev}>
<Button
className="contract-prev-button"
icon={<LeftOutlined height={22} width={20} />}
onClick={onPrev}>
{prevLabel ?? t('label.previous')}
</Button>
<Button type="primary" onClick={onNext}>
<Button
className="contract-next-button"
type="primary"
onClick={onNext}>
{nextLabel ?? t('label.next')}
<ArrowRightOutlined />
<RightOutlined height={15} width={8} />
</Button>
</div>
</>

View File

@ -24,21 +24,92 @@
}
.contract-semantic-form-container {
.expanded {
.ant-card.expanded {
.ant-card-head {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.expandable-card {
.ant-card.new-header-border-card.expandable-card {
margin-top: 16px;
border: 1px solid @border-color-7 !important;
border: 1px solid @border-color-7;
box-shadow: 0 1px 2px 0 @grey-27;
.ant-card-head {
background: @white !important;
border-bottom: 1px solid @grey-200 !important;
background: @white;
border-bottom: 1px solid @grey-200;
.semantic-form-item-title-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 12px;
.enable-form-item {
margin-bottom: 0;
}
> div:first-child {
flex: 1;
min-width: 0;
overflow: hidden;
}
.semantic-form-item-title {
font-size: 16px;
font-weight: 600;
line-height: 32px;
}
.semantic-form-item-description {
font-size: 14px;
font-weight: 400;
color: @grey-600;
}
.ant-btn.edit-expand-button {
border-radius: 6px;
border: 1px solid @border-light;
svg {
font-size: 20px;
color: @grey-600;
}
}
.ant-btn.delete-expand-button {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border-radius: 6px;
background: transparent;
border-color: @error-color;
svg {
width: 20px;
color: @error-color;
}
}
}
.ant-card-extra {
.ant-btn.expand-collapse-icon {
width: 30px;
height: 30px;
border-radius: 6px;
border: 1px solid @border-light;
svg {
font-size: 20px;
color: @grey-600;
}
}
}
}
}
@ -77,6 +148,8 @@
.ant-btn-group {
.action--DELETE {
border: 1px solid @grey-34;
height: 40px;
width: 40px;
.anticon {
color: @grey-400;
@ -92,9 +165,14 @@
}
.semantic-rule-editor-view-only {
.ant-divider,
.group--conjunctions {
display: none !important;
.ant-divider {
display: none;
}
.query-builder {
.group--conjunctions {
display: none;
}
}
.ant-card-body {

View File

@ -26,6 +26,7 @@ import Loader from '../../common/Loader/Loader';
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
import AddDataContract from '../AddDataContract/AddDataContract';
import { ContractDetail } from '../ContractDetailTab/ContractDetail';
import './contract-tab.less';
export const ContractTab = () => {
const {
@ -118,5 +119,9 @@ export const ContractTab = () => {
}
}, [tabMode, contract]);
return isLoading ? <Loader /> : content;
return isLoading ? (
<Loader />
) : (
<div className="contract-tab-container">{content}</div>
);
};

View File

@ -0,0 +1,43 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.contract-tab-container {
.contract-divider {
height: 24px;
border-left: 1px solid @grey-15;
}
.ant-btn.ant-btn-default.contract-export-button {
font-weight: 600;
color: @grey-700;
border: 1px solid @grey-300;
box-shadow: 0px 1px 2px rgba(10, 13, 18, 0.05),
inset 0px -2px 0px rgba(10, 13, 18, 0.05);
.anticon {
transform: rotate(45deg);
}
&:hover {
color: @grey-700;
background-color: @grey-50;
}
&:focus {
color: @grey-700;
background-color: @grey-50;
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Radio, RadioChangeEvent } from 'antd';
import { ReactComponent as BookOutline } from '../../../assets/svg/bookoutline.svg';
import { ReactComponent as CodeOutline } from '../../../assets/svg/codeOutline.svg';
import { DE_ACTIVE_COLOR } from '../../../constants/constants';
import { DataContractMode } from '../../../constants/DataContract.constants';
import './contract-view-switch-tab.less';
const ContractViewSwitchTab = ({
mode,
handleModeChange,
}: {
mode: DataContractMode;
handleModeChange: (e: RadioChangeEvent) => void;
}) => {
return (
<Radio.Group
className="contract-mode-radio-group"
optionType="button"
options={[
{
label: (
<CodeOutline
className="align-middle"
color={DE_ACTIVE_COLOR}
height={20}
width={20}
/>
),
value: DataContractMode.YAML,
},
{
label: (
<BookOutline
className="align-middle"
color={DE_ACTIVE_COLOR}
height={20}
width={20}
/>
),
value: DataContractMode.UI,
},
]}
value={mode}
onChange={handleModeChange}
/>
);
};
export default ContractViewSwitchTab;

View File

@ -0,0 +1,35 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.contract-mode-radio-group {
border-radius: @size-xs;
border: 1px solid @border-color-7;
padding: 4px;
overflow: hidden;
.ant-radio-button-wrapper:first-child {
border-radius: @size-xs 0 0 @size-xs;
}
.ant-radio-button-wrapper:last-child {
border-radius: 0 @size-xs @size-xs 0;
}
.ant-radio-button-wrapper-checked {
svg {
path {
stroke: @primary-7;
}
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import yaml from 'js-yaml';
import { useMemo } from 'react';
import { CSMode } from '../../../enums/codemirror.enum';
import { DataContract } from '../../../generated/entity/data/dataContract';
import { getUpdatedContractDetails } from '../../../utils/DataContract/DataContractUtils';
import SchemaEditor from '../../Database/SchemaEditor/SchemaEditor';
import './contract-yaml.less';
const ContractYaml = ({ contract }: { contract: DataContract }) => {
const schemaEditorValue = useMemo(() => {
return yaml.dump(getUpdatedContractDetails(contract, contract));
}, [contract]);
return (
<div className="contract-yaml-container">
<SchemaEditor
className="contract-yaml-schema-editor"
editorClass="custom-entity-schema"
mode={{ name: CSMode.YAML }}
value={schemaEditorValue}
/>
</div>
);
};
export default ContractYaml;

View File

@ -0,0 +1,30 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.contract-yaml-container {
width: 100%;
.contract-yaml-schema-editor {
border-radius: 12px;
border: 1px solid @grey-200;
overflow: hidden;
}
.contract-schema-editor > .CodeMirror {
height: calc(
100vh - @data-asset-header-height - @tab-height - @om-navbar-height -
@size-xs - 120px
);
}
}

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import Icon from '@ant-design/icons';
import { Button, Tooltip } from 'antd';
import classNames from 'classnames';
import { Editor, EditorChange } from 'codemirror';
@ -29,7 +30,7 @@ import { isUndefined } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import { useTranslation } from 'react-i18next';
import { ReactComponent as CopyIcon } from '../../../assets/svg/icon-copy.svg';
import { ReactComponent as CopyIcon } from '../../../assets/svg/ic-duplicate.svg';
import { JSON_TAB_SIZE } from '../../../constants/constants';
import { CSMode } from '../../../enums/codemirror.enum';
import { useClipboard } from '../../../hooks/useClipBoard';
@ -122,7 +123,7 @@ const SchemaEditor = ({
return (
<div
className={classNames('relative', className)}
className={classNames('schema-editor-container relative', className)}
data-testid="code-mirror-container">
{showCopyButton && (
<div className="query-editor-button">
@ -131,9 +132,9 @@ const SchemaEditor = ({
hasCopied ? t('label.copied') : t('message.copy-to-clipboard')
}>
<Button
className="flex-center bg-white"
className="query-editor-copy-button"
data-testid="query-copy-button"
icon={<CopyIcon height={16} width={16} />}
icon={<Icon component={CopyIcon} />}
onClick={onCopyToClipBoard}
/>
</Tooltip>

View File

@ -11,9 +11,22 @@
* limitations under the License.
*/
.query-editor-button {
position: absolute;
z-index: 10;
top: 14px;
right: 14px;
@import (reference) url('../../../styles/variables.less');
.schema-editor-container {
.query-editor-button {
position: absolute;
z-index: 10;
top: 14px;
right: 14px;
}
.query-editor-copy-button {
background: @white;
color: @grey-600;
svg {
font-size: 16px;
}
}
}

View File

@ -31,6 +31,7 @@ const TableTags = <T extends TableUnion>({
showInlineEditTagButton,
handleTagSelection,
entityType,
newLook = false,
}: TableTagsComponentProps<T>) => {
const { onThreadLinkSelect, updateActiveTagDropdownKey } =
useGenericContext();
@ -54,6 +55,7 @@ const TableTags = <T extends TableUnion>({
selectedTags={tags}
showInlineEditButton={showInlineEditTagButton}
sizeCap={TAG_LIST_SIZE}
tagNewLook={newLook}
tagType={type}
onSelectionChange={async (selectedTags) => {
await handleTagSelection(selectedTags, record);

View File

@ -34,6 +34,7 @@ export interface TableTagsComponentProps<T> {
selectedTags: EntityTags[],
editColumnTag: T
) => Promise<void>;
newLook?: boolean;
}
export interface TableTagsProps {

View File

@ -37,4 +37,5 @@ export interface TagsContainerV2Props {
newLook?: boolean;
// Props to control the dropdown state from the Generic Provider
useGenericControls?: boolean;
tagNewLook?: boolean;
}

View File

@ -76,6 +76,7 @@ const TagsContainerV2 = ({
newLook = false,
sizeCap = LIST_SIZE,
useGenericControls,
tagNewLook = false,
}: TagsContainerV2Props) => {
const navigate = useNavigate();
const [form] = Form.useForm();
@ -231,6 +232,7 @@ const TagsContainerV2 = ({
<Col span={24}>
<TagsViewer
displayType={displayType}
newLook={tagNewLook}
showNoDataPlaceholder={showNoDataPlaceholder}
sizeCap={sizeCap}
tagType={tagType}
@ -388,6 +390,7 @@ const TagsContainerV2 = ({
) : null}
<TagsViewer
displayType={displayType}
newLook={newLook}
showNoDataPlaceholder={showNoDataPlaceholder}
sizeCap={sizeCap}
tags={tags?.[tagType] ?? []}

View File

@ -120,11 +120,26 @@ const TagsV1 = ({
[color]
);
const tagChipStyleClass = useMemo(() => {
if (newLook && !tag.style?.color) {
return 'new-chip-style';
}
if (newLook && tag.style?.color) {
return 'new-chip-style-with-color';
}
return '';
}, [newLook, tag.style?.color]);
const tagContent = useMemo(
() => (
<div className="d-flex w-full h-full">
{tagColorBar}
<div className="d-flex items-center p-x-xs w-full">
<div
className={classNames(
'd-flex items-center p-x-xs w-full',
tagChipStyleClass
)}>
{tag.style?.iconURL ? (
<img
className="m-r-xss"
@ -155,12 +170,14 @@ const TagsV1 = ({
<Tag
className={classNames(
className,
'tag-chip tag-chip-content',
tagChipStyleClass,
{
'tag-highlight': Boolean(
(tag as HighlightedTagLabel).isHighlighted
),
},
'tag-chip tag-chip-content',
size,
'cursor-pointer'
)}

View File

@ -52,7 +52,34 @@
margin: inherit;
}
}
.ant-tag.tag-chip-content.new-chip-style {
.ant-select-selection-item {
border: 1px solid @blue-30;
border-radius: @border-rad-xs;
background: @blue-29;
height: 30px;
}
.ant-select-selection-item {
display: flex;
align-items: center;
}
border: 1px solid @blue-30;
background: @blue-29;
border-radius: @border-rad-xs;
.tags-label {
color: @tag-text;
font-weight: @font-regular;
}
}
.ant-tag.tag-chip-content.new-chip-style-with-color {
border: 1px solid @blue-30;
border-radius: @border-rad-xs;
.tags-label {
color: @tag-text;
font-weight: @font-regular;
}
}
.ant-tag.tag-chip-add-button {
padding: 3px 8px;
background: @white;

View File

@ -20,6 +20,7 @@ export interface TagsViewerProps {
displayType?: DisplayType;
showNoDataPlaceholder?: boolean;
tagType?: TagSource;
newLook?: boolean;
}
export enum DisplayType {

View File

@ -29,6 +29,7 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
sizeCap = LIST_SIZE,
displayType = DisplayType.POPOVER,
showNoDataPlaceholder = true,
newLook = false,
}: TagsViewerProps) => {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
@ -42,6 +43,7 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
)}
isVersionPage={tag?.added || tag?.removed}
key={tag.tagFQN}
newLook={newLook}
showOnlyName={tag.source === TagSource.Glossary}
startWith={TAG_START_WITH.SOURCE_ICON}
tag={tag}

View File

@ -38,15 +38,14 @@ import { debounce, isEmpty, isUndefined } from 'lodash';
import Qs from 'qs';
import { FC, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
EntityFields,
EntityReferenceFields,
} from '../../../../../../enums/AdvancedSearch.enum';
import { EntityType } from '../../../../../../enums/entity.enum';
import { SearchIndex } from '../../../../../../enums/search.enum';
import { QueryFilterInterface } from '../../../../../../pages/ExplorePage/ExplorePage.interface';
import { searchQuery } from '../../../../../../rest/searchAPI';
import { getEmptyJsonTreeForQueryBuilder } from '../../../../../../utils/AdvancedSearchUtils';
import {
getEmptyJsonTree,
getEmptyJsonTreeForQueryBuilder,
} from '../../../../../../utils/AdvancedSearchUtils';
import { elasticSearchFormat } from '../../../../../../utils/QueryBuilderElasticsearchFormatUtils';
import {
addEntityTypeFilter,
@ -201,16 +200,12 @@ const QueryBuilderWidget: FC<WidgetProps> = ({
}
}
} else {
const emptyJsonTree = getEmptyJsonTreeForQueryBuilder(
const emptyJsonTree =
outputType === SearchOutputType.JSONLogic
? EntityReferenceFields.OWNERS
: EntityFields.OWNERS
);
? getEmptyJsonTreeForQueryBuilder()
: getEmptyJsonTree();
const tree = QbUtils.Validation.sanitizeTree(
QbUtils.loadTree(emptyJsonTree),
config
).fixedTree;
const tree = QbUtils.loadTree(emptyJsonTree);
onTreeUpdate(tree, config);
}

View File

@ -535,6 +535,7 @@
"embed-link": "Link einbetten",
"enable": "Aktivieren",
"enable-debug-log": "Debug-Protokoll aktivieren",
"enable-entity": "Enable {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "aktivieren",
"enable-partition": "Partition aktivieren",

View File

@ -535,6 +535,7 @@
"embed-link": "Embed link",
"enable": "Enable",
"enable-debug-log": "Enable Debug Log",
"enable-entity": "Enable {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "enable",
"enable-partition": "Enable Partition",

View File

@ -535,6 +535,7 @@
"embed-link": "Insertar Link",
"enable": "Habilitar",
"enable-debug-log": "Activar logs con debug",
"enable-entity": "Habilitar {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "habilitar",
"enable-partition": "Habilitar partición",

View File

@ -535,6 +535,7 @@
"embed-link": "Incorporer lien",
"enable": "Activer",
"enable-debug-log": "Activer le Journal de Débogage",
"enable-entity": "Activer {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "activer",
"enable-partition": "Activer la Partition",

View File

@ -535,6 +535,7 @@
"embed-link": "Incrustar ligazón",
"enable": "Activar",
"enable-debug-log": "Activar rexistro de depuración",
"enable-entity": "Habilitar {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "activar",
"enable-partition": "Activar partición",

View File

@ -535,6 +535,7 @@
"embed-link": "הטמע קישור",
"enable": "הפעל",
"enable-debug-log": "הפעל יומן דיבוג",
"enable-entity": "הפעל {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "הפעל",
"enable-partition": "הפעל מחיצה",

View File

@ -535,6 +535,7 @@
"embed-link": "リンクを埋め込む",
"enable": "有効化",
"enable-debug-log": "デバッグログを有効化",
"enable-entity": "{{entity}} を有効化",
"enable-incident-management": "インシデント管理を有効化",
"enable-lowercase": "有効化",
"enable-partition": "パーティションを有効化",

View File

@ -535,6 +535,7 @@
"embed-link": "링크 삽입",
"enable": "활성화",
"enable-debug-log": "디버그 로그 활성화",
"enable-entity": "{{entity}} 활성화",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "활성화",
"enable-partition": "파티션 활성화",

View File

@ -535,6 +535,7 @@
"embed-link": "लिंक एम्बेड करा",
"enable": "सक्षम करा",
"enable-debug-log": "डिबग लॉग सक्षम करा",
"enable-entity": "{{entity}} सक्षम करा",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "सक्षम करा",
"enable-partition": "विभाजन सक्षम करा",

View File

@ -535,6 +535,7 @@
"embed-link": "Koppeling insluiten",
"enable": "Inschakelen",
"enable-debug-log": "Debuglog inschakelen",
"enable-entity": "{{entity}} inschakelen",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "inschakelen",
"enable-partition": "Partitie inschakelen",

View File

@ -535,6 +535,7 @@
"embed-link": "قرار دادن لینک",
"enable": "فعال کردن",
"enable-debug-log": "فعال کردن گزارش اشکال‌زدایی",
"enable-entity": "فعال کردن {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "فعال کردن",
"enable-partition": "فعال کردن پارتیشن",

View File

@ -535,6 +535,7 @@
"embed-link": "Incorporar link",
"enable": "Habilitar",
"enable-debug-log": "Habilitar Log de Depuração",
"enable-entity": "Habilitar {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "habilitar",
"enable-partition": "Habilitar Partição",

View File

@ -535,6 +535,7 @@
"embed-link": "Incorporar link",
"enable": "Habilitar",
"enable-debug-log": "Habilitar Log de Depuração",
"enable-entity": "Habilitar {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "habilitar",
"enable-partition": "Habilitar Partição",

View File

@ -535,6 +535,7 @@
"embed-link": "Встроить ссылку",
"enable": "Включить",
"enable-debug-log": "Включить журнал отладки",
"enable-entity": "Включить {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "включить",
"enable-partition": "Включить раздел",

View File

@ -535,6 +535,7 @@
"embed-link": "ฝังลิงก์",
"enable": "เปิดใช้งาน",
"enable-debug-log": "เปิดใช้งานบันทึกการดีบัก",
"enable-entity": "เปิดใช้งาน {{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "เปิดใช้งาน",
"enable-partition": "เปิดใช้งานการแบ่งส่วน",

View File

@ -535,6 +535,7 @@
"embed-link": "Bağlantı göm",
"enable": "Etkinleştir",
"enable-debug-log": "Hata Ayıklama Günlüğünü Etkinleştir",
"enable-entity": "{{entity}} Etkinleştir",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "etkinleştir",
"enable-partition": "Bölümü Etkinleştir",

View File

@ -535,6 +535,7 @@
"embed-link": "插入链接",
"enable": "启用",
"enable-debug-log": "启用调试日志",
"enable-entity": "启用{{entity}}",
"enable-incident-management": "Enable Incident Management",
"enable-lowercase": "启用",
"enable-partition": "启用分区",

View File

@ -90,6 +90,7 @@
@purple-3: #a2a1ff;
@purple-4: #efedfe80;
@purple-5: #6941c6;
@purple-6: #f9f5ff;
@blue-1: #ebf6fe;
@blue-2: #3ca2f4;
@blue-3: #0950c5;

View File

@ -33,7 +33,10 @@ import {
LINEAGE_DROPDOWN_ITEMS,
} from '../constants/AdvancedSearch.constants';
import { NOT_INCLUDE_AGGREGATION_QUICK_FILTER } from '../constants/explore.constants';
import { EntityFields } from '../enums/AdvancedSearch.enum';
import {
EntityFields,
EntityReferenceFields,
} from '../enums/AdvancedSearch.enum';
import { EntityType } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum';
import {
@ -451,23 +454,42 @@ export const getEmptyJsonTree = (
* This structure allows easy addition of groups and rules
*/
export const getEmptyJsonTreeForQueryBuilder = (
defaultField: string = EntityFields.OWNERS
defaultField: string = EntityReferenceFields.OWNERS
): OldJsonTree => {
const uuid1 = QbUtils.uuid();
const uuid2 = QbUtils.uuid();
const uuid3 = QbUtils.uuid();
return {
id: QbUtils.uuid(),
id: uuid1,
type: 'group',
properties: {
conjunction: 'AND',
not: false,
},
children1: {
[QbUtils.uuid()]: {
type: 'rule',
[uuid2]: {
type: 'rule_group',
id: uuid2,
properties: {
conjunction: 'AND',
not: false,
mode: 'some',
field: defaultField,
operator: null,
value: [],
valueSrc: ['value'],
fieldSrc: 'field',
},
children1: {
[uuid3]: {
type: 'rule',
id: uuid3,
properties: {
field: 'owners.fullyQualifiedName',
operator: 'select_equals',
value: [],
valueSrc: ['value'],
fieldSrc: 'field',
},
},
},
},
},

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import i18next from 'i18next';
import yaml from 'js-yaml';
import { omit } from 'lodash';
import { ReactComponent as QualityIcon } from '../../assets/svg/policies.svg';
import { ReactComponent as SemanticsIcon } from '../../assets/svg/semantics.svg';
@ -183,3 +184,17 @@ export const getUpdatedContractDetails = (
'incrementalChangeDescription',
]);
};
export const downloadContractYamlFile = (contract: DataContract) => {
const data = yaml.dump(getUpdatedContractDetails(contract, contract));
const element = document.createElement('a');
const file = new Blob([data], { type: 'text/plain' });
element.textContent = 'download-file';
element.href = URL.createObjectURL(file);
element.download = `${contract.name}.yaml`;
document.body.appendChild(element);
element.click();
URL.revokeObjectURL(element.href);
document.body.removeChild(element);
};

View File

@ -178,7 +178,7 @@ export const getField = (field: FieldProp) => {
break;
case FieldTypes.TAG_SUGGESTION:
fieldElement = (
<TagSuggestion {...(props as unknown as TagSuggestionProps)} />
<TagSuggestion {...(props as unknown as TagSuggestionProps)} newLook />
);
break;

View File

@ -4457,6 +4457,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/js-yaml@^4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.9"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz"
@ -10015,7 +10020,7 @@ js-yaml@^3.13.1:
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"