From adb0bacffb969bdcd23bfe15985be8ee40e05999 Mon Sep 17 00:00:00 2001 From: Dhruv Parmar <83108871+dhruvjsx@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:36:55 +0530 Subject: [PATCH] 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 Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com> --- .../src/main/resources/ui/package.json | 2 + .../ui/src/assets/svg/bookoutline.svg | 6 + .../ui/src/assets/svg/codeOutline.svg | 5 + .../ui/src/assets/svg/edit-new-thick.svg | 3 + .../ui/src/assets/svg/left-arrow.svg | 3 + .../DataAssetsHeader.component.tsx | 28 +- .../DataAssetsHeader/data-asset-header.less | 33 +- .../AddDataContract/AddDataContract.tsx | 41 +- .../AddDataContract/add-data-contract.less | 117 ++- .../ContractDetailFormTab.tsx | 11 +- .../ContractDetailTab/ContractDetail.tsx | 719 ++++++++++-------- .../ContractDetailTab/contract-detail.less | 68 +- .../ContractQualityFormTab.tsx | 28 +- .../contract-quality-form-tab.less | 8 +- .../ContractScehmaFormTab.tsx | 65 +- .../ContractSemanticFormTab.tsx | 135 ++-- .../contract-semantic-form-tab.less | 98 ++- .../DataContract/ContractTab/ContractTab.tsx | 7 +- .../ContractTab/contract-tab.less | 43 ++ .../ContractViewSwitchTab.component.tsx | 61 ++ .../contract-view-switch-tab.less | 35 + .../ContractYaml/ContractYaml.component.tsx | 38 + .../ContractYaml/contract-yaml.less | 30 + .../Database/SchemaEditor/SchemaEditor.tsx | 9 +- .../Database/SchemaEditor/schema-editor.less | 23 +- .../TableTags/TableTags.component.tsx | 2 + .../Database/TableTags/TableTags.interface.ts | 1 + .../TagsContainerV2.interface.ts | 1 + .../Tag/TagsContainerV2/TagsContainerV2.tsx | 3 + .../Tag/TagsV1/TagsV1.component.tsx | 21 +- .../ui/src/components/Tag/TagsV1/tagsV1.less | 27 + .../Tag/TagsViewer/TagsViewer.interface.ts | 1 + .../components/Tag/TagsViewer/TagsViewer.tsx | 2 + .../QueryBuilderWidget/QueryBuilderWidget.tsx | 21 +- .../ui/src/locale/languages/de-de.json | 1 + .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/gl-es.json | 1 + .../ui/src/locale/languages/he-he.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/ko-kr.json | 1 + .../ui/src/locale/languages/mr-in.json | 1 + .../ui/src/locale/languages/nl-nl.json | 1 + .../ui/src/locale/languages/pr-pr.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/pt-pt.json | 1 + .../ui/src/locale/languages/ru-ru.json | 1 + .../ui/src/locale/languages/th-th.json | 1 + .../ui/src/locale/languages/tr-tr.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../resources/ui/src/styles/variables.less | 1 + .../ui/src/utils/AdvancedSearchUtils.tsx | 38 +- .../utils/DataContract/DataContractUtils.ts | 15 + .../main/resources/ui/src/utils/formUtils.tsx | 2 +- .../src/main/resources/ui/yarn.lock | 7 +- 56 files changed, 1230 insertions(+), 545 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/bookoutline.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/codeOutline.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new-thick.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/left-arrow.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/contract-tab.less create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/ContractViewSwitchTab.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/contract-view-switch-tab.less create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/ContractYaml.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/contract-yaml.less diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 77be79eb4cf..47fd8a27ee0 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/bookoutline.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/bookoutline.svg new file mode 100644 index 00000000000..140e2de14e2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/bookoutline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/codeOutline.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/codeOutline.svg new file mode 100644 index 00000000000..d381243f7b8 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/codeOutline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new-thick.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new-thick.svg new file mode 100644 index 00000000000..4779273f02e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new-thick.svg @@ -0,0 +1,3 @@ + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/left-arrow.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/left-arrow.svg new file mode 100644 index 00000000000..3fdca6db631 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/left-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx index f8cbeb90a2b..8c00e3e1be8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx @@ -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 ( - - {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'), })} - + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/data-asset-header.less b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/data-asset-header.less index 84d393c45b2..4092d720fca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/data-asset-header.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/data-asset-header.less @@ -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; + } } } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.tsx index b907790080c..f5774ae0ab6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.tsx @@ -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) => { @@ -220,27 +211,13 @@ const AddDataContract: React.FC<{ const cardTitle = useMemo(() => { return (
-
-
- - {t('label.add-contract-detail-plural')} - - - {t('message.add-contract-detail-description')} - -
-
- , value: DataContractMode.YAML }, - { label: , value: DataContractMode.UI }, - ]} - value={mode} - onChange={handleModeChange} - /> - -
+
+ + {t('label.add-contract-detail-plural')} + + + {t('message.add-contract-detail-description')} +
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/ContractDetail.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/ContractDetail.tsx index 02cc1dc9d08..62f98f7a03c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/ContractDetail.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/ContractDetail.tsx @@ -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(null); + useState(); const [testCaseSummary, setTestCaseSummary] = useState(); const [testCaseResult, setTestCaseResult] = useState([]); + const [mode, setMode] = useState(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 ( + + + + {getEntityName(contract)} + + + + {t('message.modified-time-ago-by', { + time: getRelativeTime(contract.updatedAt), + by: contract.updatedBy, + })} + + +
+ + + +
+ + +
+ {!isEmpty(contract.owners) && ( +
+ {t('label.owner-plural')} + +
+ )} + + + + + + + + + + +
+ +
+ ); + }, [ + 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 */} - - - - - {getEntityName(contract)} - - - - {t('message.modified-time-ago-by', { - time: getRelativeTime(contract.updatedAt), - by: contract.updatedBy, - })} - - -
- - - -
- - -
- {!isEmpty(contract.owners) && ( -
- {t('label.owner-plural')} - -
- )} - - - -
- -
-
- - - {/* Left Column */} - - - - - - {t('label.entity-detail-plural', { - entity: t('label.contract'), - })} - - - {t('message.expected-schema-structure-of-this-asset')} - -
- ), - }}> -
- -
- - - - - - - {t('label.schema')} - - - {t('message.expected-schema-structure-of-this-asset')} - -
- ), - }}> - - - - - - - {/* Right Column */} - - {/* Contract Status Card */} - - - {contract?.latestResult?.resultId && ( + + {mode === DataContractMode.YAML ? ( + + ) : ( + + {/* Left Column */} + + - {t('label.contract-status')} + {t('label.entity-detail-plural', { + entity: t('label.contract'), + })} - {t('message.contract-status-description')} - - - ), - }}> - {isLoading ? ( - - ) : ( - <> - {latestContractResults?.result && ( - - )} - - {constraintStatus.map((item) => ( -
-
- - -
- - {item.label} - -
- - {item.desc} - - - {item.time} - -
-
-
- - -
- ))} - - )} -
- - )} - - {/* Semantics Card */} - {contract?.semantics && contract?.semantics.length > 0 && ( - - - - {t('label.semantic-plural')} - - - {t('message.semantics-description')} + {t('message.expected-schema-structure-of-this-asset')} ), }}>
- - {t('label.custom-integrity-rules')} - -
- {(contract?.semantics ?? []).map((item) => ( -
- - {item.name}{' '} - - {item.description} - -
- ))} -
+
- )} - {/* Quality Card */} - {contract?.testSuite?.id && ( - {t('label.quality')} + {t('label.schema')} - {t('message.data-quality-test-contract-title')} + {t('message.expected-schema-structure-of-this-asset')} ), }}> -
- {isTestCaseLoading ? ( +
+ + + + + + {/* Right Column */} + + {/* Contract Status Card */} + + + {contract?.latestResult?.resultId && ( + + + + {t('label.contract-status')} + + + {t('message.contract-status-description')} + + + ), + }}> + {isLoading ? ( ) : ( -
-
- {testCaseSummaryChartItems.map((item) => ( -
- - {item.label} - + <> + {showContractStatusAlert && ( + + )} - - - {item.chartData.map((entry, index) => ( - - ))} - - - {item.value} - - -
- ))} -
- - {testCaseResult.map((item) => { - return ( -
- {getTestCaseStatusIcon(item)} -
- - {item.name} + {constraintStatus.map((item) => ( +
+
+ + +
+ + {item.label} + +
+ + {item.desc} - - + + {item.time}
- ); - })} - -
+
+ + +
+ ))} + )} -
- - - )} - - - - + + + )} + + {/* Semantics Card */} + {contract?.semantics && contract?.semantics.length > 0 && ( +
+ + + {t('label.semantic-plural')} + + + {t('message.semantics-description')} + + + ), + }}> +
+ + {t('label.custom-integrity-rules')} + +
+ {(contract?.semantics ?? []).map((item) => ( +
+ + {item.name}{' '} + + {item.description} + +
+ ))} +
+
+
+ + )} + + {/* Quality Card */} + {contract?.testSuite?.id && ( + + + + {t('label.quality')} + + + {t('message.data-quality-test-contract-title')} + + + ), + }}> +
+ {isTestCaseLoading ? ( + + ) : ( +
+ {showTestCaseSummaryChart && ( +
+ {testCaseSummaryChartItems.map((item) => ( +
+ + {item.label} + + + + + {item.chartData.map((entry, index) => ( + + ))} + + + {item.value} + + +
+ ))} +
+ )} + + + {testCaseResult.map((item) => { + return ( +
+ {getTestCaseStatusIcon(item)} +
+ + {item.name} + + + + +
+
+ ); + })} +
+
+ )} +
+
+ + )} + + + + )} + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/contract-detail.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/contract-detail.less index a07ecd26bcd..f42a024a0cc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/contract-detail.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailTab/contract-detail.less @@ -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; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/ContractQualityFormTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/ContractQualityFormTab.tsx index 3d104fb7a05..086d0e2ae3c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/ContractQualityFormTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/ContractQualityFormTab.tsx @@ -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<{
-
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/contract-quality-form-tab.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/contract-quality-form-tab.less index 231975733b0..eb27b63ce62 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/contract-quality-form-tab.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractQualityFormTab/contract-quality-form-tab.less @@ -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); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSchemaFormTab/ContractScehmaFormTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSchemaFormTab/ContractScehmaFormTab.tsx index 0bfbc2b8807..be0c51da64d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSchemaFormTab/ContractScehmaFormTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSchemaFormTab/ContractScehmaFormTab.tsx @@ -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) => ( 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) => ( - - 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 ( + + 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<{ />
- -
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx index 4d06b1b2d90..1f0c2cc9d0f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx @@ -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(null); const [queryBuilderAddRule, setQueryBuilderAddRule] = useState(); 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<{
{ @@ -134,34 +156,50 @@ export const ContractSemanticFormTab: React.FC<{ title: (
{editingKey === field.key ? null : ( - <> +
- - {semanticsData[field.key]?.name || + + {semanticsFormData?.[field.key]?.name || t('label.untitled')} - - {semanticsData[field.key] + + {semanticsFormData?.[field.key] ?.description || t('label.no-description')}
- setEditingKey(field.key)} - /> - +
+ setEditingKey(field.key)} + /> + +
+
)}
), @@ -188,13 +226,20 @@ export const ContractSemanticFormTab: React.FC<{
- - - +
+ + + + + {t('label.enable-entity', { + entity: t('label.semantic-plural'), + })} + +
)} @@ -278,12 +317,18 @@ export const ContractSemanticFormTab: React.FC<{
- -
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/contract-semantic-form-tab.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/contract-semantic-form-tab.less index 0b3de7ce503..d05020e3694 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/contract-semantic-form-tab.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/contract-semantic-form-tab.less @@ -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 { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/ContractTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/ContractTab.tsx index 5845101e507..06da44375c3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/ContractTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/ContractTab.tsx @@ -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 ? : content; + return isLoading ? ( + + ) : ( +
{content}
+ ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/contract-tab.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/contract-tab.less new file mode 100644 index 00000000000..edb0ff7d216 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractTab/contract-tab.less @@ -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; + } + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/ContractViewSwitchTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/ContractViewSwitchTab.component.tsx new file mode 100644 index 00000000000..8aac053a9ea --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/ContractViewSwitchTab.component.tsx @@ -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 ( + + ), + value: DataContractMode.YAML, + }, + { + label: ( + + ), + value: DataContractMode.UI, + }, + ]} + value={mode} + onChange={handleModeChange} + /> + ); +}; + +export default ContractViewSwitchTab; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/contract-view-switch-tab.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/contract-view-switch-tab.less new file mode 100644 index 00000000000..3715cb42157 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractViewSwitchTab/contract-view-switch-tab.less @@ -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; + } + } + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/ContractYaml.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/ContractYaml.component.tsx new file mode 100644 index 00000000000..b9039831486 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/ContractYaml.component.tsx @@ -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 ( +
+ +
+ ); +}; + +export default ContractYaml; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/contract-yaml.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/contract-yaml.less new file mode 100644 index 00000000000..856ad4aeb91 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractYaml/contract-yaml.less @@ -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 + ); + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/SchemaEditor.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/SchemaEditor.tsx index b97b08d83e0..f917376f1fd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/SchemaEditor.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/SchemaEditor.tsx @@ -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 (
{showCopyButton && (
@@ -131,9 +132,9 @@ const SchemaEditor = ({ hasCopied ? t('label.copied') : t('message.copy-to-clipboard') }>
{ + 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( () => (
{tagColorBar} -
+
{tag.style?.iconURL ? ( = ({ 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 = ({ )} isVersionPage={tag?.added || tag?.removed} key={tag.tagFQN} + newLook={newLook} showOnlyName={tag.source === TagSource.Glossary} startWith={TAG_START_WITH.SOURCE_ICON} tag={tag} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index 4623c9074f0..7b9f16cb8f3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -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 = ({ } } } 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); } diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 80d754374e8..2a60c101ac0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 088c73d1b17..52e44a74ae3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index c8c9f8cdaf3..611f7951f5d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index a04b789e3d7..439a52006d8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json index dd77654c453..1ecdc0eb154 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index fee2a52eb9d..857dad90389 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -535,6 +535,7 @@ "embed-link": "הטמע קישור", "enable": "הפעל", "enable-debug-log": "הפעל יומן דיבוג", + "enable-entity": "הפעל {{entity}}", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "הפעל", "enable-partition": "הפעל מחיצה", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 33b5c4047bb..99ff3757b30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -535,6 +535,7 @@ "embed-link": "リンクを埋め込む", "enable": "有効化", "enable-debug-log": "デバッグログを有効化", + "enable-entity": "{{entity}} を有効化", "enable-incident-management": "インシデント管理を有効化", "enable-lowercase": "有効化", "enable-partition": "パーティションを有効化", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json index 94e31b006d4..ba770a1fae3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json @@ -535,6 +535,7 @@ "embed-link": "링크 삽입", "enable": "활성화", "enable-debug-log": "디버그 로그 활성화", + "enable-entity": "{{entity}} 활성화", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "활성화", "enable-partition": "파티션 활성화", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json index ef0a2766fc2..fe3e0af1771 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json @@ -535,6 +535,7 @@ "embed-link": "लिंक एम्बेड करा", "enable": "सक्षम करा", "enable-debug-log": "डिबग लॉग सक्षम करा", + "enable-entity": "{{entity}} सक्षम करा", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "सक्षम करा", "enable-partition": "विभाजन सक्षम करा", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 8a771f08cd3..60c1cb9cfb5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json index 282f7506a5e..209da155ef8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json @@ -535,6 +535,7 @@ "embed-link": "قرار دادن لینک", "enable": "فعال کردن", "enable-debug-log": "فعال کردن گزارش اشکال‌زدایی", + "enable-entity": "فعال کردن {{entity}}", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "فعال کردن", "enable-partition": "فعال کردن پارتیشن", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 49a993dbbd0..71226f71dec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json index 3d82b458648..68baf9b2a90 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 0fa14dcef5b..ae20597f733 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -535,6 +535,7 @@ "embed-link": "Встроить ссылку", "enable": "Включить", "enable-debug-log": "Включить журнал отладки", + "enable-entity": "Включить {{entity}}", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "включить", "enable-partition": "Включить раздел", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json index bff9fd7b906..8d6636cfecc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json @@ -535,6 +535,7 @@ "embed-link": "ฝังลิงก์", "enable": "เปิดใช้งาน", "enable-debug-log": "เปิดใช้งานบันทึกการดีบัก", + "enable-entity": "เปิดใช้งาน {{entity}}", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "เปิดใช้งาน", "enable-partition": "เปิดใช้งานการแบ่งส่วน", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json index 8ad924c7f64..e7c9438e3c9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index e36957c1874..36de879d830 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -535,6 +535,7 @@ "embed-link": "插入链接", "enable": "启用", "enable-debug-log": "启用调试日志", + "enable-entity": "启用{{entity}}", "enable-incident-management": "Enable Incident Management", "enable-lowercase": "启用", "enable-partition": "启用分区", diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index 6dfe276cd54..75b2b9ed540 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -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; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx index 9107e5bcd17..e6f7f4dd4e4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -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', + }, + }, }, }, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts index 0e3095900b2..5df164a7857 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts @@ -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); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx index 386b3b72e7a..ca9ac24d6e4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx @@ -178,7 +178,7 @@ export const getField = (field: FieldProp) => { break; case FieldTypes.TAG_SUGGESTION: fieldElement = ( - + ); break; diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 5666c4cee03..d9a8aa0f479 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -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"