mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
fix(ui): show user info with displayName and profile on version timeline (#15233)
This commit is contained in:
parent
ff2ecc56f2
commit
ad9a6df282
@ -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() {
|
||||
|
@ -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);
|
||||
};
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
@ -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');
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user