mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
Merge branch 'main' into fix/impression-data-label
This commit is contained in:
commit
b345ea45d7
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "unleash-frontend",
|
"name": "unleash-frontend",
|
||||||
"description": "unleash your features",
|
"description": "unleash your features",
|
||||||
"version": "4.7.2",
|
"version": "4.8.0-beta.1",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"unleash",
|
"unleash",
|
||||||
"feature toggle",
|
"feature toggle",
|
||||||
@ -54,7 +54,6 @@
|
|||||||
"@types/react-test-renderer": "17.0.1",
|
"@types/react-test-renderer": "17.0.1",
|
||||||
"@types/react-timeago": "4.1.3",
|
"@types/react-timeago": "4.1.3",
|
||||||
"@welldone-software/why-did-you-render": "6.2.3",
|
"@welldone-software/why-did-you-render": "6.2.3",
|
||||||
"array-move": "3.0.1",
|
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"copy-to-clipboard": "3.3.1",
|
"copy-to-clipboard": "3.3.1",
|
||||||
"craco": "0.0.3",
|
"craco": "0.0.3",
|
||||||
@ -64,13 +63,10 @@
|
|||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"deep-diff": "1.0.2",
|
"deep-diff": "1.0.2",
|
||||||
"fast-json-patch": "3.1.0",
|
"fast-json-patch": "3.1.0",
|
||||||
"fetch-mock": "9.11.0",
|
|
||||||
"http-proxy-middleware": "2.0.2",
|
"http-proxy-middleware": "2.0.2",
|
||||||
"immutable": "4.0.0",
|
|
||||||
"@types/lodash.clonedeep": "4.5.6",
|
"@types/lodash.clonedeep": "4.5.6",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.flow": "3.5.0",
|
"lodash.flow": "3.5.0",
|
||||||
"node-fetch": "2.6.7",
|
|
||||||
"prettier": "2.5.1",
|
"prettier": "2.5.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
@ -79,15 +75,10 @@
|
|||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-hooks-global-state": "1.0.2",
|
"react-hooks-global-state": "1.0.2",
|
||||||
"react-outside-click-handler": "1.3.0",
|
"react-outside-click-handler": "1.3.0",
|
||||||
"react-redux": "7.2.6",
|
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "5.3.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"react-test-renderer": "16.14.0",
|
"react-test-renderer": "16.14.0",
|
||||||
"react-timeago": "6.2.1",
|
"react-timeago": "6.2.1",
|
||||||
"redux": "4.1.2",
|
|
||||||
"redux-devtools-extension": "2.13.9",
|
|
||||||
"redux-mock-store": "1.5.4",
|
|
||||||
"redux-thunk": "2.4.1",
|
|
||||||
"sass": "1.49.7",
|
"sass": "1.49.7",
|
||||||
"swr": "1.2.1",
|
"swr": "1.2.1",
|
||||||
"typescript": "4.5.5",
|
"typescript": "4.5.5",
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { Map as $MAp } from 'immutable';
|
|
||||||
|
|
||||||
export const createFakeStore = permissions => {
|
|
||||||
return {
|
|
||||||
getState: () => ({
|
|
||||||
user: new $MAp({
|
|
||||||
permissions,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
@ -9,18 +9,12 @@ import SWRProvider from './providers/SWRProvider/SWRProvider';
|
|||||||
import ToastRenderer from './common/ToastRenderer/ToastRenderer';
|
import ToastRenderer from './common/ToastRenderer/ToastRenderer';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||||
import { RouteComponentProps } from 'react-router';
|
|
||||||
import { routes } from './menu/routes';
|
import { routes } from './menu/routes';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useAuthDetails } from '../hooks/api/getters/useAuth/useAuthDetails';
|
import { useAuthDetails } from '../hooks/api/getters/useAuth/useAuthDetails';
|
||||||
import { useAuthUser } from '../hooks/api/getters/useAuth/useAuthUser';
|
import { useAuthUser } from '../hooks/api/getters/useAuth/useAuthUser';
|
||||||
import { useAuthSplash } from '../hooks/api/getters/useAuth/useAuthSplash';
|
import { useAuthSplash } from '../hooks/api/getters/useAuth/useAuthSplash';
|
||||||
|
|
||||||
interface IAppProps extends RouteComponentProps {
|
export const App = () => {
|
||||||
fetchUiBootstrap: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const App = ({ fetchUiBootstrap }: IAppProps) => {
|
|
||||||
const { splash, refetchSplash } = useAuthSplash();
|
const { splash, refetchSplash } = useAuthSplash();
|
||||||
const { authDetails } = useAuthDetails();
|
const { authDetails } = useAuthDetails();
|
||||||
const { user } = useAuthUser();
|
const { user } = useAuthUser();
|
||||||
@ -29,10 +23,6 @@ export const App = ({ fetchUiBootstrap }: IAppProps) => {
|
|||||||
const hasFetchedAuth = Boolean(authDetails || user);
|
const hasFetchedAuth = Boolean(authDetails || user);
|
||||||
const showEnvSplash = isLoggedIn && splash?.environment === false;
|
const showEnvSplash = isLoggedIn && splash?.environment === false;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchUiBootstrap();
|
|
||||||
}, [fetchUiBootstrap, authDetails?.type]);
|
|
||||||
|
|
||||||
const renderMainLayoutRoutes = () => {
|
const renderMainLayoutRoutes = () => {
|
||||||
return routes.filter(route => route.layout === 'main').map(renderRoute);
|
return routes.filter(route => route.layout === 'main').map(renderRoute);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { App } from './App';
|
|
||||||
import { fetchUiBootstrap } from '../store/ui-bootstrap/actions';
|
|
||||||
|
|
||||||
export default connect(null, { fetchUiBootstrap })(App);
|
|
@ -0,0 +1,64 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`renders correctly if no application 1`] = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate"
|
||||||
|
role="progressbar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-bar2Indeterminate MuiLinearProgress-barColorPrimary"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders correctly with permissions 1`] = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate"
|
||||||
|
role="progressbar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-bar2Indeterminate MuiLinearProgress-barColorPrimary"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders correctly without permission 1`] = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate"
|
||||||
|
role="progressbar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="MuiLinearProgress-bar MuiLinearProgress-bar2Indeterminate MuiLinearProgress-barColorPrimary"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,158 @@
|
|||||||
|
import { ThemeProvider } from '@material-ui/core';
|
||||||
|
import { ApplicationEdit } from '../ApplicationEdit/ApplicationEdit';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
|
import theme from '../../../themes/main-theme';
|
||||||
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
|
import UIProvider from '../../providers/UIProvider/UIProvider';
|
||||||
|
|
||||||
|
test('renders correctly if no application', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<UIProvider>
|
||||||
|
<MemoryRouter initialEntries={['/test']}>
|
||||||
|
<ApplicationEdit
|
||||||
|
fetchApplication={() => Promise.resolve({})}
|
||||||
|
storeApplicationMetaData={jest.fn()}
|
||||||
|
deleteApplication={jest.fn()}
|
||||||
|
history={{}}
|
||||||
|
locationSettings={{ locale: 'en-GB' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
</UIProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</AccessProvider>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders correctly without permission', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<UIProvider>
|
||||||
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
|
<ApplicationEdit
|
||||||
|
fetchApplication={() => Promise.resolve({})}
|
||||||
|
storeApplicationMetaData={jest.fn()}
|
||||||
|
deleteApplication={jest.fn()}
|
||||||
|
history={{}}
|
||||||
|
application={{
|
||||||
|
appName: 'test-app',
|
||||||
|
instances: [
|
||||||
|
{
|
||||||
|
instanceId: 'instance-1',
|
||||||
|
clientIp: '123.123.123.123',
|
||||||
|
lastSeen: '2017-02-23T15:56:49',
|
||||||
|
sdkVersion: '4.0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
strategies: [
|
||||||
|
{
|
||||||
|
name: 'StrategyA',
|
||||||
|
description: 'A description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'StrategyB',
|
||||||
|
description: 'B description',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
seenToggles: [
|
||||||
|
{
|
||||||
|
name: 'ToggleA',
|
||||||
|
description: 'this is A toggle',
|
||||||
|
enabled: true,
|
||||||
|
project: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ToggleB',
|
||||||
|
description: 'this is B toggle',
|
||||||
|
enabled: false,
|
||||||
|
notFound: true,
|
||||||
|
project: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: 'http://example.org',
|
||||||
|
description: 'app description',
|
||||||
|
}}
|
||||||
|
locationSettings={{ locale: 'en-GB' }}
|
||||||
|
/>
|
||||||
|
</AccessProvider>
|
||||||
|
</UIProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders correctly with permissions', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<UIProvider>
|
||||||
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
|
<ApplicationEdit
|
||||||
|
fetchApplication={() => Promise.resolve({})}
|
||||||
|
storeApplicationMetaData={jest.fn()}
|
||||||
|
history={{}}
|
||||||
|
deleteApplication={jest.fn()}
|
||||||
|
application={{
|
||||||
|
appName: 'test-app',
|
||||||
|
instances: [
|
||||||
|
{
|
||||||
|
instanceId: 'instance-1',
|
||||||
|
clientIp: '123.123.123.123',
|
||||||
|
lastSeen: '2017-02-23T15:56:49',
|
||||||
|
sdkVersion: '4.0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
strategies: [
|
||||||
|
{
|
||||||
|
name: 'StrategyA',
|
||||||
|
description: 'A description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'StrategyB',
|
||||||
|
description: 'B description',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
seenToggles: [
|
||||||
|
{
|
||||||
|
name: 'ToggleA',
|
||||||
|
description: 'this is A toggle',
|
||||||
|
enabled: true,
|
||||||
|
project: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ToggleB',
|
||||||
|
description: 'this is B toggle',
|
||||||
|
enabled: false,
|
||||||
|
notFound: true,
|
||||||
|
project: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: 'http://example.org',
|
||||||
|
description: 'app description',
|
||||||
|
}}
|
||||||
|
locationSettings={{ locale: 'en-GB' }}
|
||||||
|
/>
|
||||||
|
</AccessProvider>
|
||||||
|
</UIProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
@ -4,14 +4,13 @@ import renderer from 'react-test-renderer';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
|
||||||
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
import UIProvider from '../../providers/UIProvider/UIProvider';
|
import UIProvider from '../../providers/UIProvider/UIProvider';
|
||||||
|
|
||||||
test('renders correctly if no application', () => {
|
test('renders correctly if no application', () => {
|
||||||
const tree = renderer
|
const tree = renderer
|
||||||
.create(
|
.create(
|
||||||
<AccessProvider store={createFakeStore([{ permission: ADMIN }])}>
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<MemoryRouter initialEntries={['/test']}>
|
<MemoryRouter initialEntries={['/test']}>
|
||||||
@ -38,7 +37,7 @@ test('renders correctly without permission', () => {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider store={createFakeStore([])}>
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
<ApplicationEdit
|
<ApplicationEdit
|
||||||
fetchApplication={() => Promise.resolve({})}
|
fetchApplication={() => Promise.resolve({})}
|
||||||
storeApplicationMetaData={jest.fn()}
|
storeApplicationMetaData={jest.fn()}
|
||||||
@ -101,9 +100,7 @@ test('renders correctly with permissions', () => {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
store={createFakeStore([{ permission: ADMIN }])}
|
|
||||||
>
|
|
||||||
<ApplicationEdit
|
<ApplicationEdit
|
||||||
fetchApplication={() => Promise.resolve({})}
|
fetchApplication={() => Promise.resolve({})}
|
||||||
storeApplicationMetaData={jest.fn()}
|
storeApplicationMetaData={jest.fn()}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Snackbar, IconButton } from '@material-ui/core';
|
|
||||||
import { Close, QuestionAnswer } from '@material-ui/icons';
|
|
||||||
|
|
||||||
const ErrorComponent = ({ errors, muteError }) => {
|
|
||||||
const showError = errors.length > 0;
|
|
||||||
const error = showError ? errors[0] : undefined;
|
|
||||||
return (
|
|
||||||
<Snackbar
|
|
||||||
action={
|
|
||||||
<React.Fragment>
|
|
||||||
<IconButton size="small" aria-label="close" color="inherit">
|
|
||||||
<Close />
|
|
||||||
</IconButton>
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
|
||||||
open={showError}
|
|
||||||
onClose={() => muteError(error)}
|
|
||||||
autoHideDuration={10000}
|
|
||||||
message={
|
|
||||||
<div key={error}>
|
|
||||||
<QuestionAnswer />
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorComponent.propTypes = {
|
|
||||||
errors: PropTypes.array.isRequired,
|
|
||||||
muteError: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorComponent;
|
|
@ -1,19 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import ErrorComponent from './error-component';
|
|
||||||
import { muteError } from '../../store/error/actions';
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
muteError,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
errors: state.error
|
|
||||||
.get('list')
|
|
||||||
.toArray()
|
|
||||||
.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ErrorComponent);
|
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -23,17 +22,18 @@ import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureAp
|
|||||||
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
const CopyFeature = props => {
|
export const CopyFeatureToggle = () => {
|
||||||
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
|
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
|
||||||
const [replaceGroupId, setReplaceGroupId] = useState(true);
|
const [replaceGroupId, setReplaceGroupId] = useState(true);
|
||||||
const [apiError, setApiError] = useState('');
|
const [apiError, setApiError] = useState('');
|
||||||
const [nameError, setNameError] = useState(undefined);
|
const [nameError, setNameError] = useState(undefined);
|
||||||
const [newToggleName, setNewToggleName] = useState();
|
const [newToggleName, setNewToggleName] = useState();
|
||||||
const { cloneFeatureToggle } = useFeatureApi();
|
const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const { name: copyToggleName, id: projectId } = useParams();
|
const { name: copyToggleName, id: projectId } = useParams();
|
||||||
const { feature } = useFeature(projectId, copyToggleName);
|
const { feature } = useFeature(projectId, copyToggleName);
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
@ -50,7 +50,7 @@ const CopyFeature = props => {
|
|||||||
|
|
||||||
const onValidateName = async () => {
|
const onValidateName = async () => {
|
||||||
try {
|
try {
|
||||||
await props.validateName(newToggleName);
|
await validateFeatureToggleName(newToggleName);
|
||||||
|
|
||||||
setNameError(undefined);
|
setNameError(undefined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -70,7 +70,7 @@ const CopyFeature = props => {
|
|||||||
name: newToggleName,
|
name: newToggleName,
|
||||||
replaceGroupId,
|
replaceGroupId,
|
||||||
});
|
});
|
||||||
props.history.push(
|
history.push(
|
||||||
getTogglePath(projectId, newToggleName, uiConfig.flags.E)
|
getTogglePath(projectId, newToggleName, uiConfig.flags.E)
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -137,10 +137,3 @@ const CopyFeature = props => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CopyFeature.propTypes = {
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
validateName: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CopyFeature;
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import CopyFeatureComponent from './CopyFeature';
|
|
||||||
import { validateName } from '../../../store/feature-toggle/actions';
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
history: props.history,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
validateName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const FormAddContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(CopyFeatureComponent);
|
|
||||||
|
|
||||||
export default FormAddContainer;
|
|
@ -3,7 +3,6 @@ import classnames from 'classnames';
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import { Grid } from '@material-ui/core';
|
import { Grid } from '@material-ui/core';
|
||||||
import styles from '../../styles.module.scss';
|
import styles from '../../styles.module.scss';
|
||||||
import ErrorContainer from '../../error/error-container';
|
|
||||||
import Header from '../../menu/Header/Header';
|
import Header from '../../menu/Header/Header';
|
||||||
import Footer from '../../menu/Footer/Footer';
|
import Footer from '../../menu/Footer/Footer';
|
||||||
import Proclamation from '../../common/Proclamation/Proclamation';
|
import Proclamation from '../../common/Proclamation/Proclamation';
|
||||||
@ -27,7 +26,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
interface IMainLayoutProps {
|
interface IMainLayoutProps {
|
||||||
children: ReactNode
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MainLayout = ({ children }: IMainLayoutProps) => {
|
export const MainLayout = ({ children }: IMainLayoutProps) => {
|
||||||
@ -48,7 +47,6 @@ export const MainLayout = ({ children }: IMainLayoutProps) => {
|
|||||||
<Proclamation toast={uiConfig.toast} />
|
<Proclamation toast={uiConfig.toast} />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<ErrorContainer />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<div style={{ overflow: 'hidden' }}>
|
<div style={{ overflow: 'hidden' }}>
|
||||||
<div
|
<div
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import CopyFeatureToggle from '../../page/features/copy';
|
|
||||||
import { FeatureToggleListContainer } from '../feature/FeatureToggleList/FeatureToggleListContainer';
|
import { FeatureToggleListContainer } from '../feature/FeatureToggleList/FeatureToggleListContainer';
|
||||||
import { StrategyForm } from '../strategies/StrategyForm/StrategyForm';
|
import { StrategyForm } from '../strategies/StrategyForm/StrategyForm';
|
||||||
import { StrategyView } from '../../component/strategies/StrategyView/StrategyView';
|
import { StrategyView } from '../../component/strategies/StrategyView/StrategyView';
|
||||||
@ -45,6 +44,7 @@ import ContextList from '../context/ContextList/ContextList';
|
|||||||
import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView';
|
import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView';
|
||||||
import { CreateAddon } from '../addons/CreateAddon/CreateAddon';
|
import { CreateAddon } from '../addons/CreateAddon/CreateAddon';
|
||||||
import { EditAddon } from '../addons/EditAddon/EditAddon';
|
import { EditAddon } from '../addons/EditAddon/EditAddon';
|
||||||
|
import { CopyFeatureToggle } from '../feature/CopyFeature/CopyFeature';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
// Project
|
// Project
|
||||||
|
@ -6,7 +6,7 @@ import { useAuthPermissions } from '../../../hooks/api/getters/useAuth/useAuthPe
|
|||||||
|
|
||||||
interface IAccessProviderProps {
|
interface IAccessProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
permissions: IPermission[];
|
permissions?: IPermission[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(olav): Mock useAuth instead of using props.permissions in tests.
|
// TODO(olav): Mock useAuth instead of using props.permissions in tests.
|
||||||
@ -77,8 +77,8 @@ const checkPermission = (
|
|||||||
environment?: string
|
environment?: string
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!permission) {
|
if (!permission) {
|
||||||
console.warn(`Missing permission for AccessProvider: ${permission}`)
|
console.warn(`Missing permission for AccessProvider: ${permission}`);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.permission === ADMIN) {
|
if (p.permission === ADMIN) {
|
||||||
|
@ -28,7 +28,51 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-headerActions-9"
|
className="makeStyles-headerActions-9"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
aria-describedby={null}
|
||||||
|
className=""
|
||||||
|
onBlur={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
|
onMouseOver={[Function]}
|
||||||
|
onTouchEnd={[Function]}
|
||||||
|
onTouchStart={[Function]}
|
||||||
|
title="Add new strategy"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
|
||||||
|
data-test="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"
|
||||||
|
>
|
||||||
|
Add new strategy
|
||||||
|
<span
|
||||||
|
className="MuiButton-endIcon MuiButton-iconSizeMedium"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -91,11 +135,11 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
onMouseOver={[Function]}
|
onMouseOver={[Function]}
|
||||||
onTouchEnd={[Function]}
|
onTouchEnd={[Function]}
|
||||||
onTouchStart={[Function]}
|
onTouchStart={[Function]}
|
||||||
title="You don't have access to perform this operation"
|
title="Deprecate activation strategy"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
|
className="MuiButtonBase-root MuiIconButton-root"
|
||||||
disabled={true}
|
disabled={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onDragLeave={[Function]}
|
onDragLeave={[Function]}
|
||||||
@ -108,7 +152,7 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
onTouchEnd={[Function]}
|
onTouchEnd={[Function]}
|
||||||
onTouchMove={[Function]}
|
onTouchMove={[Function]}
|
||||||
onTouchStart={[Function]}
|
onTouchStart={[Function]}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -125,9 +169,56 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
className="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</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
|
||||||
|
aria-hidden={true}
|
||||||
|
className="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -162,7 +253,51 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-headerActions-9"
|
className="makeStyles-headerActions-9"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
aria-describedby={null}
|
||||||
|
className=""
|
||||||
|
onBlur={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
|
onMouseOver={[Function]}
|
||||||
|
onTouchEnd={[Function]}
|
||||||
|
onTouchStart={[Function]}
|
||||||
|
title="Add new strategy"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
|
||||||
|
data-test="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"
|
||||||
|
>
|
||||||
|
Add new strategy
|
||||||
|
<span
|
||||||
|
className="MuiButton-endIcon MuiButton-iconSizeMedium"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -225,11 +360,11 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
onMouseOver={[Function]}
|
onMouseOver={[Function]}
|
||||||
onTouchEnd={[Function]}
|
onTouchEnd={[Function]}
|
||||||
onTouchStart={[Function]}
|
onTouchStart={[Function]}
|
||||||
title="You don't have access to perform this operation"
|
title="Deprecate activation strategy"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
|
className="MuiButtonBase-root MuiIconButton-root"
|
||||||
disabled={true}
|
disabled={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onDragLeave={[Function]}
|
onDragLeave={[Function]}
|
||||||
@ -242,7 +377,7 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
onTouchEnd={[Function]}
|
onTouchEnd={[Function]}
|
||||||
onTouchMove={[Function]}
|
onTouchMove={[Function]}
|
||||||
onTouchStart={[Function]}
|
onTouchStart={[Function]}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -259,9 +394,56 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
className="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</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
|
||||||
|
aria-hidden={true}
|
||||||
|
className="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,6 @@ import { StrategiesList } from '../StrategiesList/StrategiesList';
|
|||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
|
||||||
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
import UIProvider from '../../providers/UIProvider/UIProvider';
|
import UIProvider from '../../providers/UIProvider/UIProvider';
|
||||||
|
|
||||||
@ -17,7 +16,7 @@ test('renders correctly with one strategy', () => {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider store={createFakeStore()}>
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
<StrategiesList
|
<StrategiesList
|
||||||
strategies={[strategy]}
|
strategies={[strategy]}
|
||||||
fetchStrategies={jest.fn()}
|
fetchStrategies={jest.fn()}
|
||||||
@ -44,9 +43,7 @@ test('renders correctly with one strategy without permissions', () => {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
store={createFakeStore([{ permission: ADMIN }])}
|
|
||||||
>
|
|
||||||
<StrategiesList
|
<StrategiesList
|
||||||
strategies={[strategy]}
|
strategies={[strategy]}
|
||||||
fetchStrategies={jest.fn()}
|
fetchStrategies={jest.fn()}
|
||||||
|
@ -5,7 +5,6 @@ import { useStyles } from './EditProfile.styles';
|
|||||||
import { useCommonStyles } from '../../../../common.styles';
|
import { useCommonStyles } from '../../../../common.styles';
|
||||||
import PasswordChecker from '../../common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
import PasswordChecker from '../../common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
||||||
import PasswordMatcher from '../../common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
|
import PasswordMatcher from '../../common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
|
||||||
import { headers } from '../../../../store/api-helper';
|
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender';
|
||||||
import useLoading from '../../../../hooks/useLoading';
|
import useLoading from '../../../../hooks/useLoading';
|
||||||
@ -17,6 +16,7 @@ import {
|
|||||||
} from '../../../../constants/statusCodes';
|
} from '../../../../constants/statusCodes';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import PasswordField from '../../../common/PasswordField/PasswordField';
|
import PasswordField from '../../../common/PasswordField/PasswordField';
|
||||||
|
import { headers } from '../../../../utils/api-utils';
|
||||||
|
|
||||||
interface IEditProfileProps {
|
interface IEditProfileProps {
|
||||||
setEditingProfile: React.Dispatch<React.SetStateAction<boolean>>;
|
setEditingProfile: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
ForbiddenError,
|
ForbiddenError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
} from '../../../../store/api-helper';
|
} from '../../../../utils/api-utils';
|
||||||
|
|
||||||
export const handleBadRequest = async (
|
export const handleBadRequest = async (
|
||||||
setErrors?: Dispatch<SetStateAction<{}>>,
|
setErrors?: Dispatch<SetStateAction<{}>>,
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
ForbiddenError,
|
ForbiddenError,
|
||||||
headers,
|
headers,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
} from '../../../../store/api-helper';
|
} from '../../../../utils/api-utils';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
|
|
||||||
interface IUseAPI {
|
interface IUseAPI {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { headers } from '../../../../utils/api-utils';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
|
|
||||||
type PasswordLogin = (
|
type PasswordLogin = (
|
||||||
@ -16,24 +17,36 @@ interface IUseAuthApiOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthApi = (): IUseAuthApiOutput => {
|
export const useAuthApi = (): IUseAuthApiOutput => {
|
||||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
const { makeRequest, errors, loading } = useAPI({
|
||||||
propagateErrors: true,
|
propagateErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const passwordAuth = (path: string, username: string, password: string) => {
|
const passwordAuth = (path: string, username: string, password: string) => {
|
||||||
const req = createRequest(ensureRelativePath(path), {
|
const req = {
|
||||||
method: 'POST',
|
caller: () => {
|
||||||
body: JSON.stringify({ username, password }),
|
return fetch(path, {
|
||||||
});
|
headers,
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
id: 'passwordAuth',
|
||||||
|
};
|
||||||
|
|
||||||
return makeRequest(req.caller, req.id);
|
return makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const emailAuth = (path: string, email: string) => {
|
const emailAuth = (path: string, email: string) => {
|
||||||
const req = createRequest(ensureRelativePath(path), {
|
const req = {
|
||||||
method: 'POST',
|
caller: () => {
|
||||||
body: JSON.stringify({ email }),
|
return fetch(path, {
|
||||||
});
|
headers,
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ email }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
id: 'emailAuth',
|
||||||
|
};
|
||||||
|
|
||||||
return makeRequest(req.caller, req.id);
|
return makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,8 @@ import { useState, useEffect } from 'react';
|
|||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
|
|
||||||
const useUiBootstrap = (options: SWRConfiguration = {}) => {
|
const useUiBootstrap = (options: SWRConfiguration = {}) => {
|
||||||
|
// The point of the bootstrap is to get multiple datasets in one call. Therefore,
|
||||||
|
// this needs to be refactored to seed other hooks with the correct data.
|
||||||
const BOOTSTRAP_CACHE_KEY = `api/admin/ui-bootstrap`;
|
const BOOTSTRAP_CACHE_KEY = `api/admin/ui-bootstrap`;
|
||||||
|
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
|
@ -5,58 +5,34 @@ import './app.css';
|
|||||||
|
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Route, BrowserRouter as Router } from 'react-router-dom';
|
import { Route, BrowserRouter as Router } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { ThemeProvider, CssBaseline } from '@material-ui/core';
|
import { ThemeProvider, CssBaseline } from '@material-ui/core';
|
||||||
import thunkMiddleware from 'redux-thunk';
|
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import { StylesProvider } from '@material-ui/core/styles';
|
import { StylesProvider } from '@material-ui/core/styles';
|
||||||
|
|
||||||
import mainTheme from './themes/main-theme';
|
import mainTheme from './themes/main-theme';
|
||||||
import store from './store';
|
import { App } from './component/App';
|
||||||
import App from './component/AppContainer';
|
|
||||||
import ScrollToTop from './component/scroll-to-top';
|
import ScrollToTop from './component/scroll-to-top';
|
||||||
import { writeWarning } from './security-logger';
|
|
||||||
import AccessProvider from './component/providers/AccessProvider/AccessProvider';
|
import AccessProvider from './component/providers/AccessProvider/AccessProvider';
|
||||||
import { getBasePath } from './utils/format-path';
|
import { getBasePath } from './utils/format-path';
|
||||||
import UIProvider from './component/providers/UIProvider/UIProvider';
|
import UIProvider from './component/providers/UIProvider/UIProvider';
|
||||||
|
|
||||||
let composeEnhancers;
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV !== 'production' &&
|
|
||||||
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
|
||||||
) {
|
|
||||||
composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
|
|
||||||
} else {
|
|
||||||
composeEnhancers = compose;
|
|
||||||
writeWarning();
|
|
||||||
}
|
|
||||||
|
|
||||||
const unleashStore = createStore(
|
|
||||||
store,
|
|
||||||
composeEnhancers(applyMiddleware(thunkMiddleware))
|
|
||||||
);
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={unleashStore}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<DndProvider backend={HTML5Backend}>
|
<UIProvider>
|
||||||
<UIProvider>
|
<AccessProvider>
|
||||||
<AccessProvider>
|
<Router basename={`${getBasePath()}`}>
|
||||||
<Router basename={`${getBasePath()}`}>
|
<ThemeProvider theme={mainTheme}>
|
||||||
<ThemeProvider theme={mainTheme}>
|
<StylesProvider injectFirst>
|
||||||
<StylesProvider injectFirst>
|
<CssBaseline />
|
||||||
<CssBaseline />
|
<ScrollToTop>
|
||||||
<ScrollToTop>
|
<Route path="/" component={App} />
|
||||||
<Route path="/" component={App} />
|
</ScrollToTop>
|
||||||
</ScrollToTop>
|
</StylesProvider>
|
||||||
</StylesProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</Router>
|
||||||
</Router>
|
</AccessProvider>
|
||||||
</AccessProvider>
|
</UIProvider>
|
||||||
</UIProvider>
|
</DndProvider>,
|
||||||
</DndProvider>
|
|
||||||
</Provider>,
|
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
);
|
);
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import CopyFeatureToggleForm from '../../component/feature/CopyFeature';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const render = ({ history, match: { params } }) => (
|
|
||||||
<CopyFeatureToggleForm
|
|
||||||
title="Copy feature toggle"
|
|
||||||
history={history}
|
|
||||||
copyToggleName={params.copyToggle}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
render.propTypes = {
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default render;
|
|
@ -1,62 +0,0 @@
|
|||||||
import {
|
|
||||||
START_FETCH_FEATURE_TOGGLES,
|
|
||||||
FETCH_FEATURE_TOGGLES_SUCCESS,
|
|
||||||
FETCH_FEATURE_TOGGLE_ERROR,
|
|
||||||
RESET_LOADING,
|
|
||||||
} from '../feature-toggle/actions';
|
|
||||||
|
|
||||||
const apiCalls = (
|
|
||||||
state = {
|
|
||||||
fetchTogglesState: {
|
|
||||||
loading: false,
|
|
||||||
success: false,
|
|
||||||
error: null,
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
action
|
|
||||||
) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case START_FETCH_FEATURE_TOGGLES:
|
|
||||||
if (state.fetchTogglesState.count > 0) return state;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchTogglesState: {
|
|
||||||
...state.fetchTogglesState,
|
|
||||||
loading: true,
|
|
||||||
success: false,
|
|
||||||
error: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case FETCH_FEATURE_TOGGLES_SUCCESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchTogglesState: {
|
|
||||||
...state.fetchTogglesState,
|
|
||||||
loading: false,
|
|
||||||
success: true,
|
|
||||||
error: null,
|
|
||||||
count: (state.fetchTogglesState.count += 1),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case FETCH_FEATURE_TOGGLE_ERROR:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchTogglesState: {
|
|
||||||
...state.fetchTogglesState,
|
|
||||||
loading: false,
|
|
||||||
success: false,
|
|
||||||
error: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case RESET_LOADING:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchTogglesState: { ...state.fetchTogglesState, count: 0 },
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default apiCalls;
|
|
@ -1,110 +0,0 @@
|
|||||||
const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
|
|
||||||
|
|
||||||
function extractJoiMsg(body) {
|
|
||||||
return body.details.length > 0
|
|
||||||
? body.details[0].message
|
|
||||||
: defaultErrorMessage;
|
|
||||||
}
|
|
||||||
function extractLegacyMsg(body) {
|
|
||||||
return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServiceError extends Error {
|
|
||||||
constructor(statusCode = 500) {
|
|
||||||
super(defaultErrorMessage);
|
|
||||||
this.name = 'ServiceError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthenticationError extends Error {
|
|
||||||
constructor(statusCode, body) {
|
|
||||||
super('Authentication required');
|
|
||||||
this.name = 'AuthenticationError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ForbiddenError extends Error {
|
|
||||||
constructor(statusCode, body = {}) {
|
|
||||||
super(
|
|
||||||
body.details?.length > 0
|
|
||||||
? body.details[0].message
|
|
||||||
: 'You cannot perform this action'
|
|
||||||
);
|
|
||||||
this.name = 'ForbiddenError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BadRequestError extends Error {
|
|
||||||
constructor(statusCode, body = {}) {
|
|
||||||
super(
|
|
||||||
body.details?.length > 0 ? body.details[0].message : 'Bad request'
|
|
||||||
);
|
|
||||||
this.name = 'BadRequestError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotFoundError extends Error {
|
|
||||||
constructor(statusCode) {
|
|
||||||
super(
|
|
||||||
'The requested resource could not be found but may be available in the future'
|
|
||||||
);
|
|
||||||
this.name = 'NotFoundError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function throwIfNotSuccess(response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
response
|
|
||||||
.json()
|
|
||||||
.then(body =>
|
|
||||||
reject(new AuthenticationError(response.status, body))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else if (response.status === 403) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
response
|
|
||||||
.json()
|
|
||||||
.then(body =>
|
|
||||||
reject(new ForbiddenError(response.status, body))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else if (response.status === 404) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
reject(new NotFoundError(response.status));
|
|
||||||
});
|
|
||||||
} else if (response.status > 399 && response.status < 501) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
response
|
|
||||||
.json()
|
|
||||||
.then(body => {
|
|
||||||
const errorMsg =
|
|
||||||
body && body.isJoi
|
|
||||||
? extractJoiMsg(body)
|
|
||||||
: extractLegacyMsg(body);
|
|
||||||
let error = new Error(errorMsg);
|
|
||||||
error.statusCode = response.status;
|
|
||||||
reject(error);
|
|
||||||
})
|
|
||||||
.catch(() => reject(new Error(defaultErrorMessage)));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new ServiceError(response.status));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.resolve(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
export const MUTE_ERRORS = 'MUTE_ERRORS';
|
|
||||||
export const MUTE_ERROR = 'MUTE_ERROR';
|
|
||||||
|
|
||||||
export const muteErrors = () => ({ type: MUTE_ERRORS });
|
|
||||||
|
|
||||||
export const muteError = error => ({ type: MUTE_ERROR, error });
|
|
@ -1,57 +0,0 @@
|
|||||||
import { List, Map as $Map } from 'immutable';
|
|
||||||
import { MUTE_ERROR } from './actions';
|
|
||||||
import {
|
|
||||||
ERROR_FETCH_FEATURE_TOGGLES,
|
|
||||||
ERROR_CREATING_FEATURE_TOGGLE,
|
|
||||||
ERROR_REMOVE_FEATURE_TOGGLE,
|
|
||||||
ERROR_UPDATE_FEATURE_TOGGLE,
|
|
||||||
UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
|
||||||
UPDATE_FEATURE_TOGGLE,
|
|
||||||
} from '../feature-toggle/actions';
|
|
||||||
|
|
||||||
import { FORBIDDEN } from '../util';
|
|
||||||
|
|
||||||
const debug = require('debug')('unleash:error-store');
|
|
||||||
|
|
||||||
function getInitState() {
|
|
||||||
return new $Map({
|
|
||||||
list: new List(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addErrorIfNotAlreadyInList(state, error) {
|
|
||||||
debug('Got error', error);
|
|
||||||
if (state.get('list').indexOf(error) < 0) {
|
|
||||||
return state.update('list', list => list.push(error));
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const strategies = (state = getInitState(), action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case ERROR_CREATING_FEATURE_TOGGLE:
|
|
||||||
case ERROR_REMOVE_FEATURE_TOGGLE:
|
|
||||||
case ERROR_FETCH_FEATURE_TOGGLES:
|
|
||||||
case ERROR_UPDATE_FEATURE_TOGGLE:
|
|
||||||
return addErrorIfNotAlreadyInList(state, action.error.message);
|
|
||||||
case FORBIDDEN:
|
|
||||||
return addErrorIfNotAlreadyInList(
|
|
||||||
state,
|
|
||||||
action.error.message || '403 Forbidden'
|
|
||||||
);
|
|
||||||
case MUTE_ERROR:
|
|
||||||
return state.update('list', list =>
|
|
||||||
list.remove(list.indexOf(action.error))
|
|
||||||
);
|
|
||||||
// This reducer controls not only errors, but general information and success
|
|
||||||
// messages. This can be a little misleading, given it's naming. We should
|
|
||||||
// revise how this works in a future update.
|
|
||||||
case UPDATE_FEATURE_TOGGLE:
|
|
||||||
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
|
|
||||||
return addErrorIfNotAlreadyInList(state, action.info);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default strategies;
|
|
@ -1,235 +0,0 @@
|
|||||||
import api from './api';
|
|
||||||
import { dispatchError } from '../util';
|
|
||||||
import { MUTE_ERROR } from '../error/actions';
|
|
||||||
const debug = require('debug')('unleash:feature-actions');
|
|
||||||
|
|
||||||
export const ADD_FEATURE_TOGGLE = 'ADD_FEATURE_TOGGLE';
|
|
||||||
export const COPY_FEATURE_TOGGLE = 'COPY_FEATURE_TOGGLE';
|
|
||||||
export const REMOVE_FEATURE_TOGGLE = 'REMOVE_FEATURE_TOGGLE';
|
|
||||||
export const UPDATE_FEATURE_TOGGLE = 'UPDATE_FEATURE_TOGGLE';
|
|
||||||
export const TOGGLE_FEATURE_TOGGLE = 'TOGGLE_FEATURE_TOGGLE';
|
|
||||||
export const START_FETCH_FEATURE_TOGGLES = 'START_FETCH_FEATURE_TOGGLES';
|
|
||||||
export const START_UPDATE_FEATURE_TOGGLE = 'START_UPDATE_FEATURE_TOGGLE';
|
|
||||||
export const START_CREATE_FEATURE_TOGGLE = 'START_CREATE_FEATURE_TOGGLE';
|
|
||||||
export const FETCH_FEATURE_TOGGLES_SUCCESS = 'FETCH_FEATURE_TOGGLES_SUCCESS';
|
|
||||||
export const START_REMOVE_FEATURE_TOGGLE = 'START_REMOVE_FEATURE_TOGGLE';
|
|
||||||
export const RECEIVE_FEATURE_TOGGLES = 'RECEIVE_FEATURE_TOGGLES';
|
|
||||||
export const ERROR_FETCH_FEATURE_TOGGLES = 'ERROR_FETCH_FEATURE_TOGGLES';
|
|
||||||
export const ERROR_CREATING_FEATURE_TOGGLE = 'ERROR_CREATING_FEATURE_TOGGLE';
|
|
||||||
export const ERROR_UPDATE_FEATURE_TOGGLE = 'ERROR_UPDATE_FEATURE_TOGGLE';
|
|
||||||
export const ERROR_REMOVE_FEATURE_TOGGLE = 'ERROR_REMOVE_FEATURE_TOGGLE';
|
|
||||||
export const UPDATE_FEATURE_TOGGLE_STRATEGIES =
|
|
||||||
'UPDATE_FEATURE_TOGGLE_STRATEGIES';
|
|
||||||
export const FETCH_FEATURE_TOGGLE_ERROR = 'FETCH_FEATURE_TOGGLE_ERROR';
|
|
||||||
export const RESET_LOADING = 'RESET_LOADING';
|
|
||||||
|
|
||||||
export const RECEIVE_FEATURE_TOGGLE = 'RECEIVE_FEATURE_TOGGLE';
|
|
||||||
export const START_FETCH_FEATURE_TOGGLE = 'START_FETCH_FEATURE_TOGGLE';
|
|
||||||
export const ERROR_FETCH_FEATURE_TOGGLE = 'START_FETCH_FEATURE_TOGGLE';
|
|
||||||
|
|
||||||
export function toggleFeature(enable, name) {
|
|
||||||
debug('Toggle feature toggle ', name);
|
|
||||||
return dispatch => {
|
|
||||||
dispatch(requestToggleFeatureToggle(enable, name));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setStale(stale, name) {
|
|
||||||
debug('Set stale property on feature toggle ', name);
|
|
||||||
return dispatch => {
|
|
||||||
dispatch(requestSetStaleFeatureToggle(stale, name));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editFeatureToggle(featureToggle) {
|
|
||||||
debug('Update feature toggle ', featureToggle);
|
|
||||||
return dispatch => {
|
|
||||||
dispatch(requestUpdateFeatureToggle(featureToggle));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function receiveFeatureToggles(json) {
|
|
||||||
debug('reviced feature toggles', json);
|
|
||||||
return {
|
|
||||||
type: RECEIVE_FEATURE_TOGGLES,
|
|
||||||
featureToggles: json.features.map(features => features),
|
|
||||||
receivedAt: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function receiveFeatureToggle(featureToggle) {
|
|
||||||
debug('reviced feature toggle', featureToggle);
|
|
||||||
return {
|
|
||||||
type: RECEIVE_FEATURE_TOGGLE,
|
|
||||||
featureToggle,
|
|
||||||
receivedAt: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchFeatureToggles() {
|
|
||||||
debug('Start fetching feature toggles');
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_FETCH_FEATURE_TOGGLES });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.fetchAll()
|
|
||||||
.then(json => {
|
|
||||||
dispatch({ type: FETCH_FEATURE_TOGGLES_SUCCESS });
|
|
||||||
dispatch(receiveFeatureToggles(json));
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
dispatch({ type: FETCH_FEATURE_TOGGLE_ERROR });
|
|
||||||
dispatchError(dispatch, ERROR_FETCH_FEATURE_TOGGLES);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchFeatureToggle(name) {
|
|
||||||
debug('Start fetching feature toggles');
|
|
||||||
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_FETCH_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.fetchFeatureToggle(name)
|
|
||||||
.then(json => dispatch(receiveFeatureToggle(json)))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_FETCH_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createFeatureToggles(featureToggle) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_CREATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.create(featureToggle)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(createdFeature => {
|
|
||||||
dispatch({
|
|
||||||
type: ADD_FEATURE_TOGGLE,
|
|
||||||
featureToggle: createdFeature,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
dispatchError(dispatch, ERROR_CREATING_FEATURE_TOGGLE);
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestToggleFeatureToggle(enable, name) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.toggle(enable, name)
|
|
||||||
.then(() => dispatch({ type: TOGGLE_FEATURE_TOGGLE, name }))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestSetStaleFeatureToggle(stale, name) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.setStale(stale, name)
|
|
||||||
.then(featureToggle => {
|
|
||||||
const info = `${name} marked as ${stale ? 'Stale' : 'Active'}.`;
|
|
||||||
setTimeout(
|
|
||||||
() => dispatch({ type: MUTE_ERROR, error: info }),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info });
|
|
||||||
})
|
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestUpdateFeatureToggle(featureToggle) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.update(featureToggle)
|
|
||||||
.then(() => {
|
|
||||||
const info = `${featureToggle.name} successfully updated!`;
|
|
||||||
setTimeout(
|
|
||||||
() => dispatch({ type: MUTE_ERROR, error: info }),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info });
|
|
||||||
})
|
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestUpdateFeatureToggleStrategies(
|
|
||||||
featureToggle,
|
|
||||||
newStrategies
|
|
||||||
) {
|
|
||||||
return dispatch => {
|
|
||||||
featureToggle.strategies = newStrategies;
|
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.update(featureToggle)
|
|
||||||
.then(() => {
|
|
||||||
const info = `${featureToggle.name} successfully updated!`;
|
|
||||||
setTimeout(
|
|
||||||
() => dispatch({ type: MUTE_ERROR, error: info }),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
return dispatch({
|
|
||||||
type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
|
||||||
featureToggle,
|
|
||||||
info,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestUpdateFeatureToggleVariants(featureToggle, newVariants) {
|
|
||||||
return dispatch => {
|
|
||||||
const newFeature = { ...featureToggle };
|
|
||||||
newFeature.variants = newVariants;
|
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.update(newFeature)
|
|
||||||
.then(() => {
|
|
||||||
const info = `${newFeature.name} successfully updated!`;
|
|
||||||
setTimeout(
|
|
||||||
() => dispatch({ type: MUTE_ERROR, error: info }),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
return dispatch({
|
|
||||||
type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
|
||||||
featureToggle: newFeature,
|
|
||||||
info,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE);
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeFeatureToggle(featureToggleName) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: START_REMOVE_FEATURE_TOGGLE });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.remove(featureToggleName)
|
|
||||||
.then(() =>
|
|
||||||
dispatch({ type: REMOVE_FEATURE_TOGGLE, featureToggleName })
|
|
||||||
)
|
|
||||||
.catch(dispatchError(dispatch, ERROR_REMOVE_FEATURE_TOGGLE));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateName(featureToggleName) {
|
|
||||||
return api.validate({ name: featureToggleName });
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
import { formatApiPath } from '../../utils/format-path';
|
|
||||||
import { throwIfNotSuccess, headers } from '../api-helper';
|
|
||||||
|
|
||||||
const URI = formatApiPath('api/admin/features');
|
|
||||||
|
|
||||||
function validateToggle(featureToggle) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (
|
|
||||||
!featureToggle.strategies ||
|
|
||||||
featureToggle.strategies.length === 0
|
|
||||||
) {
|
|
||||||
reject(new Error('You must add at least one activation strategy'));
|
|
||||||
} else {
|
|
||||||
resolve(featureToggle);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchAll() {
|
|
||||||
return fetch(URI, { credentials: 'include' })
|
|
||||||
.then(throwIfNotSuccess)
|
|
||||||
.then(response => response.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchFeatureToggle(name) {
|
|
||||||
return fetch(`${URI}/${name}`, { credentials: 'include' })
|
|
||||||
.then(throwIfNotSuccess)
|
|
||||||
.then(response => response.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
async function create(featureToggle) {
|
|
||||||
await validateToggle(featureToggle);
|
|
||||||
|
|
||||||
return fetch(URI, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(featureToggle),
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(featureToggle) {
|
|
||||||
return fetch(`${URI}/validate`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(featureToggle),
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(featureToggle) {
|
|
||||||
return validateToggle(featureToggle)
|
|
||||||
.then(() => {
|
|
||||||
return fetch(`${URI}/${featureToggle.name}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(featureToggle),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(enable, name) {
|
|
||||||
const action = enable ? 'on' : 'off';
|
|
||||||
return fetch(`${URI}/${name}/toggle/${action}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStale(isStale, name) {
|
|
||||||
const action = isStale ? 'on' : 'off';
|
|
||||||
return fetch(`${URI}/${name}/stale/${action}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
.then(throwIfNotSuccess)
|
|
||||||
.then(response => response.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(featureToggleName) {
|
|
||||||
return fetch(`${URI}/${featureToggleName}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetchAll,
|
|
||||||
fetchFeatureToggle,
|
|
||||||
create,
|
|
||||||
validate,
|
|
||||||
update,
|
|
||||||
toggle,
|
|
||||||
setStale,
|
|
||||||
remove,
|
|
||||||
};
|
|
@ -1,68 +0,0 @@
|
|||||||
import { List, Map as $Map } from 'immutable';
|
|
||||||
import {
|
|
||||||
ADD_FEATURE_TOGGLE,
|
|
||||||
RECEIVE_FEATURE_TOGGLES,
|
|
||||||
RECEIVE_FEATURE_TOGGLE,
|
|
||||||
UPDATE_FEATURE_TOGGLE,
|
|
||||||
UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
|
||||||
REMOVE_FEATURE_TOGGLE,
|
|
||||||
TOGGLE_FEATURE_TOGGLE,
|
|
||||||
} from './actions';
|
|
||||||
|
|
||||||
const debug = require('debug')('unleash:feature-store');
|
|
||||||
|
|
||||||
const features = (state = new List([]), action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case ADD_FEATURE_TOGGLE:
|
|
||||||
debug(ADD_FEATURE_TOGGLE, action);
|
|
||||||
return state.push(new $Map(action.featureToggle));
|
|
||||||
case REMOVE_FEATURE_TOGGLE:
|
|
||||||
debug(REMOVE_FEATURE_TOGGLE, action);
|
|
||||||
return state.filter(
|
|
||||||
toggle => toggle.get('name') !== action.featureToggleName
|
|
||||||
);
|
|
||||||
case TOGGLE_FEATURE_TOGGLE:
|
|
||||||
debug(TOGGLE_FEATURE_TOGGLE, action);
|
|
||||||
return state.map(toggle => {
|
|
||||||
if (toggle.get('name') === action.name) {
|
|
||||||
return toggle.set('enabled', !toggle.get('enabled'));
|
|
||||||
} else {
|
|
||||||
return toggle;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
|
|
||||||
debug(UPDATE_FEATURE_TOGGLE_STRATEGIES, action);
|
|
||||||
return state.map(toggle => {
|
|
||||||
if (toggle.get('name') === action.featureToggle.name) {
|
|
||||||
return new $Map(action.featureToggle);
|
|
||||||
} else {
|
|
||||||
return toggle;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
case UPDATE_FEATURE_TOGGLE:
|
|
||||||
debug(UPDATE_FEATURE_TOGGLE, action);
|
|
||||||
return state.map(toggle => {
|
|
||||||
if (toggle.get('name') === action.featureToggle.name) {
|
|
||||||
return new $Map(action.featureToggle);
|
|
||||||
} else {
|
|
||||||
return toggle;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
case RECEIVE_FEATURE_TOGGLE:
|
|
||||||
debug(RECEIVE_FEATURE_TOGGLE, action);
|
|
||||||
return state.map(toggle => {
|
|
||||||
if (toggle.get('name') === action.featureToggle.name) {
|
|
||||||
return new $Map(action.featureToggle);
|
|
||||||
} else {
|
|
||||||
return toggle;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
case RECEIVE_FEATURE_TOGGLES:
|
|
||||||
debug(RECEIVE_FEATURE_TOGGLES, action);
|
|
||||||
return new List(action.featureToggles.map($Map));
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default features;
|
|
@ -1,12 +0,0 @@
|
|||||||
import { combineReducers } from 'redux';
|
|
||||||
import features from './feature-toggle';
|
|
||||||
import error from './error';
|
|
||||||
import apiCalls from './api-calls';
|
|
||||||
|
|
||||||
const unleashStore = combineReducers({
|
|
||||||
features,
|
|
||||||
error,
|
|
||||||
apiCalls,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default unleashStore;
|
|
@ -1,13 +0,0 @@
|
|||||||
import api from './api';
|
|
||||||
import { dispatchError } from '../util';
|
|
||||||
|
|
||||||
export const RECEIVE_BOOTSTRAP = 'RECEIVE_CONFIG';
|
|
||||||
export const ERROR_RECEIVE_BOOTSTRAP = 'ERROR_RECEIVE_CONFIG';
|
|
||||||
|
|
||||||
export function fetchUiBootstrap() {
|
|
||||||
return dispatch =>
|
|
||||||
api
|
|
||||||
.fetchUIBootstrap()
|
|
||||||
.then(json => {})
|
|
||||||
.catch(dispatchError(dispatch, ERROR_RECEIVE_BOOTSTRAP));
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
import { formatApiPath } from '../../utils/format-path';
|
|
||||||
import { throwIfNotSuccess } from '../api-helper';
|
|
||||||
|
|
||||||
const URI = formatApiPath('api/admin/ui-bootstrap');
|
|
||||||
|
|
||||||
function fetchUIBootstrap() {
|
|
||||||
return fetch(URI, { credentials: 'include' })
|
|
||||||
.then(throwIfNotSuccess)
|
|
||||||
.then(response => response.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetchUIBootstrap,
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
export const AUTH_REQUIRED = 'AUTH_REQUIRED';
|
|
||||||
export const FORBIDDEN = 'FORBIDDEN';
|
|
||||||
|
|
||||||
export function dispatchError(dispatch, type) {
|
|
||||||
return error => {
|
|
||||||
switch (error.statusCode) {
|
|
||||||
case 401:
|
|
||||||
dispatch({ type: AUTH_REQUIRED, error, receivedAt: Date.now() });
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
dispatch({ type: FORBIDDEN, error, receivedAt: Date.now() });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
dispatch({ type, error, receivedAt: Date.now() });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const success = (dispatch, type, val) => value => dispatch({ type, value: val ? val : value });
|
|
67
frontend/src/utils/api-utils.ts
Normal file
67
frontend/src/utils/api-utils.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
|
||||||
|
|
||||||
|
export const headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractJoiMsg = (body: any) => {
|
||||||
|
return body.details.length > 0
|
||||||
|
? body.details[0].message
|
||||||
|
: defaultErrorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractLegacyMsg = (body: any[]) => {
|
||||||
|
return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ServiceError extends Error {
|
||||||
|
constructor(statusCode = 500) {
|
||||||
|
super(defaultErrorMessage);
|
||||||
|
this.name = 'ServiceError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthenticationError extends Error {
|
||||||
|
constructor(statusCode, body) {
|
||||||
|
super('Authentication required');
|
||||||
|
this.name = 'AuthenticationError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ForbiddenError extends Error {
|
||||||
|
constructor(statusCode, body = {}) {
|
||||||
|
super(
|
||||||
|
body.details?.length > 0
|
||||||
|
? body.details[0].message
|
||||||
|
: 'You cannot perform this action'
|
||||||
|
);
|
||||||
|
this.name = 'ForbiddenError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadRequestError extends Error {
|
||||||
|
constructor(statusCode, body = {}) {
|
||||||
|
super(
|
||||||
|
body.details?.length > 0 ? body.details[0].message : 'Bad request'
|
||||||
|
);
|
||||||
|
this.name = 'BadRequestError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotFoundError extends Error {
|
||||||
|
constructor(statusCode) {
|
||||||
|
super(
|
||||||
|
'The requested resource could not be found but may be available in the future'
|
||||||
|
);
|
||||||
|
this.name = 'NotFoundError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@
|
|||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.0"
|
||||||
|
|
||||||
"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
|
"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
|
||||||
version "7.13.14"
|
version "7.13.14"
|
||||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz"
|
resolved "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz"
|
||||||
integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==
|
integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==
|
||||||
@ -1198,14 +1198,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||||
version "7.13.10"
|
version "7.13.10"
|
||||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz"
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz"
|
||||||
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
|
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.13", "@babel/runtime@^7.15.4":
|
"@babel/runtime@^7.12.13":
|
||||||
version "7.15.4"
|
version "7.15.4"
|
||||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz"
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz"
|
||||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||||
@ -2012,14 +2012,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||||
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
|
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
|
||||||
|
|
||||||
"@types/hoist-non-react-statics@^3.3.0":
|
|
||||||
version "3.3.1"
|
|
||||||
resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
|
|
||||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
|
||||||
dependencies:
|
|
||||||
"@types/react" "*"
|
|
||||||
hoist-non-react-statics "^3.3.0"
|
|
||||||
|
|
||||||
"@types/html-minifier-terser@^5.0.0":
|
"@types/html-minifier-terser@^5.0.0":
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz"
|
||||||
@ -2148,16 +2140,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-redux@^7.1.20":
|
|
||||||
version "7.1.20"
|
|
||||||
resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz"
|
|
||||||
integrity sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==
|
|
||||||
dependencies:
|
|
||||||
"@types/hoist-non-react-statics" "^3.3.0"
|
|
||||||
"@types/react" "*"
|
|
||||||
hoist-non-react-statics "^3.3.0"
|
|
||||||
redux "^4.0.0"
|
|
||||||
|
|
||||||
"@types/react-router-dom@5.3.3":
|
"@types/react-router-dom@5.3.3":
|
||||||
version "5.3.3"
|
version "5.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
|
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
|
||||||
@ -2789,7 +2771,7 @@ anymatch@^3.0.3, anymatch@~3.1.1:
|
|||||||
|
|
||||||
anymatch@~3.1.2:
|
anymatch@~3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz"
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
@ -2866,11 +2848,6 @@ array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3:
|
|||||||
get-intrinsic "^1.1.1"
|
get-intrinsic "^1.1.1"
|
||||||
is-string "^1.0.5"
|
is-string "^1.0.5"
|
||||||
|
|
||||||
array-move@3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.npmjs.org/array-move/-/array-move-3.0.1.tgz"
|
|
||||||
integrity sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==
|
|
||||||
|
|
||||||
array-union@^1.0.1:
|
array-union@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
|
||||||
@ -3721,9 +3698,9 @@ check-types@^11.1.1:
|
|||||||
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
|
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
|
||||||
|
|
||||||
"chokidar@>=3.0.0 <4.0.0":
|
"chokidar@>=3.0.0 <4.0.0":
|
||||||
version "3.5.2"
|
version "3.5.3"
|
||||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||||
dependencies:
|
dependencies:
|
||||||
anymatch "~3.1.2"
|
anymatch "~3.1.2"
|
||||||
braces "~3.0.2"
|
braces "~3.0.2"
|
||||||
@ -4151,7 +4128,7 @@ core-js@^2.4.0:
|
|||||||
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz"
|
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz"
|
||||||
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||||
|
|
||||||
core-js@^3.0.0, core-js@^3.6.5:
|
core-js@^3.6.5:
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.npmjs.org/core-js/-/core-js-3.10.0.tgz"
|
resolved "https://registry.npmjs.org/core-js/-/core-js-3.10.0.tgz"
|
||||||
integrity sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ==
|
integrity sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ==
|
||||||
@ -5702,22 +5679,6 @@ fd-slicer@~1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pend "~1.2.0"
|
pend "~1.2.0"
|
||||||
|
|
||||||
fetch-mock@9.11.0:
|
|
||||||
version "9.11.0"
|
|
||||||
resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz"
|
|
||||||
integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==
|
|
||||||
dependencies:
|
|
||||||
"@babel/core" "^7.0.0"
|
|
||||||
"@babel/runtime" "^7.0.0"
|
|
||||||
core-js "^3.0.0"
|
|
||||||
debug "^4.1.1"
|
|
||||||
glob-to-regexp "^0.4.0"
|
|
||||||
is-subset "^0.1.1"
|
|
||||||
lodash.isequal "^4.5.0"
|
|
||||||
path-to-regexp "^2.2.1"
|
|
||||||
querystring "^0.2.0"
|
|
||||||
whatwg-url "^6.5.0"
|
|
||||||
|
|
||||||
figgy-pudding@^3.5.1:
|
figgy-pudding@^3.5.1:
|
||||||
version "3.5.2"
|
version "3.5.2"
|
||||||
resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz"
|
resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz"
|
||||||
@ -6078,11 +6039,6 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0, glob-parent@~5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
glob-to-regexp@^0.4.0:
|
|
||||||
version "0.4.1"
|
|
||||||
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz"
|
|
||||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
|
||||||
|
|
||||||
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||||
version "7.1.6"
|
version "7.1.6"
|
||||||
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
|
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
|
||||||
@ -6319,7 +6275,7 @@ hmac-drbg@^1.0.1:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
|
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@ -6565,9 +6521,9 @@ immer@8.0.1:
|
|||||||
resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz"
|
resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz"
|
||||||
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
|
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
|
||||||
|
|
||||||
immutable@4.0.0, immutable@^4.0.0:
|
immutable@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||||
|
|
||||||
import-cwd@^2.0.0:
|
import-cwd@^2.0.0:
|
||||||
@ -7045,11 +7001,6 @@ is-string@^1.0.5:
|
|||||||
resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz"
|
||||||
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
|
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
|
||||||
|
|
||||||
is-subset@^0.1.1:
|
|
||||||
version "0.1.1"
|
|
||||||
resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz"
|
|
||||||
integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
|
|
||||||
|
|
||||||
is-svg@^3.0.0:
|
is-svg@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz"
|
||||||
@ -8026,16 +7977,6 @@ lodash.flow@3.5.0:
|
|||||||
resolved "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz"
|
resolved "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz"
|
||||||
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
|
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
|
||||||
|
|
||||||
lodash.isequal@^4.5.0:
|
|
||||||
version "4.5.0"
|
|
||||||
resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz"
|
|
||||||
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
|
||||||
version "4.0.6"
|
|
||||||
resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz"
|
|
||||||
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
|
||||||
|
|
||||||
lodash.memoize@^4.1.2:
|
lodash.memoize@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
|
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
|
||||||
@ -8046,11 +7987,6 @@ lodash.once@^4.1.1:
|
|||||||
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz"
|
||||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||||
|
|
||||||
lodash.sortby@^4.7.0:
|
|
||||||
version "4.7.0"
|
|
||||||
resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz"
|
|
||||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
|
||||||
|
|
||||||
lodash.template@^4.5.0:
|
lodash.template@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz"
|
resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz"
|
||||||
@ -8549,13 +8485,6 @@ no-case@^3.0.4:
|
|||||||
lower-case "^2.0.2"
|
lower-case "^2.0.2"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
node-fetch@2.6.7:
|
|
||||||
version "2.6.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
|
||||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
|
||||||
dependencies:
|
|
||||||
whatwg-url "^5.0.0"
|
|
||||||
|
|
||||||
node-forge@^0.10.0:
|
node-forge@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz"
|
resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz"
|
||||||
@ -9111,11 +9040,6 @@ path-to-regexp@^1.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isarray "0.0.1"
|
isarray "0.0.1"
|
||||||
|
|
||||||
path-to-regexp@^2.2.1:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz"
|
|
||||||
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
|
|
||||||
|
|
||||||
path-type@^2.0.0:
|
path-type@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
|
||||||
@ -10327,7 +10251,7 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
|
"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
@ -10343,18 +10267,6 @@ react-outside-click-handler@1.3.0:
|
|||||||
object.values "^1.1.0"
|
object.values "^1.1.0"
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-redux@7.2.6:
|
|
||||||
version "7.2.6"
|
|
||||||
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz"
|
|
||||||
integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.15.4"
|
|
||||||
"@types/react-redux" "^7.1.20"
|
|
||||||
hoist-non-react-statics "^3.3.2"
|
|
||||||
loose-envify "^1.4.0"
|
|
||||||
prop-types "^15.7.2"
|
|
||||||
react-is "^17.0.2"
|
|
||||||
|
|
||||||
react-refresh@^0.8.3:
|
react-refresh@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
|
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
|
||||||
@ -10564,7 +10476,7 @@ readdirp@~3.5.0:
|
|||||||
|
|
||||||
readdirp@~3.6.0:
|
readdirp@~3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
@ -10584,38 +10496,6 @@ redent@^3.0.0:
|
|||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
redux-devtools-extension@2.13.9:
|
|
||||||
version "2.13.9"
|
|
||||||
resolved "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz"
|
|
||||||
integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==
|
|
||||||
|
|
||||||
redux-mock-store@1.5.4:
|
|
||||||
version "1.5.4"
|
|
||||||
resolved "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz"
|
|
||||||
integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==
|
|
||||||
dependencies:
|
|
||||||
lodash.isplainobject "^4.0.6"
|
|
||||||
|
|
||||||
redux-thunk@2.4.1:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
|
|
||||||
integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
|
|
||||||
|
|
||||||
redux@4.1.2:
|
|
||||||
version "4.1.2"
|
|
||||||
resolved "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz"
|
|
||||||
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.9.2"
|
|
||||||
|
|
||||||
redux@^4.0.0:
|
|
||||||
version "4.0.5"
|
|
||||||
resolved "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz"
|
|
||||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
|
||||||
dependencies:
|
|
||||||
loose-envify "^1.4.0"
|
|
||||||
symbol-observable "^1.2.0"
|
|
||||||
|
|
||||||
redux@^4.1.1:
|
redux@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz"
|
||||||
@ -11411,21 +11291,16 @@ source-list-map@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
|
||||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||||
|
|
||||||
"source-map-js@>=0.6.2 <2.0.0":
|
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||||
version "1.0.1"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
|
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||||
|
|
||||||
source-map-js@^0.6.2:
|
source-map-js@^0.6.2:
|
||||||
version "0.6.2"
|
version "0.6.2"
|
||||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz"
|
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz"
|
||||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||||
|
|
||||||
source-map-js@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
|
||||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
|
||||||
|
|
||||||
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
|
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz"
|
resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz"
|
||||||
@ -11866,7 +11741,7 @@ swr@1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.1.tgz#c21a4fe2139cb1c4630450589b5b5add947a9d41"
|
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.1.tgz#c21a4fe2139cb1c4630450589b5b5add947a9d41"
|
||||||
integrity sha512-1cuWXqJqXcFwbgONGCY4PHZ8v05009JdHsC3CIC6u7d00kgbMswNr1sHnnhseOBxtzVqcCNpOHEgVDciRer45w==
|
integrity sha512-1cuWXqJqXcFwbgONGCY4PHZ8v05009JdHsC3CIC6u7d00kgbMswNr1sHnnhseOBxtzVqcCNpOHEgVDciRer45w==
|
||||||
|
|
||||||
symbol-observable@1.2.0, symbol-observable@^1.2.0:
|
symbol-observable@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz"
|
||||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||||
@ -12123,13 +11998,6 @@ tough-cookie@^4.0.0:
|
|||||||
punycode "^2.1.1"
|
punycode "^2.1.1"
|
||||||
universalify "^0.1.2"
|
universalify "^0.1.2"
|
||||||
|
|
||||||
tr46@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz"
|
|
||||||
integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
|
|
||||||
dependencies:
|
|
||||||
punycode "^2.1.0"
|
|
||||||
|
|
||||||
tr46@^2.0.2:
|
tr46@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz"
|
resolved "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz"
|
||||||
@ -12137,11 +12005,6 @@ tr46@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.1"
|
punycode "^2.1.1"
|
||||||
|
|
||||||
tr46@~0.0.3:
|
|
||||||
version "0.0.3"
|
|
||||||
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
|
||||||
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
|
|
||||||
|
|
||||||
tryer@^1.0.1:
|
tryer@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz"
|
||||||
@ -12588,16 +12451,6 @@ web-vitals@2.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
|
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
|
||||||
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
|
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
|
||||||
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
|
|
||||||
|
|
||||||
webidl-conversions@^4.0.2:
|
|
||||||
version "4.0.2"
|
|
||||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz"
|
|
||||||
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
|
||||||
|
|
||||||
webidl-conversions@^5.0.0:
|
webidl-conversions@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz"
|
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz"
|
||||||
@ -12780,23 +12633,6 @@ whatwg-mimetype@^2.3.0:
|
|||||||
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
|
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
|
||||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||||
|
|
||||||
whatwg-url@^5.0.0:
|
|
||||||
version "5.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
|
|
||||||
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
|
|
||||||
dependencies:
|
|
||||||
tr46 "~0.0.3"
|
|
||||||
webidl-conversions "^3.0.0"
|
|
||||||
|
|
||||||
whatwg-url@^6.5.0:
|
|
||||||
version "6.5.0"
|
|
||||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz"
|
|
||||||
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
|
|
||||||
dependencies:
|
|
||||||
lodash.sortby "^4.7.0"
|
|
||||||
tr46 "^1.0.1"
|
|
||||||
webidl-conversions "^4.0.2"
|
|
||||||
|
|
||||||
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
|
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
|
||||||
version "8.5.0"
|
version "8.5.0"
|
||||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz"
|
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user