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": "客户端电子邮箱",