fix(ui): show user info with displayName and profile on version timeline (#15233)

This commit is contained in:
Chirag Madlani 2024-02-23 10:36:14 +05:30 committed by GitHub
parent ff2ecc56f2
commit ad9a6df282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 300 additions and 154 deletions

View File

@ -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() {

View File

@ -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);
};

View File

@ -108,6 +108,10 @@ jest.mock('../../../components/PageLayoutV1/PageLayoutV1', () => {
return jest.fn().mockImplementation(({ children }) => <div>{children}</div>);
});
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();
});
});

View File

@ -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 (
<div
className="timeline-content p-b-md cursor-pointer"
onClick={() => onVersionSelect(toString(versionNumber))}>
<div className="timeline-wrapper">
<span
className={classNames(
'timeline-rounder',
{
selected,
},
{
major: isMajorVersion,
}
)}
data-testid={`version-selector-${versionText}`}
/>
<span className={classNames('timeline-line')} />
</div>
<div>
<Typography.Text
className={classNames('d-flex font-medium', {
'text-primary': selected,
})}>
<span>{versionText}</span>
{isMajorVersion ? (
<span
className="m-l-xs text-xs font-medium text-grey-body tw-bg-tag p-x-xs p-y-xss bg-grey rounded-4"
style={{ backgroundColor: '#EEEAF8' }}>
{t('label.major')}
</span>
) : null}
</Typography.Text>
<div
className={classNames('text-xs font-normal break-all', {
'diff-description': selected,
})}>
{getSummary({
changeDescription: changeDescription,
isGlossaryTerm: !isEmpty(glossary),
})}
</div>
<div className="text-xs d-flex gap-1 items-center flex-wrap">
<UserPopOverCard
className="font-italic"
profileWidth={16}
userName={updatedBy}>
<Link className="thread-author m-r-xss" to={getUserPath(updatedBy)}>
{getEntityName(user)}
</Link>
</UserPopOverCard>
<span className="font-medium font-italic version-timestamp">
{formatDateTime(updatedAt)}
</span>
</div>
</div>
</div>
);
};
type VersionType = 'all' | 'major' | 'minor';
const EntityVersionTimeLine: React.FC<Props> = ({
const EntityVersionTimeLine: React.FC<EntityVersionTimelineProps> = ({
versionList = {} as EntityHistory,
currentVersion,
versionHandler,
onBack,
}: Props) => {
}) => {
const { t } = useTranslation();
const [versionType] = useState<VersionType>('all');
const [uname, setUname] = useState<string>('');
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 (
<Fragment key={currV.version}>
@ -118,74 +144,17 @@ const EntityVersionTimeLine: React.FC<Props> = ({
</div>
</div>
) : null}
<div
className="timeline-content p-b-md cursor-pointer"
onClick={() => versionHandler(toString(currV?.version))}>
<div className="timeline-wrapper">
<span
className={classNames(
'timeline-rounder',
{
selected: toString(currV?.version) === currentVersion,
},
{
major: majorVersionChecks(),
}
)}
data-testid={`version-selector-${versionText}`}
/>
<span className={classNames('timeline-line')} />
</div>
<div>
<Typography.Text
className={classNames('d-flex font-medium', {
'text-primary': toString(currV?.version) === currentVersion,
})}>
<span>{versionText}</span>
{majorVersionChecks() ? (
<span
className="m-l-xs text-xs font-medium text-grey-body tw-bg-tag p-x-xs p-y-xss bg-grey rounded-4"
style={{ backgroundColor: '#EEEAF8' }}>
{t('label.major')}
</span>
) : null}
</Typography.Text>
<div
className={classNames('text-xs font-normal break-all', {
'diff-description':
toString(currV?.version) === currentVersion,
})}>
{getSummary({
changeDescription: currV?.changeDescription,
isGlossaryTerm: !isEmpty(currV?.glossary),
})}
</div>
<p className="text-xs font-italic">
<span className="font-medium">{uname}</span>
<span className="text-grey-muted">
{' '}
{t('label.updated-on')}{' '}
</span>
<span className="font-medium">
{new Date(currV?.updatedAt).toLocaleDateString('en-CA', {
hour: 'numeric',
minute: 'numeric',
})}
</span>
</p>
</div>
</div>
<VersionButton
isMajorVersion={majorVersionChecks()}
selected={toString(currV.version) === currentVersion}
version={currV}
onVersionSelect={versionHandler}
/>
</Fragment>
);
})
) : (
<p className="text-grey-muted d-flex justify-center items-center">
{t('message.no-version-type-available', {
type: capitalize(versionType),
})}
</p>
);
}, [versionList, currentVersion, versionHandler, versionType]);
}),
[versionList, currentVersion, versionHandler]
);
return (
<Drawer

View File

@ -0,0 +1,34 @@
/*
* 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 { ChangeDescription } from '../../../generated/entity/type';
import { EntityHistory } from '../../../generated/type/entityHistory';
export type EntityVersionTimelineProps = {
versionList: EntityHistory;
currentVersion: string;
versionHandler: (v: string) => 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;
};

View File

@ -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 }) => <p>{userName}</p>,
}));
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(
<VersionButton
isMajorVersion={isMajorVersion}
selected={selected}
version={version}
onVersionSelect={onVersionSelect}
/>
);
const versionNumber = screen.getByText('v1.0');
expect(versionNumber).toBeInTheDocument();
});
it('renders change description', () => {
render(
<VersionButton
isMajorVersion={isMajorVersion}
selected={selected}
version={version}
onVersionSelect={onVersionSelect}
/>
);
const changeDescription = screen.getByText('Some change description');
expect(changeDescription).toBeInTheDocument();
});
it('should render updatedBy with UserPopoverCard', async () => {
render(
<VersionButton
isMajorVersion={isMajorVersion}
selected={selected}
version={version}
onVersionSelect={onVersionSelect}
/>
);
const ownerDisplayName = await screen.findByText('John Doe');
expect(ownerDisplayName).toBeInTheDocument();
});
it('calls onVersionSelect when clicked', () => {
render(
<VersionButton
isMajorVersion={isMajorVersion}
selected={selected}
version={version}
onVersionSelect={onVersionSelect}
/>
);
const versionButton = screen.getByTestId('version-selector-v1.0');
fireEvent.click(versionButton);
expect(onVersionSelect).toHaveBeenCalledWith('1.0');
});
});

View File

@ -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;
}

View File

@ -64,6 +64,10 @@ jest.mock('../../PageLayoutV1/PageLayoutV1', () => {
return jest.fn().mockImplementation(({ children }) => <div>{children}</div>);
});
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();
});
});

View File

@ -269,9 +269,7 @@ const UserPopOverCard: FC<Props> = ({
: getUserPath(userName ?? '')
}>
{showUserProfile ? profilePicture : null}
{showUserName ? (
<span className="">{displayName ?? userName ?? ''}</span>
) : null}
{showUserName ? <span>{displayName ?? userName}</span> : null}
</Link>
)}
</Popover>