1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

fix: announce navigation to screen readers (#911)

* refactor: unify page titles

* refactor: update page title on navigation

* refactor: add AnnouncerContext to test contexts

* fix: announce navigation to screen readers
This commit is contained in:
olav 2022-04-27 09:14:44 +02:00 committed by GitHub
parent 06232a5522
commit f7266cde10
22 changed files with 913 additions and 794 deletions

View File

@ -8,7 +8,7 @@
<meta name="cdnPrefix" content="::cdnPrefix::" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="unleash" />
<title>Unleash - Enterprise ready feature toggles</title>
<title>Unleash</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link

View File

@ -1,8 +1,8 @@
import { render } from 'utils/testRenderer';
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
import { useContext, useEffect } from 'react';
import { screen } from '@testing-library/react';
import { ANNOUNCER_ELEMENT_TEST_ID } from 'utils/testIds';
test('AnnouncerContext', async () => {
const TestComponent = () => {
@ -16,12 +16,9 @@ test('AnnouncerContext', async () => {
return null;
};
render(
<AnnouncerProvider>
<TestComponent />
</AnnouncerProvider>
);
render(<TestComponent />);
expect(screen.getByRole('status')).not.toHaveTextContent('Foo');
expect(screen.getByRole('status')).toHaveTextContent('Bar');
const el = screen.getByTestId(ANNOUNCER_ELEMENT_TEST_ID);
expect(el).not.toHaveTextContent('Foo');
expect(el).toHaveTextContent('Bar');
});

View File

@ -1,5 +1,6 @@
import React, { ReactElement } from 'react';
import { useStyles } from 'component/common/Announcer/AnnouncerElement/AnnouncerElement.styles';
import { ANNOUNCER_ELEMENT_TEST_ID } from 'utils/testIds';
interface IAnnouncerElementProps {
announcement?: string;
@ -16,6 +17,7 @@ export const AnnouncerElement = ({
aria-live="polite"
aria-atomic
className={styles.container}
data-testid={ANNOUNCER_ELEMENT_TEST_ID}
>
{announcement}
</div>

View File

@ -6,6 +6,7 @@ import { Typography } from '@material-ui/core';
import ConditionallyRender from '../ConditionallyRender/ConditionallyRender';
import { useStyles } from './styles';
import { usePageTitle } from 'hooks/usePageTitle';
const HeaderTitle = ({
title,
@ -18,6 +19,8 @@ const HeaderTitle = ({
const styles = useStyles();
const headerClasses = classnames({ skeleton: loading });
usePageTitle(title);
return (
<div className={styles.headerTitleContainer}>
<div className={headerClasses} data-loading>

View File

@ -96,12 +96,12 @@ const FeatureToggleList = ({
};
const searchResultsHeader = filter.query
? `(${features.length} matches)`
? ` (${features.length} matches)`
: '';
const headerTitle = archive
? `Archived Features ${searchResultsHeader}`
: `Features ${searchResultsHeader}`;
? `Archived feature toggles${searchResultsHeader}`
: `Feature toggles${searchResultsHeader}`;
return (
<div className={styles.featureContainer}>

View File

@ -6,6 +6,7 @@ import renderer from 'react-test-renderer';
import theme from 'themes/mainTheme';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
jest.mock('./FeatureToggleListItem/FeatureToggleListItem', () => ({
__esModule: true,
@ -24,18 +25,22 @@ test('renders correctly with one feature', () => {
const tree = renderer.create(
<MemoryRouter>
<ThemeProvider theme={theme}>
<AccessProvider permissions={[{ permission: CREATE_FEATURE }]}>
<FeatureToggleList
updateSetting={jest.fn()}
filter={{}}
setFilter={jest.fn()}
sort={{}}
setSort={jest.fn()}
features={features}
fetcher={jest.fn()}
flags={{}}
/>
</AccessProvider>
<AnnouncerProvider>
<AccessProvider
permissions={[{ permission: CREATE_FEATURE }]}
>
<FeatureToggleList
updateSetting={jest.fn()}
filter={{}}
setFilter={jest.fn()}
sort={{}}
setSort={jest.fn()}
features={features}
fetcher={jest.fn()}
flags={{}}
/>
</AccessProvider>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
);
@ -52,17 +57,21 @@ test('renders correctly with one feature without permissions', () => {
const tree = renderer.create(
<MemoryRouter>
<ThemeProvider theme={theme}>
<AccessProvider permissions={[{ permission: CREATE_FEATURE }]}>
<FeatureToggleList
filter={{}}
setFilter={jest.fn()}
sort={{}}
setSort={jest.fn()}
features={features}
fetcher={jest.fn()}
flags={{}}
/>
</AccessProvider>
<AnnouncerProvider>
<AccessProvider
permissions={[{ permission: CREATE_FEATURE }]}
>
<FeatureToggleList
filter={{}}
setFilter={jest.fn()}
sort={{}}
setSort={jest.fn()}
features={features}
fetcher={jest.fn()}
flags={{}}
/>
</AccessProvider>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
);

View File

@ -1,348 +1,368 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with one feature 1`] = `
<div>
<div
className="makeStyles-searchBarContainer-3"
>
<form
className="makeStyles-container-6"
role="search"
>
<div
className="makeStyles-search-7 makeStyles-searchBar-4"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root makeStyles-searchIcon-8"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
<div
className="MuiInputBase-root makeStyles-inputRoot-9"
onClick={[Function]}
onKeyPress={[Function]}
>
<input
aria-label="search"
className="MuiInputBase-input MuiInputBase-inputTypeSearch"
onAnimationStart={[Function]}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Search..."
type="search"
value=""
/>
</div>
</div>
</form>
<a
href="/archive"
onClick={[Function]}
>
Archive
</a>
</div>
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
Array [
<div>
<div
className="makeStyles-headerContainer-10"
className="makeStyles-searchBarContainer-3"
>
<div
className="makeStyles-headerTitleContainer-14"
<form
className="makeStyles-container-6"
role="search"
>
<div
className=""
data-loading={true}
className="makeStyles-search-7 makeStyles-searchBar-4"
>
<h1
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
<svg
aria-hidden={true}
className="MuiSvgIcon-root makeStyles-searchIcon-8"
focusable="false"
viewBox="0 0 24 24"
>
Features
</h1>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
<div
className="MuiInputBase-root makeStyles-inputRoot-9"
onClick={[Function]}
onKeyPress={[Function]}
>
<input
aria-label="search"
className="MuiInputBase-input MuiInputBase-inputTypeSearch"
onAnimationStart={[Function]}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Search..."
type="search"
value=""
/>
</div>
</div>
</form>
<a
href="/archive"
onClick={[Function]}
>
Archive
</a>
</div>
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
<div
className="makeStyles-headerContainer-10"
>
<div
className="makeStyles-headerActions-16"
className="makeStyles-headerTitleContainer-14"
>
<div
className="makeStyles-actionsContainer-1"
className=""
data-loading={true}
>
<h1
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
>
Feature toggles
</h1>
</div>
<div
className="makeStyles-headerActions-16"
>
<div
className="makeStyles-actions-17"
className="makeStyles-actionsContainer-1"
>
<p
className="MuiTypography-root MuiTypography-body2"
data-loading={true}
<div
className="makeStyles-actions-17"
>
Sorted by:
</p>
<button
aria-controls="sorting"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="sorting"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Sort by"
type="button"
>
<span
className="MuiButton-label"
<p
className="MuiTypography-root MuiTypography-body2"
data-loading={true}
>
Sorted by:
</p>
<button
aria-controls="sorting"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="sorting"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Sort by"
type="button"
>
By Name
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
className="MuiButton-label"
>
By Name
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
className="MuiButton-endIcon MuiButton-iconSizeMedium"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
>
<path
d="M7 10l5 5 5-5z"
/>
<title>
Toggle
</title>
</svg>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
<title>
Toggle
</title>
</svg>
</span>
</span>
</span>
</span>
</button>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className="makeStyles-bodyContainer-11"
>
<ul
className="MuiList-root MuiList-padding"
<div
className="makeStyles-bodyContainer-11"
>
<ListItem
feature={
Object {
"name": "Another",
"reviveName": "Another",
<ul
className="MuiList-root MuiList-padding"
>
<ListItem
feature={
Object {
"name": "Another",
"reviveName": "Another",
}
}
}
flags={Object {}}
hasAccess={[Function]}
/>
</ul>
flags={Object {}}
hasAccess={[Function]}
/>
</ul>
</div>
</div>
</div>
</div>
</div>,
<div
aria-atomic={true}
aria-live="polite"
className="makeStyles-container-18"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
/>,
]
`;
exports[`renders correctly with one feature without permissions 1`] = `
<div>
<div
className="makeStyles-searchBarContainer-3"
>
<form
className="makeStyles-container-6"
role="search"
>
<div
className="makeStyles-search-7 makeStyles-searchBar-4"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root makeStyles-searchIcon-8"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
<div
className="MuiInputBase-root makeStyles-inputRoot-9"
onClick={[Function]}
onKeyPress={[Function]}
>
<input
aria-label="search"
className="MuiInputBase-input MuiInputBase-inputTypeSearch"
onAnimationStart={[Function]}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Search..."
type="search"
value=""
/>
</div>
</div>
</form>
<a
href="/archive"
onClick={[Function]}
>
Archive
</a>
</div>
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
Array [
<div>
<div
className="makeStyles-headerContainer-10"
className="makeStyles-searchBarContainer-3"
>
<div
className="makeStyles-headerTitleContainer-14"
<form
className="makeStyles-container-6"
role="search"
>
<div
className=""
data-loading={true}
className="makeStyles-search-7 makeStyles-searchBar-4"
>
<h1
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
<svg
aria-hidden={true}
className="MuiSvgIcon-root makeStyles-searchIcon-8"
focusable="false"
viewBox="0 0 24 24"
>
Features
</h1>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
<div
className="MuiInputBase-root makeStyles-inputRoot-9"
onClick={[Function]}
onKeyPress={[Function]}
>
<input
aria-label="search"
className="MuiInputBase-input MuiInputBase-inputTypeSearch"
onAnimationStart={[Function]}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Search..."
type="search"
value=""
/>
</div>
</div>
</form>
<a
href="/archive"
onClick={[Function]}
>
Archive
</a>
</div>
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
<div
className="makeStyles-headerContainer-10"
>
<div
className="makeStyles-headerActions-16"
className="makeStyles-headerTitleContainer-14"
>
<div
className="makeStyles-actionsContainer-1"
className=""
data-loading={true}
>
<h1
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
>
Feature toggles
</h1>
</div>
<div
className="makeStyles-headerActions-16"
>
<div
className="makeStyles-actions-17"
className="makeStyles-actionsContainer-1"
>
<p
className="MuiTypography-root MuiTypography-body2"
data-loading={true}
<div
className="makeStyles-actions-17"
>
Sorted by:
</p>
<button
aria-controls="sorting"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="sorting"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Sort by"
type="button"
>
<span
className="MuiButton-label"
<p
className="MuiTypography-root MuiTypography-body2"
data-loading={true}
>
Sorted by:
</p>
<button
aria-controls="sorting"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="sorting"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Sort by"
type="button"
>
By Name
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
className="MuiButton-label"
>
By Name
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
className="MuiButton-endIcon MuiButton-iconSizeMedium"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
>
<path
d="M7 10l5 5 5-5z"
/>
<title>
Toggle
</title>
</svg>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
<title>
Toggle
</title>
</svg>
</span>
</span>
</span>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
<span
className="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className="makeStyles-bodyContainer-11"
>
<ul
className="MuiList-root MuiList-padding"
<div
className="makeStyles-bodyContainer-11"
>
<ListItem
feature={
Object {
"name": "Another",
"reviveName": "Another",
<ul
className="MuiList-root MuiList-padding"
>
<ListItem
feature={
Object {
"name": "Another",
"reviveName": "Another",
}
}
}
flags={Object {}}
hasAccess={[Function]}
/>
</ul>
flags={Object {}}
hasAccess={[Function]}
/>
</ul>
</div>
</div>
</div>
</div>
</div>,
<div
aria-atomic={true}
aria-live="polite"
className="makeStyles-container-18"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
>
Navigated to Feature toggles
</div>,
]
`;

View File

@ -16,12 +16,14 @@ import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import ConditionallyRender from 'component/common/ConditionallyRender';
import { useStyles } from './FeatureMetrics.styles';
import { usePageTitle } from 'hooks/usePageTitle';
export const FeatureMetrics = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
const environments = useFeatureMetricsEnvironments(projectId, featureId);
const applications = useFeatureMetricsApplications(featureId);
const styles = useStyles();
usePageTitle('Metrics');
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
useQueryStringNumberState('hoursBack');

View File

@ -10,15 +10,16 @@ import {
formatFeaturePath,
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { usePageTitle } from 'hooks/usePageTitle';
const FeatureOverview = () => {
const styles = useStyles();
const { push } = useHistory();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const featurePath = formatFeaturePath(projectId, featureId);
const onSidebarClose = () => push(featurePath);
usePageTitle(featureId);
return (
<div className={styles.container}>

View File

@ -1,8 +1,10 @@
import { useStyles } from './FeatureVariants.styles';
import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList';
import { usePageTitle } from 'hooks/usePageTitle';
const FeatureVariants = () => {
const styles = useStyles();
usePageTitle('Variants');
return (
<div className={styles.container}>

View File

@ -8,5 +8,5 @@ export const EventHistory = () => {
return null;
}
return <EventLog history={events} title="Recent changes" />;
return <EventLog history={events} title="Event log" />;
};

View File

@ -10,7 +10,7 @@ export const FeatureEventHistory = ({ toggleName }) => {
}
return (
<EventLog history={events} hideName title="Change log" displayInline />
<EventLog history={events} hideName title="Event log" displayInline />
);
};

View File

@ -23,6 +23,13 @@ exports[`renders correctly with empty version 1`] = `
</small>
<br />
</section>
<div
aria-atomic="true"
aria-live="polite"
class="makeStyles-container-2"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
/>
</div>
</body>
`;
@ -34,7 +41,7 @@ exports[`renders correctly with ui-config 1`] = `
title="API details"
>
<h2
class="makeStyles-title-2"
class="makeStyles-title-3"
>
Unleash 1.1.0
@ -50,6 +57,13 @@ exports[`renders correctly with ui-config 1`] = `
</small>
<br />
</section>
<div
aria-atomic="true"
aria-live="polite"
class="makeStyles-container-4"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
/>
</div>
</body>
`;
@ -61,7 +75,7 @@ exports[`renders correctly with versionInfo 1`] = `
title="API details"
>
<h2
class="makeStyles-title-4"
class="makeStyles-title-7"
>
Unleash 1.2.3
@ -77,6 +91,13 @@ exports[`renders correctly with versionInfo 1`] = `
1
</small>
</section>
<div
aria-atomic="true"
aria-live="polite"
class="makeStyles-container-8"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
/>
</div>
</body>
`;
@ -88,7 +109,7 @@ exports[`renders correctly without uiConfig 1`] = `
title="API details"
>
<h2
class="makeStyles-title-3"
class="makeStyles-title-5"
>
Unleash 1.1.0
@ -97,6 +118,13 @@ exports[`renders correctly without uiConfig 1`] = `
<small />
<br />
</section>
<div
aria-atomic="true"
aria-live="polite"
class="makeStyles-container-6"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
/>
</div>
</body>
`;

View File

@ -46,7 +46,7 @@ Array [
"menu": Object {},
"parent": "/projects",
"path": "/projects/:projectId/features/:featureId/edit",
"title": "Edit Feature",
"title": "Edit feature",
"type": "protected",
},
Object {
@ -122,7 +122,7 @@ Array [
"mobile": true,
},
"path": "/features",
"title": "Feature Toggles",
"title": "Feature toggles",
"type": "protected",
},
Object {
@ -169,7 +169,7 @@ Array [
"mobile": true,
},
"path": "/context",
"title": "Context Fields",
"title": "Context fields",
"type": "protected",
},
Object {
@ -331,14 +331,14 @@ Array [
"adminSettings": true,
},
"path": "/history",
"title": "Event History",
"title": "Event log",
"type": "protected",
},
Object {
"component": [Function],
"menu": Object {},
"path": "/archive",
"title": "Archived Toggles",
"title": "Archived toggles",
"type": "protected",
},
Object {
@ -402,7 +402,7 @@ Array [
},
"parent": "/admin",
"path": "/admin/roles",
"title": "Project Roles",
"title": "Project roles",
"type": "protected",
},
Object {
@ -412,7 +412,7 @@ Array [
},
"parent": "/admin",
"path": "/admin/auth",
"title": "Single Sign-On",
"title": "Single sign-on",
"type": "protected",
},
Object {

View File

@ -98,7 +98,7 @@ export const routes: IRoute[] = [
{
path: '/projects/:projectId/features/:featureId/edit',
parent: '/projects',
title: 'Edit Feature',
title: 'Edit feature',
component: EditFeature,
type: 'protected',
menu: {},
@ -172,7 +172,7 @@ export const routes: IRoute[] = [
},
{
path: '/features',
title: 'Feature Toggles',
title: 'Feature toggles',
component: FeatureToggleListContainer,
type: 'protected',
menu: { mobile: true },
@ -216,7 +216,7 @@ export const routes: IRoute[] = [
},
{
path: '/context',
title: 'Context Fields',
title: 'Context fields',
component: ContextList,
type: 'protected',
flag: C,
@ -372,7 +372,7 @@ export const routes: IRoute[] = [
},
{
path: '/history',
title: 'Event History',
title: 'Event log',
component: EventHistoryPage,
type: 'protected',
menu: { adminSettings: true },
@ -381,7 +381,7 @@ export const routes: IRoute[] = [
// Archive
{
path: '/archive',
title: 'Archived Toggles',
title: 'Archived toggles',
component: ArchiveListContainer,
type: 'protected',
menu: {},
@ -447,7 +447,7 @@ export const routes: IRoute[] = [
{
path: '/admin/roles',
parent: '/admin',
title: 'Project Roles',
title: 'Project roles',
component: ProjectRoles,
type: 'protected',
flag: RE,
@ -456,7 +456,7 @@ export const routes: IRoute[] = [
{
path: '/admin/auth',
parent: '/admin',
title: 'Single Sign-On',
title: 'Single sign-on',
component: AuthSettings,
type: 'protected',
menu: { adminSettings: true },

View File

@ -49,7 +49,7 @@ export const ProjectFeatureToggles = ({
headerContent={
<HeaderTitle
className={styles.title}
title={`Feature toggles (${filteredFeatures.length})`}
title={`Project features (${filteredFeatures.length})`}
actions={
<div className={styles.actionsContainer}>
<SearchField

View File

@ -3,12 +3,15 @@ import ApiError from 'component/common/ApiError/ApiError';
import ConditionallyRender from 'component/common/ConditionallyRender';
import ReportToggleList from 'component/Reporting/ReportToggleList/ReportToggleList';
import { ReportCard } from 'component/Reporting/ReportCard/ReportCard';
import { usePageTitle } from 'hooks/usePageTitle';
interface IProjectHealthProps {
projectId: string;
}
const ProjectHealth = ({ projectId }: IProjectHealthProps) => {
usePageTitle('Project health');
const { healthReport, refetchHealthReport, error } =
useHealthReport(projectId);

View File

@ -6,6 +6,7 @@ import theme from 'themes/mainTheme';
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import UIProvider from 'component/providers/UIProvider/UIProvider';
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
test('renders correctly with one strategy', () => {
const strategy = {
@ -15,17 +16,19 @@ test('renders correctly with one strategy', () => {
const tree = renderer.create(
<MemoryRouter>
<ThemeProvider theme={theme}>
<UIProvider>
<AccessProvider permissions={[{ permission: ADMIN }]}>
<StrategiesList
strategies={[strategy]}
fetchStrategies={jest.fn()}
removeStrategy={jest.fn()}
deprecateStrategy={jest.fn()}
reactivateStrategy={jest.fn()}
/>
</AccessProvider>
</UIProvider>
<AnnouncerProvider>
<UIProvider>
<AccessProvider permissions={[{ permission: ADMIN }]}>
<StrategiesList
strategies={[strategy]}
fetchStrategies={jest.fn()}
removeStrategy={jest.fn()}
deprecateStrategy={jest.fn()}
reactivateStrategy={jest.fn()}
/>
</AccessProvider>
</UIProvider>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
);
@ -41,17 +44,19 @@ test('renders correctly with one strategy without permissions', () => {
const tree = renderer.create(
<MemoryRouter>
<ThemeProvider theme={theme}>
<UIProvider>
<AccessProvider permissions={[{ permission: ADMIN }]}>
<StrategiesList
strategies={[strategy]}
fetchStrategies={jest.fn()}
removeStrategy={jest.fn()}
deprecateStrategy={jest.fn()}
reactivateStrategy={jest.fn()}
/>
</AccessProvider>
</UIProvider>
<AnnouncerProvider>
<UIProvider>
<AccessProvider permissions={[{ permission: ADMIN }]}>
<StrategiesList
strategies={[strategy]}
fetchStrategies={jest.fn()}
removeStrategy={jest.fn()}
deprecateStrategy={jest.fn()}
reactivateStrategy={jest.fn()}
/>
</AccessProvider>
</UIProvider>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
);

View File

@ -1,139 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with one strategy 1`] = `
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
Array [
<div
className="makeStyles-headerContainer-2"
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
<div
className="makeStyles-headerTitleContainer-6"
className="makeStyles-headerContainer-2"
>
<div
className=""
data-loading={true}
>
<h1
className="MuiTypography-root makeStyles-headerTitle-7 MuiTypography-h1"
>
Strategies
</h1>
</div>
<div
className="makeStyles-headerActions-8"
>
<span
id="useId-0"
>
<button
aria-describedby="useId-0"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
<span
className="MuiButton-label"
>
New strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
/>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
</span>
</div>
</div>
</div>
<div
className="makeStyles-bodyContainer-3"
>
<ul
className="MuiList-root MuiList-padding"
>
<li
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
disabled={false}
className="makeStyles-headerTitleContainer-6"
>
<div
className="MuiListItemAvatar-root"
className=""
data-loading={true}
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
style={
Object {
"color": "#0000008a",
}
}
viewBox="0 0 24 24"
<h1
className="MuiTypography-root makeStyles-headerTitle-7 MuiTypography-h1"
>
<path
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</svg>
Strategies
</h1>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline"
className="makeStyles-headerActions-8"
>
<span
className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<a
href="/strategies/flexibleRollout"
onClick={[Function]}
>
<strong>
Gradual rollout
</strong>
</a>
</span>
<p
className="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.
</p>
</div>
<div>
<span
aria-describedby={null}
className=""
id="useId-1"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Deprecate strategy"
id="useId-0"
>
<button
aria-labelledby="useId-1"
className="MuiButtonBase-root MuiIconButton-root"
aria-describedby="useId-0"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@ -151,18 +54,12 @@ exports[`renders correctly with one strategy 1`] = `
type="button"
>
<span
className="MuiIconButton-label"
className="MuiButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
New strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
/>
</span>
<span
className="MuiTouchRipple-root"
@ -170,240 +67,257 @@ exports[`renders correctly with one strategy 1`] = `
</button>
</span>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
</div>
</div>
<div
className="makeStyles-bodyContainer-3"
>
<ul
className="MuiList-root MuiList-padding"
>
<li
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
disabled={false}
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
<div
className="MuiListItemAvatar-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
style={
Object {
"color": "#0000008a",
}
}
viewBox="0 0 24 24"
>
<path
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</svg>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline"
>
<span
className="MuiIconButton-label"
className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<a
href="/strategies/flexibleRollout"
onClick={[Function]}
>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
/>
<title>
Edit strategy
</title>
</svg>
<strong>
Gradual rollout
</strong>
</a>
</span>
</button>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<p
className="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.
</p>
</div>
<div>
<span
className="MuiIconButton-label"
aria-describedby={null}
className=""
id="useId-1"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Deprecate strategy"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<button
aria-labelledby="useId-1"
className="MuiButtonBase-root MuiIconButton-root"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
<span
className="MuiIconButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
</span>
<span
className="MuiTouchRipple-root"
/>
<title>
Delete strategy
</title>
</svg>
</button>
</span>
</button>
</div>
</li>
</ul>
</div>
</div>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<span
className="MuiIconButton-label"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
/>
<title>
Edit strategy
</title>
</svg>
</span>
</button>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<span
className="MuiIconButton-label"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
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>
</span>
</button>
</div>
</li>
</ul>
</div>
</div>,
<div
aria-atomic={true}
aria-live="polite"
className="makeStyles-container-11"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
>
Navigated to Strategies
</div>,
]
`;
exports[`renders correctly with one strategy without permissions 1`] = `
<div
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
Array [
<div
className="makeStyles-headerContainer-2"
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
style={
Object {
"borderRadius": "10px",
"boxShadow": "none",
}
}
>
<div
className="makeStyles-headerTitleContainer-6"
className="makeStyles-headerContainer-2"
>
<div
className=""
data-loading={true}
>
<h1
className="MuiTypography-root makeStyles-headerTitle-7 MuiTypography-h1"
>
Strategies
</h1>
</div>
<div
className="makeStyles-headerActions-8"
>
<span
id="useId-2"
>
<button
aria-describedby="useId-2"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
<span
className="MuiButton-label"
>
New strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
/>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
</span>
</div>
</div>
</div>
<div
className="makeStyles-bodyContainer-3"
>
<ul
className="MuiList-root MuiList-padding"
>
<li
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
disabled={false}
className="makeStyles-headerTitleContainer-6"
>
<div
className="MuiListItemAvatar-root"
className=""
data-loading={true}
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
style={
Object {
"color": "#0000008a",
}
}
viewBox="0 0 24 24"
<h1
className="MuiTypography-root makeStyles-headerTitle-7 MuiTypography-h1"
>
<path
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</svg>
Strategies
</h1>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline"
className="makeStyles-headerActions-8"
>
<span
className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<a
href="/strategies/flexibleRollout"
onClick={[Function]}
>
<strong>
Gradual rollout
</strong>
</a>
</span>
<p
className="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.
</p>
</div>
<div>
<span
aria-describedby={null}
className=""
id="useId-3"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Deprecate strategy"
id="useId-2"
>
<button
aria-labelledby="useId-3"
className="MuiButtonBase-root MuiIconButton-root"
aria-describedby="useId-2"
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@ -421,18 +335,12 @@ exports[`renders correctly with one strategy without permissions 1`] = `
type="button"
>
<span
className="MuiIconButton-label"
className="MuiButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
New strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
/>
</span>
<span
className="MuiTouchRipple-root"
@ -440,102 +348,216 @@ exports[`renders correctly with one strategy without permissions 1`] = `
</button>
</span>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
</div>
</div>
<div
className="makeStyles-bodyContainer-3"
>
<ul
className="MuiList-root MuiList-padding"
>
<li
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
disabled={false}
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
<div
className="MuiListItemAvatar-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
style={
Object {
"color": "#0000008a",
}
}
viewBox="0 0 24 24"
>
<path
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</svg>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline"
>
<span
className="MuiIconButton-label"
className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<a
href="/strategies/flexibleRollout"
onClick={[Function]}
>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
/>
<title>
Edit strategy
</title>
</svg>
<strong>
Gradual rollout
</strong>
</a>
</span>
</button>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<p
className="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.
</p>
</div>
<div>
<span
className="MuiIconButton-label"
aria-describedby={null}
className=""
id="useId-3"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Deprecate strategy"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
<button
aria-labelledby="useId-3"
className="MuiButtonBase-root MuiIconButton-root"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
<span
className="MuiIconButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
</span>
<span
className="MuiTouchRipple-root"
/>
<title>
Delete strategy
</title>
</svg>
</button>
</span>
</button>
</div>
</li>
</ul>
</div>
</div>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<span
className="MuiIconButton-label"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
/>
<title>
Edit strategy
</title>
</svg>
</span>
</button>
</div>
<div
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="You cannot delete a built-in strategy"
>
<button
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<span
className="MuiIconButton-label"
>
<svg
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
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>
</span>
</button>
</div>
</li>
</ul>
</div>
</div>,
<div
aria-atomic={true}
aria-live="polite"
className="makeStyles-container-11"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
>
Navigated to Strategies
</div>,
]
`;

View File

@ -0,0 +1,21 @@
import { useEffect, useContext } from 'react';
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
export const usePageTitle = (title: string) => {
const { setAnnouncement } = useContext(AnnouncerContext);
useEffect(() => {
document.title = title;
return () => {
document.title = DEFAULT_PAGE_TITLE;
};
}, [title]);
useEffect(() => {
if (title !== DEFAULT_PAGE_TITLE) {
setAnnouncement(`Navigated to ${title}`);
}
}, [setAnnouncement, title]);
};
const DEFAULT_PAGE_TITLE = 'Unleash';

View File

@ -49,3 +49,4 @@ export const INPUT_ERROR_TEXT = 'INPUT_ERROR_TEXT';
export const HEADER_USER_AVATAR = 'HEADER_USER_AVATAR';
export const SIDEBAR_MODAL_ID = 'SIDEBAR_MODAL_ID';
export const AUTH_PAGE_ID = 'AUTH_PAGE_ID';
export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID';

View File

@ -3,6 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { render as rtlRender, RenderOptions } from '@testing-library/react';
import { SWRConfig } from 'swr';
import { MainThemeProvider } from 'themes/MainThemeProvider';
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
export const render = (
ui: JSX.Element,
@ -23,7 +24,9 @@ const Wrapper: FC = ({ children }) => {
return (
<SWRConfig value={{ provider: () => new Map() }}>
<MainThemeProvider>
<Router>{children}</Router>
<AnnouncerProvider>
<Router>{children}</Router>
</AnnouncerProvider>
</MainThemeProvider>
</SWRConfig>
);