1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

refactor: improve icon labels and tooltips (#884)

This commit is contained in:
olav 2022-04-21 08:26:49 +02:00 committed by GitHub
parent 8a3db090d5
commit 2e5e25bfe5
63 changed files with 479 additions and 311 deletions

View File

@ -113,29 +113,30 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
<PermissionIconButton <PermissionIconButton
permission={UPDATE_ADDON} permission={UPDATE_ADDON}
onClick={() => toggleAddon(addon)} onClick={() => toggleAddon(addon)}
tooltip="Toggle addon"
> >
<ConditionallyRender <ConditionallyRender
condition={addon.enabled} condition={addon.enabled}
show={<Visibility titleAccess="Disable addon" />} show={<Visibility />}
elseShow={<VisibilityOff titleAccess="Enable addon" />} elseShow={<VisibilityOff />}
/> />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
permission={UPDATE_ADDON} permission={UPDATE_ADDON}
onClick={() => { tooltip="Edit Addon"
history.push(`/addons/edit/${addon.id}`); onClick={() => history.push(`/addons/edit/${addon.id}`)}
}}
> >
<Edit titleAccess="Edit Addon" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
permission={DELETE_ADDON} permission={DELETE_ADDON}
tooltip="Remove Addon"
onClick={() => { onClick={() => {
setDeletedAddon(addon); setDeletedAddon(addon);
setShowDelete(true); setShowDelete(true);
}} }}
> >
<Delete titleAccess="Remove Addon" /> <Delete />
</PermissionIconButton> </PermissionIconButton>
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>

View File

@ -8,6 +8,7 @@ import {
TableCell, TableCell,
TableHead, TableHead,
TableRow, TableRow,
Tooltip,
} from '@material-ui/core'; } from '@material-ui/core';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
@ -200,24 +201,28 @@ export const ApiTokenList = () => {
<Secret value={item.secret} /> <Secret value={item.secret} />
</TableCell> </TableCell>
<TableCell className={styles.actionsContainer}> <TableCell className={styles.actionsContainer}>
<IconButton <Tooltip title="Copy token">
onClick={() => { <IconButton
copyToken(item.secret); onClick={() => {
}} copyToken(item.secret);
> }}
<FileCopy /> >
</IconButton> <FileCopy />
</IconButton>
</Tooltip>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(DELETE_API_TOKEN)} condition={hasAccess(DELETE_API_TOKEN)}
show={ show={
<IconButton <Tooltip title="Delete token">
onClick={() => { <IconButton
setDeleteToken(item); onClick={() => {
setShowDelete(true); setDeleteToken(item);
}} setShowDelete(true);
> }}
<Delete /> >
</IconButton> <Delete />
</IconButton>
</Tooltip>
} }
/> />
</TableCell> </TableCell>

View File

