From e32b763e153d3c36e81ef8b6ef64c69327a486ed Mon Sep 17 00:00:00 2001
From: Abhishek Porwal <80886271+Abhishek332@users.noreply.github.com>
Date: Tue, 25 Jul 2023 11:34:49 +0530
Subject: [PATCH] chore(client): new UI for the tier-card (#12536)
* chore(client): tier-card ui update
* minor change
* test(client): for the new UI of tier-card
* address comments
* fix UI
* replace some css with utility classes
* add language locales for word Clear
* some css and test fix
* fix(client): cypress test for add and remove tier
---
.../e2e/Flow/AddAndRemoveTierAndOwner.spec.js | 7 +-
.../common/TierCard/TierCard.test.tsx | 80 +++++-
.../components/common/TierCard/TierCard.tsx | 269 ++++++------------
.../common/TierCard/tier-card.style.less | 88 +++---
.../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/ja-jp.json | 1 +
.../ui/src/locale/languages/pt-br.json | 1 +
.../ui/src/locale/languages/zh-cn.json | 1 +
10 files changed, 213 insertions(+), 237 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AddAndRemoveTierAndOwner.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AddAndRemoveTierAndOwner.spec.js
index fc64d4761c4..cc534f0dcbc 100644
--- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AddAndRemoveTierAndOwner.spec.js
+++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AddAndRemoveTierAndOwner.spec.js
@@ -62,16 +62,13 @@ const addRemoveOwner = () => {
const addRemoveTier = () => {
cy.get('[data-testid="edit-tier"]').click();
cy.get('[data-testid="card-list"]').first().should('be.visible').as('tier1');
- cy.get('@tier1')
- .find('[data-testid="icon"] > [data-testid="select-tier-button"]')
- .click();
+ cy.get('@tier1').find('[data-testid="radio-btn"]').click();
verifyResponseStatusCode('@patchOwner', 200);
cy.clickOutside();
cy.get('[data-testid="Tier"]').should('contain', TIER);
cy.get('[data-testid="edit-tier"]').click();
- cy.get('[data-testid="card-list"]').first().should('be.visible').as('tier1');
- cy.get('@tier1').find('[data-testid="remove-tier"]').click();
+ cy.get('[data-testid="clear-tier"]').should('be.visible').click();
verifyResponseStatusCode('@patchOwner', 200);
cy.get('[data-testid="Tier"]').should('contain', 'No Tier');
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.test.tsx
index 720a0f7cf87..f02dca47958 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.test.tsx
@@ -11,7 +11,15 @@
* limitations under the License.
*/
-import { findByTestId, render } from '@testing-library/react';
+import {
+ findByTestId,
+ getAllByTestId,
+ getByTestId,
+ getByText,
+ render,
+ waitForElementToBeRemoved,
+} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import React from 'react';
import TierCard from './TierCard';
@@ -31,10 +39,19 @@ const mockTierData = [
},
];
+const mockGetTags = jest
+ .fn()
+ .mockImplementation(() => Promise.resolve({ data: mockTierData }));
+const mockOnUpdate = jest.fn();
+const mockShowErrorToast = jest.fn();
+const mockProps = {
+ currentTier: 'currentTier',
+ updateTier: mockOnUpdate,
+ children:
Child
,
+};
+
jest.mock('rest/tagAPI', () => ({
- getTags: jest
- .fn()
- .mockImplementation(() => Promise.resolve({ data: mockTierData })),
+ getTags: jest.fn().mockImplementation(() => mockGetTags()),
}));
jest.mock('../../Loader/Loader', () => {
@@ -42,7 +59,7 @@ jest.mock('../../Loader/Loader', () => {
});
jest.mock('../../../utils/ToastUtils', () => {
- return jest.fn().mockReturnValue(showErrorToast
);
+ return jest.fn().mockImplementation(() => mockShowErrorToast());
});
// Mock Antd components
@@ -51,19 +68,58 @@ jest.mock('antd', () => ({
Popover: jest
.fn()
- .mockImplementation(({ content }) => (
- {content}
- )),
+ .mockImplementation(({ content, onOpenChange, children }) => {
+ onOpenChange(true);
+
+ return (
+ <>
+ {content}
+ {children}
+ >
+ );
+ }),
}));
-const MockOnUpdate = jest.fn();
+jest.mock('../rich-text-editor/RichTextEditorPreviewer', () => {
+ return jest.fn().mockReturnValue(RichTextEditorPreviewer
);
+});
describe('Test TierCard Component', () => {
it('Component should have card', async () => {
- const { container } = render(
-
- );
+ const { container } = render();
+
+ await expect(getByText(container, 'Loader')).toBeInTheDocument();
+
+ expect(mockGetTags).toHaveBeenCalled();
expect(await findByTestId(container, 'cards')).toBeInTheDocument();
});
+
+ it('should call the mockOnUpdate when click on radio button', async () => {
+ const { container } = render();
+
+ await waitForElementToBeRemoved(() => getByText(container, 'Loader'));
+
+ const radioBtns = getAllByTestId(container, 'radio-btn');
+
+ expect(radioBtns).toHaveLength(1);
+
+ userEvent.click(radioBtns[0]);
+
+ expect(mockOnUpdate).toHaveBeenCalled();
+ });
+
+ it('should call the mockOnUpdate when click on Clear button', async () => {
+ const { container } = render();
+
+ await waitForElementToBeRemoved(() => getByText(container, 'Loader'));
+
+ const clearTier = getByTestId(container, 'clear-tier');
+
+ expect(clearTier).toBeInTheDocument();
+
+ userEvent.click(clearTier);
+
+ expect(mockOnUpdate).toHaveBeenCalled();
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.tsx
index 13fe42d064e..e0cbec5844d 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/TierCard.tsx
@@ -11,24 +11,18 @@
* limitations under the License.
*/
-import Icon from '@ant-design/icons/lib/components/Icon';
import {
- Button,
Card,
- Col,
Collapse,
Popover,
- Row,
+ Radio,
+ RadioChangeEvent,
Space,
Typography,
} from 'antd';
-import { ReactComponent as IconRemove } from 'assets/svg/ic-remove.svg';
-import { AxiosError } from 'axios';
-import classNames from 'classnames';
import Loader from 'components/Loader/Loader';
import { t } from 'i18next';
-import { LoadingState } from 'Models';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { getTags } from 'rest/tagAPI';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { showErrorToast } from '../../../utils/ToastUtils';
@@ -39,198 +33,111 @@ import { CardWithListItems, TierCardProps } from './TierCard.interface';
const { Panel } = Collapse;
const TierCard = ({ currentTier, updateTier, children }: TierCardProps) => {
const [tierData, setTierData] = useState>([]);
- const [activeTier, setActiveTier] = useState(currentTier);
- const [statusTier, setStatusTier] = useState('initial');
const [isLoadingTierData, setIsLoadingTierData] = useState(false);
- const handleCardSelection = (cardId: string) => {
- setActiveTier(cardId);
- };
-
- const setInitialTierLoadingState = () => {
- setStatusTier('initial');
- };
-
- const getTierData = () => {
+ const getTierData = async () => {
setIsLoadingTierData(true);
- getTags({
- parent: 'Tier',
- })
- .then(({ data }) => {
- if (data) {
- const tierData: CardWithListItems[] =
- data.map((tier: { name: string; description: string }) => ({
- id: `Tier${FQN_SEPARATOR_CHAR}${tier.name}`,
- title: tier.name,
- description: tier.description.substring(
- 0,
- tier.description.indexOf('\n\n')
- ),
- data: tier.description.substring(
- tier.description.indexOf('\n\n') + 1
- ),
- })) ?? [];
- setTierData(tierData);
- } else {
- setTierData([]);
- }
- })
- .catch((err: AxiosError) => {
- showErrorToast(
- err,
- t('server.entity-fetch-error', {
- entity: t('label.tier-plural-lowercase'),
- })
- );
- })
- .finally(() => {
- setIsLoadingTierData(false);
+ try {
+ const { data } = await getTags({
+ parent: 'Tier',
});
- };
- const prepareTier = (updatedTier: string) => {
- return updatedTier !== currentTier ? updatedTier : undefined;
- };
-
- const handleTierSave = (updatedTier: string) => {
- setStatusTier('waiting');
-
- const newTier = prepareTier(updatedTier);
- updateTier?.(newTier as string);
- };
-
- const getTierSelectButton = (tier: string) => {
- switch (statusTier) {
- case 'waiting':
- return (
-
- );
-
- case 'success':
- return (
- updateTier?.()}
- />
- );
-
- default:
- return (
-
- );
- }
- };
-
- const getCardIcon = useCallback(
- (cardId: string) => {
- const isSelected = currentTier === cardId;
- const isActive = activeTier === cardId;
-
- if ((isSelected && isActive) || isSelected) {
- return (
- updateTier?.()}
- />
- );
- } else if (isActive) {
- return getTierSelectButton(cardId);
+ if (data) {
+ const tierData: CardWithListItems[] =
+ data.map((tier: { name: string; description: string }) => ({
+ id: `Tier${FQN_SEPARATOR_CHAR}${tier.name}`,
+ title: tier.name,
+ description: tier.description.substring(
+ 0,
+ tier.description.indexOf('\n\n')
+ ),
+ data: tier.description.substring(
+ tier.description.indexOf('\n\n') + 1
+ ),
+ })) ?? [];
+ setTierData(tierData);
} else {
- return (
-
- );
+ setTierData([]);
}
- },
- [currentTier, activeTier]
- );
-
- useEffect(() => {
- setActiveTier(currentTier);
- if (statusTier === 'waiting') {
- setStatusTier('success');
- setTimeout(() => {
- setInitialTierLoadingState();
- }, 300);
+ } catch (err) {
+ showErrorToast(
+ err,
+ t('server.entity-fetch-error', {
+ entity: t('label.tier-plural-lowercase'),
+ })
+ );
+ } finally {
+ setIsLoadingTierData(false);
}
- }, [currentTier]);
+ };
+
+ const handleTierSelection = ({ target: { value } }: RadioChangeEvent) => {
+ updateTier?.(value as string);
+ };
+
+ const clearTierSelection = () => {
+ updateTier?.();
+ };
return (
-
-
- {t('label.edit-entity', { entity: t('label.tier') })}
-
-
-
+
+
+ {t('label.edit-entity', { entity: t('label.tier') })}
+
+
+ {t('label.clear')}
+
+
}>
- handleCardSelection(key as string)}>
- {tierData.map((card) => (
- {getCardIcon(card.id)}}
- header={
-
-
- {card.title}
-
-
- {card.description.replace(/\*/g, '')}
-
-
- }
- key={card.id}>
-
-
- ))}
-
+
+
+ {tierData.map((card) => (
+
+
+
+
+ {card.title}
+
+
+ {card.description.replace(/\*/g, '')}
+
+
+
+ }
+ key={card.id}>
+
+
+ ))}
+
+
{isLoadingTierData && }
}
- data-testid="tier-card-container"
- overlayClassName="tier-card-container"
placement="bottomRight"
showArrow={false}
trigger="click"
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/tier-card.style.less b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/tier-card.style.less
index 518eb9481d8..1c79c7923c4 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/tier-card.style.less
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TierCard/tier-card.style.less
@@ -13,51 +13,61 @@
@import url('../../../styles/variables.less');
-.tier-card {
- width: 760px;
- border: 1px solid #dde3ea;
- box-shadow: @box-shadow-base;
- border-radius: 4px !important;
-}
-
-.tier-card-container {
- padding: 0;
+.ant-popover-inner {
+ box-shadow: none;
.ant-popover-inner-content {
padding: 0;
- }
-}
-
-.collapse-container {
- .collapse-tier-panel {
- border: @global-border;
-
- .ant-collapse-content {
- border-color: @primary-color;
- }
-
- &.ant-collapse-item-active {
- border: 1px solid @primary-color;
- background: @primary-color-hover;
- }
-
- &.selected {
- border: 1px solid @primary-color;
- background: @primary-color;
- .ant-collapse-header {
- color: @white;
+ .tier-card {
+ width: 655px;
+ .ant-card-body {
+ .ant-collapse-item {
+ .ant-collapse-header {
+ .ant-collapse-arrow {
+ position: absolute;
+ top: 20px;
+ }
+ }
+ .ant-collapse-content {
+ &.ant-collapse-content-active {
+ border: none;
+ }
+ .ant-collapse-content-box {
+ padding: 0 12px;
+ }
+ }
+ }
}
}
- &:first-child,
- &:first-child.ant-collapse-item-active {
- border-radius: 4px 4px 0 0;
- }
- &:last-child {
- border-radius: 0 0 4px 4px;
+ }
+
+ .tier-card-description {
+ .toastui-editor-contents ul {
+ padding-left: 18px;
+ font-size: 12px;
+ font-weight: 400;
+ color: @text-color-secondary;
+ margin: 4px;
+ li {
+ margin: 0;
+ }
}
}
- &.ant-collapse {
- background-color: @white;
- border: none;
+ .radio-input .ant-radio {
+ .ant-radio-inner {
+ width: 14px;
+ height: 14px;
+ border: 1.5px solid @primary-color;
+ box-shadow: none !important;
+ }
+
+ &:hover .ant-radio-inner {
+ background-color: @primary-color-hover;
+ }
+
+ &.ant-radio-checked .ant-radio-inner::after {
+ box-shadow: none !important;
+ transform: scale(0.35);
+ }
}
}
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 0f4c7f4e570..5d4b6235e66 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "Classifications",
"clean-up-policy-plural-lowercase": "clean-up policies",
+ "clear": "Clear",
"clear-entity": "Clear {{entity}}",
"click-here": "Click here",
"client-email": "Client Email",
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 4a0e5480b82..6475eb99bb6 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "Clasificaciones",
"clean-up-policy-plural-lowercase": "políticas de limpieza",
+ "clear": "Clear",
"clear-entity": "Limpiar {{entity}}",
"click-here": "Haga clic aquí",
"client-email": "Correo electrónico del cliente",
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 77345b4064c..cdcf8ad4d35 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "Classifications",
"clean-up-policy-plural-lowercase": "Nettoyer les stratégies",
+ "clear": "Clear",
"clear-entity": "Effacer {{entity}}",
"click-here": "Cliquer ici",
"client-email": "Client Email",
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 e3063071cd8..59b6f3c89c7 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "分類",
"clean-up-policy-plural-lowercase": "ポリシーの削除",
+ "clear": "Clear",
"clear-entity": "{{entity}}を削除",
"click-here": "ここをクリック",
"client-email": "Client Email",
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 a6dded86773..23086e9e2c3 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "Classificações",
"clean-up-policy-plural-lowercase": "limpar políticas",
+ "clear": "Clear",
"clear-entity": "Limpar {{entity}}",
"click-here": "Clique aqui",
"client-email": "E-mail do cliente",
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 e268aba80f0..26b5a52ab0e 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
@@ -118,6 +118,7 @@
"classification-lowercase-plural": "classifications",
"classification-plural": "分类",
"clean-up-policy-plural-lowercase": "清理策略",
+ "clear": "Clear",
"clear-entity": "清除{{entity}}",
"click-here": "点击这里",
"client-email": "客户端电子邮箱",