FIX: #14486 filter current user from reviewer list (#14495)

This commit is contained in:
Chirag Madlani 2023-12-26 10:41:05 +05:30 committed by GitHub
parent f6b72d262d
commit 8746058d83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 176 additions and 67 deletions

View File

@ -1241,7 +1241,7 @@ export const removeOwner = (entity, isGlossaryPage) => {
cy.get('[data-testid="edit-owner"]').click();
verifyResponseStatusCode('@getUsers', 200);
cy.get("[data-testid='select-owner-tabs']").should('be.visible');
cy.get('[data-testid="remove-owner"]').click();
cy.get('[data-testid="remove-owner"]').scrollIntoView().click();
verifyResponseStatusCode('@patchOwner', 200);
if (isGlossaryPage) {
cy.get('[data-testid="glossary-owner-name"] > [data-testid="Add"]').should(

View File

@ -28,34 +28,6 @@ describe('Data Insight settings page should work properly', () => {
verifyResponseStatusCode('@getApplications', 200);
});
it('Deploy & run application', () => {
interceptURL(
'GET',
'/api/v1/apps/name/DataInsightsApplication?fields=*',
'getDataInsightDetails'
);
interceptURL(
'POST',
'/api/v1/apps/deploy/DataInsightsApplication',
'deploy'
);
interceptURL(
'POST',
'/api/v1/apps/trigger/DataInsightsApplication',
'triggerPipeline'
);
cy.get(
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
).click();
verifyResponseStatusCode('@getDataInsightDetails', 200);
cy.get('[data-testid="deploy-button"]').click();
verifyResponseStatusCode('@deploy', 200);
cy.reload();
verifyResponseStatusCode('@getDataInsightDetails', 200);
cy.get('[data-testid="run-now-button"]').click();
verifyResponseStatusCode('@triggerPipeline', 200);
});
it('Edit data insight application', () => {
interceptURL(
'GET',
@ -115,6 +87,7 @@ describe('Data Insight settings page should work properly', () => {
cy.get('[data-testid="save-button"]').click();
cy.get('#cronType').click();
cy.get('[title="Day"]').click();
cy.get('[data-testid="cron-type"]').should('contain', 'Day');
cy.get('[data-testid="deploy-button"]').click();
verifyResponseStatusCode('@installApplication', 201);
verifyResponseStatusCode('@getApplications', 200);
@ -122,4 +95,32 @@ describe('Data Insight settings page should work properly', () => {
'be.visible'
);
});
it('Deploy & run application', () => {
interceptURL(
'GET',
'/api/v1/apps/name/DataInsightsApplication?fields=*',
'getDataInsightDetails'
);
interceptURL(
'POST',
'/api/v1/apps/deploy/DataInsightsApplication',
'deploy'
);
interceptURL(
'POST',
'/api/v1/apps/trigger/DataInsightsApplication',
'triggerPipeline'
);
cy.get(
'[data-testid="data-insights-application-card"] [data-testid="config-btn"]'
).click();
verifyResponseStatusCode('@getDataInsightDetails', 200);
cy.get('[data-testid="deploy-button"]').click();
verifyResponseStatusCode('@deploy', 200);
cy.reload();
verifyResponseStatusCode('@getDataInsightDetails', 200);
cy.get('[data-testid="run-now-button"]').click();
verifyResponseStatusCode('@triggerPipeline', 200);
});
});

View File

@ -39,6 +39,7 @@ import { domainTypeTooltipDataRender } from '../../../utils/DomainUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { UserTeam } from '../../common/AssigneeList/AssigneeList.interface';
import '../domain.less';
import { DomainFormType } from '../DomainPage.interface';
import { AddDomainFormProps } from './AddDomainForm.interface';
@ -243,7 +244,8 @@ const AddDomainForm = ({
{selectedOwner && (
<div className="m-b-sm" data-testid="owner-container">
<UserTag
id={selectedOwner.id}
id={selectedOwner.name ?? selectedOwner.id}
isTeam={selectedOwner.type === UserTeam.Team}
name={getEntityName(selectedOwner)}
size={UserTagSize.small}
/>
@ -260,7 +262,7 @@ const AddDomainForm = ({
size={[8, 8]}>
{expertsList.map((d) => (
<UserTag
id={d.id}
id={d.name ?? d.id}
key={'expert' + d.id}
name={getEntityName(d)}
size={UserTagSize.small}

View File

@ -30,6 +30,7 @@ import {
import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils';
import { useAuthContext } from '../../Auth/AuthProviders/AuthProvider';
import { UserTeam } from '../../common/AssigneeList/AssigneeList.interface';
import ResizablePanels from '../../common/ResizablePanels/ResizablePanels';
import TitleBreadcrumb from '../../common/TitleBreadcrumb/TitleBreadcrumb.component';
import { UserTag } from '../../common/UserTag/UserTag.component';
@ -241,7 +242,8 @@ const AddGlossary = ({
{selectedOwner && (
<div className="m-y-xs" data-testid="owner-container">
<UserTag
id={selectedOwner.id}
id={selectedOwner.name ?? selectedOwner.id}
isTeam={selectedOwner.type === UserTeam.Team}
name={getEntityName(selectedOwner)}
size={UserTagSize.small}
/>
@ -258,7 +260,7 @@ const AddGlossary = ({
size={[8, 8]}>
{reviewersList.map((d, index) => (
<UserTag
id={d.id}
id={d.name ?? d.id}
key={index}
name={getEntityName(d)}
size={UserTagSize.small}

View File

@ -31,6 +31,7 @@ import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils';
import { fetchGlossaryList } from '../../../utils/TagsUtils';
import { useAuthContext } from '../../Auth/AuthProviders/AuthProvider';
import { UserTeam } from '../../common/AssigneeList/AssigneeList.interface';
import { UserTag } from '../../common/UserTag/UserTag.component';
import { UserTagSize } from '../../common/UserTag/UserTag.interface';
import { AddGlossaryTermFormProps } from './AddGlossaryTermForm.interface';
@ -326,6 +327,7 @@ const AddGlossaryTermForm = ({
type: FieldTypes.USER_MULTI_SELECT,
props: {
hasPermission: true,
filterCurrentUser: true,
popoverProps: { placement: 'topLeft' },
children: (
<Button
@ -432,7 +434,8 @@ const AddGlossaryTermForm = ({
{owner && (
<div className="m-y-sm" data-testid="owner-container">
<UserTag
id={owner.id}
id={owner.name ?? owner.id}
isTeam={owner.type === UserTeam.Team}
name={getEntityName(owner)}
size={UserTagSize.small}
/>
@ -445,7 +448,7 @@ const AddGlossaryTermForm = ({
<Space wrap data-testid="reviewers-container" size={[8, 8]}>
{reviewersList.map((d) => (
<UserTag
id={d.id}
id={d.name ?? d.id}
key={d.id}
name={getEntityName(d)}
size={UserTagSize.small}

View File

@ -57,6 +57,14 @@ const GlossaryDetails = ({
const [isDescriptionEditable, setIsDescriptionEditable] =
useState<boolean>(false);
const getEntityFeedCount = () => {
getFeedCounts(
EntityType.GLOSSARY,
glossary.fullyQualifiedName ?? '',
setFeedCount
);
};
const handleGlossaryUpdate = async (updatedGlossary: Glossary) => {
await updateGlossary(updatedGlossary);
getEntityFeedCount();
@ -114,14 +122,6 @@ const GlossaryDetails = ({
[glossary, isVersionView]
);
const getEntityFeedCount = () => {
getFeedCounts(
EntityType.GLOSSARY,
glossary.fullyQualifiedName ?? '',
setFeedCount
);
};
const handleTabChange = (activeKey: string) => {
if (activeKey !== activeTab) {
history.push(

View File

@ -43,7 +43,6 @@ import { useEntityExportModalProvider } from '../../../components/Entity/EntityE
import { EntityHeader } from '../../../components/Entity/EntityHeader/EntityHeader.component';
import EntityDeleteModal from '../../../components/Modals/EntityDeleteModal/EntityDeleteModal';
import EntityNameModal from '../../../components/Modals/EntityNameModal/EntityNameModal.component';
import { OperationPermission } from '../../../components/PermissionProvider/PermissionProvider.interface';
import Voting from '../../../components/Voting/Voting.component';
import { VotingDataProps } from '../../../components/Voting/voting.interface';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
@ -76,19 +75,7 @@ import { showErrorToast } from '../../../utils/ToastUtils';
import { useAuthContext } from '../../Auth/AuthProviders/AuthProvider';
import { TitleBreadcrumbProps } from '../../common/TitleBreadcrumb/TitleBreadcrumb.interface';
import StyleModal from '../../Modals/StyleModal/StyleModal.component';
export interface GlossaryHeaderProps {
isVersionView?: boolean;
supportAddOwner?: boolean;
selectedData: Glossary | GlossaryTerm;
permissions: OperationPermission;
isGlossary: boolean;
onUpdate: (data: GlossaryTerm | Glossary) => void;
onDelete: (id: string) => void;
onAssetAdd?: () => void;
updateVote?: (data: VotingDataProps) => Promise<void>;
onAddGlossaryTerm: (glossaryTerm: GlossaryTerm | undefined) => void;
}
import { GlossaryHeaderProps } from './GlossaryHeader.interface';
const GlossaryHeader = ({
selectedData,

View File

@ -0,0 +1,29 @@
/*
* Copyright 2023 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 { Glossary } from '../../../generated/entity/data/glossary';
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface';
import { VotingDataProps } from '../../Voting/voting.interface';
export interface GlossaryHeaderProps {
isVersionView?: boolean;
supportAddOwner?: boolean;
permissions: OperationPermission;
selectedData: Glossary | GlossaryTerm;
isGlossary: boolean;
onUpdate: (data: GlossaryTerm | Glossary) => void;
onDelete: (id: string) => void;
onAssetAdd?: () => void;
updateVote?: (data: VotingDataProps) => Promise<void>;
onAddGlossaryTerm: (glossaryTerm: GlossaryTerm | undefined) => void;
}

View File

@ -182,7 +182,7 @@ export const AddEditPersonaForm = ({
size={[8, 8]}>
{usersList.map((d) => (
<UserTag
id={d.id}
id={d.name ?? d.id}
key={d.id}
name={getEntityName(d)}
size={UserTagSize.small}

View File

@ -241,7 +241,6 @@ export const SelectableList = ({
removeMargin
placeholder={searchPlaceholder ?? t('label.search')}
searchBarDataTestId={searchBarDataTestId}
searchValue={searchText}
typingInterval={500}
onSearch={handleSearch}
/>
@ -279,10 +278,7 @@ export const SelectableList = ({
{customTagRenderer ? (
customTagRenderer(item)
) : (
<UserTag
id={item.name ?? ''}
name={item.displayName ?? item.name ?? ''}
/>
<UserTag id={item.name ?? ''} name={getEntityName(item)} />
)}
</List.Item>
)}

View File

@ -27,6 +27,7 @@ import { searchData } from '../../../rest/miscAPI';
import { getUsers } from '../../../rest/userAPI';
import { formatUsersResponse } from '../../../utils/APIUtils';
import { getEntityReferenceListFromEntities } from '../../../utils/EntityUtils';
import { useAuthContext } from '../../Auth/AuthProviders/AuthProvider';
import { SelectableList } from '../SelectableList/SelectableList.component';
import './user-select-dropdown.less';
import { UserSelectableListProps } from './UserSelectableList.interface';
@ -38,9 +39,11 @@ export const UserSelectableList = ({
children,
popoverProps,
multiSelect = true,
filterCurrentUser = false,
}: UserSelectableListProps) => {
const [popupVisible, setPopupVisible] = useState(false);
const { t } = useTranslation();
const { currentUser } = useAuthContext();
const fetchOptions = async (searchText: string, after?: string) => {
if (searchText) {
@ -60,6 +63,13 @@ export const UserSelectableList = ({
EntityType.USER
);
if (filterCurrentUser) {
const user = data.find((user) => user.id === currentUser?.id);
if (user) {
data.splice(data.indexOf(user), 1);
}
}
return { data, paging: { total: res.data.hits.total.value } };
} catch (error) {
return { data: [], paging: { total: 0 } };
@ -75,6 +85,12 @@ export const UserSelectableList = ({
data,
EntityType.USER
);
if (filterCurrentUser) {
const user = filterData.find((user) => user.id === currentUser?.id);
if (user) {
filterData.splice(filterData.indexOf(user), 1);
}
}
return { data: filterData, paging };
} catch (error) {

View File

@ -20,6 +20,7 @@ export type UserSelectableListProps =
selectedUsers: EntityReference[];
children?: ReactNode;
popoverProps?: PopoverProps;
filterCurrentUser?: boolean;
} & (
| {
multiSelect?: true;

View File

@ -10,17 +10,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { act, render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { UserSelectableList } from './UserSelectableList.component';
const mockOnUpdate = jest.fn();
jest.mock('../../../rest/userAPI', () => ({
getUsers: jest.fn().mockResolvedValue({ data: [], paging: { total: 5 } }),
}));
jest.mock('../../../rest/miscAPI', () => ({
searchData: jest.fn().mockResolvedValue({ data: [], paging: { total: 5 } }),
}));
describe('SelectableList Component Test', () => {
jest.mock('../SelectableList/SelectableList.component', () => ({
SelectableList: jest.fn().mockReturnValue(<div>selectable-list</div>),
}));
describe('UserSelectableList Component Test', () => {
it('should render disabled button if no permission', () => {
render(
<UserSelectableList
@ -34,4 +42,34 @@ describe('SelectableList Component Test', () => {
expect(screen.getByTestId('add-user')).toBeDisabled();
});
});
it('should render enabled button if has permission', () => {
render(
<UserSelectableList
hasPermission
selectedUsers={[]}
onUpdate={mockOnUpdate}
/>
);
act(() => {
expect(screen.getByTestId('add-user')).toBeEnabled();
});
});
it('should render selectablelist if click on add-user', () => {
render(
<UserSelectableList
hasPermission
selectedUsers={[]}
onUpdate={mockOnUpdate}
/>
);
act(() => {
fireEvent.click(screen.getByTestId('add-user'));
});
expect(screen.getByText('selectable-list')).toBeInTheDocument();
});
});

View File

@ -28,6 +28,7 @@ export const UserTag = ({
bordered,
size = UserTagSize.default,
className,
isTeam = false,
}: UserTags) => {
if (isUndefined(id) && isUndefined(name)) {
return null;
@ -58,7 +59,7 @@ export const UserTag = ({
)}
data-testid="user-tag"
size={8}>
<ProfilePicture name={id} width={toString(width[size])} />
<ProfilePicture isTeam={isTeam} name={id} width={toString(width[size])} />
<Typography.Text className={fontSizes[size]}>{name}</Typography.Text>
{closable && <CloseOutlined size={width[size]} onClick={onRemove} />}
</Space>

View File

@ -18,6 +18,7 @@ export interface UserTags {
bordered?: boolean;
size?: UserTagSize;
className?: string;
isTeam?: boolean;
}
export enum UserTagSize {

View File

@ -11,9 +11,10 @@
* limitations under the License.
*/
import { render } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { UserTag } from './UserTag.component';
import { UserTagSize } from './UserTag.interface';
jest.mock('../ProfilePicture/ProfilePicture', () => {
return jest.fn().mockReturnValue(<div>ProfilePicture</div>);
@ -42,4 +43,35 @@ describe('UserTag Component', () => {
expect(queryByTestId('user-tag')).not.toBeInTheDocument();
expect(container).toHaveTextContent('');
});
const userTagProps = {
id: '123',
name: 'John Doe',
onRemove: jest.fn(),
closable: true,
bordered: true,
size: UserTagSize.default,
className: 'custom-class',
isTeam: false,
};
it('renders without crashing', () => {
render(<UserTag {...userTagProps} />);
// Add more specific assertions if needed
expect(screen.getByTestId('user-tag')).toBeInTheDocument();
});
it('calls onRemove when close icon is clicked', () => {
render(<UserTag {...userTagProps} />);
const closeIcon = screen
.getByTestId('user-tag')
.querySelector('.anticon-close');
// Simulate click on the close icon
closeIcon && fireEvent.click(closeIcon);
// Check if the onRemove callback is called
expect(userTagProps.onRemove).toHaveBeenCalledTimes(1);
});
});