Fix #6893 : Ui improvements and bug fixes 2 (#6935)

* Fix #6893 : Ui improvements and bug fixes

* Fixed failing unit tests

* Fixed failing cypress tests

* glossary page left panel styling changes
This commit is contained in:
Aniket Katkar 2022-08-26 14:14:22 +05:30 committed by GitHub
parent 6a85b34e3c
commit 1c6d2f2649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 189 additions and 139 deletions

View File

@ -146,7 +146,10 @@ describe('Teams flow should work properly', () => {
cy.get('.ant-table-row').should('contain', TEAM_DETAILS.ownername);
//Removing the added user
cy.get('.ant-btn').should('exist').should('be.visible').click();
cy.get('.ant-btn > [data-testid="image"]')
.should('exist')
.should('be.visible')
.click();
//Validating the user added
cy.get('[data-testid="body-text"]').should(
@ -156,10 +159,10 @@ describe('Teams flow should work properly', () => {
//Click on confirm button
cy.get('[data-testid="save-button"]').should('be.visible').click();
// TODO: Remove cy.wait and wait for API to be completed before querying for new element
cy.wait(2000);
//
//Verify if user is removed
cy.get('[data-testid="searchbar"]')
@ -311,6 +314,12 @@ describe('Teams flow should work properly', () => {
.should('be.visible')
.click();
cy.wait(1000);
cy.get('.ant-dropdown-menu-item')
.should('exist')
.should('be.visible')
.click();
cy.wait(1000);
//Click on permanent delete option
cy.get('[data-testid="hard-delete-option"]')

View File

@ -16,6 +16,7 @@ import classNames from 'classnames';
import { cloneDeep, isEmpty, isUndefined } from 'lodash';
import { EditorContentRef } from 'Models';
import React, { useRef, useState } from 'react';
import { getBotsPagePath, getUsersPagePath } from '../../constants/constants';
import { validEmailRegEx } from '../../constants/regex.constants';
import { PageLayoutType } from '../../enums/layout.enum';
import { CreateUser as CreateUserSchema } from '../../generated/api/teams/createUser';
@ -60,6 +61,18 @@ const CreateUser = ({
validEmail: false,
});
const slashedBreadcrumbList = [
{
name: forceBot ? 'Bots' : 'Users',
url: forceBot ? getBotsPagePath() : getUsersPagePath(),
},
{
name: `Create ${forceBot ? 'Bot' : 'User'}`,
url: '',
activeTitle: true,
},
];
/**
* common function to update user input in to the state
* @param event change event for input/selection field
@ -215,17 +228,7 @@ const CreateUser = ({
return (
<PageLayout
classes="tw-max-w-full-hd tw-h-full tw-pt-4"
header={
<TitleBreadcrumb
titleLinks={[
{
name: `Create ${forceBot ? 'Bot' : 'User'}`,
url: '',
activeTitle: true,
},
]}
/>
}
header={<TitleBreadcrumb titleLinks={slashedBreadcrumbList} />}
layout={PageLayoutType['2ColRTL']}>
<div className="tw-form-container">
<h6 className="tw-heading tw-text-base">

View File

@ -59,6 +59,19 @@ jest.mock('antd', () => ({
.fn()
.mockImplementation(({ children }) => <div>{children}</div>),
},
Dropdown: jest.fn().mockImplementation(({ children, overlay }) => (
<div>
{children}
{overlay}
</div>
)),
Menu: jest.fn().mockImplementation(({ items }) => (
<div>
{items.map((item: { key: string; label: JSX.Element }) => {
<div key={item.key}>{item.label}</div>;
})}
</div>
)),
}));
const mockProps = {

View File

@ -12,14 +12,13 @@
*/
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Col, Input, Row, Space, Typography } from 'antd';
import { Col, Dropdown, Input, Menu, Row, Space, Typography } from 'antd';
import classNames from 'classnames';
import { cloneDeep, isEmpty } from 'lodash';
import { GlossaryTermAssets, LoadingState } from 'Models';
import RcTree from 'rc-tree';
import { DataNode, EventDataNode } from 'rc-tree/lib/interface';
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { Tooltip } from 'react-tippy';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
@ -218,35 +217,43 @@ const GlossaryV1 = ({
const manageButtonContent = () => {
return (
<div
className="tw-flex tw-items-center tw-gap-5 tw-p-1.5 tw-cursor-pointer"
id="manage-button"
onClick={() => setIsDelete(true)}>
<div>
<SVGIcons
alt="Delete"
className="tw-w-12"
icon={Icons.DELETE_GRADIANT}
/>
</div>
<div className="tw-text-left" data-testid="delete-button">
<p className="tw-font-medium">
Delete Glossary {selectedData?.displayName || selectedData?.name}
</p>
<p className="tw-text-grey-muted tw-text-xs">
Deleting this Glossary{' '}
{(selectedData as GlossaryTerm)?.glossary && 'Term'} will
permanently remove its metadata from OpenMetadata.
</p>
</div>
</div>
<Menu
items={[
{
label: (
<Space
className="tw-cursor-pointer manage-button"
size={8}
onClick={(e) => {
e.stopPropagation();
setIsDelete(true);
setShowActions(false);
}}>
<SVGIcons alt="Delete" icon={Icons.DELETE} />
<div className="tw-text-left" data-testid="delete-button">
<p
className="tw-font-medium"
data-testid="delete-button-title">
Delete
</p>
<p className="tw-text-grey-muted tw-text-xs">
Deleting this Glossary will permanently remove its metadata
from OpenMetadata.
</p>
</div>
</Space>
),
key: 'delete-button',
},
]}
/>
);
};
const fetchLeftPanel = () => {
return (
<div className="tw-h-full tw-px-2" id="glossary-left-panel">
<div className="tw-bg-white tw-shadow-box tw-border tw-border-border-gray tw-rounded-md tw-h-full tw-py-2">
<div className="tw-h-full tw-px-1" id="glossary-left-panel">
<div className="tw-bg-white tw-h-full tw-py-2 left-panel-container">
<div className="tw-flex tw-justify-between tw-items-center tw-px-3">
<h6 className="tw-heading tw-text-base">Glossary</h6>
</div>
@ -333,29 +340,27 @@ const GlossaryV1 = ({
</Button>
</NonAdminAction>
<NonAdminAction position="bottom" title={TITLE_FOR_NON_ADMIN_ACTION}>
<Button
className="tw-h-8 tw-rounded tw-mb-1 tw-flex"
data-testid="manage-button"
disabled={isHasAccess}
size="small"
theme="primary"
variant="outlined"
onClick={() => setShowActions(true)}>
<span className="tw-mr-2">Manage</span>
<Tooltip
arrow
arrowSize="big"
disabled={!isAuthDisabled && !isAdminUser}
html={manageButtonContent()}
open={showActions}
position="bottom-end"
theme="light"
onRequestClose={() => setShowActions(false)}>
<Dropdown
align={{ targetOffset: [-12, 0] }}
overlay={manageButtonContent()}
overlayStyle={{ width: '350px' }}
placement="bottomRight"
trigger={['click']}
visible={showActions}
onVisibleChange={setShowActions}>
<Button
className="tw-rounded tw-flex tw-justify-center tw-w-8 tw-h-8 glossary-manage-button tw-mb-1 tw-flex"
data-testid="manage-button"
disabled={isHasAccess}
size="small"
theme="primary"
variant="outlined"
onClick={() => setShowActions(true)}>
<span>
<FontAwesomeIcon icon="ellipsis-vertical" />
</span>
</Tooltip>
</Button>
</Button>
</Dropdown>
</NonAdminAction>
</div>
</div>

View File

@ -1,3 +1,9 @@
@box-shadow-color: rgba(0, 0, 0, 0.06);
@left-panel-border-color: rgb(229, 231, 235);
@manage-button-bg-primary: #7147e8;
@manage-dropdown-bg-primary: #d9ceee;
@white: #ffffff;
.display-name {
.ant-typography {
margin-bottom: 0;
@ -17,3 +23,26 @@
border-radius: 0.2rem;
margin-right: 0.3rem;
}
.glossary-manage-button {
border: none;
background-color: @manage-dropdown-bg-primary;
}
.manage-button {
.ant-space-item:first-child {
width: 40px;
align-self: baseline;
}
}
.ant-dropdown-menu {
padding: 8px 0px;
background: @white;
border: 1px solid #dde3ea;
box-shadow: 1px 1px 3px rgb(0 0 0 / 12%);
border-radius: 4px;
.ant-dropdown-menu-item {
padding: 5px 16px;
}
}

View File

@ -28,7 +28,6 @@ import { cloneDeep, isEmpty, isUndefined, orderBy } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Tooltip as TooltipTippy } from 'react-tippy';
import AppState from '../../AppState';
import {
getTeamAndUserDetailsPath,
@ -63,9 +62,9 @@ import { hasPemission } from '../../utils/PermissionsUtils';
import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { Button } from '../buttons/Button/Button';
import DeleteWidgetModal from '../common/DeleteWidget/DeleteWidgetModal';
import Description from '../common/description/Description';
import Ellipses from '../common/Ellipses/Ellipses';
import ManageButton from '../common/entityPageInfo/ManageButton/ManageButton';
import EntitySummaryDetails from '../common/EntitySummaryDetails/EntitySummaryDetails';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import NextPrevious from '../common/next-previous/NextPrevious';
@ -121,8 +120,6 @@ const TeamDetailsV1 = ({
state: boolean;
leave: boolean;
}>(DELETE_USER_INITIAL_STATE);
const [showAction, setShowAction] = useState(false);
const [isDelete, setIsDelete] = useState<boolean>(false);
const [searchTerm, setSearchTerm] = useState('');
const [table, setTable] = useState<Team[]>([]);
const [slashedDatabaseName, setSlashedDatabaseName] = useState<
@ -212,15 +209,15 @@ const TeamDetailsV1 = ({
render: (_, record) => (
<Space
align="center"
className="tw-w-full tw-justify-center"
className="tw-w-full tw-justify-center remove-icon"
size={8}>
<Tooltip placement="bottom" title="Remove">
<ButtonAntd
icon={
<SVGIcons
alt="Remove"
className="tw-w-4"
icon={Icons.DELETE}
className="tw-w-4 tw-mb-2.5"
icon={Icons.ICON_REMOVE}
/>
}
type="text"
@ -231,33 +228,7 @@ const TeamDetailsV1 = ({
),
},
];
}, []);
const manageButtonContent = () => {
return (
<div
className="tw-flex tw-items-center tw-gap-5 tw-p-1.5 tw-cursor-pointer"
id="manage-button"
onClick={() => setIsDelete(true)}>
<div>
<SVGIcons
alt="Delete"
className="tw-w-12"
icon={Icons.DELETE_GRADIANT}
/>
</div>
<div className="tw-text-left" data-testid="delete-button">
<p className="tw-font-medium">
Delete Team {getEntityName(currentTeam)}
</p>
<p className="tw-text-grey-muted tw-text-xs">
Deleting this Team {getEntityName(currentTeam)} will permanently
remove its metadata from OpenMetadata.
</p>
</div>
</div>
);
};
}, [deleteUserHandler]);
const extraInfo: ExtraInfo = {
key: 'Owner',
@ -589,7 +560,7 @@ const TeamDetailsV1 = ({
return alreadyJoined ? (
isJoinable || hasAccess ? (
<Button
className="tw-h-8 tw-px-2 tw-mb-4 tw-ml-2"
className="tw-h-8 tw-px-2"
data-testid="join-teams"
size="small"
theme="primary"
@ -600,7 +571,7 @@ const TeamDetailsV1 = ({
) : null
) : (
<Button
className="tw-h-8 tw-rounded tw-ml-2"
className="tw-h-8 tw-rounded"
data-testid="leave-team-button"
size="small"
theme="primary"
@ -720,7 +691,7 @@ const TeamDetailsV1 = ({
className="tw-flex tw-justify-between tw-items-center"
data-testid="header">
{getTeamHeading()}
<div className="tw-flex">
<Space align="center">
{!isUndefined(currentUser) &&
teamActionButton(
!isAlreadyJoinedTeam(currentTeam.id),
@ -729,30 +700,17 @@ const TeamDetailsV1 = ({
<NonAdminAction
position="bottom"
title={TITLE_FOR_NON_ADMIN_ACTION}>
<Button
className="tw-h-8 tw-rounded tw-mb-1 tw-ml-2 tw-flex"
data-testid="manage-button"
disabled={!hasAccess}
size="small"
theme="primary"
variant="outlined"
onClick={() => setIsDelete(true)}>
<TooltipTippy
arrow
arrowSize="big"
disabled={!hasAccess}
html={manageButtonContent()}
open={showAction}
position="bottom-end"
theme="light"
onRequestClose={() => setShowAction(false)}>
<span>
<FontAwesomeIcon icon="ellipsis-vertical" />
</span>
</TooltipTippy>
</Button>
<ManageButton
afterDeleteAction={afterDeleteAction}
buttonClassName="tw-p-4"
entityId={currentTeam.id}
entityName={
currentTeam.fullyQualifiedName || currentTeam.name
}
entityType="team"
/>
</NonAdminAction>
</div>
</Space>
</div>
<div className="tw-mb-3">
<Switch
@ -857,15 +815,6 @@ const TeamDetailsV1 = ({
onConfirm={handleRemoveUser}
/>
)}
<DeleteWidgetModal
afterDeleteAction={afterDeleteAction}
entityId={currentTeam.id}
entityName={currentTeam.fullyQualifiedName || currentTeam.name}
entityType="team"
visible={isDelete}
onCancel={() => setIsDelete(false)}
/>
</div>
);
};

View File

@ -23,3 +23,10 @@
padding-left: 16px;
}
}
.remove-icon {
.ant-btn {
height: 22px;
width: 22px;
}
}

View File

@ -136,7 +136,7 @@ const UserListV1: FC<UserListV1Props> = ({
render: (_, record) => (
<Space
align="center"
className="tw-w-full tw-justify-center"
className="tw-w-full tw-justify-center action-icons"
size={8}>
{showRestore && (
<Tooltip placement="bottom" title="Restore">
@ -144,7 +144,7 @@ const UserListV1: FC<UserListV1Props> = ({
icon={
<SVGIcons
alt="Restore"
className="tw-w-4"
className="tw-w-4 tw-mb-2.5"
icon={Icons.RESTORE}
/>
}
@ -161,7 +161,7 @@ const UserListV1: FC<UserListV1Props> = ({
icon={
<SVGIcons
alt="Delete"
className="tw-w-4"
className="tw-w-4 tw-mb-2.5"
icon={Icons.DELETE}
/>
}

View File

@ -24,3 +24,10 @@
}
}
}
.action-icons {
.ant-btn {
height: 20px;
width: 20px;
}
}

View File

@ -0,0 +1,5 @@
.entity-summary-details {
.ant-space-item {
position: relative;
}
}

View File

@ -12,6 +12,7 @@ import { Button } from '../../buttons/Button/Button';
import OwnerWidgetWrapper from '../OwnerWidget/OwnerWidgetWrapper.component';
import ProfilePicture from '../ProfilePicture/ProfilePicture';
import TierCard from '../TierCard/TierCard';
import './EntitySummaryDetails.style.less';
export interface GetInfoElementsProps {
data: ExtraInfo;
@ -151,7 +152,10 @@ const EntitySummaryDetails = ({
}
return (
<Space data-testid="entity-summary-details" direction="horizontal">
<Space
className="entity-summary-details"
data-testid="entity-summary-details"
direction="horizontal">
{retVal}
{displayVal && (
<>
@ -254,6 +258,7 @@ const EntitySummaryDetails = ({
updateTier={updateTier}
/>
}
placement="bottomRight"
trigger={['click']}>
<span
className="tw-flex"

View File

@ -12,7 +12,7 @@ import {
} from '../../../utils/UserDataUtils';
import DropDownList from '../../dropdown/DropDownList';
import { Status } from '../../ManageTab/ManageTab.interface';
import './OwnerWidgetWrapper.module.css';
import './OwnerWidgetWrapper.style.less';
interface OwnerWidgetWrapperProps {
currentOwner?: Table['owner'];
@ -160,7 +160,7 @@ const OwnerWidgetWrapper = ({
return visible ? (
<DropDownList
className="dropdown"
className="edit-owner-dropdown"
dropDownList={listOwners}
groupType="tab"
isLoading={isUserLoading}

View File

@ -0,0 +1,4 @@
.dropdown-list.edit-owner-dropdown {
left: -120px;
top: 20px;
}

View File

@ -13,6 +13,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Dropdown, Menu, Space } from 'antd';
import classNames from 'classnames';
import React, { FC, useState } from 'react';
import { EntityType } from '../../../../enums/entity.enum';
import { ANNOUNCEMENT_ENTITIES } from '../../../../utils/AnnouncementsUtils';
@ -22,6 +23,8 @@ import './ManageButton.less';
interface Props {
allowSoftDelete?: boolean;
afterDeleteAction?: () => void;
buttonClassName?: string;
entityName: string;
entityId?: string;
entityType?: string;
@ -33,6 +36,8 @@ interface Props {
const ManageButton: FC<Props> = ({
allowSoftDelete,
afterDeleteAction,
buttonClassName,
deleteMessage,
entityName,
entityType,
@ -116,7 +121,10 @@ const ManageButton: FC<Props> = ({
visible={showActions}
onVisibleChange={setShowActions}>
<Button
className="tw-rounded tw-flex tw-justify-center tw-w-6 manage-dropdown-button"
className={classNames(
'tw-rounded tw-flex tw-justify-center tw-w-6 manage-dropdown-button',
buttonClassName
)}
data-testid="manage-button"
size="small"
type="default"
@ -129,6 +137,7 @@ const ManageButton: FC<Props> = ({
</Dropdown>
{isDelete && (
<DeleteWidgetModal
afterDeleteAction={afterDeleteAction}
allowSoftDelete={allowSoftDelete}
deleteMessage={deleteMessage}
entityId={entityId || ''}

View File

@ -444,6 +444,14 @@ export const getCreateUserPath = (bot: boolean) => {
return path;
};
export const getUsersPagePath = () => {
return `${ROUTES.SETTINGS}/${GlobalSettingsMenuCategory.MEMBERS}/users`;
};
export const getBotsPagePath = () => {
return `${ROUTES.SETTINGS}/${GlobalSettingsMenuCategory.INTEGRATIONS}/bots`;
};
export const TIMEOUT = {
USER_LIST: 60000, // 60 seconds for user retrieval
TOAST_DELAY: 5000, // 5 seconds timeout for toaster autohide delay