@ -1,4 +1,4 @@
import { IconButton } from '@material-ui/core'; import { IconButton, Tooltip } from '@material-ui/core';
import CopyIcon from '@material-ui/icons/FileCopy'; import CopyIcon from '@material-ui/icons/FileCopy';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
@ -14,8 +14,7 @@ export const UserToken = ({ token }: IUserTokenProps) => {
if (copy(token)) { if (copy(token)) {
setToastData({ setToastData({
type: 'success', type: 'success',
title: 'Token copied', title: 'Token copied to clipboard',
text: `Token is copied to clipboard`,
}); });
} else } else
setToastData({ setToastData({
@ -38,9 +37,11 @@ export const UserToken = ({ token }: IUserTokenProps) => {
}} }}
> >
{token} {token}
<IconButton onClick={copyToken}> <Tooltip title="Copy token">
<CopyIcon /> <IconButton onClick={copyToken}>
</IconButton> <CopyIcon />
</IconButton>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -121,7 +121,12 @@ const EnvironmentPermissionAccordion = ({
<Accordion style={{ boxShadow: 'none' }}> <Accordion style={{ boxShadow: 'none' }}>
<AccordionSummary <AccordionSummary
className={styles.accordionSummary} className={styles.accordionSummary}
expandIcon={<ExpandMore className={styles.icon} />} expandIcon={
<ExpandMore
className={styles.icon}
titleAccess="Toggle"
/>
}
> >
<div className={styles.accordionHeader}> <div className={styles.accordionHeader}>
<StringTruncator <StringTruncator

View File

@ -50,18 +50,17 @@ const RoleListItem = ({
<TableCell align="right"> <TableCell align="right">
<PermissionIconButton <PermissionIconButton
data-loading data-loading
aria-label="Edit"
disabled={type === BUILTIN_ROLE_TYPE} disabled={type === BUILTIN_ROLE_TYPE}
onClick={() => { onClick={() => {
history.push(`/admin/roles/${id}/edit`); history.push(`/admin/roles/${id}/edit`);
}} }}
permission={ADMIN} permission={ADMIN}
tooltip="Edit role"
> >
<Edit /> <Edit />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
data-loading data-loading
aria-label="Remove role"
disabled={type === BUILTIN_ROLE_TYPE} disabled={type === BUILTIN_ROLE_TYPE}
onClick={() => { onClick={() => {
// @ts-expect-error // @ts-expect-error
@ -69,6 +68,7 @@ const RoleListItem = ({
setDelDialog(true); setDelDialog(true);
}} }}
permission={ADMIN} permission={ADMIN}
tooltip="Remove role"
> >
<Delete /> <Delete />
</PermissionIconButton> </PermissionIconButton>

View File

@ -1,4 +1,4 @@
import { IconButton } from '@material-ui/core'; import { IconButton, Tooltip } from '@material-ui/core';
import CopyIcon from '@material-ui/icons/FileCopy'; import CopyIcon from '@material-ui/icons/FileCopy';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
@ -47,9 +47,11 @@ const UserInviteLink = ({ inviteLink }: IInviteLinkProps) => {
}} }}
> >
{inviteLink} {inviteLink}
<IconButton onClick={handleCopy}> <Tooltip title="Copy link">
<CopyIcon /> <IconButton onClick={handleCopy}>
</IconButton> <CopyIcon />
</IconButton>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -87,7 +87,6 @@ const UserListItem = ({
<Tooltip title="Edit user" arrow> <Tooltip title="Edit user" arrow>
<IconButton <IconButton
data-loading data-loading
aria-label="Edit user"
onClick={() => onClick={() =>
history.push(`/admin/users/${user.id}/edit`) history.push(`/admin/users/${user.id}/edit`)
} }
@ -98,7 +97,6 @@ const UserListItem = ({
<Tooltip title="Change password" arrow> <Tooltip title="Change password" arrow>
<IconButton <IconButton
data-loading data-loading
aria-label="Change password"
onClick={openPwDialog(user)} onClick={openPwDialog(user)}
> >
<Lock /> <Lock />
@ -107,7 +105,6 @@ const UserListItem = ({
<Tooltip title="Remove user" arrow> <Tooltip title="Remove user" arrow>
<IconButton <IconButton
data-loading data-loading
aria-label="Remove user"
onClick={openDelDialog(user)} onClick={openDelDialog(user)}
> >
<Delete /> <Delete />

View File

@ -112,13 +112,13 @@ export const ApplicationEdit = () => {
condition={Boolean(url)} condition={Boolean(url)}
show={ show={
<IconButton component={Link} href={url}> <IconButton component={Link} href={url}>
<LinkIcon /> <LinkIcon titleAccess={url} />
</IconButton> </IconButton>
} }
/> />
<PermissionButton <PermissionButton
title="Delete application" tooltip="Delete application"
onClick={toggleModal} onClick={toggleModal}
permission={UPDATE_APPLICATION} permission={UPDATE_APPLICATION}
> >

View File

@ -22,7 +22,7 @@ export const ApplicationList = () => {
const renderNoApplications = () => ( const renderNoApplications = () => (
<> <>
<section style={{ textAlign: 'center' }}> <section style={{ textAlign: 'center' }}>
<Warning /> <br /> <Warning titleAccess="Warning" /> <br />
<br /> <br />
Oh snap, it does not seem like you have connected any Oh snap, it does not seem like you have connected any
applications. To connect your application to Unleash you will applications. To connect your application to Unleash you will

View File

@ -46,7 +46,7 @@ export const AutocompleteBox = ({
classes={{ inputRoot: styles.inputRoot }} classes={{ inputRoot: styles.inputRoot }}
options={options} options={options}
value={value} value={value}
popupIcon={<ArrowDropDown />} popupIcon={<ArrowDropDown titleAccess="Toggle" />}
onChange={(event, value) => onChange(value || [])} onChange={(event, value) => onChange(value || [])}
renderInput={renderInput} renderInput={renderInput}
getOptionLabel={value => value.label} getOptionLabel={value => value.label}

View File

@ -12,7 +12,7 @@ const CheckMarkBadge = ({ type, className }: ICheckMarkBadgeProps) => {
return ( return (
<div className={classnames(styles.badge, className)}> <div className={classnames(styles.badge, className)}>
{type === 'error' ? ( {type === 'error' ? (
<Close className={styles.check} /> <Close className={styles.check} titleAccess="Error" />
) : ( ) : (
<Check className={styles.check} /> <Check className={styles.check} />
)} )}

View File

@ -17,6 +17,7 @@ import {
operatorsForContext, operatorsForContext,
CURRENT_TIME_CONTEXT_FIELD, CURRENT_TIME_CONTEXT_FIELD,
} from 'utils/operatorsForContext'; } from 'utils/operatorsForContext';
import { Tooltip } from '@material-ui/core';
interface IConstraintAccordionViewHeader { interface IConstraintAccordionViewHeader {
localConstraint: IConstraint; localConstraint: IConstraint;
@ -121,14 +122,16 @@ export const ConstraintAccordionEditHeader = ({
show={<p className={styles.editingBadge}>Updating...</p>} show={<p className={styles.editingBadge}>Updating...</p>}
elseShow={<p className={styles.editingBadge}>Editing</p>} elseShow={<p className={styles.editingBadge}>Editing</p>}
/> />
<a <Tooltip title="Help">
href="https://docs.getunleash.io/advanced/strategy_constraints" <a
style={{ marginLeft: 'auto' }} href="https://docs.getunleash.io/advanced/strategy_constraints"
target="_blank" style={{ marginLeft: 'auto' }}
rel="noopener noreferrer" target="_blank"
> rel="noopener noreferrer"
<Help className={styles.help} /> >
</a> <Help className={styles.help} />
</a>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -45,7 +45,7 @@ export const ConstraintAccordionView = ({
> >
<AccordionSummary <AccordionSummary
className={styles.summary} className={styles.summary}
expandIcon={<ExpandMore />} expandIcon={<ExpandMore titleAccess="Toggle" />}
> >
<ConstraintAccordionViewHeader <ConstraintAccordionViewHeader
compact={compact} compact={compact}

View File

@ -104,8 +104,9 @@ export const ConstraintAccordionViewHeader = ({
projectId={projectId} projectId={projectId}
environmentId={environmentId} environmentId={environmentId}
hidden={!onEdit} hidden={!onEdit}
tooltip="Edit constraint"
> >
<Edit titleAccess="edit constraint" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
} }
/> />
@ -116,9 +117,10 @@ export const ConstraintAccordionViewHeader = ({
onClick={onDeleteClick!} onClick={onDeleteClick!}
permission={UPDATE_FEATURE_STRATEGY} permission={UPDATE_FEATURE_STRATEGY}
projectId={projectId} projectId={projectId}
tooltip="Delete constraint"
environmentId={environmentId} environmentId={environmentId}
> >
<Delete titleAccess="delete constraint" /> <Delete />
</PermissionIconButton> </PermissionIconButton>
} }
/> />

View File

@ -6,7 +6,7 @@ export const ConstraintIcon = () => {
return ( return (
<div className={styles.constraintIconContainer}> <div className={styles.constraintIconContainer}>
<TrackChanges className={styles.constraintIcon} aria-hidden /> <TrackChanges className={styles.constraintIcon} />
</div> </div>
); );
}; };

View File

@ -12,7 +12,7 @@ const DropdownMenu = ({
id, id,
title, title,
callback, callback,
icon = <ArrowDropDown />, icon = <ArrowDropDown titleAccess="Toggle" />,
label, label,
style, style,
startIcon, startIcon,

View File

@ -9,6 +9,8 @@ interface IEnvironmentIcon {
const EnvironmentIcon = ({ enabled, className }: IEnvironmentIcon) => { const EnvironmentIcon = ({ enabled, className }: IEnvironmentIcon) => {
const theme = useTheme(); const theme = useTheme();
const title = enabled ? 'Environment enabled' : 'Environment disabled';
const container = { const container = {
backgroundColor: enabled backgroundColor: enabled
? theme.palette.primary.light ? theme.palette.primary.light
@ -31,7 +33,7 @@ const EnvironmentIcon = ({ enabled, className }: IEnvironmentIcon) => {
return ( return (
<div style={container} className={className}> <div style={container} className={className}>
<Cloud style={icon} /> <Cloud style={icon} titleAccess={title} />
</div> </div>
); );
}; };

View File

@ -1,7 +1,12 @@
import { useStyles } from './FormTemplate.styles'; import { useStyles } from './FormTemplate.styles';
import MenuBookIcon from '@material-ui/icons/MenuBook'; import MenuBookIcon from '@material-ui/icons/MenuBook';
import Codebox from '../Codebox/Codebox'; import Codebox from '../Codebox/Codebox';
import { Collapse, IconButton, useMediaQuery } from '@material-ui/core'; import {
Collapse,
IconButton,
useMediaQuery,
Tooltip,
} from '@material-ui/core';
import { FileCopy, Info } from '@material-ui/icons'; import { FileCopy, Info } from '@material-ui/icons';
import ConditionallyRender from '../ConditionallyRender'; import ConditionallyRender from '../ConditionallyRender';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
@ -95,9 +100,11 @@ const FormTemplate: React.FC<ICreateProps> = ({
> >
<h3 className={styles.subtitle}> <h3 className={styles.subtitle}>
API Command{' '} API Command{' '}
<IconButton onClick={copyCommand}> <Tooltip title="Copy command">
<FileCopy className={styles.icon} /> <IconButton onClick={copyCommand}>
</IconButton> <FileCopy className={styles.icon} />
</IconButton>
</Tooltip>
</h3> </h3>
<Codebox text={formatApiCode()} /> <Codebox text={formatApiCode()} />
</Guidance> </Guidance>
@ -126,12 +133,14 @@ const MobileGuidance = ({
<div className={styles.mobileGuidanceBgContainer}> <div className={styles.mobileGuidanceBgContainer}>
<MobileGuidanceBG className={styles.mobileGuidanceBackground} /> <MobileGuidanceBG className={styles.mobileGuidanceBackground} />
</div> </div>
<IconButton <Tooltip title="Toggle help">
className={styles.mobileGuidanceButton} <IconButton
onClick={() => setOpen(prev => !prev)} className={styles.mobileGuidanceButton}
> onClick={() => setOpen(prev => !prev)}
<Info className={styles.infoIcon} /> >
</IconButton> <Info className={styles.infoIcon} />
</IconButton>
</Tooltip>
<Collapse in={open} timeout={500}> <Collapse in={open} timeout={500}>
<Guidance <Guidance
description={description} description={description}

View File

@ -15,6 +15,9 @@ const PasswordField = ({ ...rest }) => {
e.preventDefault(); e.preventDefault();
}; };
const IconComponent = showPassword ? Visibility : VisibilityOff;
const iconTitle = 'Toggle password visibility';
return ( return (
<TextField <TextField
variant="outlined" variant="outlined"
@ -27,11 +30,10 @@ const PasswordField = ({ ...rest }) => {
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword} onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword} onMouseDown={handleMouseDownPassword}
> >
{showPassword ? <Visibility /> : <VisibilityOff />} <IconComponent titleAccess={iconTitle} />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
), ),

View File

@ -1,15 +1,19 @@
import { Button, ButtonProps, Tooltip } from '@material-ui/core'; import { Button, ButtonProps } from '@material-ui/core';
import { Lock } from '@material-ui/icons'; import { Lock } from '@material-ui/icons';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import ConditionallyRender from '../ConditionallyRender'; import ConditionallyRender from '../ConditionallyRender';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { formatAccessText } from 'utils/formatAccessText';
import { useId } from 'hooks/useId';
export interface IPermissionButtonProps extends ButtonProps { export interface IPermissionButtonProps extends Omit<ButtonProps, 'title'> {
permission: string | string[]; permission: string | string[];
onClick?: (e: any) => void; onClick?: (e: any) => void;
disabled?: boolean; disabled?: boolean;
projectId?: string; projectId?: string;
environmentId?: string; environmentId?: string;
tooltip?: string;
} }
const PermissionButton: React.FC<IPermissionButtonProps> = ({ const PermissionButton: React.FC<IPermissionButtonProps> = ({
@ -21,9 +25,11 @@ const PermissionButton: React.FC<IPermissionButtonProps> = ({
disabled, disabled,
projectId, projectId,
environmentId, environmentId,
tooltip,
...rest ...rest
}) => { }) => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const id = useId();
let access; let access;
const handleAccess = () => { const handleAccess = () => {
@ -53,30 +59,27 @@ const PermissionButton: React.FC<IPermissionButtonProps> = ({
access = handleAccess(); access = handleAccess();
const tooltipText = !access
? "You don't have access to perform this operation"
: '';
return ( return (
<Tooltip title={tooltipText} arrow> <TooltipResolver title={formatAccessText(access, tooltip)} arrow>
<span> <span id={id}>
<Button <Button
onClick={onClick} onClick={onClick}
disabled={disabled || !access} disabled={disabled || !access}
aria-describedby={id}
variant={variant} variant={variant}
color={color} color={color}
{...rest} {...rest}
endIcon={ endIcon={
<ConditionallyRender <ConditionallyRender
condition={!access} condition={!access}
show={<Lock />} show={<Lock titleAccess="Locked" />}
/> />
} }
> >
{children} {children}
</Button> </Button>
</span> </span>
</Tooltip> </TooltipResolver>
); );
}; };

View File

@ -1,14 +1,17 @@
import { IconButton, Tooltip, IconButtonProps } from '@material-ui/core'; import { IconButton, IconButtonProps } from '@material-ui/core';
import React, { useContext, ReactNode } from 'react'; import React, { useContext, ReactNode } from 'react';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { formatAccessText } from 'utils/formatAccessText';
import { useId } from 'hooks/useId';
interface IPermissionIconButtonProps { interface IPermissionIconButtonProps {
permission: string; permission: string;
projectId?: string; projectId?: string;
environmentId?: string; environmentId?: string;
className?: string; className?: string;
title?: string; tooltip?: string;
children?: ReactNode; children?: ReactNode;
disabled?: boolean; disabled?: boolean;
hidden?: boolean; hidden?: boolean;
@ -30,9 +33,11 @@ const PermissionIconButton = ({
projectId, projectId,
children, children,
environmentId, environmentId,
tooltip,
...rest ...rest
}: IButtonProps | ILinkProps) => { }: IButtonProps | ILinkProps) => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const id = useId();
let access; let access;
if (projectId && environmentId) { if (projectId && environmentId) {
@ -43,18 +48,14 @@ const PermissionIconButton = ({
access = hasAccess(permission); access = hasAccess(permission);
} }
const tooltipText = !access
? "You don't have access to perform this operation"
: '';
return ( return (
<Tooltip title={tooltipText} arrow> <TooltipResolver title={formatAccessText(access, tooltip)}>
<span> <span id={id}>
<IconButton disabled={!access} {...rest}> <IconButton {...rest} disabled={!access} aria-labelledby={id}>
{children} {children}
</IconButton> </IconButton>
</span> </span>
</Tooltip> </TooltipResolver>
); );
}; };

View File

@ -1,6 +1,8 @@
import { Switch, Tooltip, SwitchProps } from '@material-ui/core'; import { Switch, SwitchProps } from '@material-ui/core';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { formatAccessText } from 'utils/formatAccessText';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
interface IPermissionSwitchProps extends SwitchProps { interface IPermissionSwitchProps extends SwitchProps {
permission: string; permission: string;
@ -38,12 +40,8 @@ const PermissionSwitch = React.forwardRef<
access = hasAccess(permission); access = hasAccess(permission);
} }
const tooltipText = !access
? "You don't have access to perform this operation"
: '';
return ( return (
<Tooltip title={tooltipText} arrow> <TooltipResolver title={formatAccessText(access, tooltip)} arrow>
<span data-loading> <span data-loading>
<Switch <Switch
onChange={onChange} onChange={onChange}
@ -53,7 +51,7 @@ const PermissionSwitch = React.forwardRef<
{...rest} {...rest}
/> />
</span> </span>
</Tooltip> </TooltipResolver>
); );
}); });

View File

@ -53,7 +53,7 @@ export const TableActions = ({ search, onSearch }: ITableActionsProps) => {
</Tooltip> </Tooltip>
} }
/> />
<div className={styles.verticalSeparator}></div> <div className={styles.verticalSeparator} />
</> </>
); );
}; };

View File

@ -57,7 +57,6 @@ export const TableSearchField = ({
<Tooltip title="Clear search query" arrow> <Tooltip title="Clear search query" arrow>
<IconButton <IconButton
size="small" size="small"
aria-label="Clear search query"
onClick={() => { onClick={() => {
onChange(''); onChange('');
onBlur?.(true); onBlur?.(true);

View File

@ -1,7 +1,7 @@
import { useStyles } from './Toast.styles'; import { useStyles } from './Toast.styles';
import classnames from 'classnames'; import classnames from 'classnames';
import { useContext } from 'react'; import { useContext } from 'react';
import { IconButton } from '@material-ui/core'; import { IconButton, Tooltip } from '@material-ui/core';
import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge'; import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge';
import UIContext from 'contexts/UIContext'; import UIContext from 'contexts/UIContext';
import ConditionallyRender from 'component/common/ConditionallyRender'; import ConditionallyRender from 'component/common/ConditionallyRender';
@ -76,14 +76,15 @@ const Toast = ({ title, text, type, confetti }: IToast) => {
/> />
</div> </div>
</div> </div>
<Tooltip title="Close">
<IconButton <IconButton
color="primary" color="primary"
onClick={hide} onClick={hide}
className={styles.buttonStyle} className={styles.buttonStyle}
> >
<Close /> <Close />
</IconButton> </IconButton>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,21 @@
import { Tooltip, TooltipProps } from '@material-ui/core';
interface ITooltipResolverProps extends Omit<TooltipProps, 'title'> {
title: string | undefined;
}
export const TooltipResolver = ({
title,
children,
...rest
}: ITooltipResolverProps) => {
if (!title) {
return children;
}
return (
<Tooltip {...rest} title={title}>
{children}
</Tooltip>
);
};

View File

@ -79,7 +79,6 @@ const ContextList = () => {
show={ show={
<Tooltip title="Edit context field"> <Tooltip title="Edit context field">
<IconButton <IconButton
aria-label="edit"
onClick={() => onClick={() =>
history.push(`/context/edit/${field.name}`) history.push(`/context/edit/${field.name}`)
} }
@ -94,7 +93,6 @@ const ContextList = () => {
show={ show={
<Tooltip title="Delete context field"> <Tooltip title="Delete context field">
<IconButton <IconButton
aria-label="delete"
onClick={() => { onClick={() => {
setName(field.name); setName(field.name);
setShowDelDialogue(true); setShowDelDialogue(true);

View File

@ -155,46 +155,38 @@ const EnvironmentListItem = ({
<ConditionallyRender <ConditionallyRender
condition={updatePermission} condition={updatePermission}
show={ show={
<Tooltip title="Drag to reorder"> <IconButton>
<div> <DragIndicator titleAccess="Drag" cursor="grab" />
<IconButton> </IconButton>
<DragIndicator titleAccess="Drag" />
</IconButton>
</div>
</Tooltip>
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={updatePermission} condition={updatePermission}
show={ show={
<Tooltip title={`${tooltipText} environment`}> <Tooltip title={`${tooltipText} environment`}>
<div> <IconButton
<IconButton onClick={() => {
onClick={() => { setSelectedEnv(env);
setSelectedEnv(env); setToggleDialog(prev => !prev);
setToggleDialog(prev => !prev); }}
}} >
> <OfflineBolt />
<OfflineBolt titleAccess="Toggle" /> </IconButton>
</IconButton>
</div>
</Tooltip> </Tooltip>
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={updatePermission} condition={updatePermission}
show={ show={
<Tooltip title="Update environment"> <Tooltip title="Edit environment">
<div> <IconButton
<IconButton disabled={env.protected}
disabled={env.protected} onClick={() => {
onClick={() => { history.push(`/environments/${env.name}`);
history.push(`/environments/${env.name}`); }}
}} >
> <Edit />
<Edit titleAccess="Edit" /> </IconButton>
</IconButton>
</div>
</Tooltip> </Tooltip>
} }
/> />
@ -202,17 +194,15 @@ const EnvironmentListItem = ({
condition={hasAccess(DELETE_ENVIRONMENT)} condition={hasAccess(DELETE_ENVIRONMENT)}
show={ show={
<Tooltip title="Delete environment"> <Tooltip title="Delete environment">
<div> <IconButton
<IconButton disabled={env.protected}
disabled={env.protected} onClick={() => {
onClick={() => { setDeldialogue(true);
setDeldialogue(true); setSelectedEnv(env);
setSelectedEnv(env); }}
}} >
> <Delete />
<Delete titleAccess="Delete" /> </IconButton>
</IconButton>
</div>
</Tooltip> </Tooltip>
} }
/> />

View File

@ -35,7 +35,7 @@ export const CreateFeatureButton = ({
data-testid={NAVIGATE_TO_CREATE_FEATURE} data-testid={NAVIGATE_TO_CREATE_FEATURE}
disabled={!createFeature.access} disabled={!createFeature.access}
> >
<Add titleAccess="New" /> <Add />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }

View File

@ -35,7 +35,7 @@ export const FeatureStrategyMenuCard = ({
return ( return (
<Link to={createStrategyPath} className={styles.card}> <Link to={createStrategyPath} className={styles.card}>
<div className={styles.icon}> <div className={styles.icon}>
<StrategyIcon aria-hidden /> <StrategyIcon />
</div> </div>
<div> <div>
<StringTruncator <StringTruncator

View File

@ -69,9 +69,10 @@ export const FeatureStrategyRemove = ({
disabled={disabled} disabled={disabled}
permission={DELETE_FEATURE_STRATEGY} permission={DELETE_FEATURE_STRATEGY}
data-testid={STRATEGY_FORM_REMOVE_ID} data-testid={STRATEGY_FORM_REMOVE_ID}
tooltip="Delete strategy"
type="button" type="button"
> >
<Delete titleAccess="Delete strategy" /> <Delete />
</PermissionIconButton> </PermissionIconButton>
} }
elseShow={ elseShow={

View File

@ -145,6 +145,7 @@ const FeatureToggleListItem = ({
!projectExists() !projectExists()
} }
onClick={reviveFeature} onClick={reviveFeature}
tooltip="Revive feature"
> >
<Undo /> <Undo />
</PermissionIconButton> </PermissionIconButton>

View File

@ -128,14 +128,17 @@ exports[`renders correctly with one feature 1`] = `
className="material-icons MuiIcon-root" className="material-icons MuiIcon-root"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M7 10l5 5 5-5z" d="M7 10l5 5 5-5z"
/> />
<title>
Toggle
</title>
</svg> </svg>
</span> </span>
</span> </span>
@ -296,14 +299,17 @@ exports[`renders correctly with one feature without permissions 1`] = `
className="material-icons MuiIcon-root" className="material-icons MuiIcon-root"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M7 10l5 5 5-5z" d="M7 10l5 5 5-5z"
/> />
<title>
Toggle
</title>
</svg> </svg>
</span> </span>
</span> </span>

View File

@ -93,7 +93,7 @@ const FeatureOverviewEnvironment = ({
> >
<AccordionSummary <AccordionSummary
className={styles.accordionHeader} className={styles.accordionHeader}
expandIcon={<ExpandMore />} expandIcon={<ExpandMore titleAccess="Toggle" />}
> >
<div className={styles.header} data-loading> <div className={styles.header} data-loading>
<div className={styles.headerTitle}> <div className={styles.headerTitle}>

View File

@ -53,8 +53,9 @@ const FeatureOverviewEnvironmentStrategy = ({
projectId={projectId} projectId={projectId}
component={Link} component={Link}
to={editStrategyPath} to={editStrategyPath}
tooltip="Edit"
> >
<Edit titleAccess="Edit" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
<FeatureStrategyRemove <FeatureStrategyRemove
projectId={projectId} projectId={projectId}

View File

@ -52,7 +52,10 @@ const FeatureOverviewMetaData = () => {
component={Link} component={Link}
to={`/projects/${projectId}/features/${featureId}/settings`} to={`/projects/${projectId}/features/${featureId}/settings`}
> >
<Edit className={styles.editIcon} /> <Edit
className={styles.editIcon}
titleAccess="Settings"
/>
</PermissionIconButton> </PermissionIconButton>
</div> </div>
</span> </span>
@ -66,6 +69,7 @@ const FeatureOverviewMetaData = () => {
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
component={Link} component={Link}
to={`/projects/${projectId}/features/${featureId}/settings`} to={`/projects/${projectId}/features/${featureId}/settings`}
tooltip="Edit description"
> >
<Edit className={styles.editIcon} /> <Edit className={styles.editIcon} />
</PermissionIconButton> </PermissionIconButton>

View File

@ -106,7 +106,9 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
data-loading data-loading
label={t.value} label={t.value}
key={`${t.type}:${t.value}`} key={`${t.type}:${t.value}`}
deleteIcon={<Close className={styles.closeIcon} />} deleteIcon={
<Close className={styles.closeIcon} titleAccess="Remove" />
}
onDelete={ onDelete={
canDeleteTag canDeleteTag
? () => { ? () => {

View File

@ -34,8 +34,9 @@ export const FeatureSettingsInformation = ({
projectId={projectId} projectId={projectId}
data-loading data-loading
onClick={onEdit} onClick={onEdit}
tooltip="Edit"
> >
<Edit titleAccess="Edit" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
</div> </div>
<Typography> <Typography>

View File

@ -90,7 +90,10 @@ const FeatureSettingsProjectConfirm = ({
</p> </p>
<div className={styles.iconContainer}> <div className={styles.iconContainer}>
<div className={styles.errorIconContainer}> <div className={styles.errorIconContainer}>
<Error className={styles.check} /> <Error
className={styles.check}
titleAccess="Error"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Grid, IconButton, TextField } from '@material-ui/core'; import { Grid, IconButton, TextField, Tooltip } from '@material-ui/core';
import { Delete } from '@material-ui/icons'; import { Delete } from '@material-ui/icons';
import { useStyles } from './OverrideConfig.styles'; import { useStyles } from './OverrideConfig.styles';
import { Autocomplete } from '@material-ui/lab'; import { Autocomplete } from '@material-ui/lab';
@ -98,9 +98,11 @@ export const OverrideConfig = ({
/> />
</Grid> </Grid>
<Grid item md={1}> <Grid item md={1}>
<IconButton onClick={removeOverride(i)}> <Tooltip title="Remove">
<Delete /> <IconButton onClick={removeOverride(i)}>
</IconButton> <Delete />
</IconButton>
</Tooltip>
</Grid> </Grid>
</Grid> </Grid>
); );

View File

@ -1,4 +1,10 @@
import { Chip, IconButton, TableCell, TableRow } from '@material-ui/core'; import {
Chip,
IconButton,
TableCell,
TableRow,
Tooltip,
} from '@material-ui/core';
import { Delete, Edit } from '@material-ui/icons'; import { Delete, Edit } from '@material-ui/icons';
import styles from '../variants.module.scss'; import styles from '../variants.module.scss';
@ -54,24 +60,28 @@ const FeatureVariantListItem = ({
show={ show={
<TableCell className={styles.actions}> <TableCell className={styles.actions}>
<div className={styles.actionsContainer}> <div className={styles.actionsContainer}>
<IconButton <Tooltip title="Edit variant">
data-testid={'VARIANT_EDIT_BUTTON'} <IconButton
onClick={() => editVariant(variant.name)} data-testid={'VARIANT_EDIT_BUTTON'}
> onClick={() => editVariant(variant.name)}
<Edit /> >
</IconButton> <Edit />
<IconButton </IconButton>
data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`} </Tooltip>
onClick={e => { <Tooltip title="Delete variant">
e.stopPropagation(); <IconButton
setDelDialog({ data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`}
show: true, onClick={e => {
name: variant.name, e.stopPropagation();
}); setDelDialog({
}} show: true,
> name: variant.name,
<Delete /> });
</IconButton> }}
>
<Delete />
</IconButton>
</Tooltip>
</div> </div>
</TableCell> </TableCell>
} }

View File

@ -143,32 +143,36 @@ export const FeatureView = () => {
data-loading data-loading
component={Link} component={Link}
to={`/projects/${projectId}/features/${featureId}/strategies/copy`} to={`/projects/${projectId}/features/${featureId}/strategies/copy`}
tooltip="Copy feature"
> >
<FileCopy titleAccess="Copy" /> <FileCopy />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
permission={DELETE_FEATURE} permission={DELETE_FEATURE}
projectId={projectId} projectId={projectId}
tooltip="Archive feature toggle"
data-loading data-loading
onClick={() => setShowDelDialog(true)} onClick={() => setShowDelDialog(true)}
> >
<Archive titleAccess="Archive feature toggle" /> <Archive />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
onClick={() => setOpenStaleDialog(true)} onClick={() => setOpenStaleDialog(true)}
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
projectId={projectId} projectId={projectId}
tooltip="Toggle stale status"
data-loading data-loading
> >
<WatchLater titleAccess="Toggle stale status" /> <WatchLater />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
onClick={() => setOpenTagDialog(true)} onClick={() => setOpenTagDialog(true)}
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
projectId={projectId} projectId={projectId}
tooltip="Add tag"
data-loading data-loading
> >
<Label titleAccess="Add tag" /> <Label />
</PermissionIconButton> </PermissionIconButton>
</div> </div>
</div> </div>

View File

@ -175,7 +175,7 @@ const StrategyConstraintInputField = ({
</td> </td>
<td className={styles.tableCell}> <td className={styles.tableCell}>
<IconButton onClick={removeConstraint}> <IconButton onClick={removeConstraint}>
<Delete /> <Delete titleAccess="Remove constraint" />
</IconButton> </IconButton>
</td> </td>
</tr> </tr>

View File

@ -92,7 +92,10 @@ const StrategyConstraints: React.FC<IStrategyConstraintProps> = ({
<Typography variant="subtitle2"> <Typography variant="subtitle2">
{'Constraints '} {'Constraints '}
<Info style={{ fontSize: '0.9rem', color: 'gray' }} /> <Info
style={{ fontSize: '0.9rem', color: 'gray' }}
titleAccess="Help"
/>
</Typography> </Typography>
</Tooltip> </Tooltip>
<table style={{ margin: 0 }}> <table style={{ margin: 0 }}>

View File

@ -1,5 +1,5 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { Button, IconButton } from '@material-ui/core'; import { Button, IconButton, Tooltip } from '@material-ui/core';
import classnames from 'classnames'; import classnames from 'classnames';
import CloseIcon from '@material-ui/icons/Close'; import CloseIcon from '@material-ui/icons/Close';
import { ReactComponent as Logo } from 'assets/icons/logoPlain.svg'; import { ReactComponent as Logo } from 'assets/icons/logoPlain.svg';
@ -73,12 +73,14 @@ export const FeedbackNPS = ({ openUrl }: IFeedbackNPSProps) => {
commonStyles.contentSpacingY commonStyles.contentSpacingY
)} )}
> >
<IconButton <Tooltip title="Close">
className={styles.close} <IconButton
onClick={() => setShowFeedback(false)} className={styles.close}
> onClick={() => setShowFeedback(false)}
<CloseIcon /> >
</IconButton> <CloseIcon />
</IconButton>
</Tooltip>
<Logo className={styles.logo} /> <Logo className={styles.logo} />
<ConditionallyRender <ConditionallyRender
condition={answeredNotNow} condition={answeredNotNow}

View File

@ -66,12 +66,14 @@ const Header = () => {
<ConditionallyRender <ConditionallyRender
condition={smallScreen} condition={smallScreen}
show={ show={
<IconButton <Tooltip title="Menu">
className={styles.drawerButton} <IconButton
onClick={toggleDrawer} className={styles.drawerButton}
> onClick={toggleDrawer}
<MenuIcon titleAccess="Menu" /> >
</IconButton> <MenuIcon />
</IconButton>
</Tooltip>
} }
elseShow={ elseShow={
<Link <Link
@ -136,26 +138,29 @@ const Header = () => {
> >
<MenuBookIcon <MenuBookIcon
className={styles.docsIcon} className={styles.docsIcon}
titleAccess="Documentation"
/> />
</a> </a>
</Tooltip> </Tooltip>
<ConditionallyRender <ConditionallyRender
condition={admin} condition={admin}
show={ show={
<IconButton <Tooltip title="Settings">
onClick={e => <IconButton
setAnchorEl(e.currentTarget) onClick={e =>
} setAnchorEl(
> e.currentTarget
<SettingsIcon )
className={styles.docsIcon} }
titleAccess="Settings" >
/> <SettingsIcon
</IconButton> className={
styles.docsIcon
}
/>
</IconButton>
</Tooltip>
} }
/> />
<NavigationMenu <NavigationMenu
id="admin-navigation" id="admin-navigation"
options={filteredMainRoutes.adminRoutes} options={filteredMainRoutes.adminRoutes}
@ -166,7 +171,6 @@ const Header = () => {
</> </>
} }
/> />
<UserProfile /> <UserProfile />
</div> </div>
</Container> </Container>

View File

@ -128,6 +128,7 @@ const Project = () => {
permission={UPDATE_PROJECT} permission={UPDATE_PROJECT}
projectId={project?.id} projectId={project?.id}
onClick={() => history.push(`/projects/${id}/edit`)} onClick={() => history.push(`/projects/${id}/edit`)}
tooltip="Edit project"
data-loading data-loading
> >
<Edit /> <Edit />

View File

@ -56,6 +56,7 @@ const ProjectInfo = ({
className={permissionButtonClass} className={permissionButtonClass}
data-loading data-loading
to={`/projects/${id}/edit`} to={`/projects/${id}/edit`}
tooltip="Edit description"
> >
<Edit /> <Edit />
</PermissionIconButton> </PermissionIconButton>
@ -82,7 +83,9 @@ const ProjectInfo = ({
elseShow={ elseShow={
<Accordion className={styles.accordion}> <Accordion className={styles.accordion}>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore />} expandIcon={
<ExpandMore titleAccess="Toggle" />
}
className={styles.accordionBody} className={styles.accordionBody}
> >
Description Description

View File

@ -71,10 +71,9 @@ export const ProjectAccessListItem = ({
projectId={projectId} projectId={projectId}
className={styles.iconButton} className={styles.iconButton}
edge="end" edge="end"
aria-label="delete"
title="Remove access"
onClick={() => handleRemoveAccess(user)} onClick={() => handleRemoveAccess(user)}
disabled={access.users.length === 1} disabled={access.users.length === 1}
tooltip="Remove access"
> >
<Delete /> <Delete />
</PermissionIconButton> </PermissionIconButton>

View File

@ -65,7 +65,7 @@ export const ProjectCard = ({
return ( return (
<Card className={styles.projectCard} onMouseEnter={onHover}> <Card className={styles.projectCard} onMouseEnter={onHover}>
<div className={styles.header} data-loading> <div className={styles.header} data-loading>
<div className={styles.title}>{name}</div> <h2 className={styles.title}>{name}</h2>
<PermissionIconButton <PermissionIconButton
permission={UPDATE_PROJECT} permission={UPDATE_PROJECT}
@ -73,6 +73,7 @@ export const ProjectCard = ({
className={styles.actionsBtn} className={styles.actionsBtn}
data-loading data-loading
onClick={handleClick} onClick={handleClick}
tooltip="Menu"
> >
<MoreVertIcon /> <MoreVertIcon />
</PermissionIconButton> </PermissionIconButton>

View File

@ -65,12 +65,12 @@ export const SegmentListItem = ({
push(`/segments/edit/${id}`); push(`/segments/edit/${id}`);
}} }}
permission={UPDATE_SEGMENT} permission={UPDATE_SEGMENT}
tooltip="Edit segment"
> >
<Edit titleAccess="Edit segment" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
<PermissionIconButton <PermissionIconButton
data-loading data-loading
aria-label="Remove segment"
onClick={() => { onClick={() => {
setCurrentSegment({ setCurrentSegment({
id, id,
@ -83,6 +83,7 @@ export const SegmentListItem = ({
setDelDialog(true); setDelDialog(true);
}} }}
permission={ADMIN} permission={ADMIN}
tooltip="Remove segment"
data-testid={`${SEGMENT_DELETE_BTN_ID}_${name}`} data-testid={`${SEGMENT_DELETE_BTN_ID}_${name}`}
> >
<Delete /> <Delete />

View File

@ -72,6 +72,7 @@ export const StrategiesList = () => {
data-testid={ADD_NEW_STRATEGY_ID} data-testid={ADD_NEW_STRATEGY_ID}
onClick={() => history.push('/strategies/create')} onClick={() => history.push('/strategies/create')}
permission={CREATE_STRATEGY} permission={CREATE_STRATEGY}
tooltip="New strategy"
> >
<Add /> <Add />
</PermissionIconButton> </PermissionIconButton>
@ -162,16 +163,13 @@ export const StrategiesList = () => {
}; };
const reactivateButton = (strategy: ICustomStrategy) => ( const reactivateButton = (strategy: ICustomStrategy) => (
<Tooltip title="Reactivate activation strategy"> <PermissionIconButton
<div> onClick={() => onReactivateStrategy(strategy)}
<PermissionIconButton permission={UPDATE_STRATEGY}
onClick={() => onReactivateStrategy(strategy)} tooltip="Reactivate activation strategy"
permission={UPDATE_STRATEGY} >
> <VisibilityOff />
<VisibilityOff titleAccess="Reactivate" /> </PermissionIconButton>
</PermissionIconButton>
</div>
</Tooltip>
); );
const deprecateButton = (strategy: ICustomStrategy) => ( const deprecateButton = (strategy: ICustomStrategy) => (
@ -191,8 +189,9 @@ export const StrategiesList = () => {
<PermissionIconButton <PermissionIconButton
onClick={() => onDeprecateStrategy(strategy)} onClick={() => onDeprecateStrategy(strategy)}
permission={UPDATE_STRATEGY} permission={UPDATE_STRATEGY}
tooltip="Deprecate strategy"
> >
<Visibility titleAccess="Deprecate strategy" /> <Visibility />
</PermissionIconButton> </PermissionIconButton>
</div> </div>
} }
@ -208,8 +207,9 @@ export const StrategiesList = () => {
history.push(`/strategies/${strategy?.name}/edit`) history.push(`/strategies/${strategy?.name}/edit`)
} }
permission={UPDATE_STRATEGY} permission={UPDATE_STRATEGY}
tooltip="Edit strategy"
> >
<Edit titleAccess="Edit strategy" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
} }
elseShow={ elseShow={
@ -231,6 +231,7 @@ export const StrategiesList = () => {
<PermissionIconButton <PermissionIconButton
onClick={() => onDeleteStrategy(strategy)} onClick={() => onDeleteStrategy(strategy)}
permission={DELETE_STRATEGY} permission={DELETE_STRATEGY}
tooltip="Delete strategy"
> >
<Delete /> <Delete />
</PermissionIconButton> </PermissionIconButton>
@ -239,7 +240,7 @@ export const StrategiesList = () => {
<Tooltip title="You cannot delete a built-in strategy"> <Tooltip title="You cannot delete a built-in strategy">
<div> <div>
<IconButton disabled> <IconButton disabled>
<Delete /> <Delete titleAccess="Delete strategy" />
</IconButton> </IconButton>
</div> </div>
</Tooltip> </Tooltip>

View File

@ -30,17 +30,10 @@ exports[`renders correctly with one strategy 1`] = `
className="makeStyles-headerActions-8" className="makeStyles-headerActions-8"
> >
<span <span
aria-describedby={null} id="useId-0"
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title=""
> >
<button <button
aria-describedby="useId-0"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary" className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID" data-testid="ADD_NEW_STRATEGY_ID"
disabled={false} disabled={false}
@ -129,15 +122,17 @@ exports[`renders correctly with one strategy 1`] = `
<span <span
aria-describedby={null} aria-describedby={null}
className="" className=""
id="useId-1"
onBlur={[Function]} onBlur={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchStart={[Function]} onTouchStart={[Function]}
title="" title="Deprecate strategy"
> >
<button <button
aria-labelledby="useId-1"
className="MuiButtonBase-root MuiIconButton-root" className="MuiButtonBase-root MuiIconButton-root"
disabled={false} disabled={false}
onBlur={[Function]} onBlur={[Function]}
@ -159,17 +154,14 @@ exports[`renders correctly with one strategy 1`] = `
className="MuiIconButton-label" className="MuiIconButton-label"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/> />
<title>
Deprecate strategy
</title>
</svg> </svg>
</span> </span>
<span <span
@ -257,14 +249,17 @@ exports[`renders correctly with one strategy 1`] = `
className="MuiIconButton-label" className="MuiIconButton-label"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/> />
<title>
Delete strategy
</title>
</svg> </svg>
</span> </span>
</button> </button>
@ -305,17 +300,10 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="makeStyles-headerActions-8" className="makeStyles-headerActions-8"
> >
<span <span
aria-describedby={null} id="useId-2"
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title=""
> >
<button <button
aria-describedby="useId-2"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary" className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID" data-testid="ADD_NEW_STRATEGY_ID"
disabled={false} disabled={false}
@ -404,15 +392,17 @@ exports[`renders correctly with one strategy without permissions 1`] = `
<span <span
aria-describedby={null} aria-describedby={null}
className="" className=""
id="useId-3"
onBlur={[Function]} onBlur={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchStart={[Function]} onTouchStart={[Function]}
title="" title="Deprecate strategy"
> >
<button <button
aria-labelledby="useId-3"
className="MuiButtonBase-root MuiIconButton-root" className="MuiButtonBase-root MuiIconButton-root"
disabled={false} disabled={false}
onBlur={[Function]} onBlur={[Function]}
@ -434,17 +424,14 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="MuiIconButton-label" className="MuiIconButton-label"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/> />
<title>
Deprecate strategy
</title>
</svg> </svg>
</span> </span>
<span <span
@ -532,14 +519,17 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="MuiIconButton-label" className="MuiIconButton-label"
> >
<svg <svg
aria-hidden={true}
className="MuiSvgIcon-root" className="MuiSvgIcon-root"
focusable="false" focusable="false"
role="img"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/> />
<title>
Delete strategy
</title>
</svg> </svg>
</span> </span>
</button> </button>

View File

@ -1,4 +1,9 @@
import { Checkbox, FormControlLabel, IconButton } from '@material-ui/core'; import {
Checkbox,
FormControlLabel,
IconButton,
Tooltip,
} from '@material-ui/core';
import { Delete } from '@material-ui/icons'; import { Delete } from '@material-ui/icons';
import { useStyles } from './StrategyParameter.styles'; import { useStyles } from './StrategyParameter.styles';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
@ -74,7 +79,7 @@ export const StrategyParameter = ({
condition={index === 0} condition={index === 0}
show={ show={
<p className={styles.input}> <p className={styles.input}>
The parameters define how the strategy will look like. The parameters define what the strategy will look like.
</p> </p>
} }
/> />
@ -88,13 +93,15 @@ export const StrategyParameter = ({
error={Boolean(errors?.[`paramName${index}`])} error={Boolean(errors?.[`paramName${index}`])}
errorText={errors?.[`paramName${index}`]} errorText={errors?.[`paramName${index}`]}
/> />
<IconButton <Tooltip title="Remove parameter">
onClick={() => { <IconButton
setParams(params.filter((e, i) => i !== index)); onClick={() => {
}} setParams(params.filter((e, i) => i !== index));
> }}
<Delete titleAccess="Delete" /> >
</IconButton> <Delete />
</IconButton>
</Tooltip>
</div> </div>
<GeneralSelect <GeneralSelect
label="Type*" label="Type*"

View File

@ -34,18 +34,18 @@ export const StrategyDetails = ({
<ConditionallyRender <ConditionallyRender
condition={required} condition={required}
show={ show={
<Tooltip title="Required"> <ListItemAvatar>
<ListItemAvatar> <Tooltip title="Required parameter">
<Add /> <Add aria-hidden={false} />
</ListItemAvatar> </Tooltip>
</Tooltip> </ListItemAvatar>
} }
elseShow={ elseShow={
<Tooltip title="Optional"> <ListItemAvatar>
<ListItemAvatar> <Tooltip title="Optional parameter">
<RadioButtonChecked /> <RadioButtonChecked aria-hidden={false} />
</ListItemAvatar> </Tooltip>
</Tooltip> </ListItemAvatar>
} }
/> />
<ListItemText <ListItemText

View File

@ -43,8 +43,9 @@ export const StrategyView = () => {
permission={UPDATE_STRATEGY} permission={UPDATE_STRATEGY}
data-loading data-loading
onClick={handleEdit} onClick={handleEdit}
tooltip="Edit strategy"
> >
<Edit titleAccess="Edit strategy" /> <Edit />
</PermissionIconButton> </PermissionIconButton>
} }
/> />

View File

@ -19,30 +19,33 @@ export const TogglesLinkList = ({ toggles }: ITogglesLinkListProps) => (
<List style={{ textAlign: 'left' }} className={styles.truncate}> <List style={{ textAlign: 'left' }} className={styles.truncate}>
<ConditionallyRender <ConditionallyRender
condition={toggles.length > 0} condition={toggles.length > 0}
show={ show={toggles.map(({ name, description = '-', enabled }) => (
<> <ListItem key={name}>
{toggles.map(({ name, description = '-', enabled }) => ( <ListItemAvatar>
<ListItem key={name}> <ConditionallyRender
<Tooltip title={enabled ? 'Enabled' : 'Disabled'}> condition={Boolean(enabled)}
<ListItemAvatar> show={
{enabled ? <PlayArrow /> : <Pause />} <Tooltip title="Enabled">
</ListItemAvatar> <PlayArrow aria-hidden={false} />
</Tooltip> </Tooltip>
<ListItemText }
primary={ elseShow={
<Link <Tooltip title="Disabled">
key={name} <Pause aria-hidden={false} />
to={`/features/view/${name}`} </Tooltip>
> }
{name} />
</Link> </ListItemAvatar>
} <ListItemText
secondary={description} primary={
/> <Link key={name} to={`/features/view/${name}`}>
</ListItem> {name}
))} </Link>
</> }
} secondary={description}
/>
</ListItem>
))}
/> />
</List> </List>
); );

View File

@ -69,7 +69,6 @@ export const TagTypeList = () => {
show={ show={
<Tooltip title="Add tag type"> <Tooltip title="Add tag type">
<IconButton <IconButton
aria-label="add tag type"
onClick={() => onClick={() =>
history.push('/tag-types/create') history.push('/tag-types/create')
} }
@ -129,9 +128,10 @@ export const TagTypeList = () => {
<PermissionIconButton <PermissionIconButton
permission={UPDATE_TAG_TYPE} permission={UPDATE_TAG_TYPE}
component={Link} component={Link}
tooltip="Edit tag type"
to={`/tag-types/edit/${tagType.name}`} to={`/tag-types/edit/${tagType.name}`}
> >
<Edit className={styles.icon} titleAccess="Edit tag type" /> <Edit className={styles.icon} />
</PermissionIconButton> </PermissionIconButton>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(DELETE_TAG_TYPE)} condition={hasAccess(DELETE_TAG_TYPE)}

View File

@ -14,6 +14,7 @@ import {
NotFoundError, NotFoundError,
} from 'utils/apiUtils'; } from 'utils/apiUtils';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import { ACCESS_DENIED_TEXT } from 'utils/formatAccessText';
type ApiErrorHandler = ( type ApiErrorHandler = (
setErrors: Dispatch<SetStateAction<{}>>, setErrors: Dispatch<SetStateAction<{}>>,
@ -125,8 +126,7 @@ const useAPI = ({
} else { } else {
setErrors(prev => ({ setErrors(prev => ({
...prev, ...prev,
unauthorized: unauthorized: ACCESS_DENIED_TEXT,
'You are not authorized to perform this operation',
})); }));
} }

View File

@ -0,0 +1,40 @@
import { useId } from 'hooks/useId';
import { renderHook } from '@testing-library/react-hooks';
test('useId', () => {
const { result, rerender } = renderHook(() => useId());
rerender();
rerender();
expect(result).toMatchInlineSnapshot(`
Object {
"all": Array [
"useId-0",
"useId-0",
"useId-0",
],
"current": "useId-0",
"error": undefined,
}
`);
});
test('useId prefix', () => {
const { result, rerender } = renderHook(() => useId('prefix'));
rerender();
rerender();
expect(result).toMatchInlineSnapshot(`
Object {
"all": Array [
"prefix-1",
"prefix-1",
"prefix-1",
],
"current": "prefix-1",
"error": undefined,
}
`);
});

View File

@ -0,0 +1,10 @@
import { useMemo } from 'react';
// Generate a globally unique ID that is stable across renders.
export const useId = (prefix = 'useId'): string => {
return useMemo(() => {
return `${prefix}-${counter++}`;
}, [prefix]);
};
let counter = 0;

View File

@ -0,0 +1,11 @@
import { formatAccessText } from 'utils/formatAccessText';
test('formatAccessText with access', () => {
expect(formatAccessText(true)).toEqual(undefined);
expect(formatAccessText(true, 'Foo')).toEqual('Foo');
});
test('formatAccessText without access', () => {
expect(formatAccessText(false)).toEqual('Access denied');
expect(formatAccessText(false, 'Foo')).toEqual(`Foo (Access denied)`);
});

View File

@ -0,0 +1,16 @@
export const ACCESS_DENIED_TEXT = 'Access denied';
export const formatAccessText = (
hasAccess: boolean,
hasAccessText?: string
): string | undefined => {
if (hasAccess) {
return hasAccessText;
}
if (hasAccessText) {
return `${hasAccessText} (${ACCESS_DENIED_TEXT})`;
}
return ACCESS_DENIED_TEXT;
};