From 005daa374034430121514d5e62d6cc3c2605c76c Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 19 Oct 2021 13:08:25 +0200 Subject: [PATCH] Fix/login redirect (#442) * fix: use swr with login * fix: remove metrics poller * fix: do not allow retry on 401 * fix: create swr provider * fix: move accessprovider * fix: remove metrics poller test * fix: hide password auth if disableDefault is set * Update src/component/project/ProjectList/ProjectList.tsx Co-authored-by: Christopher Kolstad * fix: console log Co-authored-by: Christopher Kolstad --- .../src/__tests__/metrics-poller-test.jsx | 64 ------ frontend/src/component/App.tsx | 43 +--- .../AvailableAddons/AvailableAddons.jsx | 2 +- .../ConfiguredAddons/ConfiguredAddons.jsx | 2 +- .../api-token/ApiTokenList/ApiTokenList.tsx | 204 ++++++++++++------ .../application-edit-component-test.js | 4 +- .../application/application-edit-component.js | 2 +- .../application/application-view.jsx | 9 +- .../context/ContextList/ContextList.jsx | 2 +- .../EnvironmentListItem.tsx | 2 +- .../feature/FeatureCreate/FeatureCreate.tsx | 26 +-- .../FeatureToggleList/FeatureToggleList.jsx | 2 +- .../FeatureToggleListItem.jsx | 114 +++++----- .../__tests__/list-component-test.jsx | 7 +- .../feature/FeatureView/FeatureView.jsx | 2 +- .../FeatureOverviewStale.tsx | 2 +- .../FeatureOverviewEnvironment.tsx | 1 - .../FeatureOverviewStrategies.tsx | 2 +- .../FeatureOverviewTags.tsx | 19 +- .../FeatureSettingsMetadata.tsx | 2 +- .../FeatureSettingsProject.tsx | 2 +- .../FeatureStrategiesEnvironments.tsx | 10 +- .../FeatureStrategyEditable.tsx | 2 +- .../FeatureStrategiesList.tsx | 14 +- .../FeatureStrategyCard.tsx | 8 +- .../FeatureStrategyAccordionBody.tsx | 2 +- .../FeatureVariantsList.tsx | 6 +- .../feature/FeatureView2/FeatureView2.tsx | 6 +- .../create/CreateFeature/CreateFeature.jsx | 2 +- .../StrategyCardHeader/StrategyCardHeader.jsx | 2 +- .../view/__tests__/view-component-test.jsx | 4 +- frontend/src/component/menu/Header/Header.tsx | 2 +- frontend/src/component/menu/routes.js | 10 +- .../ProjectFeatureToggles.tsx | 7 +- .../ProjectEnvironment/ProjectEnvironment.tsx | 100 +++++---- .../project/ProjectList/ProjectList.tsx | 23 +- .../project/form-project-component.tsx | 193 +++++++++-------- .../AccessProvider/AccessProvider.tsx | 2 +- .../AccessProvider/permissions.ts | 0 .../providers/SWRProvider/SWRProvider.tsx | 67 ++++++ .../StrategiesList/StrategiesList.jsx | 2 +- .../__tests__/list-component-test.jsx | 4 +- .../strategy-details-component-test.jsx | 2 +- .../strategies/strategy-details-component.jsx | 2 +- .../tag-types/TagTypeList/TagTypeList.jsx | 2 +- .../tag-type-create-component-test.js | 4 +- .../__tests__/tag-type-list-component-test.js | 4 +- .../tag-types/form-tag-type-component.js | 2 +- .../src/component/tags/TagList/TagList.jsx | 5 +- .../user/Authentication/Authentication.tsx | 102 +++++++++ .../index.js} | 14 +- .../component/user/HostedAuth/HostedAuth.jsx | 94 ++++---- .../user/Login/{Login.jsx => Login.tsx} | 19 +- frontend/src/component/user/Login/index.js | 14 -- .../user/PasswordAuth/PasswordAuth.jsx | 112 +++++----- .../user/authentication-component.jsx | 79 ------- .../api/getters/httpErrorResponseHandler.ts | 6 +- .../src/hooks/api/getters/useUser/useUser.ts | 12 +- frontend/src/index.tsx | 5 +- frontend/src/metrics-poller.js | 31 --- frontend/src/page/admin/auth/google-auth.jsx | 24 ++- frontend/src/page/admin/auth/oidc-auth.jsx | 60 ++++-- frontend/src/page/admin/auth/saml-auth.jsx | 46 ++-- frontend/src/page/admin/invoice/index.js | 8 +- .../UsersList/UserListItem/UserListItem.tsx | 2 +- .../page/admin/users/UsersList/UsersList.jsx | 2 +- frontend/src/page/admin/users/index.js | 2 +- frontend/src/page/history/index.js | 2 +- .../src/utils/project-filter-generator.ts | 4 +- 69 files changed, 891 insertions(+), 747 deletions(-) delete mode 100644 frontend/src/__tests__/metrics-poller-test.jsx rename frontend/src/component/{ => providers}/AccessProvider/AccessProvider.tsx (96%) rename frontend/src/component/{ => providers}/AccessProvider/permissions.ts (100%) create mode 100644 frontend/src/component/providers/SWRProvider/SWRProvider.tsx create mode 100644 frontend/src/component/user/Authentication/Authentication.tsx rename frontend/src/component/user/{authentication-container.jsx => Authentication/index.js} (54%) rename frontend/src/component/user/Login/{Login.jsx => Login.tsx} (76%) delete mode 100644 frontend/src/component/user/Login/index.js delete mode 100644 frontend/src/component/user/authentication-component.jsx delete mode 100644 frontend/src/metrics-poller.js diff --git a/frontend/src/__tests__/metrics-poller-test.jsx b/frontend/src/__tests__/metrics-poller-test.jsx deleted file mode 100644 index cd652cf5e7..0000000000 --- a/frontend/src/__tests__/metrics-poller-test.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import configureStore from 'redux-mock-store'; -import { List } from 'immutable'; -import thunkMiddleware from 'redux-thunk'; -import fetchMock from 'fetch-mock'; -import MetricsPoller from '../metrics-poller'; - -const mockStore = configureStore([thunkMiddleware]); - -describe('metrics-poller.js', () => { - afterEach(() => { - fetchMock.reset(); - fetchMock.restore(); - }); - - test('Should not start poller before toggles are recieved', () => { - const initialState = { features: List.of([{ name: 'test1' }]) }; - const store = mockStore(initialState); - fetchMock.getOnce('api/admin/metrics/feature-toggles', { - body: { lastHour: {}, lastMinute: {} }, - headers: { 'content-type': 'application/json' }, - }); - - const metricsPoller = new MetricsPoller(store); - metricsPoller.start(); - - expect(metricsPoller.timer).toBeUndefined(); - }); - - test('Should not start poller when state does not contain toggles', () => { - const initialState = { features: new List([]) }; - const store = mockStore(initialState); - - const metricsPoller = new MetricsPoller(store); - metricsPoller.start(); - - store.dispatch({ - type: 'some', - receivedAt: Date.now(), - }); - - expect(metricsPoller.timer).toBeUndefined(); - }); - - test('Should start poller when state gets toggles', () => { - fetchMock.getOnce('api/admin/metrics/feature-toggles', { - body: { lastHour: {}, lastMinute: {} }, - headers: { 'content-type': 'application/json' }, - }); - - const initialState = { features: List.of([{ name: 'test1' }]) }; - const store = mockStore(initialState); - - const metricsPoller = new MetricsPoller(store); - metricsPoller.start(); - - store.dispatch({ - type: 'RECEIVE_FEATURE_TOGGLES', - featureToggles: [{ name: 'test' }], - receivedAt: Date.now(), - }); - - expect(metricsPoller.timer).toBeDefined(); - }); -}); diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx index 9ed8fce469..445ce09672 100644 --- a/frontend/src/component/App.tsx +++ b/frontend/src/component/App.tsx @@ -13,8 +13,8 @@ import IAuthStatus from '../interfaces/user'; import { useEffect } from 'react'; import NotFound from './common/NotFound/NotFound'; import Feedback from './common/Feedback'; -import { SWRConfig } from 'swr'; import useToast from '../hooks/useToast'; +import SWRProvider from './providers/SWRProvider/SWRProvider'; interface IAppProps extends RouteComponentProps { user: IAuthStatus; @@ -75,55 +75,34 @@ const App = ({ location, user, fetchUiBootstrap, feedback }: IAppProps) => { }; return ( - { - // Never retry on 404. - if (error.status === 404) { - return error; - } - setTimeout(() => revalidate({ retryCount }), 5000); - }, - onError: error => { - if (!isUnauthorized()) { - setToastData({ - show: true, - type: 'error', - text: error.message, - }); - } - }, - }} + + {' '}
{renderMainLayoutRoutes()} {renderStandaloneRoutes()} - - + + {toast}
-
+ ); }; // Set state to any for now, to avoid typing up entire state object while converting to tsx. diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx index 9f19deec45..95fb25e755 100644 --- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx +++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx @@ -9,7 +9,7 @@ import { ListItemText, } from '@material-ui/core'; import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; -import { CREATE_ADDON } from '../../../AccessProvider/permissions'; +import { CREATE_ADDON } from '../../../providers/AccessProvider/permissions'; import PropTypes from 'prop-types'; const AvailableAddons = ({ providers, getIcon, hasAccess, history }) => { diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx index 1107444ef3..7346408721 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx @@ -13,7 +13,7 @@ import ConditionallyRender from '../../../common/ConditionallyRender/Conditional import { DELETE_ADDON, UPDATE_ADDON, -} from '../../../AccessProvider/permissions'; +} from '../../../providers/AccessProvider/permissions'; import { Link } from 'react-router-dom'; import PageContent from '../../../common/PageContent/PageContent'; import PropTypes from 'prop-types'; diff --git a/frontend/src/component/api-token/ApiTokenList/ApiTokenList.tsx b/frontend/src/component/api-token/ApiTokenList/ApiTokenList.tsx index 66b56c2862..152ee21a7b 100644 --- a/frontend/src/component/api-token/ApiTokenList/ApiTokenList.tsx +++ b/frontend/src/component/api-token/ApiTokenList/ApiTokenList.tsx @@ -1,24 +1,37 @@ import { useContext, useState } from 'react'; import { Link } from 'react-router-dom'; -import { Button, IconButton, Table, TableBody, TableCell, TableHead, TableRow, } from '@material-ui/core'; +import { + Button, + IconButton, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from '@material-ui/core'; import AccessContext from '../../../contexts/AccessContext'; import useToast from '../../../hooks/useToast'; import useLoading from '../../../hooks/useLoading'; import useApiTokens from '../../../hooks/api/getters/useApiTokens/useApiTokens'; import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useApiTokensApi, { IApiTokenCreate } from '../../../hooks/api/actions/useApiTokensApi/useApiTokensApi'; +import useApiTokensApi, { + IApiTokenCreate, +} from '../../../hooks/api/actions/useApiTokensApi/useApiTokensApi'; import ApiError from '../../common/ApiError/ApiError'; import PageContent from '../../common/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; import ConditionallyRender from '../../common/ConditionallyRender'; -import { CREATE_API_TOKEN, DELETE_API_TOKEN } from '../../AccessProvider/permissions'; +import { + CREATE_API_TOKEN, + DELETE_API_TOKEN, +} from '../../providers/AccessProvider/permissions'; import { useStyles } from './ApiTokenList.styles'; import { formatDateWithLocale } from '../../common/util'; import Secret from './secret'; import { Delete, FileCopy } from '@material-ui/icons'; import ApiTokenCreate from '../ApiTokenCreate/ApiTokenCreate'; import Dialogue from '../../common/Dialogue'; -import {CREATE_API_TOKEN_BUTTON} from '../../../testIds' +import { CREATE_API_TOKEN_BUTTON } from '../../../testIds'; import { Alert } from '@material-ui/lab'; interface IApiToken { @@ -54,7 +67,6 @@ const ApiTokenList = ({ location }: IApiTokenList) => { const closeDialog = () => { setDialog(false); }; - const renderError = () => { return ( @@ -74,7 +86,7 @@ const ApiTokenList = ({ location }: IApiTokenList) => { show: true, text: 'Successfully created API token.', }); - } + }; const copyToken = (value: string) => { navigator.clipboard.writeText(value); setToastData({ @@ -85,7 +97,7 @@ const ApiTokenList = ({ location }: IApiTokenList) => { }; const onDeleteToken = async () => { - if(delToken) { + if (delToken) { await deleteToken(delToken.secret); } setDeleteToken(undefined); @@ -99,12 +111,12 @@ const ApiTokenList = ({ location }: IApiTokenList) => { }; const renderProject = (projectId: string) => { - if(!projectId || projectId === '*') { + if (!projectId || projectId === '*') { return projectId; } else { - return ({projectId}); + return {projectId}; } - } + }; const renderApiTokens = (tokens: IApiToken[]) => { return ( @@ -112,65 +124,106 @@ const ApiTokenList = ({ location }: IApiTokenList) => { Created - Username - Type - - Project - Environment - } /> + + Username + + + Type + + + + Project + + + Environment + + + } + /> Secret Token - Actions + + Actions + {tokens.map(item => { return ( - + {formatDateWithLocale( item.createdAt, location.locale )} - + {item.username} - + {item.type} - - - {renderProject(item.project)} - - - {item.environment} - - - Type: {item.type}
- Env: {item.environment}
- Project: {renderProject(item.project)} -
- } - elseShow={<> - - Type: {item.type}
- Username: {item.username} -
- } + + + {renderProject(item.project)} + + + {item.environment} + + + Type: {item.type} +
+ Env: {item.environment} +
+ Project:{' '} + {renderProject(item.project)} +
+ + } + elseShow={ + <> + + Type: {item.type} +
+ Username: {item.username} +
+ + } /> - + { - copyToken(item.secret) - } } - > - + copyToken(item.secret); + }} + > + { onClick={() => { setDeleteToken(item); setShowDelete(true); - } } + }} > - } /> + } + />
); })}
- ) - } + ); + }; return (
Create API token} />} />} - > + headerContent={ + + Create API token + + } + /> + } + /> + } + >

