mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-02 01:17:58 +02:00
refactor: fix misc test feedback (#709)
* refactor: keep feature toggle name when changing project * refactor: add missing permission button tooltip * refactor: add success toast on toggle revival * refactor: add success toast on stale toggle * refactor: fix initial user role checkbox value * refactor: remove duplicated error message * refactor: fix change-password error parsing * refactor: remove inaccurate edit toggle toast text * refactor: truncate long names in project cards * refactor: truncate long project name in title * refactor: add ellipses to truncated strings * refactor: swap truncateString with StringTruncator * refactor: remove unnecessary truncation * refactor: mark context fields as optional * refactor: show all errors from tag type creation * refactor: show all errors from strategy create/update * refactor: filter out empty strategies on create/update * refactor: add an edit button to the addons list * refactor: add missing labels * refactor: catch errors from toggling stale features
This commit is contained in:
parent
8afed18cbc
commit
878f892c50
@ -5,13 +5,13 @@ import {
|
|||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Visibility, VisibilityOff, Delete } from '@material-ui/icons';
|
import { Delete, Edit, Visibility, VisibilityOff } from '@material-ui/icons';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import {
|
import {
|
||||||
DELETE_ADDON,
|
DELETE_ADDON,
|
||||||
UPDATE_ADDON,
|
UPDATE_ADDON,
|
||||||
} from '../../../providers/AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import PageContent from '../../../common/PageContent/PageContent';
|
import PageContent from '../../../common/PageContent/PageContent';
|
||||||
import useAddons from '../../../../hooks/api/getters/useAddons/useAddons';
|
import useAddons from '../../../../hooks/api/getters/useAddons/useAddons';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useToast from '../../../../hooks/useToast';
|
||||||
@ -31,6 +31,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
const { updateAddon, removeAddon } = useAddonsApi();
|
const { updateAddon, removeAddon } = useAddonsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const history = useHistory();
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const [deletedAddon, setDeletedAddon] = useState<IAddon>({
|
const [deletedAddon, setDeletedAddon] = useState<IAddon>({
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -115,10 +116,19 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={addon.enabled}
|
condition={addon.enabled}
|
||||||
show={<Visibility />}
|
show={<Visibility titleAccess="Disable addon" />}
|
||||||
elseShow={<VisibilityOff />}
|
elseShow={<VisibilityOff titleAccess="Enable addon" />}
|
||||||
/>
|
/>
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={UPDATE_ADDON}
|
||||||
|
tooltip={'Edit Addon'}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(`/addons/edit/${addon.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit titleAccess="Edit Addon" />
|
||||||
|
</PermissionIconButton>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={DELETE_ADDON}
|
permission={DELETE_ADDON}
|
||||||
tooltip={'Remove Addon'}
|
tooltip={'Remove Addon'}
|
||||||
@ -127,7 +137,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete titleAccess="Remove Addon" />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -38,7 +38,6 @@ const EditUser = () => {
|
|||||||
} = useAddUserForm(
|
} = useAddUserForm(
|
||||||
user?.name,
|
user?.name,
|
||||||
user?.email,
|
user?.email,
|
||||||
user?.sendEmail,
|
|
||||||
user?.rootRole
|
user?.rootRole
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -114,8 +114,6 @@ const ChangePassword = ({
|
|||||||
password={data.password}
|
password={data.password}
|
||||||
callback={setValidPassword}
|
callback={setValidPassword}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p style={{ color: 'red' }}>{error.general}</p>
|
|
||||||
<TextField
|
<TextField
|
||||||
label="New password"
|
label="New password"
|
||||||
name="password"
|
name="password"
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
FlagRounded,
|
FlagRounded,
|
||||||
SvgIconComponent,
|
SvgIconComponent,
|
||||||
} from '@material-ui/icons';
|
} from '@material-ui/icons';
|
||||||
import { shorten } from '../../common';
|
|
||||||
import {
|
import {
|
||||||
CREATE_FEATURE,
|
CREATE_FEATURE,
|
||||||
CREATE_STRATEGY,
|
CREATE_STRATEGY,
|
||||||
@ -87,9 +86,14 @@ export const ApplicationView = () => {
|
|||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Link to={`${viewUrl}/${name}`}>{shorten(name, 50)}</Link>
|
<Link
|
||||||
|
to={`${viewUrl}/${name}`}
|
||||||
|
style={{ wordBreak: 'break-all' }}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
}
|
}
|
||||||
secondary={shorten(description, 60)}
|
secondary={description}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import useToast from '../../hooks/useToast';
|
|||||||
import { useFeaturesSort } from '../../hooks/useFeaturesSort';
|
import { useFeaturesSort } from '../../hooks/useFeaturesSort';
|
||||||
|
|
||||||
export const ArchiveListContainer = () => {
|
export const ArchiveListContainer = () => {
|
||||||
const { setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { reviveFeature } = useFeatureArchiveApi();
|
const { reviveFeature } = useFeatureArchiveApi();
|
||||||
const { archivedFeatures, loading, refetchArchived } = useFeaturesArchive();
|
const { archivedFeatures, loading, refetchArchived } = useFeaturesArchive();
|
||||||
@ -17,6 +17,14 @@ export const ArchiveListContainer = () => {
|
|||||||
const revive = (feature: string) => {
|
const revive = (feature: string) => {
|
||||||
reviveFeature(feature)
|
reviveFeature(feature)
|
||||||
.then(refetchArchived)
|
.then(refetchArchived)
|
||||||
|
.then(() =>
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: "And we're back!",
|
||||||
|
text: 'The feature toggle has been revived.',
|
||||||
|
confetti: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
.catch(e => setToastApiError(e.toString()));
|
.catch(e => setToastApiError(e.toString()));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,8 +5,16 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '4px',
|
top: '4px',
|
||||||
},
|
},
|
||||||
breadcrumbNavParagraph: { color: 'inherit' },
|
breadcrumbNavParagraph: {
|
||||||
|
color: 'inherit',
|
||||||
|
'& > *': {
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
},
|
||||||
|
},
|
||||||
breadcrumbLink: {
|
breadcrumbLink: {
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
|
'& > *': {
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -4,6 +4,7 @@ import ConditionallyRender from '../ConditionallyRender';
|
|||||||
import { useStyles } from './BreadcrumbNav.styles';
|
import { useStyles } from './BreadcrumbNav.styles';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import StringTruncator from '../StringTruncator/StringTruncator';
|
||||||
|
|
||||||
const BreadcrumbNav = () => {
|
const BreadcrumbNav = () => {
|
||||||
const { isAdmin } = useContext(AccessContext);
|
const { isAdmin } = useContext(AccessContext);
|
||||||
@ -51,7 +52,7 @@ const BreadcrumbNav = () => {
|
|||||||
styles.breadcrumbNavParagraph
|
styles.breadcrumbNavParagraph
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{path.substring(0, 30)}
|
<StringTruncator text={path} maxWidth="200" />
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -72,7 +73,7 @@ const BreadcrumbNav = () => {
|
|||||||
className={styles.breadcrumbLink}
|
className={styles.breadcrumbLink}
|
||||||
to={link}
|
to={link}
|
||||||
>
|
>
|
||||||
{path.substring(0, 30)}
|
<StringTruncator text={path} maxWidth="200" />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -9,7 +9,7 @@ interface IPermissionIconButtonProps
|
|||||||
> {
|
> {
|
||||||
permission: string;
|
permission: string;
|
||||||
Icon?: React.ElementType;
|
Icon?: React.ElementType;
|
||||||
tooltip: string;
|
tooltip?: string;
|
||||||
onClick?: (e: any) => void;
|
onClick?: (e: any) => void;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
environmentId?: string;
|
environmentId?: string;
|
||||||
|
@ -38,6 +38,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
|||||||
permission={permission}
|
permission={permission}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
|
tooltip={tooltip}
|
||||||
data-loading
|
data-loading
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
@ -53,6 +54,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
|
tooltip={tooltip}
|
||||||
data-loading
|
data-loading
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
@ -19,8 +19,6 @@ import ConditionallyRender from './ConditionallyRender/ConditionallyRender';
|
|||||||
|
|
||||||
export { styles };
|
export { styles };
|
||||||
|
|
||||||
export const shorten = (str, len = 50) =>
|
|
||||||
str && str.length > len ? `${str.substring(0, len)}...` : str;
|
|
||||||
export const AppsLinkList = ({ apps }) => (
|
export const AppsLinkList = ({ apps }) => (
|
||||||
<List>
|
<List>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
|
@ -112,11 +112,11 @@ const ContextForm: React.FC<IContextForm> = ({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<p className={styles.inputDescription}>
|
||||||
What is this context for?
|
What is this context for?
|
||||||
</p>
|
</p>
|
||||||
<TextField
|
<TextField
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
label="Context description"
|
label="Context description (optional)"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
@ -139,7 +139,7 @@ const ContextForm: React.FC<IContextForm> = ({
|
|||||||
})}
|
})}
|
||||||
<div className={styles.tagContainer}>
|
<div className={styles.tagContainer}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Value"
|
label="Value (optional)"
|
||||||
name="value"
|
name="value"
|
||||||
className={styles.tagInput}
|
className={styles.tagInput}
|
||||||
value={value}
|
value={value}
|
||||||
|
@ -55,7 +55,6 @@ const EditFeature = () => {
|
|||||||
history.push(`/projects/${project}/features/${name}`);
|
history.push(`/projects/${project}/features/${name}`);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Toggle updated successfully',
|
title: 'Toggle updated successfully',
|
||||||
text: 'Now you can start using your toggle.',
|
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -5,6 +5,9 @@ import { DialogContentText } from '@material-ui/core';
|
|||||||
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import Dialogue from '../../../../common/Dialogue';
|
import Dialogue from '../../../../common/Dialogue';
|
||||||
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
|
import React from 'react';
|
||||||
|
import useToast from '../../../../../hooks/useToast';
|
||||||
|
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IStaleDialogProps {
|
interface IStaleDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -13,6 +16,7 @@ interface IStaleDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
|
const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
||||||
const { patchFeatureToggle } = useFeatureApi();
|
const { patchFeatureToggle } = useFeatureApi();
|
||||||
const { refetch } = useFeature(projectId, featureId);
|
const { refetch } = useFeature(projectId, featureId);
|
||||||
@ -30,12 +34,31 @@ const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
|
|||||||
|
|
||||||
const toggleActionText = stale ? 'active' : 'stale';
|
const toggleActionText = stale ? 'active' : 'stale';
|
||||||
|
|
||||||
const onSubmit = async e => {
|
const onSubmit = async (event: React.SyntheticEvent) => {
|
||||||
e.stopPropagation();
|
event.stopPropagation();
|
||||||
const patch = [{ op: 'replace', path: '/stale', value: !stale }];
|
|
||||||
await patchFeatureToggle(projectId, featureId, patch);
|
try {
|
||||||
refetch();
|
const patch = [{ op: 'replace', path: '/stale', value: !stale }];
|
||||||
setOpen(false);
|
await patchFeatureToggle(projectId, featureId, patch);
|
||||||
|
refetch();
|
||||||
|
setOpen(false);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stale) {
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: "And we're back!",
|
||||||
|
text: 'The toggle is no longer marked as stale.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'A job well done.',
|
||||||
|
text: 'The toggle has been marked as stale.',
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
|
@ -44,4 +44,7 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
featureId: {
|
||||||
|
wordBreak: 'break-all'
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Tab, Tabs, useMediaQuery } from '@material-ui/core';
|
import { Tab, Tabs, useMediaQuery } from '@material-ui/core';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { WatchLater, Archive, FileCopy, Label } from '@material-ui/icons';
|
import { Archive, FileCopy, Label, WatchLater } from '@material-ui/icons';
|
||||||
import { Link, Route, useHistory, useParams } from 'react-router-dom';
|
import { Link, Route, useHistory, useParams } from 'react-router-dom';
|
||||||
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
|
||||||
@ -115,7 +115,8 @@ const FeatureView = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
The feature <strong>{featureId.substring(0, 30)}</strong>{' '}
|
The feature{' '}
|
||||||
|
<strong className={styles.featureId}>{featureId}</strong>{' '}
|
||||||
does not exist. Do you want to
|
does not exist. Do you want to
|
||||||
<Link
|
<Link
|
||||||
to={getCreateTogglePath(projectId, uiConfig.flags.E, {
|
to={getCreateTogglePath(projectId, uiConfig.flags.E, {
|
||||||
|
@ -29,9 +29,10 @@ const useFeatureForm = (
|
|||||||
}, [initialType]);
|
}, [initialType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!toggleQueryName) setName(initialName);
|
if (!name) {
|
||||||
else setName(toggleQueryName);
|
setName(toggleQueryName || initialName);
|
||||||
}, [initialName, toggleQueryName]);
|
}
|
||||||
|
}, [name, initialName, toggleQueryName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!projectId) setProject(initialProject);
|
if (!projectId) setProject(initialProject);
|
||||||
|
@ -27,7 +27,7 @@ const Header = () => {
|
|||||||
const [anchorEl, setAnchorEl] = useState();
|
const [anchorEl, setAnchorEl] = useState();
|
||||||
const [anchorElAdvanced, setAnchorElAdvanced] = useState();
|
const [anchorElAdvanced, setAnchorElAdvanced] = useState();
|
||||||
const [admin, setAdmin] = useState(false);
|
const [admin, setAdmin] = useState(false);
|
||||||
const { permissions } = useAuthPermissions()
|
const { permissions } = useAuthPermissions();
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
@ -68,12 +68,19 @@ const Header = () => {
|
|||||||
className={styles.drawerButton}
|
className={styles.drawerButton}
|
||||||
onClick={toggleDrawer}
|
onClick={toggleDrawer}
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon titleAccess="Menu" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<Link to="/" className={commonStyles.flexRow}>
|
<Link
|
||||||
<UnleashLogo className={styles.logo} />{' '}
|
to="/"
|
||||||
|
className={commonStyles.flexRow}
|
||||||
|
aria-label="Home"
|
||||||
|
>
|
||||||
|
<UnleashLogo
|
||||||
|
className={styles.logo}
|
||||||
|
aria-label="Unleash logo"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -127,6 +134,7 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
<MenuBookIcon
|
<MenuBookIcon
|
||||||
className={styles.docsIcon}
|
className={styles.docsIcon}
|
||||||
|
titleAccess="Documentation"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -140,6 +148,7 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
<SettingsIcon
|
<SettingsIcon
|
||||||
className={styles.docsIcon}
|
className={styles.docsIcon}
|
||||||
|
titleAccess="Settings"
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
|
@ -32,4 +32,18 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
width: 'auto',
|
width: 'auto',
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr auto',
|
||||||
|
alignItems: 'center',
|
||||||
|
gridGap: '1rem',
|
||||||
|
},
|
||||||
|
titleText: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useHistory, useParams } from 'react-router';
|
import { useHistory, useParams } from 'react-router';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
|
||||||
import useProject from '../../../hooks/api/getters/useProject/useProject';
|
import useProject from '../../../hooks/api/getters/useProject/useProject';
|
||||||
import useLoading from '../../../hooks/useLoading';
|
import useLoading from '../../../hooks/useLoading';
|
||||||
import ApiError from '../../common/ApiError/ApiError';
|
import ApiError from '../../common/ApiError/ApiError';
|
||||||
@ -25,7 +24,6 @@ const Project = () => {
|
|||||||
const { project, error, loading, refetch } = useProject(id);
|
const { project, error, loading, refetch } = useProject(id);
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const commonStyles = useCommonStyles();
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@ -121,10 +119,10 @@ const Project = () => {
|
|||||||
<div className={styles.innerContainer}>
|
<div className={styles.innerContainer}>
|
||||||
<h2
|
<h2
|
||||||
data-loading
|
data-loading
|
||||||
className={commonStyles.title}
|
className={styles.title}
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
>
|
>
|
||||||
Project: {project?.name}{' '}
|
<div className={styles.titleText}>{project?.name}</div>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_PROJECT}
|
permission={UPDATE_PROJECT}
|
||||||
tooltip="Edit"
|
tooltip="Edit"
|
||||||
|
@ -23,6 +23,11 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
title: {
|
title: {
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
|
lineClamp: 2,
|
||||||
|
display: '-webkit-box',
|
||||||
|
boxOrient: 'vertical',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden'
|
||||||
},
|
},
|
||||||
|
|
||||||
projectIcon: {
|
projectIcon: {
|
||||||
|
@ -46,7 +46,7 @@ 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>
|
||||||
<h2 className={styles.title}>{name}</h2>
|
<div className={styles.title}>{name}</div>
|
||||||
|
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_PROJECT}
|
permission={UPDATE_PROJECT}
|
||||||
|
@ -11,6 +11,7 @@ import useStrategiesApi from '../../../hooks/api/actions/useStrategiesApi/useStr
|
|||||||
import { IStrategy } from '../../../interfaces/strategy';
|
import { IStrategy } from '../../../interfaces/strategy';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies';
|
import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface ICustomStrategyParams {
|
interface ICustomStrategyParams {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -78,7 +79,7 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => {
|
|||||||
setParams(prev => [...parameters]);
|
setParams(prev => [...parameters]);
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
try {
|
try {
|
||||||
await updateStrategy({ name, description, parameters: params });
|
await updateStrategy({ name, description, parameters });
|
||||||
history.push(`/strategies/view/${name}`);
|
history.push(`/strategies/view/${name}`);
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -86,12 +87,12 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => {
|
|||||||
text: 'Successfully updated strategy',
|
text: 'Successfully updated strategy',
|
||||||
});
|
});
|
||||||
refetchStrategies();
|
refetchStrategies();
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await createStrategy({ name, description, parameters: params });
|
await createStrategy({ name, description, parameters });
|
||||||
history.push(`/strategies`);
|
history.push(`/strategies`);
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -99,13 +100,8 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => {
|
|||||||
text: 'Successfully created new strategy',
|
text: 'Successfully created new strategy',
|
||||||
});
|
});
|
||||||
refetchStrategies();
|
refetchStrategies();
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
const STRATEGY_EXIST_ERROR = 'Error: Strategy with name';
|
setToastApiError(formatUnknownError(error));
|
||||||
if (e.toString().includes(STRATEGY_EXIST_ERROR)) {
|
|
||||||
setErrors({
|
|
||||||
name: 'A strategy with this name already exists',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
|
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
|
const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
|
||||||
const [tagName, setTagName] = useState(initialTagName);
|
const [tagName, setTagName] = useState(initialTagName);
|
||||||
@ -22,8 +23,6 @@ const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAME_EXISTS_ERROR =
|
|
||||||
'There already exists a tag-type with the name simple';
|
|
||||||
const validateNameUniqueness = async () => {
|
const validateNameUniqueness = async () => {
|
||||||
if (tagName.length === 0) {
|
if (tagName.length === 0) {
|
||||||
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
|
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
|
||||||
@ -39,14 +38,12 @@ const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
|
|||||||
try {
|
try {
|
||||||
await validateTagName(tagName);
|
await validateTagName(tagName);
|
||||||
return true;
|
return true;
|
||||||
} catch (e: any) {
|
} catch (err: unknown) {
|
||||||
if (e.toString().includes(NAME_EXISTS_ERROR)) {
|
setErrors(prev => ({
|
||||||
setErrors(prev => ({
|
...prev,
|
||||||
...prev,
|
name: formatUnknownError(err)
|
||||||
name: NAME_EXISTS_ERROR,
|
}));
|
||||||
}));
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,10 +14,11 @@ export const handleBadRequest = async (
|
|||||||
if (!setErrors || !requestId) return;
|
if (!setErrors || !requestId) return;
|
||||||
if (res) {
|
if (res) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
const message = data.isJoi ? data.details[0].message : data[0].msg;
|
||||||
|
|
||||||
setErrors(prev => ({
|
setErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[requestId]: data[0].msg,
|
[requestId]: message,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,10 +48,11 @@ export const handleUnauthorized = async (
|
|||||||
if (!setErrors || !requestId) return;
|
if (!setErrors || !requestId) return;
|
||||||
if (res) {
|
if (res) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
const message = data.isJoi ? data.details[0].message : data[0].msg;
|
||||||
|
|
||||||
setErrors(prev => ({
|
setErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[requestId]: data[0].msg,
|
[requestId]: message,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,6 @@ export const handleForbidden = async (
|
|||||||
if (!setErrors || !requestId) return;
|
if (!setErrors || !requestId) return;
|
||||||
if (res) {
|
if (res) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const message = data.isJoi ? data.details[0].message : data[0].msg;
|
const message = data.isJoi ? data.details[0].message : data[0].msg;
|
||||||
|
|
||||||
setErrors(prev => ({
|
setErrors(prev => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user