1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-15 17:50:48 +02:00

Fix: UI improvements (#1114)

* fix: segments table author column width

* fix: update feature form ui

* fix: strategies breadcrumbs

* fix: api token page title

* fix: deprecated strategy label color

* fix: project access remove user toast

* fix: addon enable toast message

* fix: ces from ui

* fix: ui improvements with dialog typography

* fix: revert ces

* fix: change password error type
This commit is contained in:
Tymoteusz Czech 2022-06-28 12:58:10 +02:00 committed by GitHub
parent 566d0613a4
commit c0b52fa672
25 changed files with 74 additions and 67 deletions

View File

@ -53,7 +53,7 @@ export const ConfiguredAddons = () => {
type: 'success', type: 'success',
title: 'Success', title: 'Success',
text: !addon.enabled text: !addon.enabled
? 'Addon is now active' ? 'Addon is now enabled'
: 'Addon is now disabled', : 'Addon is now disabled',
}); });
} catch (error: unknown) { } catch (error: unknown) {

View File

@ -11,6 +11,9 @@ import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
import { useState } from 'react'; import { useState } from 'react';
import { scrollToTop } from 'component/common/util'; import { scrollToTop } from 'component/common/util';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { usePageTitle } from 'hooks/usePageTitle';
const pageTitle = 'Create API token';
export const CreateApiToken = () => { export const CreateApiToken = () => {
const { setToastApiError } = useToast(); const { setToastApiError } = useToast();
@ -36,6 +39,8 @@ export const CreateApiToken = () => {
const { createToken, loading } = useApiTokensApi(); const { createToken, loading } = useApiTokensApi();
usePageTitle(pageTitle);
const handleSubmit = async (e: Event) => { const handleSubmit = async (e: Event) => {
e.preventDefault(); e.preventDefault();
if (!isValid()) { if (!isValid()) {
@ -76,7 +81,7 @@ export const CreateApiToken = () => {
return ( return (
<FormTemplate <FormTemplate
loading={loading} loading={loading}
title="Create Api Token" title={pageTitle}
description="In order to connect to Unleash clients will need an API token to grant access. A client SDK will need to token with 'client privileges', which allows them to fetch feature toggle configuration and post usage metrics back." description="In order to connect to Unleash clients will need an API token to grant access. A client SDK will need to token with 'client privileges', which allows them to fetch feature toggle configuration and post usage metrics back."
documentationLink="https://docs.getunleash.io/reference/api-tokens-and-client-keys" documentationLink="https://docs.getunleash.io/reference/api-tokens-and-client-keys"
documentationLinkLabel="API tokens documentation" documentationLinkLabel="API tokens documentation"

View File

@ -10,24 +10,24 @@ import PasswordChecker, {
import { useThemeStyles } from 'themes/themeStyles'; import { useThemeStyles } from 'themes/themeStyles';
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher'; import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import { IUser } from 'interfaces/user'; import { IUser } from 'interfaces/user';
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
interface IChangePasswordProps { interface IChangePasswordProps {
showDialog: boolean; showDialog: boolean;
closeDialog: () => void; closeDialog: () => void;
changePassword: (userId: number, password: string) => Promise<Response>;
user: IUser; user: IUser;
} }
const ChangePassword = ({ const ChangePassword = ({
showDialog, showDialog,
closeDialog, closeDialog,
changePassword,
user, user,
}: IChangePasswordProps) => { }: IChangePasswordProps) => {
const [data, setData] = useState<Record<string, string>>({}); const [data, setData] = useState<Record<string, string>>({});
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
const [validPassword, setValidPassword] = useState(false); const [validPassword, setValidPassword] = useState(false);
const { classes: themeStyles } = useThemeStyles(); const { classes: themeStyles } = useThemeStyles();
const { changePassword } = useAdminUsersApi();
const updateField: React.ChangeEventHandler<HTMLInputElement> = event => { const updateField: React.ChangeEventHandler<HTMLInputElement> = event => {
setError(undefined); setError(undefined);

View File

@ -45,8 +45,7 @@ const UsersList = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { users, roles, refetch, loading } = useUsers(); const { users, roles, refetch, loading } = useUsers();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { removeUser, changePassword, userLoading, userApiErrors } = const { removeUser, userLoading, userApiErrors } = useAdminUsersApi();
useAdminUsersApi();
const [pwDialog, setPwDialog] = useState<{ open: boolean; user?: IUser }>({ const [pwDialog, setPwDialog] = useState<{ open: boolean; user?: IUser }>({
open: false, open: false,
}); });
@ -320,7 +319,6 @@ const UsersList = () => {
<ChangePassword <ChangePassword
showDialog={pwDialog.open} showDialog={pwDialog.open}
closeDialog={closePwDialog} closeDialog={closePwDialog}
changePassword={changePassword}
user={pwDialog.user!} user={pwDialog.user!}
/> />
)} )}

View File

@ -23,7 +23,6 @@ const BreadcrumbNav = () => {
item !== 'logs' && item !== 'logs' &&
item !== 'metrics' && item !== 'metrics' &&
item !== 'copy' && item !== 'copy' &&
item !== 'strategies' &&
item !== 'features' && item !== 'features' &&
item !== 'features2' && item !== 'features2' &&
item !== 'create-toggle' && item !== 'create-toggle' &&

View File

@ -1,5 +1,5 @@
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { DialogContentText } from '@mui/material'; import { Typography } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
import React from 'react'; import React from 'react';
@ -25,14 +25,13 @@ export const FeatureStaleDialog = ({
const { patchFeatureToggle } = useFeatureApi(); const { patchFeatureToggle } = useFeatureApi();
const toggleToStaleContent = ( const toggleToStaleContent = (
<DialogContentText> <Typography>Setting a toggle to stale marks it for cleanup</Typography>
Setting a toggle to stale marks it for cleanup
</DialogContentText>
); );
const toggleToActiveContent = ( const toggleToActiveContent = (
<DialogContentText> <Typography>
Setting a toggle to active marks it as in active use Setting a toggle to active marks it as in active use
</DialogContentText> </Typography>
); );
const toggleActionText = isStale ? 'active' : 'stale'; const toggleActionText = isStale ? 'active' : 'stale';

View File

@ -16,7 +16,7 @@ export const useStyles = makeStyles()(theme => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.grey[300]}`, border: `1px solid ${theme.palette.grey[500]}`,
borderRadius: theme.shape.borderRadiusExtraLarge, borderRadius: theme.shape.borderRadiusExtraLarge,
padding: '3px 5px 3px 12px', padding: '3px 5px 3px 12px',
width: '100%', width: '100%',

View File

@ -76,7 +76,7 @@ const CreateFeature = () => {
return ( return (
<FormTemplate <FormTemplate
loading={loading} loading={loading}
title="Create Feature toggle" title="Create feature toggle"
description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing. description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing.
The feature toggle is disabled when created and you decide when to enable" The feature toggle is disabled when created and you decide when to enable"
documentationLink="https://docs.getunleash.io/advanced/feature_toggle_types" documentationLink="https://docs.getunleash.io/advanced/feature_toggle_types"
@ -102,7 +102,7 @@ const CreateFeature = () => {
clearErrors={clearErrors} clearErrors={clearErrors}
> >
<CreateButton <CreateButton
name="Feature" name="feature toggle"
permission={CREATE_FEATURE} permission={CREATE_FEATURE}
projectId={project} projectId={project}
data-testid={CF_CREATE_BTN_ID} data-testid={CF_CREATE_BTN_ID}

View File

@ -33,10 +33,11 @@ export const useStyles = makeStyles()(theme => ({
}, },
inputDescription: { inputDescription: {
marginBottom: '0.5rem', marginBottom: '0.5rem',
color: theme.palette.text.secondary,
}, },
typeDescription: { typeDescription: {
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
color: theme.palette.grey[600], color: theme.palette.text.secondary,
top: '-13px', top: '-13px',
position: 'relative', position: 'relative',
}, },

View File

@ -4,7 +4,7 @@ export const useStyles = makeStyles()(theme => ({
title: { title: {
margin: 0, margin: 0,
marginBottom: '.5rem', marginBottom: '.5rem',
fontSize: theme.fontSizes.smallerBody, fontSize: theme.fontSizes.smallBody,
fontWeight: theme.fontWeight.thin, fontWeight: theme.fontWeight.thin,
color: theme.palette.grey[800], color: theme.palette.grey[800],
}, },

View File

@ -2,8 +2,7 @@ import { FeatureMetricsTable } from '../FeatureMetricsTable/FeatureMetricsTable'
import { IFeatureMetricsRaw } from 'interfaces/featureToggle'; import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
import { FeatureMetricsStatsRaw } from '../FeatureMetricsStats/FeatureMetricsStatsRaw'; import { FeatureMetricsStatsRaw } from '../FeatureMetricsStats/FeatureMetricsStatsRaw';
import { FeatureMetricsChart } from '../FeatureMetricsChart/FeatureMetricsChart'; import { FeatureMetricsChart } from '../FeatureMetricsChart/FeatureMetricsChart';
import { FeatureMetricsEmpty } from '../FeatureMetricsEmpty/FeatureMetricsEmpty'; import { Box, Typography } from '@mui/material';
import { Box } from '@mui/material';
import theme from 'themes/theme'; import theme from 'themes/theme';
import { useId } from 'hooks/useId'; import { useId } from 'hooks/useId';
@ -22,7 +21,14 @@ export const FeatureMetricsContent = ({
if (metrics.length === 0) { if (metrics.length === 0) {
return ( return (
<Box mt={6}> <Box mt={6}>
<FeatureMetricsEmpty /> <Typography variant="body1" paragraph>
We have yet to receive any metrics for this feature toggle
in the selected time period.
</Typography>
<Typography variant="body1" paragraph>
Please note that, since the SDKs send metrics on an
interval, it might take some time before metrics appear.
</Typography>
</Box> </Box>
); );
} }

View File

@ -1,16 +0,0 @@
import { Typography } from '@mui/material';
export const FeatureMetricsEmpty = () => {
return (
<>
<Typography variant="body1" paragraph>
We have yet to receive any metrics for this feature toggle in
the selected time period.
</Typography>
<Typography variant="body1" paragraph>
Please note that, since the SDKs send metrics on an interval, it
might take some time before metrics appear.
</Typography>
</>
);
};

View File

@ -1,4 +1,4 @@
import { DialogContentText } from '@mui/material'; import { Typography } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
import Input from 'component/common/Input/Input'; import Input from 'component/common/Input/Input';
@ -30,7 +30,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
const { addTagToFeature, loading } = useFeatureApi(); const { addTagToFeature, loading } = useFeatureApi();
const { refetch } = useTags(featureId); const { refetch } = useTags(featureId);
const [errors, setErrors] = useState({ tagError: '' }); const [errors, setErrors] = useState({ tagError: '' });
const { setToastData, setToastApiError } = useToast(); const { setToastData } = useToast();
const [tag, setTag] = useState(DEFAULT_TAG); const [tag, setTag] = useState(DEFAULT_TAG);
const onCancel = () => { const onCancel = () => {
@ -64,7 +64,6 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
const message = formatUnknownError(error); const message = formatUnknownError(error);
setToastApiError(message);
setErrors({ tagError: message }); setErrors({ tagError: message });
} }
}; };
@ -84,9 +83,9 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
formId={formId} formId={formId}
> >
<> <>
<DialogContentText> <Typography paragraph>
Tags allow you to group features together Tags allow you to group features together
</DialogContentText> </Typography>
<form id={formId} onSubmit={onSubmit}> <form id={formId} onSubmit={onSubmit}>
<section className={styles.dialogFormContent}> <section className={styles.dialogFormContent}>
<TagSelect <TagSelect

View File

@ -27,8 +27,6 @@ export const useStyles = makeStyles()(theme => ({
position: 'absolute', position: 'absolute',
top: 0, top: 0,
right: 0, right: 0,
padding: '1rem',
cursor: 'pointer',
}, },
closeIcon: { closeIcon: {
fontSize: '1.5rem', fontSize: '1.5rem',

View File

@ -1,4 +1,4 @@
import { Modal } from '@mui/material'; import { IconButton, Modal } from '@mui/material';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { import {
feedbackCESContext, feedbackCESContext,
@ -16,12 +16,6 @@ export const FeedbackCES = ({ state }: IFeedbackCESProps) => {
const { hideFeedbackCES } = useContext(feedbackCESContext); const { hideFeedbackCES } = useContext(feedbackCESContext);
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const closeButton = (
<button className={styles.close} onClick={hideFeedbackCES}>
<CloseOutlined titleAccess="Close" className={styles.closeIcon} />
</button>
);
const modalContent = state && ( const modalContent = state && (
<FeedbackCESForm state={state} onClose={hideFeedbackCES} /> <FeedbackCESForm state={state} onClose={hideFeedbackCES} />
); );
@ -34,7 +28,14 @@ export const FeedbackCES = ({ state }: IFeedbackCESProps) => {
> >
<div className={styles.overlay}> <div className={styles.overlay}>
<div className={styles.modal}> <div className={styles.modal}>
{closeButton} <div className={styles.close}>
<IconButton onClick={hideFeedbackCES} size="large">
<CloseOutlined
titleAccess="Close"
className={styles.closeIcon}
/>
</IconButton>
</div>
{modalContent} {modalContent}
</div> </div>
</div> </div>

View File

@ -14,7 +14,7 @@ export const useStyles = makeStyles()(theme => ({
all: 'unset', all: 'unset',
display: 'block', display: 'block',
textAlign: 'center', textAlign: 'center',
color: theme.palette.grey[600], color: theme.palette.text.secondary,
}, },
subtitle: { subtitle: {
all: 'unset', all: 'unset',

View File

@ -8,9 +8,9 @@ export const useStyles = makeStyles()(theme => ({
margin: '0 auto', margin: '0 auto',
}, },
scoreHelp: { scoreHelp: {
width: '8rem', width: '6.25rem',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
color: theme.palette.grey[600], color: theme.palette.text.secondary,
'&:first-of-type': { '&:first-of-type': {
textAlign: 'right', textAlign: 'right',
}, },

View File

@ -7,7 +7,7 @@ exports[`FeedbackCESForm 1`] = `
class="tss-fdcp7c-container" class="tss-fdcp7c-container"
> >
<h1 <h1
class="tss-1a5bydb-title" class="tss-iyd7t0-title"
> >
Please help us improve Please help us improve
</h1> </h1>
@ -24,7 +24,7 @@ exports[`FeedbackCESForm 1`] = `
class="tss-io6e1g-scoreInput" class="tss-io6e1g-scoreInput"
> >
<span <span
class="tss-b4a690-scoreHelp" class="tss-16omcck-scoreHelp"
> >
Very difficult Very difficult
</span> </span>
@ -113,7 +113,7 @@ exports[`FeedbackCESForm 1`] = `
</span> </span>
</label> </label>
<span <span
class="tss-b4a690-scoreHelp" class="tss-16omcck-scoreHelp"
> >
Very easy Very easy
</span> </span>

View File

@ -57,7 +57,9 @@ export const ProjectAccessPage = () => {
refetchProjectAccess(); refetchProjectAccess();
setToastData({ setToastData({
type: 'success', type: 'success',
title: 'The user has been removed from project', title: `${
user.email || user.username || 'The user'
} has been removed from project`,
}); });
} catch (err: any) { } catch (err: any) {
setToastData({ setToastData({

View File

@ -1,3 +1,4 @@
import React, { useContext } from 'react';
import { CreateButton } from 'component/common/CreateButton/CreateButton'; import { CreateButton } from 'component/common/CreateButton/CreateButton';
import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions'; import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
@ -6,7 +7,6 @@ import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValida
import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import React, { useContext } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { useSegmentForm } from '../hooks/useSegmentForm'; import { useSegmentForm } from '../hooks/useSegmentForm';

View File

@ -162,7 +162,7 @@ const COLUMNS = [
{ {
Header: 'Name', Header: 'Name',
accessor: 'name', accessor: 'name',
width: '80%', width: '60%',
Cell: ({ value, row: { original } }: any) => ( Cell: ({ value, row: { original } }: any) => (
<HighlightCell value={value} subtitle={original.description} /> <HighlightCell value={value} subtitle={original.description} />
), ),
@ -177,6 +177,7 @@ const COLUMNS = [
{ {
Header: 'Created by', Header: 'Created by',
accessor: 'createdBy', accessor: 'createdBy',
width: '25%',
}, },
{ {
Header: 'Actions', Header: 'Actions',

View File

@ -4,7 +4,11 @@ export const useStyles = makeStyles()(theme => ({
paramsContainer: { paramsContainer: {
maxWidth: '400px', maxWidth: '400px',
}, },
divider: { borderStyle: 'dashed', marginBottom: '1rem !important' }, divider: {
borderStyle: 'dashed',
marginBottom: '1rem !important',
borderColor: theme.palette.grey[500],
},
nameContainer: { nameContainer: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',

View File

@ -1,4 +1,10 @@
import { Checkbox, FormControlLabel, IconButton, Tooltip } from '@mui/material'; import {
Checkbox,
Divider,
FormControlLabel,
IconButton,
Tooltip,
} from '@mui/material';
import { Delete } from '@mui/icons-material'; import { Delete } from '@mui/icons-material';
import { useStyles } from './StrategyParameter.styles'; import { useStyles } from './StrategyParameter.styles';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
@ -69,7 +75,7 @@ export const StrategyParameter = ({
return ( return (
<div className={styles.paramsContainer}> <div className={styles.paramsContainer}>
<hr className={styles.divider} /> <Divider className={styles.divider} />
<ConditionallyRender <ConditionallyRender
condition={index === 0} condition={index === 0}
show={ show={

View File

@ -5,6 +5,7 @@ import {
ListItemAvatar, ListItemAvatar,
ListItemText, ListItemText,
Tooltip, Tooltip,
useTheme,
} from '@mui/material'; } from '@mui/material';
import { Add, RadioButtonChecked } from '@mui/icons-material'; import { Add, RadioButtonChecked } from '@mui/icons-material';
import { AppsLinkList } from 'component/common'; import { AppsLinkList } from 'component/common';
@ -26,6 +27,7 @@ export const StrategyDetails = ({
applications, applications,
toggles, toggles,
}: IStrategyDetailsProps) => { }: IStrategyDetailsProps) => {
const theme = useTheme();
const { parameters = [] } = strategy; const { parameters = [] } = strategy;
const renderParameters = (params: IStrategyParameter[]) => { const renderParameters = (params: IStrategyParameter[]) => {
if (params.length > 0) { if (params.length > 0) {
@ -70,7 +72,9 @@ export const StrategyDetails = ({
condition={strategy.deprecated} condition={strategy.deprecated}
show={ show={
<Grid item> <Grid item>
<h5 style={{ color: '#ff0000' }}>Deprecated</h5> <h5 style={{ color: theme.palette.error.main }}>
Deprecated
</h5>
</Grid> </Grid>
} }
/> />

View File

@ -35,7 +35,7 @@ exports[`renders an empty list correctly 1`] = `
className="tss-119iiqp-container" className="tss-119iiqp-container"
> >
<div <div
className="tss-1mtd8gr-search search-container" className="tss-1xjrf9m-search search-container"
> >
<svg <svg
aria-hidden={true} aria-hidden={true}