diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
index 233d57354b..78ef5188cc 100644
--- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
+++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
@@ -5,13 +5,13 @@ import {
ListItemSecondaryAction,
ListItemText,
} 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 {
DELETE_ADDON,
UPDATE_ADDON,
} from '../../../providers/AccessProvider/permissions';
-import { Link } from 'react-router-dom';
+import { Link, useHistory } from 'react-router-dom';
import PageContent from '../../../common/PageContent/PageContent';
import useAddons from '../../../../hooks/api/getters/useAddons/useAddons';
import useToast from '../../../../hooks/useToast';
@@ -31,6 +31,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
const { updateAddon, removeAddon } = useAddonsApi();
const { setToastData, setToastApiError } = useToast();
const { hasAccess } = useContext(AccessContext);
+ const history = useHistory();
const [showDelete, setShowDelete] = useState(false);
const [deletedAddon, setDeletedAddon] = useState({
id: 0,
@@ -115,10 +116,19 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
>
}
- elseShow={}
+ show={}
+ elseShow={}
/>
+ {
+ history.push(`/addons/edit/${addon.id}`);
+ }}
+ >
+
+
{
setShowDelete(true);
}}
>
-
+
diff --git a/frontend/src/component/admin/users/EditUser/EditUser.tsx b/frontend/src/component/admin/users/EditUser/EditUser.tsx
index a42e4e6de0..e5db03f2b1 100644
--- a/frontend/src/component/admin/users/EditUser/EditUser.tsx
+++ b/frontend/src/component/admin/users/EditUser/EditUser.tsx
@@ -38,7 +38,6 @@ const EditUser = () => {
} = useAddUserForm(
user?.name,
user?.email,
- user?.sendEmail,
user?.rootRole
);
diff --git a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
index 976a6ec8d2..bd0cb82fc5 100644
--- a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
+++ b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
@@ -114,8 +114,6 @@ const ChangePassword = ({
password={data.password}
callback={setValidPassword}
/>
-
- {error.general}
{
{shorten(name, 50)}
+
+ {name}
+
}
- secondary={shorten(description, 60)}
+ secondary={description}
/>
);
diff --git a/frontend/src/component/archive/ArchiveListContainer.tsx b/frontend/src/component/archive/ArchiveListContainer.tsx
index 3ce133bc57..698259fd57 100644
--- a/frontend/src/component/archive/ArchiveListContainer.tsx
+++ b/frontend/src/component/archive/ArchiveListContainer.tsx
@@ -7,7 +7,7 @@ import useToast from '../../hooks/useToast';
import { useFeaturesSort } from '../../hooks/useFeaturesSort';
export const ArchiveListContainer = () => {
- const { setToastApiError } = useToast();
+ const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const { reviveFeature } = useFeatureArchiveApi();
const { archivedFeatures, loading, refetchArchived } = useFeaturesArchive();
@@ -17,6 +17,14 @@ export const ArchiveListContainer = () => {
const revive = (feature: string) => {
reviveFeature(feature)
.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()));
};
diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts
index a551eba549..d1dfa48d38 100644
--- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts
+++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts
@@ -5,8 +5,16 @@ export const useStyles = makeStyles(theme => ({
position: 'absolute',
top: '4px',
},
- breadcrumbNavParagraph: { color: 'inherit' },
+ breadcrumbNavParagraph: {
+ color: 'inherit',
+ '& > *': {
+ verticalAlign: 'middle',
+ },
+ },
breadcrumbLink: {
textDecoration: 'none',
+ '& > *': {
+ verticalAlign: 'middle',
+ },
},
}));
diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
index b08c182b97..d299010ca6 100644
--- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
+++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
@@ -4,6 +4,7 @@ import ConditionallyRender from '../ConditionallyRender';
import { useStyles } from './BreadcrumbNav.styles';
import AccessContext from '../../../contexts/AccessContext';
import { useContext } from 'react';
+import StringTruncator from '../StringTruncator/StringTruncator';
const BreadcrumbNav = () => {
const { isAdmin } = useContext(AccessContext);
@@ -51,7 +52,7 @@ const BreadcrumbNav = () => {
styles.breadcrumbNavParagraph
}
>
- {path.substring(0, 30)}
+
);
}
@@ -72,7 +73,7 @@ const BreadcrumbNav = () => {
className={styles.breadcrumbLink}
to={link}
>
- {path.substring(0, 30)}
+
);
})}
diff --git a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx
index ff6103efd2..d987e05a94 100644
--- a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx
+++ b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx
@@ -9,7 +9,7 @@ interface IPermissionIconButtonProps
> {
permission: string;
Icon?: React.ElementType;
- tooltip: string;
+ tooltip?: string;
onClick?: (e: any) => void;
projectId?: string;
environmentId?: string;
diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx
index aa18fd1ff0..0edb2cb94f 100644
--- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx
+++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx
@@ -38,6 +38,7 @@ const ResponsiveButton: React.FC = ({
permission={permission}
projectId={projectId}
environmentId={environmentId}
+ tooltip={tooltip}
data-loading
{...rest}
>
@@ -53,6 +54,7 @@ const ResponsiveButton: React.FC = ({
variant="contained"
disabled={disabled}
environmentId={environmentId}
+ tooltip={tooltip}
data-loading
{...rest}
>
diff --git a/frontend/src/component/common/index.js b/frontend/src/component/common/index.js
index 8d437a6e0a..ffc2fe9395 100644
--- a/frontend/src/component/common/index.js
+++ b/frontend/src/component/common/index.js
@@ -19,8 +19,6 @@ import ConditionallyRender from './ConditionallyRender/ConditionallyRender';
export { styles };
-export const shorten = (str, len = 50) =>
- str && str.length > len ? `${str.substring(0, len)}...` : str;
export const AppsLinkList = ({ apps }) => (
= ({
autoFocus
/>
- What is this context for?
+ What is this context for?
= ({
})}
{
history.push(`/projects/${project}/features/${name}`);
setToastData({
title: 'Toggle updated successfully',
- text: 'Now you can start using your toggle.',
type: 'success',
});
} catch (e: any) {
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
index ab7a3473e0..51464afe25 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
@@ -5,6 +5,9 @@ import { DialogContentText } from '@material-ui/core';
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
import Dialogue from '../../../../common/Dialogue';
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 {
open: boolean;
@@ -13,6 +16,7 @@ interface IStaleDialogProps {
}
const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
+ const { setToastData, setToastApiError } = useToast();
const { projectId, featureId } = useParams();
const { patchFeatureToggle } = useFeatureApi();
const { refetch } = useFeature(projectId, featureId);
@@ -30,12 +34,31 @@ const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
const toggleActionText = stale ? 'active' : 'stale';
- const onSubmit = async e => {
- e.stopPropagation();
- const patch = [{ op: 'replace', path: '/stale', value: !stale }];
- await patchFeatureToggle(projectId, featureId, patch);
- refetch();
- setOpen(false);
+ const onSubmit = async (event: React.SyntheticEvent) => {
+ event.stopPropagation();
+
+ try {
+ const patch = [{ op: 'replace', path: '/stale', value: !stale }];
+ 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 = () => {
diff --git a/frontend/src/component/feature/FeatureView/FeatureView.styles.ts b/frontend/src/component/feature/FeatureView/FeatureView.styles.ts
index 3b59a9fd91..1677064e08 100644
--- a/frontend/src/component/feature/FeatureView/FeatureView.styles.ts
+++ b/frontend/src/component/feature/FeatureView/FeatureView.styles.ts
@@ -44,4 +44,7 @@ export const useStyles = makeStyles(theme => ({
flexDirection: 'column',
},
},
+ featureId: {
+ wordBreak: 'break-all'
+ }
}));
diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx
index a612c964eb..031bee5280 100644
--- a/frontend/src/component/feature/FeatureView/FeatureView.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx
@@ -1,6 +1,6 @@
import { Tab, Tabs, useMediaQuery } from '@material-ui/core';
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 useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
@@ -115,7 +115,8 @@ const FeatureView = () => {
return (
- The feature {featureId.substring(0, 30)}{' '}
+ The feature{' '}
+ {featureId}{' '}
does not exist. Do you want to
{
- if (!toggleQueryName) setName(initialName);
- else setName(toggleQueryName);
- }, [initialName, toggleQueryName]);
+ if (!name) {
+ setName(toggleQueryName || initialName);
+ }
+ }, [name, initialName, toggleQueryName]);
useEffect(() => {
if (!projectId) setProject(initialProject);
diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx
index 516233ddca..f83ac83a07 100644
--- a/frontend/src/component/menu/Header/Header.tsx
+++ b/frontend/src/component/menu/Header/Header.tsx
@@ -27,7 +27,7 @@ const Header = () => {
const [anchorEl, setAnchorEl] = useState();
const [anchorElAdvanced, setAnchorElAdvanced] = useState();
const [admin, setAdmin] = useState(false);
- const { permissions } = useAuthPermissions()
+ const { permissions } = useAuthPermissions();
const commonStyles = useCommonStyles();
const { uiConfig } = useUiConfig();
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
@@ -68,12 +68,19 @@ const Header = () => {
className={styles.drawerButton}
onClick={toggleDrawer}
>
-
+
}
elseShow={
-
- {' '}
+
+
}
/>
@@ -127,6 +134,7 @@ const Header = () => {
>
@@ -140,6 +148,7 @@ const Header = () => {
>
}
diff --git a/frontend/src/component/project/Project/Project.styles.ts b/frontend/src/component/project/Project/Project.styles.ts
index 7658ac653f..70b3461a19 100644
--- a/frontend/src/component/project/Project/Project.styles.ts
+++ b/frontend/src/component/project/Project/Project.styles.ts
@@ -32,4 +32,18 @@ export const useStyles = makeStyles(theme => ({
width: 'auto',
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',
+ },
}));
diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx
index 29f7468de5..c61f71dbe8 100644
--- a/frontend/src/component/project/Project/Project.tsx
+++ b/frontend/src/component/project/Project/Project.tsx
@@ -1,5 +1,4 @@
import { useHistory, useParams } from 'react-router';
-import { useCommonStyles } from '../../../common.styles';
import useProject from '../../../hooks/api/getters/useProject/useProject';
import useLoading from '../../../hooks/useLoading';
import ApiError from '../../common/ApiError/ApiError';
@@ -25,7 +24,6 @@ const Project = () => {
const { project, error, loading, refetch } = useProject(id);
const ref = useLoading(loading);
const { setToastData } = useToast();
- const commonStyles = useCommonStyles();
const styles = useStyles();
const history = useHistory();
@@ -121,10 +119,10 @@ const Project = () => {
- Project: {project?.name}{' '}
+
{project?.name}
({
title: {
fontWeight: 'normal',
fontSize: '1rem',
+ lineClamp: 2,
+ display: '-webkit-box',
+ boxOrient: 'vertical',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden'
},
projectIcon: {
diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.tsx b/frontend/src/component/project/ProjectCard/ProjectCard.tsx
index 3fc61331f5..6b4c71f7c2 100644
--- a/frontend/src/component/project/ProjectCard/ProjectCard.tsx
+++ b/frontend/src/component/project/ProjectCard/ProjectCard.tsx
@@ -46,7 +46,7 @@ const ProjectCard = ({
return (
-
{name}
+
{name}
{
setParams(prev => [...parameters]);
if (editMode) {
try {
- await updateStrategy({ name, description, parameters: params });
+ await updateStrategy({ name, description, parameters });
history.push(`/strategies/view/${name}`);
setToastData({
type: 'success',
@@ -86,12 +87,12 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => {
text: 'Successfully updated strategy',
});
refetchStrategies();
- } catch (e: any) {
- setToastApiError(e.toString());
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
}
} else {
try {
- await createStrategy({ name, description, parameters: params });
+ await createStrategy({ name, description, parameters });
history.push(`/strategies`);
setToastData({
type: 'success',
@@ -99,13 +100,8 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => {
text: 'Successfully created new strategy',
});
refetchStrategies();
- } catch (e: any) {
- const STRATEGY_EXIST_ERROR = 'Error: Strategy with name';
- if (e.toString().includes(STRATEGY_EXIST_ERROR)) {
- setErrors({
- name: 'A strategy with this name already exists',
- });
- }
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
}
}
};
diff --git a/frontend/src/component/tags/TagTypeForm/useTagTypeForm.ts b/frontend/src/component/tags/TagTypeForm/useTagTypeForm.ts
index fe7452914e..976e3fc539 100644
--- a/frontend/src/component/tags/TagTypeForm/useTagTypeForm.ts
+++ b/frontend/src/component/tags/TagTypeForm/useTagTypeForm.ts
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
+import { formatUnknownError } from '../../../utils/format-unknown-error';
const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
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 () => {
if (tagName.length === 0) {
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
@@ -39,14 +38,12 @@ const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => {
try {
await validateTagName(tagName);
return true;
- } catch (e: any) {
- if (e.toString().includes(NAME_EXISTS_ERROR)) {
- setErrors(prev => ({
- ...prev,
- name: NAME_EXISTS_ERROR,
- }));
- return false;
- }
+ } catch (err: unknown) {
+ setErrors(prev => ({
+ ...prev,
+ name: formatUnknownError(err)
+ }));
+ return false;
}
};
diff --git a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
index 522bab536e..0761089d09 100644
--- a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
+++ b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
@@ -14,10 +14,11 @@ export const handleBadRequest = async (
if (!setErrors || !requestId) return;
if (res) {
const data = await res.json();
+ const message = data.isJoi ? data.details[0].message : data[0].msg;
setErrors(prev => ({
...prev,
- [requestId]: data[0].msg,
+ [requestId]: message,
}));
}
@@ -47,10 +48,11 @@ export const handleUnauthorized = async (
if (!setErrors || !requestId) return;
if (res) {
const data = await res.json();
+ const message = data.isJoi ? data.details[0].message : data[0].msg;
setErrors(prev => ({
...prev,
- [requestId]: data[0].msg,
+ [requestId]: message,
}));
}
@@ -65,7 +67,6 @@ export const handleForbidden = async (
if (!setErrors || !requestId) return;
if (res) {
const data = await res.json();
-
const message = data.isJoi ? data.details[0].message : data[0].msg;
setErrors(prev => ({