Read the{' '} @@ -218,7 +287,9 @@ const ApiTokenList = ({ location }: IApiTokenList) => {


API URL: {' '} -
{uiConfig.unleashUrl}/api/
+
+                        {uiConfig.unleashUrl}/api/
+                    
@@ -230,7 +301,11 @@ const ApiTokenList = ({ location }: IApiTokenList) => { />
{toast} - + { title="Confirm deletion" >
- Are you sure you want to delete the following API token?
+ Are you sure you want to delete the following API token? +
    -
  • username: {delToken?.username}
  • -
  • type: {delToken?.type}
  • +
  • + username:{' '} + {delToken?.username} +
  • +
  • + type:{' '} + {delToken?.type} +
diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js index 7c184d3f7a..e30be6ed7f 100644 --- a/frontend/src/component/application/__tests__/application-edit-component-test.js +++ b/frontend/src/component/application/__tests__/application-edit-component-test.js @@ -4,11 +4,11 @@ import { ThemeProvider } from '@material-ui/core'; import ClientApplications from '../application-edit-component'; import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; -import { ADMIN } from '../../AccessProvider/permissions'; +import { ADMIN } from '../../providers/AccessProvider/permissions'; import theme from '../../../themes/main-theme'; import { createFakeStore } from '../../../accessStoreFake'; -import AccessProvider from '../../AccessProvider/AccessProvider'; +import AccessProvider from '../../providers/AccessProvider/AccessProvider'; test('renders correctly if no application', () => { const tree = renderer diff --git a/frontend/src/component/application/application-edit-component.js b/frontend/src/component/application/application-edit-component.js index d2e7fdccda..ecab61cd3e 100644 --- a/frontend/src/component/application/application-edit-component.js +++ b/frontend/src/component/application/application-edit-component.js @@ -18,7 +18,7 @@ import { formatFullDateTimeWithLocale, formatDateWithLocale, } from '../common/util'; -import { UPDATE_APPLICATION } from '../AccessProvider/permissions'; +import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; import ApplicationView from './application-view'; import ApplicationUpdate from './application-update'; import TabNav from '../common/TabNav/TabNav'; diff --git a/frontend/src/component/application/application-view.jsx b/frontend/src/component/application/application-view.jsx index 2bc1dd5c85..db769e2533 100644 --- a/frontend/src/component/application/application-view.jsx +++ b/frontend/src/component/application/application-view.jsx @@ -13,7 +13,10 @@ import { import { Report, Extension, Timeline } from '@material-ui/icons'; import { shorten } from '../common'; -import { CREATE_FEATURE, CREATE_STRATEGY } from '../AccessProvider/permissions'; +import { + CREATE_FEATURE, + CREATE_STRATEGY, +} from '../providers/AccessProvider/permissions'; import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; import { getTogglePath } from '../../utils/route-path-helpers'; function ApplicationView({ @@ -33,9 +36,7 @@ function ApplicationView({ {name} - } + primary={{name}} secondary={'Missing, want to create?'} /> diff --git a/frontend/src/component/context/ContextList/ContextList.jsx b/frontend/src/component/context/ContextList/ContextList.jsx index 0d9c2feac7..21e2d8b191 100644 --- a/frontend/src/component/context/ContextList/ContextList.jsx +++ b/frontend/src/component/context/ContextList/ContextList.jsx @@ -5,7 +5,7 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR import { CREATE_CONTEXT_FIELD, DELETE_CONTEXT_FIELD, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; import { IconButton, List, diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx index 081f73b50c..cfcfa6742f 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx @@ -20,7 +20,7 @@ import AccessContext from '../../../../contexts/AccessContext'; import { DELETE_ENVIRONMENT, UPDATE_ENVIRONMENT, -} from '../../../AccessProvider/permissions'; +} from '../../../providers/AccessProvider/permissions'; import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; import { XYCoord } from 'dnd-core'; diff --git a/frontend/src/component/feature/FeatureCreate/FeatureCreate.tsx b/frontend/src/component/feature/FeatureCreate/FeatureCreate.tsx index 757c66b7e9..ffc487b739 100644 --- a/frontend/src/component/feature/FeatureCreate/FeatureCreate.tsx +++ b/frontend/src/component/feature/FeatureCreate/FeatureCreate.tsx @@ -30,7 +30,7 @@ const FeatureCreate = () => { const { projectId } = useParams(); const { createFeatureToggle, validateFeatureToggleName } = useFeatureApi(); const history = useHistory(); - const [ toggle, setToggle ] = useState({ + const [toggle, setToggle] = useState({ name: loadNameFromUrl(), description: '', type: 'release', @@ -41,7 +41,6 @@ const FeatureCreate = () => { }); const [errors, setErrors] = useState({}); - useEffect(() => { window.onbeforeunload = () => 'Data will be lost if you leave the page, are you sure?'; @@ -52,10 +51,7 @@ const FeatureCreate = () => { }; }, []); - const onCancel = () => history.push( - `/projects/${projectId}` - ); - + const onCancel = () => history.push(`/projects/${projectId}`); const validateName = async (featureToggleName: string) => { const e = { ...errors }; @@ -80,25 +76,25 @@ const FeatureCreate = () => { try { await createFeatureToggle(projectId, toggle).then(() => - history.push( - getTogglePath(toggle.project, toggle.name, true) - ) + history.push(getTogglePath(toggle.project, toggle.name, true)) ); // Trigger } catch (e: any) { if (e.toString().includes('not allowed to be empty')) { - setErrors({ name: 'Name is not allowed to be empty' }) + setErrors({ name: 'Name is not allowed to be empty' }); } } }; - const setValue = (field:string, value:string) => { - setToggle({...toggle, [field]: value}) - } - + const setValue = (field: string, value: string) => { + setToggle({ ...toggle, [field]: value }); + }; return ( - +
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx index 984ec20be4..6e7c64b633 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx @@ -15,7 +15,7 @@ import HeaderTitle from '../../common/HeaderTitle'; import loadingFeatures from './loadingFeatures'; -import { CREATE_FEATURE } from '../../AccessProvider/permissions'; +import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions'; import AccessContext from '../../../contexts/AccessContext'; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx index 717030db3c..0ddb6c0d9d 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx @@ -10,7 +10,7 @@ import TimeAgo from 'react-timeago'; import Status from '../../status-component'; import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; -import { UPDATE_FEATURE } from '../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; import { styles as commonStyles } from '../../../common'; import { useStyles } from './styles'; @@ -18,8 +18,6 @@ import { getTogglePath } from '../../../../utils/route-path-helpers'; import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus'; import FeatureType from '../../FeatureView2/FeatureType/FeatureType'; - - const FeatureToggleListItem = ({ feature, toggleFeature, @@ -37,7 +35,7 @@ const FeatureToggleListItem = ({ const { name, description, type, stale, createdAt, project, lastSeenAt } = feature; - + return ( - + - - - + + + {name}  - + - - - - - -
- - {description} - -
- - } elseShow={ - <> - - - {name}  -z - - - - - - -
- - {description} - -
- - }/> - - - + + + + +
+ + {description} + +
+ + } + elseShow={ + <> + + + {name}  z{' '} + + + + + + +
+ + {description} + +
+ + } + />
)} > - - + + ({ __esModule: true, diff --git a/frontend/src/component/feature/FeatureView/FeatureView.jsx b/frontend/src/component/feature/FeatureView/FeatureView.jsx index c801a41941..5f39e67d2d 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.jsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.jsx @@ -21,7 +21,7 @@ import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; import StatusComponent from '../status-component'; import FeatureTagComponent from '../feature-tag-component'; import StatusUpdateComponent from '../view/status-update-component'; diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStale/FeatureOverviewStale.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStale/FeatureOverviewStale.tsx index 841a822ac0..e709e446e8 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStale/FeatureOverviewStale.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStale/FeatureOverviewStale.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames'; import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature'; import { useParams } from 'react-router-dom'; import { IFeatureViewParams } from '../../../../../interfaces/params'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; import { useState } from 'react'; import StaleDialog from './StaleDialog/StaleDialog'; import PermissionButton from '../../../../common/PermissionButton/PermissionButton'; diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStrategies/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStrategies/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index 6d1f307b88..fbdc71de0d 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStrategies/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewStrategies/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -31,7 +31,6 @@ const FeatureOverviewEnvironment = ({ return strategies.map(strategy => { return ( { data-loading label={t.value} key={`${t.type}:${t.value}`} - onDelete={canDeleteTag ? () => { - setShowDelDialog(true); - setSelectedTag({ type: t.type, value: t.value }); - }: undefined} + onDelete={ + canDeleteTag + ? () => { + setShowDelDialog(true); + setSelectedTag({ type: t.type, value: t.value }); + } + : undefined + } /> ); diff --git a/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx b/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx index f4b39ae383..0e87ec8c59 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx @@ -5,7 +5,7 @@ import PermissionButton from '../../../../common/PermissionButton/PermissionButt import FeatureTypeSelect from './FeatureTypeSelect/FeatureTypeSelect'; import { useParams } from 'react-router'; import AccessContext from '../../../../../contexts/AccessContext'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature'; import { IFeatureViewParams } from '../../../../../interfaces/params'; import useToast from '../../../../../hooks/useToast'; diff --git a/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx b/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx index 9d8372e220..c5dbd19f9c 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx @@ -10,7 +10,7 @@ import { projectFilterGenerator } from '../../../../../utils/project-filter-gene import { CREATE_FEATURE, UPDATE_FEATURE, -} from '../../../../AccessProvider/permissions'; +} from '../../../../providers/AccessProvider/permissions'; import ConditionallyRender from '../../../../common/ConditionallyRender'; import PermissionButton from '../../../../common/PermissionButton/PermissionButton'; import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect'; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx index 7fa36412ad..e48cb93aad 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx @@ -20,7 +20,7 @@ import NoItems from '../../../../common/NoItems/NoItems'; import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton'; import { Add } from '@material-ui/icons'; import AccessContext from '../../../../../contexts/AccessContext'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; import useQueryParams from '../../../../../hooks/useQueryParams'; const FeatureStrategiesEnvironments = () => { @@ -178,8 +178,12 @@ const FeatureStrategiesEnvironments = () => { // Check groupId - const cacheParamKeys = Object.keys(cachedStrategy?.parameters || {}); - const strategyParamKeys = Object.keys(strategy?.parameters || {}); + const cacheParamKeys = Object.keys( + cachedStrategy?.parameters || {} + ); + const strategyParamKeys = Object.keys( + strategy?.parameters || {} + ); // Check length of parameters if (cacheParamKeys.length !== strategyParamKeys.length) { equal = false; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx index 01152b4bd0..15733d4f86 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx @@ -22,7 +22,7 @@ import { UPDATE_STRATEGY_BUTTON_ID, } from '../../../../../../testIds'; import AccessContext from '../../../../../../contexts/AccessContext'; -import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions'; import useFeatureApi from '../../../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; interface IFeatureStrategyEditable { diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx index 64ef118a50..0c3ff62597 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx @@ -8,10 +8,9 @@ import classnames from 'classnames'; import { Button, IconButton, Tooltip, useMediaQuery } from '@material-ui/core'; import { DoubleArrow } from '@material-ui/icons'; import ConditionallyRender from '../../../../common/ConditionallyRender'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; import AccessContext from '../../../../../contexts/AccessContext'; - const FeatureStrategiesList = () => { const smallScreen = useMediaQuery('(max-width:700px)'); const { expandedSidebar, setExpandedSidebar } = useContext( @@ -53,7 +52,7 @@ const FeatureStrategiesList = () => { const iconClasses = classnames(styles.icon, { [styles.expandedIcon]: expandedSidebar, }); - + return (
{
} /> - + { const strategy = getStrategyObject(strategies, name, featureId); @@ -83,7 +83,9 @@ const FeatureStrategyCard = ({ diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx index 3022ec5a1e..4dc281ac04 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx @@ -19,7 +19,7 @@ import FeatureStrategiesSeparator from '../../FeatureStrategiesEnvironments/Feat import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy'; import { ADD_CONSTRAINT_ID } from '../../../../../../testIds'; import AccessContext from '../../../../../../contexts/AccessContext'; -import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions'; interface IFeatureStrategyAccordionBodyProps { strategy: IFeatureStrategy; diff --git a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx index 33b2b802bd..3c7eaebaf9 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx @@ -19,7 +19,7 @@ import { useParams } from 'react-router'; import { IFeatureViewParams } from '../../../../../interfaces/params'; import AccessContext from '../../../../../contexts/AccessContext'; import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; import ConditionallyRender from '../../../../common/ConditionallyRender'; import useUnleashContext from '../../../../../hooks/api/getters/useUnleashContext/useUnleashContext'; import GeneralSelect from '../../../../common/GeneralSelect/GeneralSelect'; @@ -121,8 +121,8 @@ const FeatureOverviewVariants = () => { style={{ display: 'block', marginTop: '0.5rem' }} > By overriding the stickiness you can control which parameter - is used to ensure consistent traffic - allocation across variants.{' '} + is used to ensure consistent traffic allocation across + variants.{' '} { return (

- The feature {featureId.substring(0,30)} does not exist. Do - you want to   + The feature {featureId.substring(0, 30)}{' '} + does not exist. Do you want to   create it  ?

diff --git a/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx b/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx index 73677d77a2..cb2e1370b2 100644 --- a/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx +++ b/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx @@ -16,7 +16,7 @@ import { CF_NAME_ID, CF_TYPE_ID, } from '../../../../testIds'; -import { CREATE_FEATURE } from '../../../AccessProvider/permissions'; +import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions'; import { projectFilterGenerator } from '../../../../utils/project-filter-generator'; import { useHistory } from 'react-router-dom'; import useQueryParams from '../../../../hooks/useQueryParams'; diff --git a/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.jsx b/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.jsx index 774960ae61..ddd86b1b1a 100644 --- a/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.jsx +++ b/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.jsx @@ -8,7 +8,7 @@ import { useStyles } from './StrategyCardHeader.styles.js'; import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg'; import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender'; import AccessContext from '../../../../../contexts/AccessContext'; -import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions'; const StrategyCardHeader = ({ name, diff --git a/frontend/src/component/feature/view/__tests__/view-component-test.jsx b/frontend/src/component/feature/view/__tests__/view-component-test.jsx index b233037c6d..6d34aa8299 100644 --- a/frontend/src/component/feature/view/__tests__/view-component-test.jsx +++ b/frontend/src/component/feature/view/__tests__/view-component-test.jsx @@ -11,11 +11,11 @@ import { ADMIN, DELETE_FEATURE, UPDATE_FEATURE, -} from '../../../AccessProvider/permissions'; +} from '../../../providers/AccessProvider/permissions'; import theme from '../../../../themes/main-theme'; import { createFakeStore } from '../../../../accessStoreFake'; -import AccessProvider from '../../../AccessProvider/AccessProvider'; +import AccessProvider from '../../../providers/AccessProvider/AccessProvider'; jest.mock('../update-strategies-container', () => ({ __esModule: true, diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index f1233d6380..379f0773cf 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -14,7 +14,7 @@ import { ReactComponent as UnleashLogo } from '../../../assets/img/logo-dark-wit import { useStyles } from './Header.styles'; import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; import { useCommonStyles } from '../../../common.styles'; -import { ADMIN } from '../../AccessProvider/permissions'; +import { ADMIN } from '../../providers/AccessProvider/permissions'; import useUser from '../../../hooks/api/getters/useUser/useUser'; import { IPermission } from '../../../interfaces/user'; import NavigationMenu from './NavigationMenu/NavigationMenu'; diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index 9228f8ebb9..a162b07727 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -28,7 +28,7 @@ import AdminApi from '../../page/admin/api'; import AdminUsers from '../../page/admin/users'; import AdminInvoice from '../../page/admin/invoice'; import AdminAuth from '../../page/admin/auth'; -import Login from '../user/Login'; +import Login from '../user/Login/Login'; import { P, C, E, EEA } from '../common/flags'; import NewUser from '../user/NewUser'; import ResetPassword from '../user/ResetPassword/ResetPassword'; @@ -40,7 +40,7 @@ import RedirectArchive from '../feature/RedirectArchive/RedirectArchive'; import EnvironmentList from '../environments/EnvironmentList/EnvironmentList'; import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment'; import FeatureView2 from '../feature/FeatureView2/FeatureView2'; -import FeatureCreate from '../feature/FeatureCreate/FeatureCreate' +import FeatureCreate from '../feature/FeatureCreate/FeatureCreate'; export const routes = [ // Project @@ -164,7 +164,7 @@ export const routes = [ layout: 'main', menu: { mobile: true }, }, - + // Applications { path: '/applications/:name', @@ -305,8 +305,8 @@ export const routes = [ menu: {}, }, - // Addons - { + // Addons + { path: '/addons/create/:provider', parent: '/addons', title: 'Create', diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 4d395d7583..65e393c533 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -14,7 +14,7 @@ import PageContent from '../../../common/PageContent'; import ResponsiveButton from '../../../common/ResponsiveButton/ResponsiveButton'; import FeatureToggleListNew from '../../../feature/FeatureToggleListNew/FeatureToggleListNew'; import { useStyles } from './ProjectFeatureToggles.styles'; -import { CREATE_FEATURE } from '../../../AccessProvider/permissions'; +import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions'; import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; interface IProjectFeatureToggles { @@ -61,7 +61,10 @@ const ProjectFeatureToggles = ({ history.push( - getCreateTogglePath(id, uiConfig.flags.E) + getCreateTogglePath( + id, + uiConfig.flags.E + ) ) } maxWidth="700px" diff --git a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx index fc067e8cee..22cd198e0d 100644 --- a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx +++ b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx @@ -6,7 +6,7 @@ import useLoading from '../../../hooks/useLoading'; import PageContent from '../../common/PageContent'; import AccessContext from '../../../contexts/AccessContext'; import HeaderTitle from '../../common/HeaderTitle'; -import { UPDATE_PROJECT } from '../../AccessProvider/permissions'; +import { UPDATE_PROJECT } from '../../providers/AccessProvider/permissions'; import ApiError from '../../common/ApiError/ApiError'; import useToast from '../../../hooks/useToast'; @@ -19,7 +19,6 @@ import EnvironmentDisableConfirm from './EnvironmentDisableConfirm/EnvironmentDi import { Link } from 'react-router-dom'; import { Alert } from '@material-ui/lab'; - export interface ProjectEnvironment { name: string; enabled: boolean; @@ -51,7 +50,6 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => { const ref = useLoading(loading); const styles = useStyles(); - const refetch = () => { refetchEnvs(); refetchProject(); @@ -164,52 +162,62 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => { headerContent={ } + /> + } > - - - - Important! In order for your application to retrieve configured activation strategies for a specific environment, - the application
must use an environment specific API key. You can look up the environment-specific API keys {' '} - + + - here. - -
-
- Your administrator can configure an environment-specific API key to be used in the SDK. - If you are an administrator you can {' '} - - create a new API key. - + Important! In order for your application + to retrieve configured activation strategies for + a specific environment, the application +
must use an environment specific API key. + You can look up the environment-specific API + keys here. +
+
+ Your administrator can configure an + environment-specific API key to be used in the + SDK. If you are an administrator you can{' '} + + create a new API key. + +
+ No environments available.
} + elseShow={renderEnvironments()} + /> + + + } + elseShow={ + + This feature has not been Unleashed for you yet. - No environments available.} - elseShow={renderEnvironments()} - /> - - - - } elseShow={ - - This feature has not been Unleashed for you yet. - - } /> - + } + /> + {toast} diff --git a/frontend/src/component/project/ProjectList/ProjectList.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx index 9e77ee26c3..470cd788e1 100644 --- a/frontend/src/component/project/ProjectList/ProjectList.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -14,7 +14,7 @@ import PageContent from '../../common/PageContent'; import AccessContext from '../../../contexts/AccessContext'; import HeaderTitle from '../../common/HeaderTitle'; import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton'; -import { CREATE_PROJECT } from '../../AccessProvider/permissions'; +import { CREATE_PROJECT } from '../../providers/AccessProvider/permissions'; import { Add } from '@material-ui/icons'; import ApiError from '../../common/ApiError/ApiError'; @@ -26,21 +26,21 @@ type projectMap = { }; function resolveCreateButtonData(isOss: boolean, hasAccess: boolean) { - if(isOss) { + if (isOss) { return { title: 'You must be on a paid subscription to create new projects', - disabled: true - } + disabled: true, + }; } else if (!hasAccess) { return { - title: 'You do not have permissions create new projects', - disabled: true - } + title: 'You do not have permission to create new projects', + disabled: true, + }; } else { return { title: 'Click to create a new project', - disabled: false - } + disabled: false, + }; } } @@ -64,7 +64,10 @@ const ProjectListNew = () => { setFetchedProjects(prev => ({ ...prev, [projectId]: true })); }; - const createButtonData = resolveCreateButtonData(isOss(), hasAccess(CREATE_PROJECT)); + const createButtonData = resolveCreateButtonData( + isOss(), + hasAccess(CREATE_PROJECT) + ); const renderError = () => { return ( diff --git a/frontend/src/component/project/form-project-component.tsx b/frontend/src/component/project/form-project-component.tsx index 4909e92136..00c4919042 100644 --- a/frontend/src/component/project/form-project-component.tsx +++ b/frontend/src/component/project/form-project-component.tsx @@ -9,7 +9,7 @@ import { trim } from '../common/util'; import PageContent from '../common/PageContent/PageContent'; import AccessContext from '../../contexts/AccessContext'; import ConditionallyRender from '../common/ConditionallyRender'; -import { CREATE_PROJECT } from '../AccessProvider/permissions'; +import { CREATE_PROJECT } from '../providers/AccessProvider/permissions'; import HeaderTitle from '../common/HeaderTitle'; import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig'; import { Alert } from '@material-ui/lab'; @@ -28,29 +28,28 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => { const { editMode } = props; const { hasAccess } = useContext(AccessContext); - const [project, setProject ] = useState(props.project || {}); - const [errors, setErrors ] = useState({}); + const [project, setProject] = useState(props.project || {}); + const [errors, setErrors] = useState({}); const { isOss, loading } = useUiConfig(); const ref = useLoading(loading); - useEffect(() => { - if(!project.id && props.project.id) { + if (!project.id && props.project.id) { setProject(props.project); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.project]); const setValue = (field: string, value: string) => { - const p = {...project} + const p = { ...project }; p[field] = value; - setProject(p) + setProject(p); }; const validateId = async (id: string) => { if (editMode) return true; - const e = {...errors}; + const e = { ...errors }; try { await props.validateId(id); e.id = undefined; @@ -58,14 +57,14 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => { e.id = err.message; } - setErrors(e) + setErrors(e); if (e.id) return false; return true; }; const validateName = () => { if (project.name.length === 0) { - setErrors({...errors, name: 'Name can not be empty.' }) + setErrors({ ...errors, name: 'Name can not be empty.' }); return false; } return true; @@ -111,91 +110,103 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => { /> } > - - - {submitText} project requires a paid version of Unleash. - Check out
getunleash.io{' '} - to learn more. - - } elseShow={ - <> - - Projects allows you to group feature toggles together in the - management UI. - - - validateId(v.target.value)} - onChange={v => - setValue('id', trim(v.target.value)) - } - /> -
- setValue('name', v.target.value)} - /> - - setValue('description', v.target.value) - } - /> + + {submitText} project requires a paid version of + Unleash. Check out{' '} + + getunleash.io + {' '} + to learn more. + + } + elseShow={ + <> + + Projects allows you to group feature toggles + together in the management UI. + + + validateId(v.target.value)} + onChange={v => + setValue('id', trim(v.target.value)) + } + /> +
+ + setValue('name', v.target.value) + } + /> + + setValue('description', v.target.value) + } + /> - - - - } - /> - - - } /> + + + + } + /> + + + } + />
); -} +}; ProjectFormComponent.propTypes = { project: PropTypes.object.isRequired, diff --git a/frontend/src/component/AccessProvider/AccessProvider.tsx b/frontend/src/component/providers/AccessProvider/AccessProvider.tsx similarity index 96% rename from frontend/src/component/AccessProvider/AccessProvider.tsx rename to frontend/src/component/providers/AccessProvider/AccessProvider.tsx index 31d2c9f3c3..94978d5a0a 100644 --- a/frontend/src/component/AccessProvider/AccessProvider.tsx +++ b/frontend/src/component/providers/AccessProvider/AccessProvider.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; -import AccessContext from '../../contexts/AccessContext'; +import AccessContext from '../../../contexts/AccessContext'; import { ADMIN } from './permissions'; // TODO: Type up redux store diff --git a/frontend/src/component/AccessProvider/permissions.ts b/frontend/src/component/providers/AccessProvider/permissions.ts similarity index 100% rename from frontend/src/component/AccessProvider/permissions.ts rename to frontend/src/component/providers/AccessProvider/permissions.ts diff --git a/frontend/src/component/providers/SWRProvider/SWRProvider.tsx b/frontend/src/component/providers/SWRProvider/SWRProvider.tsx new file mode 100644 index 0000000000..31d87304f1 --- /dev/null +++ b/frontend/src/component/providers/SWRProvider/SWRProvider.tsx @@ -0,0 +1,67 @@ +import { USER_CACHE_KEY } from '../../../hooks/api/getters/useUser/useUser'; +import { mutate, SWRConfig, useSWRConfig } from 'swr'; +import { useHistory } from 'react-router'; +import { IToast } from '../../../hooks/useToast'; + +interface ISWRProviderProps { + setToastData: (toastData: IToast) => void; + isUnauthorized: () => boolean; +} + +const SWRProvider: React.FC = ({ + children, + setToastData, + isUnauthorized, +}) => { + const { cache } = useSWRConfig(); + const history = useHistory(); + + const handleFetchError = error => { + if (error.status === 401) { + cache.clear(); + const path = location.pathname; + + mutate(USER_CACHE_KEY, { ...error.info }, false); + if (path === '/login') { + return; + } + + history.push('/login'); + return; + } + + if (!isUnauthorized()) { + setToastData({ + show: true, + type: 'error', + text: error.message, + }); + } + }; + + return ( + { + // Never retry on 404 or 401. + if (error.status < 499) { + return error; + } + + setTimeout(() => revalidate({ retryCount }), 5000); + }, + onError: handleFetchError, + }} + > + {children} + + ); +}; + +export default SWRProvider; diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx index ca035ea138..30dcd3ac77 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx @@ -24,7 +24,7 @@ import { import { CREATE_STRATEGY, DELETE_STRATEGY, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import PageContent from '../../common/PageContent/PageContent'; diff --git a/frontend/src/component/strategies/__tests__/list-component-test.jsx b/frontend/src/component/strategies/__tests__/list-component-test.jsx index 02872e8ff1..bc9abb9a02 100644 --- a/frontend/src/component/strategies/__tests__/list-component-test.jsx +++ b/frontend/src/component/strategies/__tests__/list-component-test.jsx @@ -5,9 +5,9 @@ import { ThemeProvider } from '@material-ui/core'; import StrategiesListComponent from '../StrategiesList/StrategiesList'; import renderer from 'react-test-renderer'; import theme from '../../../themes/main-theme'; -import AccessProvider from '../../AccessProvider/AccessProvider'; +import AccessProvider from '../../providers/AccessProvider/AccessProvider'; import { createFakeStore } from '../../../accessStoreFake'; -import { ADMIN } from '../../AccessProvider/permissions'; +import { ADMIN } from '../../providers/AccessProvider/permissions'; test('renders correctly with one strategy', () => { const strategy = { diff --git a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx index 9e560ace50..f63c0c6c78 100644 --- a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx +++ b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx @@ -5,7 +5,7 @@ import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import theme from '../../../themes/main-theme'; import { createFakeStore } from '../../../accessStoreFake'; -import AccessProvider from '../../AccessProvider/AccessProvider'; +import AccessProvider from '../../providers/AccessProvider/AccessProvider'; test('renders correctly with one strategy', () => { const strategy = { diff --git a/frontend/src/component/strategies/strategy-details-component.jsx b/frontend/src/component/strategies/strategy-details-component.jsx index 58b55ef6eb..df23ced4df 100644 --- a/frontend/src/component/strategies/strategy-details-component.jsx +++ b/frontend/src/component/strategies/strategy-details-component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Grid, Typography } from '@material-ui/core'; import ShowStrategy from './show-strategy-component'; import EditStrategy from './CreateStrategy'; -import { UPDATE_STRATEGY } from '../AccessProvider/permissions'; +import { UPDATE_STRATEGY } from '../providers/AccessProvider/permissions'; import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; import TabNav from '../common/TabNav/TabNav'; import PageContent from '../common/PageContent/PageContent'; diff --git a/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx b/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx index 2a0c00a099..46a984ee9e 100644 --- a/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx +++ b/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx @@ -19,7 +19,7 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR import { CREATE_TAG_TYPE, DELETE_TAG_TYPE, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; import Dialogue from '../../common/Dialogue/Dialogue'; import useMediaQuery from '@material-ui/core/useMediaQuery'; diff --git a/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js b/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js index 0e1bd7250d..f43bb99bde 100644 --- a/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js +++ b/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js @@ -3,12 +3,12 @@ import { ThemeProvider } from '@material-ui/core'; import TagTypes from '../form-tag-type-component'; import renderer from 'react-test-renderer'; import theme from '../../../themes/main-theme'; -import AccessProvider from '../../AccessProvider/AccessProvider'; +import AccessProvider from '../../providers/AccessProvider/AccessProvider'; import { createFakeStore } from '../../../accessStoreFake'; import { CREATE_TAG_TYPE, UPDATE_TAG_TYPE, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; jest.mock('@material-ui/core/TextField'); diff --git a/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js b/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js index ebdedf1750..49174f12f9 100644 --- a/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js +++ b/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js @@ -6,14 +6,14 @@ import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from '@material-ui/styles'; import theme from '../../../themes/main-theme'; import { createFakeStore } from '../../../accessStoreFake'; -import AccessProvider from '../../AccessProvider/AccessProvider'; +import AccessProvider from '../../providers/AccessProvider/AccessProvider'; import { ADMIN, CREATE_TAG_TYPE, UPDATE_TAG_TYPE, DELETE_TAG_TYPE, -} from '../../AccessProvider/permissions'; +} from '../../providers/AccessProvider/permissions'; test('renders an empty list correctly', () => { const tree = renderer.create( diff --git a/frontend/src/component/tag-types/form-tag-type-component.js b/frontend/src/component/tag-types/form-tag-type-component.js index 6caab8c50d..e0e80c38c6 100644 --- a/frontend/src/component/tag-types/form-tag-type-component.js +++ b/frontend/src/component/tag-types/form-tag-type-component.js @@ -12,7 +12,7 @@ import AccessContext from '../../contexts/AccessContext'; import { CREATE_TAG_TYPE, UPDATE_TAG_TYPE, -} from '../AccessProvider/permissions'; +} from '../providers/AccessProvider/permissions'; import ConditionallyRender from '../common/ConditionallyRender'; const AddTagTypeComponent = ({ diff --git a/frontend/src/component/tags/TagList/TagList.jsx b/frontend/src/component/tags/TagList/TagList.jsx index 442b9bce17..0369048b4c 100644 --- a/frontend/src/component/tags/TagList/TagList.jsx +++ b/frontend/src/component/tags/TagList/TagList.jsx @@ -14,7 +14,10 @@ import { } from '@material-ui/core'; import { Add, Label, Delete } from '@material-ui/icons'; -import { CREATE_TAG, DELETE_TAG } from '../../AccessProvider/permissions'; +import { + CREATE_TAG, + DELETE_TAG, +} from '../../providers/AccessProvider/permissions'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import HeaderTitle from '../../common/HeaderTitle'; import PageContent from '../../common/PageContent/PageContent'; diff --git a/frontend/src/component/user/Authentication/Authentication.tsx b/frontend/src/component/user/Authentication/Authentication.tsx new file mode 100644 index 0000000000..a48dc932b5 --- /dev/null +++ b/frontend/src/component/user/Authentication/Authentication.tsx @@ -0,0 +1,102 @@ +import SimpleAuth from '../SimpleAuth/SimpleAuth'; +import AuthenticationCustomComponent from '../authentication-custom-component'; +import PasswordAuth from '../PasswordAuth/PasswordAuth'; +import HostedAuth from '../HostedAuth/HostedAuth'; +import DemoAuth from '../DemoAuth'; + +import { + SIMPLE_TYPE, + DEMO_TYPE, + PASSWORD_TYPE, + HOSTED_TYPE, +} from '../../../constants/authTypes'; +import SecondaryLoginActions from '../common/SecondaryLoginActions/SecondaryLoginActions'; +import useUser from '../../../hooks/api/getters/useUser/useUser'; +import { IUser } from '../../../interfaces/user'; +import { useHistory } from 'react-router'; +import useQueryParams from '../../../hooks/useQueryParams'; +import ConditionallyRender from '../../common/ConditionallyRender'; +import { Alert } from '@material-ui/lab'; + +interface IAuthenticationProps { + insecureLogin: (path: string, user: IUser) => void; + passwordLogin: (path: string, user: IUser) => void; + demoLogin: (path: string, user: IUser) => void; + history: any; +} + +const Authentication = ({ + insecureLogin, + passwordLogin, + demoLogin, +}: IAuthenticationProps) => { + const { authDetails } = useUser(); + const history = useHistory(); + const params = useQueryParams(); + + const error = params.get('errorMsg'); + + if (!authDetails) return null; + + let content; + if (authDetails.type === PASSWORD_TYPE) { + content = ( + <> + + } + /> + + ); + } else if (authDetails.type === SIMPLE_TYPE) { + content = ( + + ); + } else if (authDetails.type === DEMO_TYPE) { + content = ( + + ); + } else if (authDetails.type === HOSTED_TYPE) { + content = ( + <> + + } + /> + + ); + } else { + content = ; + } + return ( + <> +
+ {error}} + /> +
+ {content} + + ); +}; + +export default Authentication; diff --git a/frontend/src/component/user/authentication-container.jsx b/frontend/src/component/user/Authentication/index.js similarity index 54% rename from frontend/src/component/user/authentication-container.jsx rename to frontend/src/component/user/Authentication/index.js index 8a5556dc8b..ad12103f8b 100644 --- a/frontend/src/component/user/authentication-container.jsx +++ b/frontend/src/component/user/Authentication/index.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; -import AuthenticationComponent from './authentication-component'; +import AuthenticationComponent from './Authentication'; import { insecureLogin, passwordLogin, demoLogin, -} from '../../store/user/actions'; +} from '../../../store/user/actions'; const mapDispatchToProps = (dispatch, props) => ({ demoLogin: (path, user) => demoLogin(path, user)(dispatch), @@ -12,12 +12,4 @@ const mapDispatchToProps = (dispatch, props) => ({ passwordLogin: (path, user) => passwordLogin(path, user)(dispatch), }); -const mapStateToProps = state => ({ - user: state.user.toJS(), - flags: state.uiConfig.toJS().flags, -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AuthenticationComponent); +export default connect(null, mapDispatchToProps)(AuthenticationComponent); diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.jsx b/frontend/src/component/user/HostedAuth/HostedAuth.jsx index cdd6b7996b..4289d69e3a 100644 --- a/frontend/src/component/user/HostedAuth/HostedAuth.jsx +++ b/frontend/src/component/user/HostedAuth/HostedAuth.jsx @@ -79,50 +79,58 @@ const HostedAuth = ({ authDetails, passwordLogin }) => { } /> -
- - {apiError} - -
- setUsername(evt.target.value)} - value={username} - error={!!usernameError} - helperText={usernameError} - variant="outlined" - size="small" - /> - setPassword(evt.target.value)} - name="password" - type="password" - value={password} - error={!!passwordError} - helperText={passwordError} - variant="outlined" - size="small" - /> - - - -
-
+ {apiError} + +
+ setUsername(evt.target.value)} + value={username} + error={!!usernameError} + helperText={usernameError} + variant="outlined" + size="small" + /> + setPassword(evt.target.value)} + name="password" + type="password" + value={password} + error={!!passwordError} + helperText={passwordError} + variant="outlined" + size="small" + /> + + + +
+ + } + /> ); }; diff --git a/frontend/src/component/user/Login/Login.jsx b/frontend/src/component/user/Login/Login.tsx similarity index 76% rename from frontend/src/component/user/Login/Login.jsx rename to frontend/src/component/user/Login/Login.tsx index e1966aae12..29fd52f91f 100644 --- a/frontend/src/component/user/Login/Login.jsx +++ b/frontend/src/component/user/Login/Login.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import AuthenticationContainer from '../authentication-container'; +import AuthenticationContainer from '../Authentication'; import ConditionallyRender from '../../common/ConditionallyRender'; import { useStyles } from './Login.styles'; @@ -8,22 +8,21 @@ import useQueryParams from '../../../hooks/useQueryParams'; import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess'; import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout'; import { DEMO_TYPE } from '../../../constants/authTypes'; +import useUser from '../../../hooks/api/getters/useUser/useUser'; +import { useHistory } from 'react-router'; -const Login = ({ history, user, fetchUser }) => { +const Login = () => { const styles = useStyles(); + const { permissions, authDetails } = useUser(); const query = useQueryParams(); + const history = useHistory(); useEffect(() => { - fetchUser(); - /* eslint-disable-next-line */ - }, []); - - useEffect(() => { - if (user.permissions.length > 0) { + if (permissions?.length > 0) { history.push('features'); } /* eslint-disable-next-line */ - }, [user.permissions]); + }, [permissions.length]); const resetPassword = query.get('reset') === 'true'; @@ -31,7 +30,7 @@ const Login = ({ history, user, fetchUser }) => {
Login to continue the great work diff --git a/frontend/src/component/user/Login/index.js b/frontend/src/component/user/Login/index.js deleted file mode 100644 index 149911f048..0000000000 --- a/frontend/src/component/user/Login/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { connect } from 'react-redux'; -import { fetchUser } from '../../../store/user/actions'; -import Login from './Login'; - -const mapStateToProps = state => ({ - user: state.user.toJS(), - flags: state.uiConfig.toJS().flags, -}); - -const mapDispatchToProps = { - fetchUser, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx index d79ef045fc..c9f0432439 100644 --- a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx +++ b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx @@ -79,59 +79,67 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => { const { usernameError, passwordError, apiError } = errors; return ( -
- - {apiError} - - } - /> + + + {apiError} + + } + /> -
- setUsername(evt.target.value)} - value={username} - error={!!usernameError} - helperText={usernameError} - variant="outlined" - autoComplete="true" - size="small" - data-test={LOGIN_EMAIL_ID} - /> - setPassword(evt.target.value)} - name="password" - type="password" - value={password} - error={!!passwordError} - helperText={passwordError} - variant="outlined" - autoComplete="true" - size="small" - data-test={LOGIN_PASSWORD_ID} - /> - -
- +
+ setUsername(evt.target.value)} + value={username} + error={!!usernameError} + helperText={usernameError} + variant="outlined" + autoComplete="true" + size="small" + data-test={LOGIN_EMAIL_ID} + /> + setPassword(evt.target.value)} + name="password" + type="password" + value={password} + error={!!passwordError} + helperText={passwordError} + variant="outlined" + autoComplete="true" + size="small" + data-test={LOGIN_PASSWORD_ID} + /> + +
+ + } + /> ); }; diff --git a/frontend/src/component/user/authentication-component.jsx b/frontend/src/component/user/authentication-component.jsx deleted file mode 100644 index 37d7c86041..0000000000 --- a/frontend/src/component/user/authentication-component.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import SimpleAuth from './SimpleAuth/SimpleAuth'; -import AuthenticationCustomComponent from './authentication-custom-component'; -import PasswordAuth from './PasswordAuth/PasswordAuth'; -import HostedAuth from './HostedAuth/HostedAuth'; -import DemoAuth from './DemoAuth'; - -import { - SIMPLE_TYPE, - DEMO_TYPE, - PASSWORD_TYPE, - HOSTED_TYPE, -} from '../../constants/authTypes'; -import SecondaryLoginActions from './common/SecondaryLoginActions/SecondaryLoginActions'; - -class AuthComponent extends React.Component { - static propTypes = { - user: PropTypes.object.isRequired, - demoLogin: PropTypes.func.isRequired, - insecureLogin: PropTypes.func.isRequired, - passwordLogin: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - }; - - render() { - const authDetails = this.props.user.authDetails; - - if (!authDetails) return null; - - let content; - if (authDetails.type === PASSWORD_TYPE) { - content = ( - <> - - - - ); - } else if (authDetails.type === SIMPLE_TYPE) { - content = ( - - ); - } else if (authDetails.type === DEMO_TYPE) { - content = ( - - ); - } else if (authDetails.type === HOSTED_TYPE) { - content = ( - <> - - - - ); - } else { - content = ( - - ); - } - return <>{content}; - } -} - -export default AuthComponent; diff --git a/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts b/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts index 4222588146..5d5a86f8fe 100644 --- a/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts +++ b/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts @@ -1,6 +1,8 @@ const handleErrorResponses = (target: string) => async (res: Response) => { if (!res.ok) { - const error = new Error(`An error occurred while trying to get ${target}`); + const error = new Error( + `An error occurred while trying to get ${target}` + ); // Try to resolve body, but don't rethrow res.json is not a function try { // @ts-ignore @@ -16,6 +18,6 @@ const handleErrorResponses = (target: string) => async (res: Response) => { throw error; } return res; -} +}; export default handleErrorResponses; diff --git a/frontend/src/hooks/api/getters/useUser/useUser.ts b/frontend/src/hooks/api/getters/useUser/useUser.ts index 69e855ca18..d6178a5fec 100644 --- a/frontend/src/hooks/api/getters/useUser/useUser.ts +++ b/frontend/src/hooks/api/getters/useUser/useUser.ts @@ -4,20 +4,23 @@ import { formatApiPath } from '../../../../utils/format-path'; import { IPermission } from '../../../../interfaces/user'; import handleErrorResponses from '../httpErrorResponseHandler'; +export const USER_CACHE_KEY = `api/admin/user`; + const useUser = () => { - const KEY = `api/admin/user`; const fetcher = () => { const path = formatApiPath(`api/admin/user`); return fetch(path, { method: 'GET', - }).then(handleErrorResponses('User info')).then(res => res.json()); + }) + .then(handleErrorResponses('User info')) + .then(res => res.json()); }; - const { data, error } = useSWR(KEY, fetcher); + const { data, error } = useSWR(USER_CACHE_KEY, fetcher); const [loading, setLoading] = useState(!error && !data); const refetch = () => { - mutate(KEY); + mutate(USER_CACHE_KEY); }; useEffect(() => { @@ -28,6 +31,7 @@ const useUser = () => { user: data?.user || {}, permissions: (data?.permissions || []) as IPermission[], feedback: data?.feedback || [], + authDetails: data || {}, error, loading, refetch, diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index eceea903ea..462b9f0e01 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -15,11 +15,10 @@ import { StylesProvider } from '@material-ui/core/styles'; import mainTheme from './themes/main-theme'; import store from './store'; -import MetricsPoller from './metrics-poller'; import App from './component/AppContainer'; import ScrollToTop from './component/scroll-to-top'; import { writeWarning } from './security-logger'; -import AccessProvider from './component/AccessProvider/AccessProvider'; +import AccessProvider from './component/providers/AccessProvider/AccessProvider'; import { getBasePath } from './utils/format-path'; let composeEnhancers; @@ -38,8 +37,6 @@ const unleashStore = createStore( store, composeEnhancers(applyMiddleware(thunkMiddleware)) ); -const metricsPoller = new MetricsPoller(unleashStore); -metricsPoller.start(); ReactDOM.render( diff --git a/frontend/src/metrics-poller.js b/frontend/src/metrics-poller.js deleted file mode 100644 index 940e6b4c73..0000000000 --- a/frontend/src/metrics-poller.js +++ /dev/null @@ -1,31 +0,0 @@ -import { fetchFeatureMetrics } from './store/feature-metrics/actions'; - -class MetricsPoller { - constructor(store) { - this.store = store; - this.timer = undefined; - } - - start() { - this.store.subscribe(() => { - const features = this.store.getState().features; - if (!this.timer && features.size > 0) { - this.timer = setInterval(this.fetchMetrics.bind(this), 5000); - this.fetchMetrics(); - } - }); - } - - fetchMetrics() { - this.store.dispatch(fetchFeatureMetrics()); - } - - destroy() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = undefined; - } - } -} - -export default MetricsPoller; diff --git a/frontend/src/page/admin/auth/google-auth.jsx b/frontend/src/page/admin/auth/google-auth.jsx index 73a824ea75..cec55b9145 100644 --- a/frontend/src/page/admin/auth/google-auth.jsx +++ b/frontend/src/page/admin/auth/google-auth.jsx @@ -1,10 +1,16 @@ import React, { useState, useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; -import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core'; +import { + Button, + FormControlLabel, + Grid, + Switch, + TextField, +} from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import PageContent from '../../../component/common/PageContent/PageContent'; import AccessContext from '../../../contexts/AccessContext'; -import { ADMIN } from '../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../component/providers/AccessProvider/permissions'; const initialState = { enabled: false, @@ -93,12 +99,14 @@ function GoogleAuth({ } + control={ + + } label={data.enabled ? 'Enabled' : 'Disabled'} /> diff --git a/frontend/src/page/admin/auth/oidc-auth.jsx b/frontend/src/page/admin/auth/oidc-auth.jsx index b0e2a92e8c..dafd4a9207 100644 --- a/frontend/src/page/admin/auth/oidc-auth.jsx +++ b/frontend/src/page/admin/auth/oidc-auth.jsx @@ -1,10 +1,16 @@ import React, { useState, useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; -import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core'; +import { + Button, + FormControlLabel, + Grid, + Switch, + TextField, +} from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import PageContent from '../../../component/common/PageContent/PageContent'; import AccessContext from '../../../contexts/AccessContext'; -import { ADMIN } from '../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../component/providers/AccessProvider/permissions'; import AutoCreateForm from './AutoCreateForm/AutoCreateForm'; const initialState = { @@ -57,7 +63,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { ...data, [field]: value, }); - } + }; const onSubmit = async e => { e.preventDefault(); @@ -100,12 +106,14 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { } + control={ + + } label={data.enabled ? 'Enabled' : 'Disabled'} /> @@ -125,7 +133,6 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { style={{ width: '400px' }} variant="outlined" size="small" - /> @@ -151,7 +158,9 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { Client secret -

(Required) Client secret of your OpenID application.

+

+ (Required) Client secret of your OpenID application.{' '} +

(Optional) Enable Single Sign-Out -

If you enable Single Sign-Out Unleash will redirect the user to the IDP as part of the Sign-out process.

+

+ If you enable Single Sign-Out Unleash will redirect + the user to the IDP as part of the Sign-out process. +

} - label={data.enableSingleSignOut ? 'Enabled' : 'Disabled'} + control={ + + } + label={ + data.enableSingleSignOut + ? 'Enabled' + : 'Disabled' + } />
@@ -199,7 +217,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { Save {' '} {info} - {error} + {error}
diff --git a/frontend/src/page/admin/auth/saml-auth.jsx b/frontend/src/page/admin/auth/saml-auth.jsx index 10dd50c467..d0d703cb79 100644 --- a/frontend/src/page/admin/auth/saml-auth.jsx +++ b/frontend/src/page/admin/auth/saml-auth.jsx @@ -1,10 +1,16 @@ import React, { useState, useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; -import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core'; +import { + Button, + FormControlLabel, + Grid, + Switch, + TextField, +} from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import PageContent from '../../../component/common/PageContent/PageContent'; import AccessContext from '../../../contexts/AccessContext'; -import { ADMIN } from '../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../component/providers/AccessProvider/permissions'; import AutoCreateForm from './AutoCreateForm/AutoCreateForm'; const initialState = { @@ -51,7 +57,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { ...data, [field]: value, }); - } + }; const onSubmit = async e => { e.preventDefault(); @@ -92,12 +98,14 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { } + control={ + + } label={data.enabled ? 'Enabled' : 'Disabled'} /> @@ -136,7 +144,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { name="signOnUrl" value={data.signOnUrl || ''} disabled={!data.enabled} - style={{ width: '400px'}} + style={{ width: '400px' }} variant="outlined" size="small" required @@ -158,12 +166,13 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { name="certificate" value={data.certificate || ''} disabled={!data.enabled} - style={{width: '100%'}} + style={{ width: '100%' }} InputProps={{ style: { fontSize: '0.6em', fontFamily: 'monospace', - }}} + }, + }} multiline rows={14} rowsMax={14} @@ -189,7 +198,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { name="signOutUrl" value={data.signOutUrl || ''} disabled={!data.enabled} - style={{ width: '400px'}} + style={{ width: '400px' }} variant="outlined" size="small" /> @@ -199,8 +208,10 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { Service Provider X.509 Certificate

- (Optional) The private certificate used by the Service Provider used to sign the SAML 2.0 - request towards the IDP. E.g. used to sign single logout requests (SLO). + (Optional) The private certificate used by the + Service Provider used to sign the SAML 2.0 request + towards the IDP. E.g. used to sign single logout + requests (SLO).

@@ -210,12 +221,13 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { name="spCertificate" value={data.spCertificate || ''} disabled={!data.enabled} - style={{width: '100%'}} + style={{ width: '100%' }} InputProps={{ style: { fontSize: '0.6em', fontFamily: 'monospace', - }}} + }, + }} multiline rows={14} rowsMax={14} diff --git a/frontend/src/page/admin/invoice/index.js b/frontend/src/page/admin/invoice/index.js index c6e79ad81d..43ea093daf 100644 --- a/frontend/src/page/admin/invoice/index.js +++ b/frontend/src/page/admin/invoice/index.js @@ -2,7 +2,7 @@ import { useContext } from 'react'; import PropTypes from 'prop-types'; import InvoiceList from './invoice-container'; import AccessContext from '../../../contexts/AccessContext'; -import { ADMIN } from '../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../component/providers/AccessProvider/permissions'; import ConditionallyRender from '../../../component/common/ConditionallyRender'; import { Alert } from '@material-ui/lab'; @@ -13,17 +13,13 @@ const InvoiceAdminPage = ({ history }) => {
- } + show={} elseShow={ You need to be instance admin to access this section. } - /> -
); }; diff --git a/frontend/src/page/admin/users/UsersList/UserListItem/UserListItem.tsx b/frontend/src/page/admin/users/UsersList/UserListItem/UserListItem.tsx index cf791b93fe..db130f87e9 100644 --- a/frontend/src/page/admin/users/UsersList/UserListItem/UserListItem.tsx +++ b/frontend/src/page/admin/users/UsersList/UserListItem/UserListItem.tsx @@ -7,7 +7,7 @@ import { } from '@material-ui/core'; import { Edit, Lock, Delete } from '@material-ui/icons'; import { SyntheticEvent, useContext } from 'react'; -import { ADMIN } from '../../../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../../../component/providers/AccessProvider/permissions'; import ConditionallyRender from '../../../../../component/common/ConditionallyRender'; import { formatDateWithLocale } from '../../../../../component/common/util'; import AccessContext from '../../../../../contexts/AccessContext'; diff --git a/frontend/src/page/admin/users/UsersList/UsersList.jsx b/frontend/src/page/admin/users/UsersList/UsersList.jsx index 980135434b..e85a23c786 100644 --- a/frontend/src/page/admin/users/UsersList/UsersList.jsx +++ b/frontend/src/page/admin/users/UsersList/UsersList.jsx @@ -14,7 +14,7 @@ import UpdateUser from '../update-user-component'; import DelUser from '../del-user-component'; import ConditionallyRender from '../../../../component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from '../../../../contexts/AccessContext'; -import { ADMIN } from '../../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../../component/providers/AccessProvider/permissions'; import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded'; import useUsers from '../../../../hooks/api/getters/useUsers/useUsers'; import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; diff --git a/frontend/src/page/admin/users/index.js b/frontend/src/page/admin/users/index.js index c3e081146a..bd175339ac 100644 --- a/frontend/src/page/admin/users/index.js +++ b/frontend/src/page/admin/users/index.js @@ -5,7 +5,7 @@ import AdminMenu from '../admin-menu'; import PageContent from '../../../component/common/PageContent/PageContent'; import AccessContext from '../../../contexts/AccessContext'; import ConditionallyRender from '../../../component/common/ConditionallyRender'; -import { ADMIN } from '../../../component/AccessProvider/permissions'; +import { ADMIN } from '../../../component/providers/AccessProvider/permissions'; import { Alert } from '@material-ui/lab'; import HeaderTitle from '../../../component/common/HeaderTitle'; import { Button } from '@material-ui/core'; diff --git a/frontend/src/page/history/index.js b/frontend/src/page/history/index.js index 7ca21fa67a..a5f86a8c30 100644 --- a/frontend/src/page/history/index.js +++ b/frontend/src/page/history/index.js @@ -1,6 +1,6 @@ import { Alert } from '@material-ui/lab'; import React, { useContext } from 'react'; -import { ADMIN } from '../../component/AccessProvider/permissions'; +import { ADMIN } from '../../component/providers/AccessProvider/permissions'; import ConditionallyRender from '../../component/common/ConditionallyRender'; import HistoryComponent from '../../component/history/EventHistory'; import AccessContext from '../../contexts/AccessContext'; diff --git a/frontend/src/utils/project-filter-generator.ts b/frontend/src/utils/project-filter-generator.ts index d8c6dc101f..3e271fd478 100644 --- a/frontend/src/utils/project-filter-generator.ts +++ b/frontend/src/utils/project-filter-generator.ts @@ -1,4 +1,4 @@ -import { ADMIN } from '../component/AccessProvider/permissions'; +import { ADMIN } from '../component/providers/AccessProvider/permissions'; import IAuthStatus, { IPermission } from '../interfaces/user'; type objectIdx = { @@ -24,6 +24,6 @@ export const projectFilterGenerator = ( {} ); return (projectId: string) => { - return admin || permissionMap[projectId] + return admin || permissionMap[projectId]; }; };