From 52e07cf2c3e9baadc16f10ebfddd404110a0cf7e Mon Sep 17 00:00:00 2001 From: Dhruv Parmar <83108871+dhruvjsx@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:52:39 +0530 Subject: [PATCH] Redesign : certificates and source URL (#20368) * Redesigned certificate || source url * updated extrainfolabel * updated extrainfolabel * fixed metricHeaderInfo|| gave consistent spacing * fixed test cases * fixed metric text overflow * fixed tests * fixed comments * fixed sonar cases --- .../resources/ui/playwright/utils/metric.ts | 12 ++-- .../ui/src/assets/svg/link-icon-with-bg.svg | 11 +++ .../DataAssetsHeader.component.tsx | 49 +++++++++++-- .../DataAssetsHeader.test.tsx | 69 ++++++++++++++++++ .../DataAssetsHeader/data-asset-header.less | 24 ++++++- .../RetentionPeriod.component.tsx | 2 +- .../RetentionPeriod/retention-period.less | 4 ++ .../EntityHeaderTitle.component.tsx | 13 +--- .../EntityHeaderTitle.interface.ts | 2 - .../entity-header-title.less | 2 + .../MetricHeaderInfo/MetricHeaderInfo.tsx | 72 ++++++++++--------- .../MetricHeaderInfo/metric-header-info.less | 13 ++++ .../CertificationTag/CertificationTag.tsx | 19 ++++- .../CertificationTag/certification-tag.less | 14 ++++ .../DomainLabel/DomainLabel.component.tsx | 2 +- .../common/DomainLabel/domain-label.less | 6 +- .../common/NoOwner/NoOwnerFound.tsx | 6 +- .../OwnerLabel/OwnerLabel.component.tsx | 6 +- .../common/OwnerLabel/owner-label.less | 4 ++ .../ui/src/locale/languages/en-us.json | 2 +- .../ui/src/locale/languages/es-es.json | 2 +- .../ui/src/locale/languages/fr-fr.json | 2 +- .../ui/src/locale/languages/he-he.json | 2 +- .../ui/src/locale/languages/ja-jp.json | 2 +- .../ui/src/locale/languages/nl-nl.json | 2 +- .../ui/src/locale/languages/pt-br.json | 2 +- .../ui/src/locale/languages/ru-ru.json | 2 +- .../ui/src/locale/languages/zh-cn.json | 2 +- .../resources/ui/src/styles/variables.less | 1 + .../src/utils/DataAssetsHeader.utils.test.tsx | 16 ----- .../ui/src/utils/DataAssetsHeader.utils.tsx | 17 ----- 31 files changed, 273 insertions(+), 109 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/link-icon-with-bg.svg diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/metric.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/metric.ts index d47806de7df..eda2c2af21f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/metric.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/metric.ts @@ -48,7 +48,7 @@ export const updateUnitOfMeasurement = async ( page: Page, unitOfMeasurement: string ) => { - await page.click(`[data-testid="edit-unit-of-measurement-button"]`); + await page.click(`[data-testid="edit-measurement-unit-button"]`); const patchPromise = page.waitForResponse( (response) => response.request().method() === 'PATCH' ); @@ -60,21 +60,21 @@ export const updateUnitOfMeasurement = async ( // verify the unit of measurement is updated await expect( - page.getByText(`Unit of Measurement${unitOfMeasurement.toUpperCase()}`) + page.getByText(`Measurement Unit${unitOfMeasurement.toUpperCase()}`) ).toBeVisible(); }; export const removeUnitOfMeasurement = async (page: Page) => { - await page.click(`[data-testid="edit-unit-of-measurement-button"]`); + await page.click(`[data-testid="edit-measurement-unit-button"]`); const patchPromise = page.waitForResponse( (response) => response.request().method() === 'PATCH' ); - await page.getByTestId('remove-unit-of-measurement-button').click(); + await page.getByTestId('remove-measurement-unit-button').click(); await patchPromise; // verify the unit of measurement is updated - await expect(page.getByText('Unit of Measurement--')).toBeVisible(); + await expect(page.getByText('Measurement Unit--')).toBeVisible(); }; export const updateGranularity = async (page: Page, granularity: string) => { @@ -267,7 +267,7 @@ export const addMetric = async (page: Page) => { await expect( page.getByText( - `Unit of Measurement${metricData.unitOfMeasurement.toUpperCase()}` + `Measurement Unit${metricData.unitOfMeasurement.toUpperCase()}` ) ).toBeVisible(); diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/link-icon-with-bg.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/link-icon-with-bg.svg new file mode 100644 index 00000000000..fe5a3e160fd --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/link-icon-with-bg.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file 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 3ab8b3efd4e..a404e4faed1 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 @@ -25,6 +25,7 @@ import { ReactComponent as IconExternalLink } from '../../../assets/svg/external import { ReactComponent as RedAlertIcon } from '../../../assets/svg/ic-alert-red.svg'; import { ReactComponent as TaskOpenIcon } from '../../../assets/svg/ic-open-task.svg'; import { ReactComponent as VersionIcon } from '../../../assets/svg/ic-version.svg'; +import { ReactComponent as LinkIcon } from '../../../assets/svg/link-icon-with-bg.svg'; import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; import { DomainLabel } from '../../../components/common/DomainLabel/DomainLabel.component'; import { OwnerLabel } from '../../../components/common/OwnerLabel/OwnerLabel.component'; @@ -67,6 +68,7 @@ import serviceUtilClassBase from '../../../utils/ServiceUtilClassBase'; import tableClassBase from '../../../utils/TableClassBase'; import { getTierTags } from '../../../utils/TableUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; +import CertificationTag from '../../common/CertificationTag/CertificationTag'; import AnnouncementCard from '../../common/EntityPageInfos/AnnouncementCard/AnnouncementCard'; import AnnouncementDrawer from '../../common/EntityPageInfos/AnnouncementDrawer/AnnouncementDrawer'; import ManageButton from '../../common/EntityPageInfos/ManageButton/ManageButton'; @@ -94,7 +96,7 @@ export const ExtraInfoLabel = ({ inlineLayout = false, }: { label: string; - value: string | number; + value: string | number | React.ReactNode; dataTestId?: string; showAsATag?: boolean; inlineLayout?: boolean; @@ -116,19 +118,19 @@ export const ExtraInfoLabel = ({ } return ( -
+
{!isEmpty(label) && ( {label} )} - {value} - +
); @@ -489,7 +491,6 @@ export const DataAssetsHeader = ({ + {(dataAsset as Table).sourceUrl && ( + + + + )} {tier ? ( -
+
{t('label.tier')} @@ -696,6 +713,24 @@ export const DataAssetsHeader = ({ onUpdateMetricDetails={onMetricUpdate} /> )} + + {(dataAsset as Table)?.certification && ( + <> + + + } + /> + + )} {extraInfo}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx index cbe20d8faf5..4bd75424c65 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx @@ -25,6 +25,8 @@ import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils'; import { DataAssetsHeader, ExtraInfoLink } from './DataAssetsHeader.component'; import { DataAssetsHeaderProps } from './DataAssetsHeader.interface'; +import { LabelType, State, TagSource } from '../../../generated/tests/testCase'; +import { AssetCertification } from '../../../generated/type/assetCertification'; const mockProps: DataAssetsHeaderProps = { dataAsset: { id: 'assets-id', @@ -223,4 +225,71 @@ describe('DataAssetsHeader component', () => { mockIsAlertSupported = false; }); + + it('should render source URL button when sourceUrl is present', () => { + const mockSourceUrl = 'http://test-source.com'; + + render( + + ); + + const sourceUrlButton = screen.getByTestId('source-url-button'); + + const sourceUrlLink = screen.getByRole('link'); + + expect(sourceUrlButton).toBeInTheDocument(); + expect(sourceUrlLink).toHaveAttribute('href', mockSourceUrl); + expect(sourceUrlLink).toHaveAttribute('target', '_blank'); + expect(screen.getByText('label.source-url')).toBeInTheDocument(); + }); + + it('should not render source URL button when sourceUrl is not present', () => { + render(); + + expect(screen.queryByTestId('source-url-button')).not.toBeInTheDocument(); + }); + + it('should render certification when certification is present', () => { + const mockCertification: AssetCertification = { + tagLabel: { + tagFQN: 'Certification.Bronze', + name: 'Bronze', + displayName: 'Bronze_Medal', + description: 'Bronze certified Data Asset test', + style: { + color: '#C08329', + iconURL: 'BronzeCertification.svg', + }, + source: TagSource.Classification, + labelType: LabelType.Manual, + state: State.Confirmed, + }, + appliedDate: 1732814645688, + expiryDate: 1735406645688, + }; + render( + + ); + + expect(screen.getByText('label.certification')).toBeInTheDocument(); + + const certificatComponent = screen.getByTestId( + `certification-${mockCertification.tagLabel.tagFQN}` + ); + + expect(certificatComponent).toBeInTheDocument(); + expect(certificatComponent).toHaveTextContent('Bronze_Medal'); + }); }); 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 639154128a6..312a78236ea 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 @@ -24,7 +24,7 @@ border-radius: 12px; border: 0.5px solid @grey-300; padding: 20px; - gap: 50px; + gap: 28px; background-color: @background-color; } .ant-space-item { @@ -83,6 +83,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + width: 148px; } .ant-btn-group { @@ -101,6 +102,23 @@ border-radius: 8px; } } + .ant-btn.source-url-button { + background-color: @blue-11; + .ant-typography { + color: @blue-9; + } + span.anticon { + border-radius: 50%; + background-color: @blue-20; + padding: 4px; + height: 32px; + width: 32px; + border: 4px solid @blue-14; + } + svg { + fill: transparent; + } + } .ant-btn-group { .ant-btn { @@ -111,4 +129,8 @@ .ant-divider.ant-divider-vertical.vertical-divider { height: 50px; } + .tier-container, + .extra-info-container { + width: 148px; + } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/RetentionPeriod.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/RetentionPeriod.component.tsx index 5d93bd761cf..b55e874ae3a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/RetentionPeriod.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/RetentionPeriod.component.tsx @@ -118,7 +118,7 @@ const RetentionPeriod = ({ return (
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/retention-period.less b/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/retention-period.less index da6d9b7c4c6..93a2407cb59 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/retention-period.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/RetentionPeriod/retention-period.less @@ -20,3 +20,7 @@ span.extra-info-label-heading { color: @primary-heading-color; font-weight: 500; } + +.retention-period-container { + width: 148px; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx index 260333945d2..265c461711d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ import Icon, { ExclamationCircleFilled } from '@ant-design/icons'; -import { Badge, Button, Col, Divider, Row, Tooltip, Typography } from 'antd'; +import { Badge, Button, Col, Row, Tooltip, Typography } from 'antd'; import classNames from 'classnames'; import { capitalize, isEmpty } from 'lodash'; import React, { useMemo, useState } from 'react'; @@ -25,7 +25,6 @@ import { useClipboard } from '../../../hooks/useClipBoard'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import { getEntityName } from '../../../utils/EntityUtils'; import { stringToHTML } from '../../../utils/StringsUtils'; -import CertificationTag from '../../common/CertificationTag/CertificationTag'; import './entity-header-title.less'; import { EntityHeaderTitleProps } from './EntityHeaderTitle.interface'; @@ -42,7 +41,6 @@ const EntityHeaderTitle = ({ className, showName = true, showOnlyDisplayName = false, - certification, excludeEntityService, isFollowing, isFollowingLoading, @@ -164,14 +162,7 @@ const EntityHeaderTitle = ({ )}
- {certification && ( - -
- - -
- - )} + {isDisabled && ( = ({ ); return ( - - - {hasPermission && !metricDetails.deleted && ( - - -
+
+ {value ?? NO_DATA_PLACEHOLDER} +
+ +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Metric/MetricHeaderInfo/metric-header-info.less b/openmetadata-ui/src/main/resources/ui/src/components/Metric/MetricHeaderInfo/metric-header-info.less index 758b5118d2b..b36fcf99ff0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Metric/MetricHeaderInfo/metric-header-info.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Metric/MetricHeaderInfo/metric-header-info.less @@ -10,6 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import (reference) '../../../styles/variables.less'; .metric-header-info-popover { min-width: 250px; .ant-popover-inner-content { @@ -19,3 +20,15 @@ background-color: #f2f6fc; } } +.ant-btn.ant-btn-text.edit-metrics { + border: 1px solid @border-light; + width: 20px; + height: 20px; + border-radius: 4px; + svg { + color: @grey-600; + } +} +.metric-header-info-container { + width: 148px; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/CertificationTag.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/CertificationTag.tsx index bd2f168ba5b..bfa84cee405 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/CertificationTag.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/CertificationTag.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ import { Tag, Tooltip } from 'antd'; +import classNames from 'classnames'; import React from 'react'; import { AssetCertification } from '../../../generated/entity/data/table'; import { getEntityName } from '../../../utils/EntityUtils'; @@ -19,8 +20,10 @@ import './certification-tag.less'; const CertificationTag = ({ certification, + showName = false, }: { certification: AssetCertification; + showName?: boolean; }) => { if (certification.tagLabel.style?.iconURL) { const name = getEntityName(certification.tagLabel); @@ -31,12 +34,26 @@ const CertificationTag = ({ className="cursor-pointer" title={getTagTooltip(name, certification.tagLabel.description)} trigger="hover"> -
+
{`certification: + {showName && ( + + {name} + + )}
); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/certification-tag.less b/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/certification-tag.less index d90a08823e6..8d413964b82 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/certification-tag.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CertificationTag/certification-tag.less @@ -10,6 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import (reference) '../../../styles/variables.less'; .certification-tag { display: flex; align-items: center; @@ -28,3 +29,16 @@ width: 28px; height: 28px; } + +.certification-tag-with-name { + border-radius: 16px; + padding: 2px 8px 2px 10px; + + .certification-img { + width: 16px; + height: 16px; + } + span { + color: @yellow-11; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainLabel/DomainLabel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainLabel/DomainLabel.component.tsx index 3c09d298f5c..741c4dc71de 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainLabel/DomainLabel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainLabel/DomainLabel.component.tsx @@ -202,7 +202,7 @@ export const DomainLabel = ({ } return ( -
+
{headerLayout && (
= ({ return (
{isCompactView && ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx index 8e3fb492fa4..ebc75afcb8f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx @@ -35,7 +35,7 @@ export const OwnerLabel = ({ team: false, }, tooltipText, - isCompactView = true, + isCompactView = true, // renders owner profile followed by its name }: OwnerLabelProps) => { const { t } = useTranslation(); const [showAllOwners, setShowAllOwners] = useState(false); @@ -69,7 +69,9 @@ export const OwnerLabel = ({ return (
{ expect(assetData.breadcrumbs).toEqual([{ name: 'entityName', url: 'url' }]); // contains extra data for source url - expect(JSON.stringify(assetData.extraInfo)).toContain( - 'http://localhost:8080/tree?dag_id=snowflake_etl' - ); // If Data does not present - const assetWithNoExtraData = getDataAssetsHeaderInfo( - EntityType.PIPELINE, - { ...mockPipelineData, sourceUrl: '' }, - 'snowflake_etl', - [] - ); // contains extra data for source url - expect(JSON.stringify(assetWithNoExtraData.extraInfo)).not.toContain( - 'http://localhost:8080/tree?dag_id=snowflake_etl' - ); }); // Test for MlModel entity @@ -494,10 +482,6 @@ describe('Tests for DataAssetsHeaderUtils', () => { expect(JSON.stringify(assetData.extraInfo)).toContain('label.language'); expect(JSON.stringify(assetData.extraInfo)).toContain('SQL'); - expect(JSON.stringify(assetData.extraInfo)).toContain( - 'http://localhost:8585/api/v1/databaseSchemas/48261b8c-4c99-4c5d-9ec7-cb758cc9f9c1' - ); - // If Data does not present const assetWithNoExtraData = getDataAssetsHeaderInfo( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx index 7eb42b9878f..285baec5e33 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx @@ -59,7 +59,6 @@ import { getBreadcrumbForEntitiesWithServiceOnly, getBreadcrumbForTable, getEntityBreadcrumbs, - getEntityName, } from './EntityUtils'; import { getEntityDetailsPath } from './RouterUtils'; import { bytesToSize } from './StringsUtils'; @@ -667,22 +666,6 @@ export const getDataAssetsHeaderInfo = ( break; } - if ('sourceUrl' in dataAsset && dataAsset.sourceUrl) { - returnData.extraInfo = ( - <> - {returnData.extraInfo} - - - - ); - } - return returnData; };