From ad9a6df282502f76245ee616cb74b41e207277d7 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:36:14 +0530 Subject: [PATCH] fix(ui): show user info with displayName and profile on version timeline (#15233) --- .../ui/cypress/common/Entities/EntityClass.ts | 2 + .../ui/cypress/common/Utils/Versions.ts | 20 ++ .../DomainVersion/DomainVersion.test.tsx | 11 +- .../EntityVersionTimeLine.tsx | 247 ++++++++---------- .../EntityVersionTimeline.interface.ts | 34 +++ .../EntityVersionTimeline.test.tsx | 108 ++++++++ .../entity-version-timeline.less | 17 ++ .../GlossaryVersion/GlossaryVersion.test.tsx | 11 +- .../common/PopOverCard/UserPopOverCard.tsx | 4 +- 9 files changed, 300 insertions(+), 154 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Versions.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeline.interface.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeline.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/entity-version-timeline.less diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts index 241d71380fc..12e25b3a254 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts @@ -57,6 +57,7 @@ import { } from '../Utils/Owner'; import { assignTags, removeTags, udpateTags } from '../Utils/Tags'; import { addTier, removeTier, updateTier } from '../Utils/Tier'; +import { validateDomain } from '../Utils/Versions'; import { downVoteEntity, upVoteEntity } from '../Utils/Voting'; const description = @@ -366,6 +367,7 @@ class EntityClass { assignDomain() { addDomainToEntity(domainDetails1.displayName); + validateDomain(domainDetails1.displayName, this.endPoint); } updateDomain() { diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Versions.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Versions.ts new file mode 100644 index 00000000000..e3c53097665 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Versions.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 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 { EntityType } from '../../constants/Entity.interface'; +import { interceptURL, verifyResponseStatusCode } from '../common'; + +export const validateDomain = (domain: string, entityType: EntityType) => { + interceptURL('GET', `/api/v1/${entityType}/*/versions/0.2`, 'getVersion'); + cy.get('[data-testid="version-button"]').should('contain', '0.2').click(); + verifyResponseStatusCode('@getVersion', 200); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainVersion/DomainVersion.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainVersion/DomainVersion.test.tsx index 26f5a10d59f..148f1dd33e1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainVersion/DomainVersion.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainVersion/DomainVersion.test.tsx @@ -108,6 +108,10 @@ jest.mock('../../../components/PageLayoutV1/PageLayoutV1', () => { return jest.fn().mockImplementation(({ children }) =>
{children}
); }); +jest.mock('../../Entity/EntityVersionTimeLine/EntityVersionTimeLine', () => { + return jest.fn().mockReturnValue(<>Version timeline); +}); + describe('DomainVersion', () => { it('renders domain version', async () => { await act(async () => { @@ -118,11 +122,6 @@ describe('DomainVersion', () => { expect(await screen.findByText('Domain component')).toBeInTheDocument(); // Check that the version timeline is displayed - expect( - screen.getByText('label.version-plural-history') - ).toBeInTheDocument(); - - // check active version visible - expect(screen.getByText('v0.4')).toBeInTheDocument(); + expect(screen.getByText('Version timeline')).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeLine.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeLine.tsx index b13eeee6be6..519a0bc3009 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeLine.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeLine.tsx @@ -13,101 +13,127 @@ import { Col, Divider, Drawer, Row, Typography } from 'antd'; import classNames from 'classnames'; -import { capitalize, isEmpty, toString } from 'lodash'; -import React, { Fragment, useMemo, useState } from 'react'; +import { isEmpty, toString } from 'lodash'; +import React, { Fragment, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { getUserPath } from '../../../constants/constants'; import { EntityHistory } from '../../../generated/type/entityHistory'; -import { getUserByName } from '../../../rest/userAPI'; +import { useUserProfile } from '../../../hooks/user-profile/useUserProfile'; +import { formatDateTime } from '../../../utils/date-time/DateTimeUtils'; import { getEntityName } from '../../../utils/EntityUtils'; import { getSummary, isMajorVersion } from '../../../utils/EntityVersionUtils'; +import UserPopOverCard from '../../common/PopOverCard/UserPopOverCard'; import CloseIcon from '../../Modals/CloseIcon.component'; +import './entity-version-timeline.less'; +import { + EntityVersionButtonProps, + EntityVersionTimelineProps, +} from './EntityVersionTimeline.interface'; -type Props = { - versionList: EntityHistory; - currentVersion: string; - versionHandler: (v: string) => void; - onBack: () => void; +export const VersionButton = ({ + version, + onVersionSelect, + selected, + isMajorVersion, +}: EntityVersionButtonProps) => { + const { t } = useTranslation(); + + const { + updatedBy, + version: versionNumber, + changeDescription, + updatedAt, + glossary, + } = version; + const [, , user] = useUserProfile({ + permission: true, + name: updatedBy, + }); + + const versionText = `v${parseFloat(versionNumber).toFixed(1)}`; + + return ( +
onVersionSelect(toString(versionNumber))}> +
+ + +
+
+ + {versionText} + {isMajorVersion ? ( + + {t('label.major')} + + ) : null} + +
+ {getSummary({ + changeDescription: changeDescription, + isGlossaryTerm: !isEmpty(glossary), + })} +
+
+ + + {getEntityName(user)} + + + + {formatDateTime(updatedAt)} + +
+
+
+ ); }; -type VersionType = 'all' | 'major' | 'minor'; -const EntityVersionTimeLine: React.FC = ({ +const EntityVersionTimeLine: React.FC = ({ versionList = {} as EntityHistory, currentVersion, versionHandler, onBack, -}: Props) => { +}) => { const { t } = useTranslation(); - const [versionType] = useState('all'); - const [uname, setUname] = useState(''); - const fetchUserName = async (userName: string) => { - try { - const userData = await getUserByName(userName); - - const name: string = getEntityName(userData); - setUname(name); - } catch (err) { - setUname(userName); - } - }; - - const versions = useMemo(() => { - let versionTypeList = []; - const list = versionList.versions ?? []; - - switch (versionType) { - case 'major': - versionTypeList = list.filter((v) => { - const currV = JSON.parse(v); + const versions = useMemo( + () => + versionList.versions.map((v, i) => { + const currV = JSON.parse(v); + const majorVersionChecks = () => { return isMajorVersion( parseFloat(currV?.changeDescription?.previousVersion) .toFixed(1) .toString(), parseFloat(currV?.version).toFixed(1).toString() ); - }); - - break; - case 'minor': - versionTypeList = list.filter((v) => { - const currV = JSON.parse(v); - - return !isMajorVersion( - parseFloat(currV?.changeDescription?.previousVersion) - .toFixed(1) - .toString(), - parseFloat(currV?.version).toFixed(1).toString() - ); - }); - - break; - case 'all': - default: - versionTypeList = list; - - break; - } - - return versionTypeList.length ? ( - versionTypeList.map((v, i) => { - const currV = JSON.parse(v); - const userId: string = currV?.updatedBy; - fetchUserName(userId); - { - userId === 'admin' ? setUname('admin') : ' '; - } - const majorVersionChecks = () => { - return ( - isMajorVersion( - parseFloat(currV?.changeDescription?.previousVersion) - .toFixed(1) - .toString(), - parseFloat(currV?.version).toFixed(1).toString() - ) && versionType === 'all' - ); }; - const versionText = `v${parseFloat(currV?.version).toFixed(1)}`; return ( @@ -118,74 +144,17 @@ const EntityVersionTimeLine: React.FC = ({ ) : null} -
versionHandler(toString(currV?.version))}> -
- - -
-
- - {versionText} - {majorVersionChecks() ? ( - - {t('label.major')} - - ) : null} - -
- {getSummary({ - changeDescription: currV?.changeDescription, - isGlossaryTerm: !isEmpty(currV?.glossary), - })} -
-

- {uname} - - {' '} - {t('label.updated-on')}{' '} - - - {new Date(currV?.updatedAt).toLocaleDateString('en-CA', { - hour: 'numeric', - minute: 'numeric', - })} - -

-
-
+
); - }) - ) : ( -

- {t('message.no-version-type-available', { - type: capitalize(versionType), - })} -

- ); - }, [versionList, currentVersion, versionHandler, versionType]); + }), + [versionList, currentVersion, versionHandler] + ); return ( void; + onBack: () => void; +}; + +export type EntityVersionButtonProps = { + version: { + updatedBy: string; + version: string; + changeDescription: ChangeDescription; + updatedAt: number; + glossary: string; + }; + onVersionSelect: (v: string) => void; + selected: boolean; + isMajorVersion: boolean; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeline.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeline.test.tsx new file mode 100644 index 00000000000..e4e2fea8757 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/EntityVersionTimeline.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2024 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 { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { ChangeDescription } from '../../../generated/entity/type'; +import { VersionButton } from './EntityVersionTimeLine'; + +jest.mock('../../common/PopOverCard/UserPopOverCard', () => ({ + __esModule: true, + default: ({ userName }: { userName: string }) =>

{userName}

, +})); + +const user = { + name: 'John Doe displayName', + displayName: 'John Doe displayName', +}; + +jest.mock('../../../hooks/user-profile/useUserProfile', () => ({ + useUserProfile: jest.fn().mockImplementation(() => [false, false, user]), +})); + +jest.mock('../../../utils/EntityVersionUtils', () => ({ + __esModule: true, + getSummary: jest.fn().mockReturnValue('Some change description'), + isMajorVersion: jest.fn().mockReturnValue(false), +})); + +describe('VersionButton', () => { + const version = { + updatedBy: 'John Doe', + version: '1.0', + changeDescription: {} as ChangeDescription, + updatedAt: 123, + glossary: '', + }; + + const onVersionSelect = jest.fn(); + const selected = false; + const isMajorVersion = false; + + it('renders version number', () => { + render( + + ); + const versionNumber = screen.getByText('v1.0'); + + expect(versionNumber).toBeInTheDocument(); + }); + + it('renders change description', () => { + render( + + ); + const changeDescription = screen.getByText('Some change description'); + + expect(changeDescription).toBeInTheDocument(); + }); + + it('should render updatedBy with UserPopoverCard', async () => { + render( + + ); + + const ownerDisplayName = await screen.findByText('John Doe'); + + expect(ownerDisplayName).toBeInTheDocument(); + }); + + it('calls onVersionSelect when clicked', () => { + render( + + ); + const versionButton = screen.getByTestId('version-selector-v1.0'); + fireEvent.click(versionButton); + + expect(onVersionSelect).toHaveBeenCalledWith('1.0'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/entity-version-timeline.less b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/entity-version-timeline.less new file mode 100644 index 00000000000..cf9614ce5a7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/entity-version-timeline.less @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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. + */ +.version-timestamp::before { + content: '\2022'; + margin-right: 6px; + margin-left: 4px; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryVersion/GlossaryVersion.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryVersion/GlossaryVersion.test.tsx index a0ee405936b..aaba3a92b6d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryVersion/GlossaryVersion.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryVersion/GlossaryVersion.test.tsx @@ -64,6 +64,10 @@ jest.mock('../../PageLayoutV1/PageLayoutV1', () => { return jest.fn().mockImplementation(({ children }) =>
{children}
); }); +jest.mock('../../Entity/EntityVersionTimeLine/EntityVersionTimeLine', () => { + return jest.fn().mockReturnValue(<>Version timeline); +}); + describe('GlossaryVersion', () => { it('renders glossary version', async () => { const glossaryName = '305b0130-b9c1-4441-a0fc-6463fd019540'; @@ -82,11 +86,6 @@ describe('GlossaryVersion', () => { expect(await screen.findByText('Glossary component')).toBeInTheDocument(); // Check that the version timeline is displayed - expect( - screen.getByText('label.version-plural-history') - ).toBeInTheDocument(); - - // check active version visible - expect(screen.getByText('v0.1')).toBeInTheDocument(); + expect(screen.getByText('Version timeline')).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx index b4573708366..c24848d2df5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx @@ -269,9 +269,7 @@ const UserPopOverCard: FC = ({ : getUserPath(userName ?? '') }> {showUserProfile ? profilePicture : null} - {showUserName ? ( - {displayName ?? userName ?? ''} - ) : null} + {showUserName ? {displayName ?? userName} : null} )}