diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx index 4b75a07523..e8ff881547 100644 --- a/frontend/src/component/App.tsx +++ b/frontend/src/component/App.tsx @@ -1,4 +1,6 @@ -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { createElement } from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS'; import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker'; import Loader from 'component/common/Loader/Loader'; @@ -6,12 +8,12 @@ import NotFound from 'component/common/NotFound/NotFound'; import ProtectedRoute from 'component/common/ProtectedRoute/ProtectedRoute'; import SWRProvider from 'component/providers/SWRProvider/SWRProvider'; import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer'; -import styles from 'component/styles.module.scss'; -import { Redirect, Route, Switch } from 'react-router-dom'; import { routes } from 'component/menu/routes'; import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect'; +import { IRoute } from 'interfaces/route'; +import styles from 'component/styles.module.scss'; export const App = () => { const { authDetails } = useAuthDetails(); @@ -23,8 +25,7 @@ export const App = () => { return !isLoggedIn; }; - // Change this to IRoute once snags with HashRouter and TS is worked out - const renderRoute = (route: any) => { + const renderRoute = (route: IRoute) => { if (route.type === 'protected') { const unauthorized = isUnauthorized(); return ( @@ -40,13 +41,7 @@ export const App = () => { ( - - )} + render={() => createElement(route.component, {}, null)} /> ); }; @@ -65,8 +60,9 @@ export const App = () => { exact path="/" unauthorized={isUnauthorized()} - component={Redirect} - renderProps={{ to: '/features' }} + component={() => ( + + )} /> {routes.map(renderRoute)} diff --git a/frontend/src/component/Reporting/ReportCard/ReportCard.tsx b/frontend/src/component/Reporting/ReportCard/ReportCard.tsx index 671b7c59c1..db3251126f 100644 --- a/frontend/src/component/Reporting/ReportCard/ReportCard.tsx +++ b/frontend/src/component/Reporting/ReportCard/ReportCard.tsx @@ -2,7 +2,7 @@ import classnames from 'classnames'; import { Paper } from '@material-ui/core'; import CheckIcon from '@material-ui/icons/Check'; import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import styles from './ReportCard.module.scss'; import ReactTimeAgo from 'react-timeago'; import { IProjectHealthReport } from 'interfaces/project'; diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.tsx similarity index 77% rename from frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx rename to frontend/src/component/Reporting/ReportToggleList/ReportToggleList.tsx index 2d394a46e9..b14cfa9885 100644 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.tsx @@ -1,25 +1,34 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, VFC } from 'react'; import { Paper, MenuItem } from '@material-ui/core'; -import PropTypes from 'prop-types'; -import ReportToggleListItem from './ReportToggleListItem/ReportToggleListItem'; -import ReportToggleListHeader from './ReportToggleListHeader/ReportToggleListHeader'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useFeaturesSort } from 'hooks/useFeaturesSort'; +import { IFeatureToggleListItem } from 'interfaces/featureToggle'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu'; import { getObjectProperties, getCheckedState, applyCheckedToFeatures, } from '../utils'; +import { ReportToggleListItem } from './ReportToggleListItem/ReportToggleListItem'; +import { ReportToggleListHeader } from './ReportToggleListHeader/ReportToggleListHeader'; import { useStyles } from './ReportToggleList.styles'; -import { useFeaturesSort } from 'hooks/useFeaturesSort'; /* FLAG TO TOGGLE UNFINISHED BULK ACTIONS FEATURE */ const BULK_ACTIONS_ON = false; -const ReportToggleList = ({ features, selectedProject }) => { +interface IReportToggleListProps { + selectedProject: string; + features: IFeatureToggleListItem[]; +} + +export const ReportToggleList: VFC = ({ + features, + selectedProject, +}) => { const styles = useStyles(); const [checkAll, setCheckAll] = useState(false); - const [localFeatures, setFeatures] = useState([]); + const [localFeatures, setFeatures] = useState([]); + // @ts-expect-error const { setSort, sorted } = useFeaturesSort(localFeatures); useEffect(() => { @@ -32,10 +41,12 @@ const ReportToggleList = ({ features, selectedProject }) => { 'stale', 'type' ), + // @ts-expect-error checked: getCheckedState(feature.name, features), setFeatures, })); + // @ts-expect-error setFeatures(formattedFeatures); }, [features, selectedProject]); @@ -50,6 +61,7 @@ const ReportToggleList = ({ features, selectedProject }) => { const renderListRows = () => sorted.map(feature => ( + // @ts-expect-error { @@ -95,10 +108,3 @@ const ReportToggleList = ({ features, selectedProject }) => { ); }; - -ReportToggleList.propTypes = { - selectedProject: PropTypes.string.isRequired, - features: PropTypes.array.isRequired, -}; - -export default ReportToggleList; diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.tsx similarity index 73% rename from frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx rename to frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.tsx index e14fb82e0f..d799b81bbe 100644 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.tsx @@ -1,25 +1,27 @@ -import React from 'react'; +import { Dispatch, SetStateAction, VFC } from 'react'; import { Checkbox } from '@material-ui/core'; import UnfoldMoreOutlinedIcon from '@material-ui/icons/UnfoldMoreOutlined'; -import PropTypes from 'prop-types'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; -import { - NAME, - LAST_SEEN, - CREATED, - EXPIRED, - STATUS, -} from 'component/Reporting/constants'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ReportingSortType } from 'component/Reporting/constants'; import { useStyles } from '../ReportToggleList.styles'; -const ReportToggleListHeader = ({ +interface IReportToggleListHeaderProps { + checkAll: boolean; + setSort: Dispatch< + SetStateAction<{ type: ReportingSortType; desc?: boolean }> + >; + bulkActionsOn: boolean; + handleCheckAll: () => void; +} + +export const ReportToggleListHeader: VFC = ({ handleCheckAll, checkAll, setSort, bulkActionsOn, }) => { const styles = useStyles(); - const handleSort = type => { + const handleSort = (type: ReportingSortType) => { setSort(prev => ({ type, desc: !prev.desc, @@ -47,7 +49,7 @@ const ReportToggleListHeader = ({ role="button" tabIndex={0} style={{ width: '150px' }} - onClick={() => handleSort(NAME)} + onClick={() => handleSort('name')} > Name @@ -56,7 +58,7 @@ const ReportToggleListHeader = ({ role="button" className={styles.hideColumnLastSeen} tabIndex={0} - onClick={() => handleSort(LAST_SEEN)} + onClick={() => handleSort('last-seen')} > Last seen @@ -65,7 +67,7 @@ const ReportToggleListHeader = ({ role="button" tabIndex={0} className={styles.hideColumn} - onClick={() => handleSort(CREATED)} + onClick={() => handleSort('created')} > Created @@ -74,7 +76,7 @@ const ReportToggleListHeader = ({ role="button" tabIndex={0} className={styles.hideColumn} - onClick={() => handleSort(EXPIRED)} + onClick={() => handleSort('expired')} > Expired @@ -83,7 +85,7 @@ const ReportToggleListHeader = ({ role="button" tabIndex={0} className={styles.hideColumnStatus} - onClick={() => handleSort(STATUS)} + onClick={() => handleSort('status')} > Status @@ -91,7 +93,7 @@ const ReportToggleListHeader = ({ handleSort(EXPIRED)} + onClick={() => handleSort('expired')} > Report @@ -100,12 +102,3 @@ const ReportToggleListHeader = ({ ); }; - -ReportToggleListHeader.propTypes = { - checkAll: PropTypes.bool.isRequired, - setSort: PropTypes.func.isRequired, - bulkActionsOn: PropTypes.bool.isRequired, - handleCheckAll: PropTypes.func.isRequired, -}; - -export default ReportToggleListHeader; diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx deleted file mode 100644 index ed021876c4..0000000000 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import classnames from 'classnames'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; -import { Checkbox } from '@material-ui/core'; -import CheckIcon from '@material-ui/icons/Check'; -import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; -import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; -import { - pluralize, - getDates, - expired, - toggleExpiryByTypeMap, - getDiffInDays, -} from 'component/Reporting/utils'; -import { KILLSWITCH, PERMISSION } from 'constants/featureToggleTypes'; -import { useStyles } from '../ReportToggleList.styles'; -import { getTogglePath } from 'utils/routePathHelpers'; - -const ReportToggleListItem = ({ - name, - stale, - lastSeenAt, - createdAt, - project, - type, - checked, - bulkActionsOn, - setFeatures, -}) => { - const styles = useStyles(); - const nameMatches = feature => feature.name === name; - - const handleChange = () => { - setFeatures(prevState => { - const newState = [...prevState]; - - return newState.map(feature => { - if (nameMatches(feature)) { - return { ...feature, checked: !feature.checked }; - } - return feature; - }); - }); - }; - - const formatCreatedAt = () => { - const [date, now] = getDates(createdAt); - - const diff = getDiffInDays(date, now); - if (diff === 0) return '1 day'; - - const formatted = pluralize(diff, 'day'); - - return `${formatted} ago`; - }; - - const formatExpiredAt = () => { - if (type === KILLSWITCH || type === PERMISSION) { - return 'N/A'; - } - - const [date, now] = getDates(createdAt); - const diff = getDiffInDays(date, now); - - if (expired(diff, type)) { - const result = diff - toggleExpiryByTypeMap[type]; - if (result === 0) return '1 day'; - - return pluralize(result, 'day'); - } - return 'N/A'; - }; - - const formatLastSeenAt = () => { - return ( - - ); - }; - - const renderStatus = (icon, text) => ( - - {icon} - {text} - - ); - - const formatReportStatus = () => { - if (type === KILLSWITCH || type === PERMISSION) { - return renderStatus( - , - 'Healthy' - ); - } - - const [date, now] = getDates(createdAt); - const diff = getDiffInDays(date, now); - - if (expired(diff, type)) { - return renderStatus( - , - 'Potentially stale' - ); - } - - return renderStatus( - , - 'Healthy' - ); - }; - - const statusClasses = classnames(styles.active, styles.hideColumnStatus, { - [styles.stale]: stale, - }); - - return ( - - - - - } - /> - - - {name} - - - {formatLastSeenAt()} - {formatCreatedAt()} - - {formatExpiredAt()} - - {stale ? 'Stale' : 'Active'} - {formatReportStatus()} - - ); -}; - -ReportToggleListItem.propTypes = { - name: PropTypes.string.isRequired, - stale: PropTypes.bool.isRequired, - lastSeenAt: PropTypes.string, - createdAt: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - checked: PropTypes.bool.isRequired, - bulkActionsOn: PropTypes.bool.isRequired, - setFeatures: PropTypes.func.isRequired, -}; - -export default React.memo(ReportToggleListItem); diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.tsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.tsx new file mode 100644 index 0000000000..5fe410225f --- /dev/null +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.tsx @@ -0,0 +1,173 @@ +import { memo, ReactNode } from 'react'; +import classnames from 'classnames'; +import { Link } from 'react-router-dom'; +import { Checkbox } from '@material-ui/core'; +import CheckIcon from '@material-ui/icons/Check'; +import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; +import { + pluralize, + getDates, + expired, + toggleExpiryByTypeMap, + getDiffInDays, +} from 'component/Reporting/utils'; +import { KILLSWITCH, PERMISSION } from 'constants/featureToggleTypes'; +import { useStyles } from '../ReportToggleList.styles'; +import { getTogglePath } from 'utils/routePathHelpers'; + +interface IReportToggleListItemProps { + name: string; + stale: boolean; + project: string; + lastSeenAt?: string; + createdAt: string; + type: string; + checked: boolean; + bulkActionsOn: boolean; + setFeatures: () => void; +} + +export const ReportToggleListItem = memo( + ({ + name, + stale, + lastSeenAt, + createdAt, + project, + type, + checked, + bulkActionsOn, + setFeatures, + }) => { + const styles = useStyles(); + const nameMatches = (feature: { name: string }) => + feature.name === name; + + const handleChange = () => { + // @ts-expect-error + setFeatures(prevState => { + const newState = [...prevState]; + + return newState.map(feature => { + if (nameMatches(feature)) { + return { ...feature, checked: !feature.checked }; + } + return feature; + }); + }); + }; + + const formatCreatedAt = () => { + const [date, now] = getDates(createdAt); + + const diff = getDiffInDays(date, now); + if (diff === 0) return '1 day'; + + const formatted = pluralize(diff, 'day'); + + return `${formatted} ago`; + }; + + const formatExpiredAt = () => { + if (type === KILLSWITCH || type === PERMISSION) { + return 'N/A'; + } + + const [date, now] = getDates(createdAt); + const diff = getDiffInDays(date, now); + + if (expired(diff, type)) { + const result = diff - toggleExpiryByTypeMap[type]; + if (result === 0) return '1 day'; + + return pluralize(result, 'day'); + } + return 'N/A'; + }; + + const formatLastSeenAt = () => { + return ( + + ); + }; + + const renderStatus = (icon: ReactNode, text: ReactNode) => ( + + {icon} + {text} + + ); + + const formatReportStatus = () => { + if (type === KILLSWITCH || type === PERMISSION) { + return renderStatus( + , + 'Healthy' + ); + } + + const [date, now] = getDates(createdAt); + const diff = getDiffInDays(date, now); + + if (expired(diff, type)) { + return renderStatus( + , + 'Potentially stale' + ); + } + + return renderStatus( + , + 'Healthy' + ); + }; + + const statusClasses = classnames( + styles.active, + styles.hideColumnStatus, + { + [styles.stale]: stale, + } + ); + + return ( + + + + + } + /> + + + {name} + + + + {formatLastSeenAt()} + + {formatCreatedAt()} + + {formatExpiredAt()} + + {stale ? 'Stale' : 'Active'} + {formatReportStatus()} + + ); + } +); diff --git a/frontend/src/component/Reporting/constants.ts b/frontend/src/component/Reporting/constants.ts index 043590e411..775a4651b4 100644 --- a/frontend/src/component/Reporting/constants.ts +++ b/frontend/src/component/Reporting/constants.ts @@ -1,11 +1,7 @@ -/* SORT TYPES */ -export const NAME = 'name'; -export const LAST_SEEN = 'last-seen'; -export const CREATED = 'created'; -export const EXPIRED = 'expired'; -export const STATUS = 'status'; -export const REPORT = 'report'; - -/* DAYS */ -export const FOURTYDAYS = 40; -export const SEVENDAYS = 7; +export type ReportingSortType = + | 'name' + | 'last-seen' + | 'created' + | 'expired' + | 'status' + | 'report'; diff --git a/frontend/src/component/Reporting/utils.ts b/frontend/src/component/Reporting/utils.ts index c8785fee75..c1821e3a65 100644 --- a/frontend/src/component/Reporting/utils.ts +++ b/frontend/src/component/Reporting/utils.ts @@ -3,13 +3,15 @@ import differenceInDays from 'date-fns/differenceInDays'; import { EXPERIMENT, OPERATIONAL, RELEASE } from 'constants/featureToggleTypes'; -import { FOURTYDAYS, SEVENDAYS } from './constants'; import { IFeatureToggleListItem } from 'interfaces/featureToggle'; +const FORTY_DAYS = 40; +const SEVEN_DAYS = 7; + export const toggleExpiryByTypeMap: Record = { - [EXPERIMENT]: FOURTYDAYS, - [RELEASE]: FOURTYDAYS, - [OPERATIONAL]: SEVENDAYS, + [EXPERIMENT]: FORTY_DAYS, + [RELEASE]: FORTY_DAYS, + [OPERATIONAL]: SEVEN_DAYS, }; export interface IFeatureToggleListItemCheck extends IFeatureToggleListItem { diff --git a/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx b/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx index 1e7680cb49..e483b9bc5f 100644 --- a/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx +++ b/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx @@ -5,12 +5,12 @@ import { styles as commonStyles } from 'component/common'; import { IAddonProvider } from 'interfaces/addons'; interface IAddonProps { - provider: IAddonProvider; + provider?: IAddonProvider; checkedEvents: string[]; setEventValue: ( name: string ) => (event: React.ChangeEvent) => void; - error: Record; + error?: string; } export const AddonEvents = ({ diff --git a/frontend/src/component/addons/AddonForm/AddonForm.jsx b/frontend/src/component/addons/AddonForm/AddonForm.tsx similarity index 55% rename from frontend/src/component/addons/AddonForm/AddonForm.jsx rename to frontend/src/component/addons/AddonForm/AddonForm.tsx index d4a1e6d3d7..2b86332cae 100644 --- a/frontend/src/component/addons/AddonForm/AddonForm.jsx +++ b/frontend/src/component/addons/AddonForm/AddonForm.tsx @@ -1,8 +1,17 @@ -import { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; +import { + useState, + useEffect, + ChangeEvent, + VFC, + ChangeEventHandler, + FormEventHandler, + MouseEventHandler, +} from 'react'; import { TextField, FormControlLabel, Switch, Button } from '@material-ui/core'; +import produce from 'immer'; import { styles as commonStyles } from 'component/common'; import { trim } from 'component/common/util'; +import { IAddon, IAddonProvider } from 'interfaces/addons'; import { AddonParameters } from './AddonParameters/AddonParameters'; import { AddonEvents } from './AddonEvents/AddonEvents'; import cloneDeep from 'lodash.clonedeep'; @@ -11,22 +20,48 @@ import { useHistory } from 'react-router-dom'; import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; import useToast from 'hooks/useToast'; import { makeStyles } from '@material-ui/core/styles'; +import { formatUnknownError } from 'utils/formatUnknownError'; const useStyles = makeStyles(theme => ({ nameInput: { marginRight: '1.5rem', }, formSection: { padding: '10px 28px' }, + buttonsSection: { + padding: '10px 28px', + '& > *': { + marginRight: theme.spacing(1), + }, + }, })); -export const AddonForm = ({ editMode, provider, addon, fetch }) => { +interface IAddonFormProps { + provider?: IAddonProvider; + addon: IAddon; + fetch: () => void; + editMode: boolean; +} + +export const AddonForm: VFC = ({ + editMode, + provider, + addon: initialValues, + fetch, +}) => { const { createAddon, updateAddon } = useAddonsApi(); const { setToastData, setToastApiError } = useToast(); const history = useHistory(); const styles = useStyles(); - const [config, setConfig] = useState(addon); - const [errors, setErrors] = useState({ + const [formValues, setFormValues] = useState(initialValues); + const [errors, setErrors] = useState<{ + containsErrors: boolean; + parameters: Record; + events?: string; + general?: string; + description?: string; + }>({ + containsErrors: false, parameters: {}, }); const submitText = editMode ? 'Update' : 'Create'; @@ -38,68 +73,73 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { }, [fetch, provider]); // empty array => fetch only first time useEffect(() => { - setConfig({ ...addon }); + setFormValues({ ...initialValues }); /* eslint-disable-next-line */ - }, [addon.description, addon.provider]); + }, [initialValues.description, initialValues.provider]); useEffect(() => { - if (provider && !config.provider) { - setConfig({ ...addon, provider: provider.name }); + if (provider && !formValues.provider) { + setFormValues({ ...initialValues, provider: provider.name }); } - }, [provider, addon, config.provider]); + }, [provider, initialValues, formValues.provider]); - const setFieldValue = field => evt => { - evt.preventDefault(); - const newConfig = { ...config }; - newConfig[field] = evt.target.value; - setConfig(newConfig); + const setFieldValue = + (field: string): ChangeEventHandler => + event => { + event.preventDefault(); + setFormValues({ ...formValues, [field]: event.target.value }); + }; + + const onEnabled: MouseEventHandler = event => { + event.preventDefault(); + setFormValues(({ enabled }) => ({ ...formValues, enabled: !enabled })); }; - const onEnabled = evt => { - evt.preventDefault(); - const enabled = !config.enabled; - setConfig({ ...config, enabled }); - }; + const setParameterValue = + (param: string): ChangeEventHandler => + event => { + event.preventDefault(); + setFormValues( + produce(draft => { + draft.parameters[param] = event.target.value; + }) + ); + }; - const setParameterValue = param => evt => { - evt.preventDefault(); - const newConfig = { ...config }; - newConfig.parameters[param] = evt.target.value; - setConfig(newConfig); - }; - - const setEventValue = name => evt => { - const newConfig = { ...config }; - if (evt.target.checked) { - newConfig.events.push(name); - } else { - newConfig.events = newConfig.events.filter(e => e !== name); - } - setConfig(newConfig); - setErrors({ ...errors, events: undefined }); - }; + const setEventValue = + (name: string) => (event: ChangeEvent) => { + const newConfig = { ...formValues }; + if (event.target.checked) { + newConfig.events.push(name); + } else { + newConfig.events = newConfig.events.filter(e => e !== name); + } + setFormValues(newConfig); + setErrors({ ...errors, events: undefined }); + }; const onCancel = () => { history.goBack(); }; - const onSubmit = async evt => { - evt.preventDefault(); + const onSubmit: FormEventHandler = async event => { + event.preventDefault(); if (!provider) return; const updatedErrors = cloneDeep(errors); updatedErrors.containsErrors = false; // Validations - if (config.events.length === 0) { + if (formValues.events.length === 0) { updatedErrors.events = 'You must listen to at least one event'; updatedErrors.containsErrors = true; } - provider.parameters.forEach(p => { - const value = trim(config.parameters[p.name]); - if (p.required && !value) { - updatedErrors.parameters[p.name] = 'This field is required'; + provider.parameters.forEach(parameterConfig => { + const value = trim(formValues.parameters[parameterConfig.name]); + if (parameterConfig.required && !value) { + updatedErrors.parameters[parameterConfig.name] = + 'This field is required'; updatedErrors.containsErrors = true; } }); @@ -111,14 +151,14 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { try { if (editMode) { - await updateAddon(config); + await updateAddon(formValues); history.push('/addons'); setToastData({ type: 'success', title: 'Addon updated successfully', }); } else { - await createAddon(config); + await createAddon(formValues); history.push('/addons'); setToastData({ type: 'success', @@ -126,11 +166,14 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { title: 'Addon created successfully', }); } - } catch (e) { - setToastApiError({ - text: e.toString(), + } catch (error) { + const message = formatUnknownError(error); + setToastApiError(message); + setErrors({ + parameters: {}, + general: message, + containsErrors: true, }); - setErrors({ parameters: {}, general: e.message }); } }; @@ -138,7 +181,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { name, description, documentationUrl = 'https://unleash.github.io/docs/addons', - } = provider ? provider : {}; + } = provider ? provider : ({} as Partial); return ( @@ -155,7 +198,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { size="small" label="Provider" name="provider" - value={config.provider} + value={formValues.provider} disabled variant="outlined" className={styles.nameInput} @@ -163,24 +206,25 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { } - label={config.enabled ? 'Enabled' : 'Disabled'} + label={formValues.enabled ? 'Enabled' : 'Disabled'} />
@@ -188,7 +232,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
@@ -196,13 +240,13 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
-
+
@@ -214,12 +258,3 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { ); }; - -AddonForm.propTypes = { - provider: PropTypes.object, - addon: PropTypes.object.isRequired, - fetch: PropTypes.func.isRequired, - submit: PropTypes.func.isRequired, - cancel: PropTypes.func.isRequired, - editMode: PropTypes.bool.isRequired, -}; diff --git a/frontend/src/component/addons/AddonForm/AddonParameters/AddonParameter/AddonParameter.tsx b/frontend/src/component/addons/AddonForm/AddonParameters/AddonParameter/AddonParameter.tsx index 9b396492ed..a33fb1a76b 100644 --- a/frontend/src/component/addons/AddonForm/AddonParameters/AddonParameter/AddonParameter.tsx +++ b/frontend/src/component/addons/AddonForm/AddonParameters/AddonParameter/AddonParameter.tsx @@ -1,9 +1,6 @@ import { TextField } from '@material-ui/core'; -import { - IAddonConfig, - IAddonProvider, - IAddonProviderParams, -} from 'interfaces/addons'; +import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons'; +import { ChangeEventHandler } from 'react'; const resolveType = ({ type = 'text', sensitive = false }, value: string) => { if (sensitive && value === MASKED_VALUE) { @@ -17,31 +14,29 @@ const resolveType = ({ type = 'text', sensitive = false }, value: string) => { const MASKED_VALUE = '*****'; -interface IAddonParameterProps { - provider: IAddonProvider; - errors: Record; +export interface IAddonParameterProps { + parametersErrors: Record; definition: IAddonProviderParams; - setParameterValue: (param: string) => void; + setParameterValue: (param: string) => ChangeEventHandler; config: IAddonConfig; } export const AddonParameter = ({ definition, config, - errors, + parametersErrors, setParameterValue, }: IAddonParameterProps) => { const value = config.parameters[definition.name] || ''; const type = resolveType(definition, value); - // @ts-expect-error - const error = errors.parameters[definition.name]; + const error = parametersErrors[definition.name]; return (
; + provider?: IAddonProvider; + parametersErrors: IAddonParameterProps['parametersErrors']; editMode: boolean; - setParameterValue: (param: string) => void; - config: IAddonConfig; + setParameterValue: IAddonParameterProps['setParameterValue']; + config: IAddonParameterProps['config']; } export const AddonParameters = ({ provider, config, - errors, + parametersErrors, setParameterValue, editMode, }: IAddonParametersProps) => { @@ -30,11 +33,10 @@ export const AddonParameters = ({

) : null} {provider.parameters.map(parameter => ( - // @ts-expect-error diff --git a/frontend/src/component/addons/AddonList/AddonList.tsx b/frontend/src/component/addons/AddonList/AddonList.tsx index 01e670807a..1407d3f2a0 100644 --- a/frontend/src/component/addons/AddonList/AddonList.tsx +++ b/frontend/src/component/addons/AddonList/AddonList.tsx @@ -3,7 +3,7 @@ import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; import { AvailableAddons } from './AvailableAddons/AvailableAddons'; import { Avatar } from '@material-ui/core'; import { DeviceHub } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import slackIcon from 'assets/icons/slack.svg'; import jiraIcon from 'assets/icons/jira.svg'; import webhooksIcon from 'assets/icons/webhooks.svg'; diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx index de970024cc..41887a8071 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx @@ -6,7 +6,7 @@ import { ListItemText, } from '@material-ui/core'; import { Delete, Edit, Visibility, VisibilityOff } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { DELETE_ADDON, UPDATE_ADDON, @@ -20,7 +20,7 @@ import { ReactElement, useContext, useState } from 'react'; import AccessContext from 'contexts/AccessContext'; import { IAddon } from 'interfaces/addons'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { formatUnknownError } from 'utils/formatUnknownError'; interface IConfigureAddonsProps { diff --git a/frontend/src/component/addons/CreateAddon/CreateAddon.tsx b/frontend/src/component/addons/CreateAddon/CreateAddon.tsx index 72943851ea..a701db85ac 100644 --- a/frontend/src/component/addons/CreateAddon/CreateAddon.tsx +++ b/frontend/src/component/addons/CreateAddon/CreateAddon.tsx @@ -2,18 +2,19 @@ import { useParams } from 'react-router-dom'; import useAddons from 'hooks/api/getters/useAddons/useAddons'; import { AddonForm } from '../AddonForm/AddonForm'; import cloneDeep from 'lodash.clonedeep'; +import { IAddon } from 'interfaces/addons'; interface IAddonCreateParams { providerId: string; } -const DEFAULT_DATA = { +export const DEFAULT_DATA = { provider: '', description: '', enabled: true, parameters: {}, events: [], -}; +} as unknown as IAddon; // TODO: improve type export const CreateAddon = () => { const { providerId } = useParams(); @@ -31,7 +32,6 @@ export const CreateAddon = () => { }; return ( - // @ts-expect-error { const { addonId } = useParams(); @@ -26,12 +19,10 @@ export const EditAddon = () => { (addon: IAddon) => addon.id === Number(addonId) ) || { ...cloneDeep(DEFAULT_DATA) }; const provider = addon - ? // @ts-expect-error - providers.find(provider => provider.name === addon.provider) + ? providers.find(provider => provider.name === addon.provider) : undefined; return ( - // @ts-expect-error { const { pathname } = useLocation(); diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx index 6b093a51c9..58cadb6161 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx @@ -17,7 +17,7 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox'; import { IAutocompleteBoxOption } from 'component/common/AutocompleteBox/AutocompleteBox'; import { useStyles } from '../ApiTokenForm.styles'; import { SelectAllButton } from './SelectAllButton/SelectAllButton'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; const ALL_PROJECTS = '*'; @@ -107,7 +107,7 @@ export const SelectProjectInput: VFC = ({ const renderInput = (params: AutocompleteRenderInputParams) => ( { } /> - + + ************************************ + diff --git a/frontend/src/component/admin/apiToken/ApiTokenList/secret.jsx b/frontend/src/component/admin/apiToken/ApiTokenList/secret.jsx deleted file mode 100644 index ad53de8897..0000000000 --- a/frontend/src/component/admin/apiToken/ApiTokenList/secret.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types'; -function Secret({ value }) { - return ( -
- - ************************************ - -
- ); -} - -Secret.propTypes = { - value: PropTypes.string, -}; - -export default Secret; diff --git a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx index 4d7563ef6c..5bda1724f8 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx @@ -4,8 +4,8 @@ import { Button } from '@material-ui/core'; import AccessContext from 'contexts/AccessContext'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import PageContent from 'component/common/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { CREATE_API_TOKEN, READ_API_TOKEN, diff --git a/frontend/src/component/admin/apiToken/ConfirmToken/ConfirmToken.tsx b/frontend/src/component/admin/apiToken/ConfirmToken/ConfirmToken.tsx index a11ba3b425..ea592c9a8f 100644 --- a/frontend/src/component/admin/apiToken/ConfirmToken/ConfirmToken.tsx +++ b/frontend/src/component/admin/apiToken/ConfirmToken/ConfirmToken.tsx @@ -1,6 +1,6 @@ import { Typography } from '@material-ui/core'; import { useCommonStyles } from 'themes/commonStyles'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { UserToken } from './UserToken/UserToken'; interface IConfirmUserLink { diff --git a/frontend/src/component/admin/auth/AuthSettings.tsx b/frontend/src/component/admin/auth/AuthSettings.tsx index 6cfea73838..ed3fbf36e0 100644 --- a/frontend/src/component/admin/auth/AuthSettings.tsx +++ b/frontend/src/component/admin/auth/AuthSettings.tsx @@ -2,7 +2,7 @@ import React from 'react'; import AdminMenu from '../menu/AdminMenu'; import { Alert } from '@material-ui/lab'; import PageContent from 'component/common/PageContent/PageContent'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { OidcAuth } from './OidcAuth/OidcAuth'; import { SamlAuth } from './SamlAuth/SamlAuth'; diff --git a/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx b/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx index 3353c01597..94ca9fc9e6 100644 --- a/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx +++ b/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx @@ -104,7 +104,7 @@ export const AutoCreateForm = ({ label="Email domains" name="emailDomains" disabled={!data.autoCreate || !data.enabled} - required={!!data.autoCreate} + required={Boolean(data.autoCreate)} value={data.emailDomains || ''} placeholder="@company.com, @anotherCompany.com" style={{ width: '400px' }} diff --git a/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx b/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx index a0f2a262da..f7b2f17859 100644 --- a/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx +++ b/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react'; import InvoiceList from './InvoiceList'; import AccessContext from 'contexts/AccessContext'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Alert } from '@material-ui/lab'; const InvoiceAdminPage = () => { diff --git a/frontend/src/component/admin/invoice/InvoiceList.tsx b/frontend/src/component/admin/invoice/InvoiceList.tsx index 2bc1d59a66..a6c6030c51 100644 --- a/frontend/src/component/admin/invoice/InvoiceList.tsx +++ b/frontend/src/component/admin/invoice/InvoiceList.tsx @@ -9,8 +9,8 @@ import { } from '@material-ui/core'; import OpenInNew from '@material-ui/icons/OpenInNew'; import PageContent from 'component/common/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { formatApiPath } from 'utils/formatPath'; import useInvoices from 'hooks/api/getters/useInvoices/useInvoices'; import { IInvoice } from 'interfaces/invoice'; diff --git a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx b/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx index 56827861d2..32bcc2f98e 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx @@ -9,7 +9,7 @@ import { import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import { useStyles } from './ProjectRoleForm.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import React, { ReactNode } from 'react'; import { IPermission } from 'interfaces/project'; import { diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.tsx index 0356647915..802aa9be02 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.tsx @@ -1,7 +1,7 @@ import { Alert } from '@material-ui/lab'; import React from 'react'; import { IProjectRole } from 'interfaces/role'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import Input from 'component/common/Input/Input'; import { useStyles } from './ProjectRoleDeleteConfirm.styles'; diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx index 3fef9d2e62..c41ba47f6b 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx @@ -2,8 +2,8 @@ import { Button } from '@material-ui/core'; import { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import AccessContext from 'contexts/AccessContext'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PageContent from 'component/common/PageContent'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import AdminMenu from 'component/admin/menu/AdminMenu'; diff --git a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.tsx b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.tsx index 0eafa574f2..78571202f7 100644 --- a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.tsx +++ b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.tsx @@ -1,5 +1,5 @@ import { Typography } from '@material-ui/core'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { ReactComponent as EmailIcon } from 'assets/icons/email.svg'; import { useStyles } from './ConfirmUserEmail.styles'; diff --git a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserLink/ConfirmUserLink.tsx b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserLink/ConfirmUserLink.tsx index d9ac14cc0a..3e8589ff8f 100644 --- a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserLink/ConfirmUserLink.tsx +++ b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserLink/ConfirmUserLink.tsx @@ -1,7 +1,7 @@ import { Typography } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { useCommonStyles } from 'themes/commonStyles'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import UserInviteLink from './UserInviteLink/UserInviteLink'; interface IConfirmUserLink { diff --git a/frontend/src/component/admin/users/UserForm/UserForm.tsx b/frontend/src/component/admin/users/UserForm/UserForm.tsx index bbd6c701cc..9af8ca3c3a 100644 --- a/frontend/src/component/admin/users/UserForm/UserForm.tsx +++ b/frontend/src/component/admin/users/UserForm/UserForm.tsx @@ -11,7 +11,7 @@ import { import { useStyles } from './UserForm.styles'; import React from 'react'; import useUsers from 'hooks/api/getters/useUsers/useUsers'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { EDIT } from 'constants/misc'; import useUiBootstrap from 'hooks/api/getters/useUiBootstrap/useUiBootstrap'; diff --git a/frontend/src/component/admin/users/UsersAdmin.tsx b/frontend/src/component/admin/users/UsersAdmin.tsx index 03e8210500..1e0a15e4e4 100644 --- a/frontend/src/component/admin/users/UsersAdmin.tsx +++ b/frontend/src/component/admin/users/UsersAdmin.tsx @@ -3,10 +3,10 @@ import UsersList from './UsersList/UsersList'; import AdminMenu from '../menu/AdminMenu'; import PageContent from 'component/common/PageContent/PageContent'; import AccessContext from 'contexts/AccessContext'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import HeaderTitle from 'component/common/HeaderTitle'; import { TableActions } from 'component/common/Table/TableActions/TableActions'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { Button } from '@material-ui/core'; import { useStyles } from './UserAdmin.styles'; import { useHistory } from 'react-router-dom'; diff --git a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx index 49a49861fb..c3fce6bcf5 100644 --- a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx +++ b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx @@ -3,11 +3,11 @@ import classnames from 'classnames'; import { Avatar, TextField, Typography } from '@material-ui/core'; import { trim } from 'component/common/util'; import { modalStyles } from 'component/admin/users/util'; -import Dialogue from 'component/common/Dialogue/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import PasswordChecker from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker'; import { useCommonStyles } from 'themes/commonStyles'; import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Alert } from '@material-ui/lab'; import { IUser } from 'interfaces/user'; diff --git a/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx b/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx index cad956fd50..407249f1f8 100644 --- a/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx +++ b/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Dialogue from 'component/common/Dialogue/Dialogue'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { REMOVE_USER_ERROR } from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; import { Alert } from '@material-ui/lab'; import useLoading from 'hooks/useLoading'; diff --git a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx index be706c5de1..bfae6664e2 100644 --- a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx +++ b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx @@ -10,7 +10,7 @@ import classnames from 'classnames'; import { Delete, Edit, Lock } from '@material-ui/icons'; import { SyntheticEvent, useContext } from 'react'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from 'contexts/AccessContext'; import { IUser } from 'interfaces/user'; import { useHistory } from 'react-router-dom'; diff --git a/frontend/src/component/admin/users/UsersList/UsersList.tsx b/frontend/src/component/admin/users/UsersList/UsersList.tsx index 07d11b2835..794dcf5450 100644 --- a/frontend/src/component/admin/users/UsersList/UsersList.tsx +++ b/frontend/src/component/admin/users/UsersList/UsersList.tsx @@ -10,7 +10,7 @@ import { import classnames from 'classnames'; import ChangePassword from './ChangePassword/ChangePassword'; import DeleteUser from './DeleteUser/DeleteUser'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from 'contexts/AccessContext'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded'; diff --git a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx index da550b4540..53af486e7f 100644 --- a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx +++ b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx @@ -9,13 +9,14 @@ import { Typography, } from '@material-ui/core'; import { Link as LinkIcon } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { UPDATE_APPLICATION } from 'component/providers/AccessProvider/permissions'; import { ApplicationView } from '../ApplicationView/ApplicationView'; import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate'; -import Dialogue from 'component/common/Dialogue'; +import { TabNav } from 'component/common/TabNav/TabNav/TabNav'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import PageContent from 'component/common/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import AccessContext from 'contexts/AccessContext'; import useApplicationsApi from 'hooks/api/actions/useApplicationsApi/useApplicationsApi'; import useApplication from 'hooks/api/getters/useApplication/useApplication'; @@ -25,7 +26,6 @@ import useToast from 'hooks/useToast'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import { formatDateYMD } from 'utils/formatDate'; import { formatUnknownError } from 'utils/formatUnknownError'; -import { TabNav } from 'component/common/TabNav/TabNav/TabNav'; export const ApplicationEdit = () => { const history = useHistory(); @@ -93,7 +93,7 @@ export const ApplicationEdit = () => { { {appName} } + title={appName} actions={ <> { const { applications, loading } = useApplications(); diff --git a/frontend/src/component/application/ApplicationView/ApplicationView.tsx b/frontend/src/component/application/ApplicationView/ApplicationView.tsx index 0fe60dbca6..58cf144998 100644 --- a/frontend/src/component/application/ApplicationView/ApplicationView.tsx +++ b/frontend/src/component/application/ApplicationView/ApplicationView.tsx @@ -19,7 +19,7 @@ import { CREATE_FEATURE, CREATE_STRATEGY, } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { getTogglePath } from 'utils/routePathHelpers'; import useApplication from 'hooks/api/getters/useApplication/useApplication'; import AccessContext from 'contexts/AccessContext'; diff --git a/frontend/src/component/archive/ArchiveListContainer.tsx b/frontend/src/component/archive/ArchiveListContainer.tsx index b345294d6d..139021b564 100644 --- a/frontend/src/component/archive/ArchiveListContainer.tsx +++ b/frontend/src/component/archive/ArchiveListContainer.tsx @@ -1,5 +1,5 @@ import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; -import FeatureToggleList from '../feature/FeatureToggleList/FeatureToggleList'; +import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleList'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useFeaturesFilter } from 'hooks/useFeaturesFilter'; import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; @@ -20,7 +20,7 @@ export const ArchiveListContainer = () => { const { filtered, filter, setFilter } = useFeaturesFilter(archivedFeatures); const { sorted, sort, setSort } = useFeaturesSort(filtered); - const revive = (feature: string) => { + const onRevive = (feature: string) => { reviveFeature(feature) .then(refetchArchived) .then(() => @@ -38,13 +38,13 @@ export const ArchiveListContainer = () => { ); }; diff --git a/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx b/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx index 4da5fee6b7..e05f212a43 100644 --- a/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx +++ b/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef, FC } from 'react'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IAnimateOnMountProps { mounted: boolean; diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx index 0cb6e401fa..e1c3214d4b 100644 --- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx +++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx @@ -1,6 +1,6 @@ import Breadcrumbs from '@material-ui/core/Breadcrumbs'; import { Link, useLocation } from 'react-router-dom'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './BreadcrumbNav.styles'; import AccessContext from 'contexts/AccessContext'; import { useContext } from 'react'; diff --git a/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx b/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx index da6ae55a30..b87028d087 100644 --- a/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx +++ b/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx @@ -15,7 +15,7 @@ type TargetElement = type RenderFunc = () => JSX.Element; -const ConditionallyRender = ({ +export const ConditionallyRender = ({ condition, show, elseShow, @@ -51,5 +51,3 @@ const ConditionallyRender = ({ } return null; }; - -export default ConditionallyRender; diff --git a/frontend/src/component/common/ConditionallyRender/index.jsx b/frontend/src/component/common/ConditionallyRender/index.jsx deleted file mode 100644 index 474cd34e84..0000000000 --- a/frontend/src/component/common/ConditionallyRender/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import ConditionallyRender from './ConditionallyRender'; - -export default ConditionallyRender; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx index 982202862e..c6abd4e958 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx @@ -1,5 +1,5 @@ import { IConstraint } from 'interfaces/strategy'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConstraintAccordionEdit } from './ConstraintAccordionEdit/ConstraintAccordionEdit'; import { ConstraintAccordionView } from './ConstraintAccordionView/ConstraintAccordionView'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx index bdb454f53c..93c9ecc0b6 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx @@ -6,7 +6,7 @@ import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeade import { useStyles } from './ConstraintAccordionEditBody.styles'; import React from 'react'; import { newOperators } from 'constants/operators'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { oneOf } from 'utils/oneOf'; import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/RestrictiveLegalValues/RestrictiveLegalValues.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/RestrictiveLegalValues/RestrictiveLegalValues.tsx index 398c644596..ab13d4abe0 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/RestrictiveLegalValues/RestrictiveLegalValues.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/RestrictiveLegalValues/RestrictiveLegalValues.tsx @@ -1,7 +1,7 @@ +import { useEffect, useState } from 'react'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Checkbox } from '@material-ui/core'; import { useCommonStyles } from 'themes/commonStyles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import React, { useEffect, useState } from 'react'; import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; import { ILegalValue } from 'interfaces/context'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/SingleLegalValue/SingleLegalValue.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/SingleLegalValue/SingleLegalValue.tsx index 2191b52bf6..52d53e47ba 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/SingleLegalValue/SingleLegalValue.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/SingleLegalValue/SingleLegalValue.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; import { FormControl, RadioGroup, Radio } from '@material-ui/core'; import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useCommonStyles } from 'themes/commonStyles'; import { ILegalValue } from 'interfaces/context'; import { diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx index c233f7829d..81b5eaca4e 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx @@ -5,7 +5,7 @@ import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashCon import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; import { Help } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { dateOperators, DATE_AFTER, IN } from 'constants/operators'; import { SAVE } from '../ConstraintAccordionEdit'; import { resolveText } from './helpers'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx index a5ccbd9d24..a2a5f61045 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx @@ -7,8 +7,8 @@ import { useWeakMap } from 'hooks/useWeakMap'; import { objectId } from 'utils/objectId'; import { useStyles } from './ConstraintAccordionList.styles'; import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; -import ConditionallyRender from 'component/common/ConditionallyRender'; import { Button } from '@material-ui/core'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IConstraintAccordionListProps { constraints: IConstraint[]; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx index e5b761cb0a..df0d960f2d 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; import { stringOperators } from 'constants/operators'; import { IConstraint } from 'interfaces/strategy'; import { oneOf } from 'utils/oneOf'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles'; import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; import { formatConstraintValue } from 'utils/formatConstraintValue'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx index a785317d9c..53c4f6080e 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx @@ -5,7 +5,7 @@ import { Delete, Edit } from '@material-ui/icons'; import { IConstraint } from 'interfaces/strategy'; import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import React from 'react'; import { formatConstraintValue } from 'utils/formatConstraintValue'; import { useLocationSettings } from 'hooks/useLocationSettings'; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch.tsx index 75094f0392..d9a8e671ca 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch.tsx @@ -1,6 +1,6 @@ import { TextField, InputAdornment, Chip } from '@material-ui/core'; import { Search } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IConstraintValueSearchProps { filter: string; diff --git a/frontend/src/component/common/Dialogue/Dialogue.tsx b/frontend/src/component/common/Dialogue/Dialogue.tsx index 4e81a58230..b9e5391410 100644 --- a/frontend/src/component/common/Dialogue/Dialogue.tsx +++ b/frontend/src/component/common/Dialogue/Dialogue.tsx @@ -7,7 +7,7 @@ import { DialogTitle, } from '@material-ui/core'; -import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './Dialogue.styles'; import { DIALOGUE_CONFIRM_ID } from 'utils/testIds'; @@ -15,7 +15,7 @@ interface IDialogue { primaryButtonText?: string; secondaryButtonText?: string; open: boolean; - onClick: (e: React.SyntheticEvent) => void; + onClick?: (e: React.SyntheticEvent) => void; onClose?: (e: React.SyntheticEvent) => void; style?: object; title: string; @@ -26,7 +26,7 @@ interface IDialogue { permissionButton?: JSX.Element; } -const Dialogue: React.FC = ({ +export const Dialogue: React.FC = ({ children, open, onClick, @@ -44,7 +44,9 @@ const Dialogue: React.FC = ({ const handleClick = formId ? (e: React.SyntheticEvent) => { e.preventDefault(); - onClick(e); + if (onClick) { + onClick(e); + } } : onClick; return ( @@ -103,5 +105,3 @@ const Dialogue: React.FC = ({ ); }; - -export default Dialogue; diff --git a/frontend/src/component/common/Dialogue/index.jsx b/frontend/src/component/common/Dialogue/index.jsx deleted file mode 100644 index 815bc7f9c2..0000000000 --- a/frontend/src/component/common/Dialogue/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import Dialogue from './Dialogue'; - -export default Dialogue; diff --git a/frontend/src/component/common/DropdownMenu/DropdownButton/DropdownButton.tsx b/frontend/src/component/common/DropdownMenu/DropdownButton/DropdownButton.tsx new file mode 100644 index 0000000000..b9584df0f0 --- /dev/null +++ b/frontend/src/component/common/DropdownMenu/DropdownButton/DropdownButton.tsx @@ -0,0 +1,23 @@ +import { ReactNode, VFC } from 'react'; +import { Button, ButtonProps, Icon } from '@material-ui/core'; + +interface IDropdownButtonProps { + label: string; + id?: string; + title?: ButtonProps['title']; + className?: string; + icon?: ReactNode; + startIcon?: ButtonProps['startIcon']; + style?: ButtonProps['style']; + onClick: ButtonProps['onClick']; +} + +export const DropdownButton: VFC = ({ + label, + icon, + ...rest +}) => ( + +); diff --git a/frontend/src/component/common/DropdownMenu/DropdownMenu.jsx b/frontend/src/component/common/DropdownMenu/DropdownMenu.tsx similarity index 59% rename from frontend/src/component/common/DropdownMenu/DropdownMenu.jsx rename to frontend/src/component/common/DropdownMenu/DropdownMenu.tsx index 50761535d6..c803f56994 100644 --- a/frontend/src/component/common/DropdownMenu/DropdownMenu.jsx +++ b/frontend/src/component/common/DropdownMenu/DropdownMenu.tsx @@ -1,13 +1,26 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import { + CSSProperties, + MouseEventHandler, + ReactNode, + useState, + VFC, +} from 'react'; import { Menu } from '@material-ui/core'; import { ArrowDropDown } from '@material-ui/icons'; +import { DropdownButton } from './DropdownButton/DropdownButton'; -import { DropdownButton } from '..'; +export interface IDropdownMenuProps { + renderOptions: () => ReactNode; + id: string; + title?: string; + callback?: MouseEventHandler; + icon?: ReactNode; + label: string; + startIcon?: ReactNode; + style?: CSSProperties; +} -import styles from '../common.module.scss'; - -const DropdownMenu = ({ +const DropdownMenu: VFC = ({ renderOptions, id, title, @@ -18,11 +31,13 @@ const DropdownMenu = ({ startIcon, ...rest }) => { - const [anchor, setAnchor] = React.useState(null); + const [anchor, setAnchor] = useState(null); - const handleOpen = e => setAnchor(e.currentTarget); + const handleOpen: MouseEventHandler = e => { + setAnchor(e.currentTarget); + }; - const handleClose = e => { + const handleClose: MouseEventHandler = e => { if (callback && typeof callback === 'function') { callback(e); } @@ -46,7 +61,6 @@ const DropdownMenu = ({ /> = ({ title, + titleElement, actions, subtitle, variant, @@ -28,25 +39,15 @@ const HeaderTitle = ({ variant={variant || 'h1'} className={classnames(styles.headerTitle, className)} > - {title} + {titleElement || title} {subtitle && {subtitle}}
{actions}} /> ); }; - -export default HeaderTitle; - -HeaderTitle.propTypes = { - title: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), - subtitle: PropTypes.string, - variant: PropTypes.string, - loading: PropTypes.bool, - actions: PropTypes.element, -}; diff --git a/frontend/src/component/common/HeaderTitle/index.jsx b/frontend/src/component/common/HeaderTitle/index.jsx deleted file mode 100644 index a2a7ea9d8d..0000000000 --- a/frontend/src/component/common/HeaderTitle/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import HeaderTitle from './HeaderTitle'; - -export default HeaderTitle; diff --git a/frontend/src/component/common/InputListField.jsx b/frontend/src/component/common/InputListField.jsx deleted file mode 100644 index b221049242..0000000000 --- a/frontend/src/component/common/InputListField.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { TextField } from '@material-ui/core'; - -function InputListField({ - label, - values = [], - error, - errorText, - name, - updateValues, - placeholder = '', - onBlur = () => {}, - FormHelperTextProps, -}) { - const handleChange = evt => { - const values = evt.target.value.split(/,\s?/); - const trimmedValues = values.map(v => v.trim()); - updateValues(trimmedValues); - }; - - const handleKeyDown = evt => { - if (evt.key === 'Backspace') { - const currentValue = evt.target.value; - if (currentValue.endsWith(', ')) { - evt.preventDefault(); - const value = currentValue.slice(0, -2); - updateValues(value.split(/,\s*/)); - } - } - }; - - return ( - - ); -} - -InputListField.propTypes = { - label: PropTypes.string.isRequired, - values: PropTypes.array, - error: PropTypes.string, - placeholder: PropTypes.string, - name: PropTypes.string.isRequired, - updateValues: PropTypes.func.isRequired, - onBlur: PropTypes.func, -}; - -export default InputListField; diff --git a/frontend/src/component/common/InputListField/InputListField.tsx b/frontend/src/component/common/InputListField/InputListField.tsx new file mode 100644 index 0000000000..44134ca574 --- /dev/null +++ b/frontend/src/component/common/InputListField/InputListField.tsx @@ -0,0 +1,53 @@ +import { VFC } from 'react'; +import { TextField, TextFieldProps } from '@material-ui/core'; + +interface IInputListFieldProps { + label: string; + values?: any[]; + error?: boolean; + placeholder?: string; + name: string; + updateValues: (values: string[]) => void; + onBlur?: TextFieldProps['onBlur']; + helperText?: TextFieldProps['helperText']; + FormHelperTextProps?: TextFieldProps['FormHelperTextProps']; +} + +export const InputListField: VFC = ({ + values = [], + updateValues, + placeholder = '', + error, + ...rest +}) => { + const handleChange: TextFieldProps['onChange'] = event => { + const values = event.target.value.split(/,\s?/); + const trimmedValues = values.map(v => v.trim()); + updateValues(trimmedValues); + }; + + const handleKeyDown: TextFieldProps['onKeyDown'] = event => { + if (event.key === 'Backspace') { + const currentValue = (event.target as HTMLInputElement).value; + if (currentValue.endsWith(', ')) { + event.preventDefault(); + const value = currentValue.slice(0, -2); + updateValues(value.split(/,\s*/)); + } + } + }; + + return ( + + ); +}; diff --git a/frontend/src/component/common/ListPlaceholder/ListPlaceholder.tsx b/frontend/src/component/common/ListPlaceholder/ListPlaceholder.tsx index dde79ce169..b2fdf3d181 100644 --- a/frontend/src/component/common/ListPlaceholder/ListPlaceholder.tsx +++ b/frontend/src/component/common/ListPlaceholder/ListPlaceholder.tsx @@ -1,6 +1,6 @@ import { ListItem } from '@material-ui/core'; import { Link } from 'react-router-dom'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from 'component/common/ListPlaceholder/ListPlaceholder.styles'; interface IListPlaceholderProps { diff --git a/frontend/src/component/common/PageContent/PageContent.jsx b/frontend/src/component/common/PageContent/PageContent.jsx index 0d0bd30664..a4e46eac78 100644 --- a/frontend/src/component/common/PageContent/PageContent.jsx +++ b/frontend/src/component/common/PageContent/PageContent.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import HeaderTitle from '../HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { Paper } from '@material-ui/core'; import { useStyles } from './styles'; diff --git a/frontend/src/component/common/PaginateUI/PaginateUI.tsx b/frontend/src/component/common/PaginateUI/PaginateUI.tsx index 6b834353a7..0d7eadb4a6 100644 --- a/frontend/src/component/common/PaginateUI/PaginateUI.tsx +++ b/frontend/src/component/common/PaginateUI/PaginateUI.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import classnames from 'classnames'; import { useStyles } from './PaginationUI.styles'; diff --git a/frontend/src/component/common/PasswordField/PasswordField.tsx b/frontend/src/component/common/PasswordField/PasswordField.tsx index 14d2fd66b0..8027829194 100644 --- a/frontend/src/component/common/PasswordField/PasswordField.tsx +++ b/frontend/src/component/common/PasswordField/PasswordField.tsx @@ -1,8 +1,13 @@ -import { IconButton, InputAdornment, TextField } from '@material-ui/core'; +import { + IconButton, + InputAdornment, + TextField, + TextFieldProps, +} from '@material-ui/core'; import { Visibility, VisibilityOff } from '@material-ui/icons'; -import React, { useState } from 'react'; +import React, { useState, VFC } from 'react'; -const PasswordField = ({ ...rest }) => { +const PasswordField: VFC = ({ ...rest }) => { const [showPassword, setShowPassword] = useState(false); const handleClickShowPassword = () => { diff --git a/frontend/src/component/common/PermissionButton/PermissionButton.tsx b/frontend/src/component/common/PermissionButton/PermissionButton.tsx index 505fe2307a..a544f0805b 100644 --- a/frontend/src/component/common/PermissionButton/PermissionButton.tsx +++ b/frontend/src/component/common/PermissionButton/PermissionButton.tsx @@ -2,7 +2,7 @@ import { Button, ButtonProps } from '@material-ui/core'; import { Lock } from '@material-ui/icons'; import AccessContext from 'contexts/AccessContext'; import React, { useContext } from 'react'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; import { formatAccessText } from 'utils/formatAccessText'; import { useId } from 'hooks/useId'; diff --git a/frontend/src/component/common/Proclamation/Proclamation.tsx b/frontend/src/component/common/Proclamation/Proclamation.tsx index ea4a61b2ca..911e7cd55d 100644 --- a/frontend/src/component/common/Proclamation/Proclamation.tsx +++ b/frontend/src/component/common/Proclamation/Proclamation.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { Alert } from '@material-ui/lab'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Typography } from '@material-ui/core'; import { useStyles } from './Proclamation.styles'; import { IProclamationToast } from 'interfaces/uiConfig'; diff --git a/frontend/src/component/common/ProjectSelect/ProjectSelect.jsx b/frontend/src/component/common/ProjectSelect/ProjectSelect.jsx deleted file mode 100644 index 45493aade4..0000000000 --- a/frontend/src/component/common/ProjectSelect/ProjectSelect.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { MenuItem } from '@material-ui/core'; -import PropTypes from 'prop-types'; -import DropdownMenu from '../DropdownMenu/DropdownMenu'; -import useProjects from 'hooks/api/getters/useProjects/useProjects'; - -const ALL_PROJECTS = { id: '*', name: '> All projects' }; - -const ProjectSelect = ({ currentProjectId, updateCurrentProject, ...rest }) => { - const { projects } = useProjects(); - - const setProject = v => { - const id = v && typeof v === 'string' ? v.trim() : '*'; - updateCurrentProject(id); - }; - - // TODO fixme - let curentProject = projects.find(i => i.id === currentProjectId); - if (!curentProject) { - curentProject = ALL_PROJECTS; - } - - const handleChangeProject = e => { - const target = e.target.getAttribute('data-target'); - setProject(target); - }; - - const renderProjectItem = (selectedId, item) => ( - - {item.name} - - ); - - const renderProjectOptions = () => { - const start = [ - - {ALL_PROJECTS.name} - , - ]; - - return [ - ...start, - ...projects.map(p => renderProjectItem(currentProjectId, p)), - ]; - }; - - const { updateSetting, ...passDown } = rest; - - return ( - - - - ); -}; - -ProjectSelect.propTypes = { - currentProjectId: PropTypes.string.isRequired, - updateCurrentProject: PropTypes.func.isRequired, -}; - -export default ProjectSelect; diff --git a/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx b/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx new file mode 100644 index 0000000000..642d1e2f32 --- /dev/null +++ b/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx @@ -0,0 +1,74 @@ +import React, { MouseEventHandler, useMemo, VFC } from 'react'; +import { MenuItem, Typography } from '@material-ui/core'; +import DropdownMenu, { IDropdownMenuProps } from '../DropdownMenu/DropdownMenu'; +import useProjects from 'hooks/api/getters/useProjects/useProjects'; +import { IProjectCard } from 'interfaces/project'; + +const ALL_PROJECTS = { id: '*', name: '> All projects' }; + +interface IProjectSelectProps { + currentProjectId: string; + updateCurrentProject: (id: string) => void; +} + +const ProjectSelect: VFC> = ({ + currentProjectId, + updateCurrentProject, + ...rest +}) => { + const { projects } = useProjects(); + + const setProject = (value?: string | null) => { + const id = value && typeof value === 'string' ? value.trim() : '*'; + updateCurrentProject(id); + }; + + const currentProject = useMemo(() => { + const project = projects.find(i => i.id === currentProjectId); + return project || ALL_PROJECTS; + }, [currentProjectId, projects]); + + const handleChangeProject: MouseEventHandler = event => { + const target = (event.target as Element).getAttribute('data-target'); + setProject(target); + }; + + const renderProjectItem = (selectedId: string, item: IProjectCard) => ( + + {item.name} + + ); + + const renderProjectOptions = () => [ + + {ALL_PROJECTS.name} + , + ...projects.map(project => + renderProjectItem(currentProjectId, project) + ), + ]; + + return ( + + + + ); +}; + +export default ProjectSelect; diff --git a/frontend/src/component/common/ProtectedRoute/ProtectedRoute.jsx b/frontend/src/component/common/ProtectedRoute/ProtectedRoute.jsx deleted file mode 100644 index 7f0e538d29..0000000000 --- a/frontend/src/component/common/ProtectedRoute/ProtectedRoute.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Route, useLocation, Redirect } from 'react-router-dom'; - -const ProtectedRoute = ({ - component: Component, - unauthorized, - renderProps = {}, - ...rest -}) => { - const { pathname, search } = useLocation(); - const redirect = encodeURIComponent(pathname + search); - const loginLink = `/login?redirect=${redirect}`; - - return ( - { - if (unauthorized) { - return ; - } else { - return ; - } - }} - /> - ); -}; - -export default ProtectedRoute; diff --git a/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx b/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx new file mode 100644 index 0000000000..aad100495a --- /dev/null +++ b/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx @@ -0,0 +1,24 @@ +import { VFC } from 'react'; +import { Route, useLocation, Redirect, RouteProps } from 'react-router-dom'; + +interface IProtectedRouteProps { + unauthorized?: boolean; +} + +const ProtectedRoute: VFC = ({ + component: Component, + unauthorized, + ...rest +}) => { + const { pathname, search } = useLocation(); + const redirect = encodeURIComponent(pathname + search); + const loginLink = `/login?redirect=${redirect}`; + + return unauthorized ? ( + } /> + ) : ( + + ); +}; + +export default ProtectedRoute; diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx index 1320868650..515f77eed2 100644 --- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx +++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx @@ -1,5 +1,5 @@ import { useMediaQuery } from '@material-ui/core'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PermissionButton from '../PermissionButton/PermissionButton'; import PermissionIconButton from '../PermissionIconButton/PermissionIconButton'; import React from 'react'; diff --git a/frontend/src/component/common/SearchField/SearchField.tsx b/frontend/src/component/common/SearchField/SearchField.tsx index d724fbadc4..2d78dfd5ef 100644 --- a/frontend/src/component/common/SearchField/SearchField.tsx +++ b/frontend/src/component/common/SearchField/SearchField.tsx @@ -1,24 +1,24 @@ -import React, { useState } from 'react'; +import React, { useState, VFC } from 'react'; import classnames from 'classnames'; import { debounce } from 'debounce'; import { InputBase, Chip } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import { useStyles } from 'component/common/SearchField/styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface ISearchFieldProps { - updateValue: React.Dispatch>; + updateValue: (value: string) => void; initialValue?: string; className?: string; showValueChip?: boolean; } -export const SearchField = ({ +export const SearchField: VFC = ({ updateValue, initialValue = '', className = '', showValueChip, -}: ISearchFieldProps) => { +}) => { const styles = useStyles(); const [localValue, setLocalValue] = useState(initialValue); const debounceUpdateValue = debounce(updateValue, 500); diff --git a/frontend/src/component/common/SearchField/index.jsx b/frontend/src/component/common/SearchField/index.jsx deleted file mode 100644 index 8a4300dca9..0000000000 --- a/frontend/src/component/common/SearchField/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import SearchField from './SearchField'; - -export default SearchField; diff --git a/frontend/src/component/common/StatusChip/StatusChip.tsx b/frontend/src/component/common/StatusChip/StatusChip.tsx index cf08a689ea..92379d8364 100644 --- a/frontend/src/component/common/StatusChip/StatusChip.tsx +++ b/frontend/src/component/common/StatusChip/StatusChip.tsx @@ -3,7 +3,7 @@ import { useStyles } from './StatusChip.styles'; interface IStatusChip { stale: boolean; - showActive?: true; + showActive?: boolean; } const StatusChip = ({ stale, showActive = true }: IStatusChip) => { diff --git a/frontend/src/component/common/StringTruncator/StringTruncator.tsx b/frontend/src/component/common/StringTruncator/StringTruncator.tsx index 00526e758a..8125668d2c 100644 --- a/frontend/src/component/common/StringTruncator/StringTruncator.tsx +++ b/frontend/src/component/common/StringTruncator/StringTruncator.tsx @@ -1,5 +1,5 @@ import { Tooltip } from '@material-ui/core'; -import ConditionallyRender from '../ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IStringTruncatorProps { text: string; diff --git a/frontend/src/component/common/Table/TableActions/TableActions.tsx b/frontend/src/component/common/Table/TableActions/TableActions.tsx index 42dd1c43ba..e59f2caa2a 100644 --- a/frontend/src/component/common/Table/TableActions/TableActions.tsx +++ b/frontend/src/component/common/Table/TableActions/TableActions.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { IconButton, Tooltip } from '@material-ui/core'; import { Search } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount'; import { TableSearchField } from 'component/common/Table/TableActions/TableSearchField/TableSearchField'; import { useStyles } from 'component/common/Table/TableActions/TableActions.styles'; diff --git a/frontend/src/component/common/Table/TableActions/TableSearchField/TableSearchField.tsx b/frontend/src/component/common/Table/TableActions/TableSearchField/TableSearchField.tsx index dd10c3c322..b726dad709 100644 --- a/frontend/src/component/common/Table/TableActions/TableSearchField/TableSearchField.tsx +++ b/frontend/src/component/common/Table/TableActions/TableSearchField/TableSearchField.tsx @@ -1,6 +1,6 @@ import { IconButton, InputBase, Tooltip } from '@material-ui/core'; import { Search, Close } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from 'component/common/Table/TableActions/TableSearchField/TableSearchField.styles'; import classnames from 'classnames'; diff --git a/frontend/src/component/common/Table/TableCellSortable/TableCellSortable.tsx b/frontend/src/component/common/Table/TableCellSortable/TableCellSortable.tsx index 88dfcbac04..33450ea2a1 100644 --- a/frontend/src/component/common/Table/TableCellSortable/TableCellSortable.tsx +++ b/frontend/src/component/common/Table/TableCellSortable/TableCellSortable.tsx @@ -7,7 +7,7 @@ import { KeyboardArrowUp, } from '@material-ui/icons'; import { IUsersSort, UsersSortType } from 'hooks/useUsersSort'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from 'component/common/Table/TableCellSortable/TableCellSortable.styles'; import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext'; diff --git a/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx b/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx index 2bb132ef8d..fe328fdd49 100644 --- a/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx +++ b/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx @@ -4,7 +4,7 @@ import { useContext } from 'react'; import { IconButton, Tooltip } from '@material-ui/core'; import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge'; import UIContext from 'contexts/UIContext'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import Close from '@material-ui/icons/Close'; import { IToast } from 'interfaces/toast'; diff --git a/frontend/src/component/common/common.module.scss b/frontend/src/component/common/common.module.scss index bf657f0cb2..24c955c562 100644 --- a/frontend/src/component/common/common.module.scss +++ b/frontend/src/component/common/common.module.scss @@ -83,10 +83,6 @@ min-height: 200px; } -.dropdownButton { - font-weight: normal; -} - .toggleName { color: #37474f !important; font-weight: 700; diff --git a/frontend/src/component/common/index.js b/frontend/src/component/common/index.js index f5efa9123b..1f596945dc 100644 --- a/frontend/src/component/common/index.js +++ b/frontend/src/component/common/index.js @@ -15,7 +15,7 @@ import { import { Apps } from '@material-ui/icons'; import styles from './common.module.scss'; -import ConditionallyRender from './ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from './ConditionallyRender/ConditionallyRender'; export { styles }; @@ -114,38 +114,6 @@ IconLink.propTypes = { icon: PropTypes.object, }; -export const DropdownButton = ({ - label, - id, - className = styles.dropdownButton, - title, - icon, - startIcon, - style, - ...rest -}) => ( - -); - -DropdownButton.propTypes = { - label: PropTypes.string, - style: PropTypes.object, - id: PropTypes.string, - title: PropTypes.string, - icon: PropTypes.object, - startIcon: PropTypes.object, -}; - export const MenuItemWithIcon = React.forwardRef( ({ icon: IconComponent, label, disabled, ...menuItemProps }, ref) => ( { +const ContextList: VFC = () => { const { hasAccess } = useContext(AccessContext); const [showDelDialogue, setShowDelDialogue] = useState(false); const smallScreen = useMediaQuery('(max-width:700px)'); - const [name, setName] = useState(); + const [name, setName] = useState(); const { context, refetchUnleashContext } = useUnleashContext(); const { removeContext } = useContextsApi(); const { setToastData, setToastApiError } = useToast(); const history = useHistory(); const styles = useStyles(); - const onDeleteContext = async name => { + const onDeleteContext = async () => { try { + if (name === undefined) { + throw new Error(); + } await removeContext(name); refetchUnleashContext(); setToastData({ @@ -93,6 +96,7 @@ const ContextList = () => { show={ { setName(field.name); setShowDelDialogue(true); @@ -133,6 +137,7 @@ const ContextList = () => { } /> ); + return ( { onDeleteContext(name)} + onClick={onDeleteContext} onClose={() => { setName(undefined); setShowDelDialogue(false); diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx index 4387587e4c..d979918fee 100644 --- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx +++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx @@ -10,10 +10,10 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PageContent from 'component/common/PageContent/PageContent'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import HeaderTitle from 'component/common/HeaderTitle/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { formatUnknownError } from 'utils/formatUnknownError'; const CreateEnvironment = () => { diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx index aa6f2456f1..6538c3bf9b 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx @@ -1,7 +1,7 @@ import { Alert } from '@material-ui/lab'; import React from 'react'; import { IEnvironment } from 'interfaces/environments'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import Input from 'component/common/Input/Input'; import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; import { useStyles } from './EnvironmentDeleteConfirm.styles'; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx index 157dce099c..ee0e27e8b2 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx @@ -1,21 +1,21 @@ -import HeaderTitle from 'component/common/HeaderTitle'; -import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; -import { Add } from '@material-ui/icons'; -import PageContent from 'component/common/PageContent'; -import { List } from '@material-ui/core'; -import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; -import { IEnvironment, ISortOrderPayload } from 'interfaces/environments'; import { useState } from 'react'; import { useHistory } from 'react-router-dom'; -import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDeleteConfirm'; +import { List } from '@material-ui/core'; +import { Add } from '@material-ui/icons'; import useToast from 'hooks/useToast'; +import { IEnvironment, ISortOrderPayload } from 'interfaces/environments'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; +import PageContent from 'component/common/PageContent'; +import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; -import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; -import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { formatUnknownError } from 'utils/formatUnknownError'; +import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; +import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; +import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDeleteConfirm'; const EnvironmentList = () => { const defaultEnv = { diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx index 6fe04e47cc..f8487a2fbb 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx @@ -12,7 +12,7 @@ import { Edit, OfflineBolt, } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { IEnvironment } from 'interfaces/environments'; import React, { useContext, useRef } from 'react'; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentToggleConfirm/EnvironmentToggleConfirm.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentToggleConfirm/EnvironmentToggleConfirm.tsx index e0ce15a3a4..675422cc9c 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentToggleConfirm/EnvironmentToggleConfirm.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentToggleConfirm/EnvironmentToggleConfirm.tsx @@ -2,8 +2,8 @@ import { capitalize } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { IEnvironment } from 'interfaces/environments'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import Dialogue from 'component/common/Dialogue'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; interface IEnvironmentToggleConfirmProps { diff --git a/frontend/src/component/feature/CopyFeature/CopyFeature.jsx b/frontend/src/component/feature/CopyFeature/CopyFeature.tsx similarity index 77% rename from frontend/src/component/feature/CopyFeature/CopyFeature.jsx rename to frontend/src/component/feature/CopyFeature/CopyFeature.tsx index 853b9faded..988863c1dc 100644 --- a/frontend/src/component/feature/CopyFeature/CopyFeature.jsx +++ b/frontend/src/component/feature/CopyFeature/CopyFeature.tsx @@ -1,4 +1,10 @@ -import { useState, useRef, useEffect } from 'react'; +import { + useState, + useRef, + useEffect, + FormEventHandler, + ChangeEventHandler, +} from 'react'; import { Link, useHistory, useParams } from 'react-router-dom'; import { Button, @@ -9,33 +15,35 @@ import { } from '@material-ui/core'; import { FileCopy } from '@material-ui/icons'; import { styles as commonStyles } from 'component/common'; +import { formatUnknownError } from 'utils/formatUnknownError'; import styles from './CopyFeature.module.scss'; import { trim } from 'component/common/util'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Alert } from '@material-ui/lab'; import { getTogglePath } from 'utils/routePathHelpers'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; export const CopyFeatureToggle = () => { const [replaceGroupId, setReplaceGroupId] = useState(true); const [apiError, setApiError] = useState(''); - const [nameError, setNameError] = useState(undefined); - const [newToggleName, setNewToggleName] = useState(); + const [nameError, setNameError] = useState(); + const [newToggleName, setNewToggleName] = useState(); const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi(); - const inputRef = useRef(); - const { name: copyToggleName, id: projectId } = useParams(); + const inputRef = useRef(); + const { name: copyToggleName, id: projectId } = useParams<{ + name: string; + id: string; + }>(); const { feature } = useFeature(projectId, copyToggleName); - const { uiConfig } = useUiConfig(); const history = useHistory(); useEffect(() => { inputRef.current?.focus(); }, []); - const setValue = evt => { - const value = trim(evt.target.value); + const setValue: ChangeEventHandler = event => { + const value = trim(event.target.value); setNewToggleName(value); }; @@ -48,13 +56,13 @@ export const CopyFeatureToggle = () => { await validateFeatureToggleName(newToggleName); setNameError(undefined); - } catch (err) { - setNameError(err.message); + } catch (error) { + setNameError(formatUnknownError(error)); } }; - const onSubmit = async evt => { - evt.preventDefault(); + const onSubmit: FormEventHandler = async event => { + event.preventDefault(); if (nameError) { return; @@ -62,14 +70,12 @@ export const CopyFeatureToggle = () => { try { await cloneFeatureToggle(projectId, copyToggleName, { - name: newToggleName, + name: newToggleName as string, replaceGroupId, }); - history.push( - getTogglePath(projectId, newToggleName, uiConfig.flags.E) - ); - } catch (e) { - setApiError(e.toString()); + history.push(getTogglePath(projectId, newToggleName as string)); + } catch (error) { + setApiError(formatUnknownError(error)); } }; @@ -84,7 +90,7 @@ export const CopyFeatureToggle = () => {

Copy {copyToggleName}

{apiError}} />
@@ -116,7 +122,6 @@ export const CopyFeatureToggle = () => { } diff --git a/frontend/src/component/feature/CreateFeatureButton/CreateFeatureButton.tsx b/frontend/src/component/feature/CreateFeatureButton/CreateFeatureButton.tsx index 56c3537652..c73a93cbc4 100644 --- a/frontend/src/component/feature/CreateFeatureButton/CreateFeatureButton.tsx +++ b/frontend/src/component/feature/CreateFeatureButton/CreateFeatureButton.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { Button, IconButton, Tooltip } from '@material-ui/core'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { Add } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds'; import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; import { useCreateFeaturePath } from 'component/feature/CreateFeatureButton/useCreateFeaturePath'; diff --git a/frontend/src/component/feature/FeatureForm/FeatureForm.tsx b/frontend/src/component/feature/FeatureForm/FeatureForm.tsx index 12bcb32fb8..84fa898597 100644 --- a/frontend/src/component/feature/FeatureForm/FeatureForm.tsx +++ b/frontend/src/component/feature/FeatureForm/FeatureForm.tsx @@ -12,7 +12,7 @@ import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes'; import { KeyboardArrowDownOutlined } from '@material-ui/icons'; import { projectFilterGenerator } from 'utils/projectFilterGenerator'; import FeatureProjectSelect from '../FeatureView/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { trim } from 'component/common/util'; import Input from 'component/common/Input/Input'; import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEnabled/FeatureStrategyEnabled.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEnabled/FeatureStrategyEnabled.tsx index 420ba59317..02bf18ffd1 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEnabled/FeatureStrategyEnabled.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEnabled/FeatureStrategyEnabled.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Alert } from '@material-ui/lab'; import { IFeatureToggle } from 'interfaces/featureToggle'; import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx index 97e452dd71..670c9af2d8 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx @@ -13,7 +13,7 @@ import { useStyles } from './FeatureStrategyForm.styles'; import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; import { useHistory } from 'react-router-dom'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds'; import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation'; import AccessContext from 'contexts/AccessContext'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx index 76a8b14098..21135785fa 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx @@ -1,4 +1,4 @@ -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Alert } from '@material-ui/lab'; import { Checkbox, FormControlLabel } from '@material-ui/core'; import { PRODUCTION } from 'constants/environmentTypes'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove.tsx index cd44236066..1deb3dca87 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove.tsx @@ -4,13 +4,13 @@ import { formatUnknownError } from 'utils/formatUnknownError'; import { useHistory } from 'react-router-dom'; import useToast from 'hooks/useToast'; import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Alert } from '@material-ui/lab'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import { DELETE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { STRATEGY_FORM_REMOVE_ID } from 'utils/testIds'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { Delete } from '@material-ui/icons'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip.tsx index f5e2c8c4b1..7278be4ed3 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { ISegment } from 'interfaces/segment'; import { Clear, VisibilityOff, Visibility } from '@material-ui/icons'; import { useStyles } from './FeatureStrategySegmentChip.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; import { Tooltip } from '@material-ui/core'; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx index 66b79ec996..a4f4bb42a0 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx @@ -1,7 +1,7 @@ import React, { Fragment, useState } from 'react'; import { ISegment } from 'interfaces/segment'; import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip'; import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx deleted file mode 100644 index d4d6ca90a5..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { ThemeProvider } from '@material-ui/core'; -import FeatureToggleList from './FeatureToggleList'; -import renderer from 'react-test-renderer'; -import theme from 'themes/mainTheme'; -import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import AccessProvider from 'component/providers/AccessProvider/AccessProvider'; -import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; - -jest.mock('./FeatureToggleListItem/FeatureToggleListItem', () => ({ - __esModule: true, - default: 'ListItem', -})); - -jest.mock('component/common/ProjectSelect/ProjectSelect'); - -test('renders correctly with one feature', () => { - const features = [ - { - name: 'Another', - }, - ]; - - const tree = renderer.create( - - - - - - - - - - ); - - expect(tree).toMatchSnapshot(); -}); - -test('renders correctly with one feature without permissions', () => { - const features = [ - { - name: 'Another', - }, - ]; - const tree = renderer.create( - - - - - - - - - - ); - - expect(tree).toMatchSnapshot(); -}); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.tsx similarity index 68% rename from frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx rename to frontend/src/component/feature/FeatureToggleList/FeatureToggleList.tsx index 9a41d138fb..7babfee543 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.tsx @@ -1,26 +1,58 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; +import { Dispatch, SetStateAction, useContext, VFC } from 'react'; import classnames from 'classnames'; import { Link } from 'react-router-dom'; import { List, ListItem } from '@material-ui/core'; import useMediaQuery from '@material-ui/core/useMediaQuery'; -import FeatureToggleListItem from './FeatureToggleListItem'; +import { IFlags } from 'interfaces/uiConfig'; import { SearchField } from 'component/common/SearchField/SearchField'; -import FeatureToggleListActions from './FeatureToggleListActions'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PageContent from 'component/common/PageContent/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; -import loadingFeatures from './loadingFeatures'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import AccessContext from 'contexts/AccessContext'; -import { useStyles } from './styles'; import ListPlaceholder from 'component/common/ListPlaceholder/ListPlaceholder'; +import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; +import { FeatureToggleListItem } from './FeatureToggleListItem/FeatureToggleListItem'; +import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions'; import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; import { useCreateFeaturePath } from '../CreateFeatureButton/useCreateFeaturePath'; +import { IFeaturesSort } from 'hooks/useFeaturesSort'; +import { FeatureSchema } from 'openapi'; +import { useStyles } from './styles'; -const FeatureToggleList = ({ +interface IFeatureToggleListProps { + features: FeatureSchema[]; + loading?: boolean; + flags?: IFlags; + filter: IFeaturesFilter; + setFilter: Dispatch>; + sort: IFeaturesSort; + setSort: Dispatch>; + onRevive?: (feature: string) => void; + isArchive?: boolean; +} + +const loadingFeaturesPlaceholder: FeatureSchema[] = Array(10) + .fill({ + createdAt: '2021-03-19T09:16:21.329Z', + description: ' ', + enabled: true, + lastSeenAt: '2021-03-24T10:46:38.036Z', + name: '', + project: 'default', + stale: true, + strategies: [], + variants: [], + type: 'release', + archived: false, + environments: [], + impressionData: false, + }) + .map((feature, index) => ({ ...feature, name: `${index}` })); // ID for React key + +export const FeatureToggleList: VFC = ({ features, - revive, - archive, + onRevive, + isArchive, loading, flags, filter, @@ -34,22 +66,18 @@ const FeatureToggleList = ({ const smallScreen = useMediaQuery('(max-width:800px)'); const mobileView = useMediaQuery('(max-width:600px)'); - const setFilterQuery = v => { + const setFilterQuery = (v: string) => { const query = v && typeof v === 'string' ? v.trim() : ''; setFilter(prev => ({ ...prev, query })); }; const renderFeatures = () => { - features.forEach(e => { - e.reviveName = e.name; - }); - if (loading) { - return loadingFeatures.map(feature => ( + return loadingFeaturesPlaceholder.map(feature => ( ))} elseShow={ No archived features. @@ -83,7 +111,7 @@ const FeatureToggleList = ({ show={() => ( )} @@ -99,12 +127,12 @@ const FeatureToggleList = ({ ? ` (${features.length} matches)` : ''; - const headerTitle = archive - ? `Archived feature toggles${searchResultsHeader}` - : `Feature toggles${searchResultsHeader}`; + const headerTitle = isArchive + ? `Archived Features ${searchResultsHeader}` + : `Features ${searchResultsHeader}`; return ( -
+
Archive} />
@@ -140,11 +168,11 @@ const FeatureToggleList = ({ } /> } /> @@ -158,17 +186,3 @@ const FeatureToggleList = ({
); }; - -FeatureToggleList.propTypes = { - features: PropTypes.array.isRequired, - revive: PropTypes.func, - loading: PropTypes.bool, - archive: PropTypes.bool, - flags: PropTypes.object, - filter: PropTypes.object.isRequired, - setFilter: PropTypes.func.isRequired, - sort: PropTypes.object.isRequired, - setSort: PropTypes.func.isRequired, -}; - -export default FeatureToggleList; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx similarity index 68% rename from frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.jsx rename to frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx index e8236c5a90..4235e13f4a 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx @@ -1,34 +1,48 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - +import { Dispatch, MouseEventHandler, SetStateAction, VFC } from 'react'; import { MenuItem, Typography } from '@material-ui/core'; import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu'; import ProjectSelect from 'component/common/ProjectSelect/ProjectSelect'; -import { useStyles } from './styles'; import useLoading from 'hooks/useLoading'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import { createFeaturesFilterSortOptions } from 'hooks/useFeaturesSort'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { + createFeaturesFilterSortOptions, + FeaturesSortType, + IFeaturesSort, +} from 'hooks/useFeaturesSort'; +import { useStyles } from './styles'; +import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; const sortOptions = createFeaturesFilterSortOptions(); -const FeatureToggleListActions = ({ +interface IFeatureToggleListActionsProps { + filter: IFeaturesFilter; + setFilter: Dispatch>; + sort: IFeaturesSort; + setSort: Dispatch>; + loading?: boolean; +} + +export const FeatureToggleListActions: VFC = ({ filter, setFilter, sort, setSort, - loading, + loading = false, }) => { const styles = useStyles(); const { uiConfig } = useUiConfig(); const ref = useLoading(loading); - const handleSort = e => { - const type = e.target.getAttribute('data-target')?.trim(); - type && setSort(prev => ({ ...prev, type })); + const handleSort: MouseEventHandler = e => { + const type = (e.target as Element) + .getAttribute('data-target') + ?.trim() as FeaturesSortType; + if (type) { + setSort(prev => ({ ...prev, type })); + } }; - const isDisabled = s => s === sort.type; const selectedOption = sortOptions.find(o => o.type === sort.type) || sortOptions[0]; @@ -37,7 +51,7 @@ const FeatureToggleListActions = ({ {option.name} @@ -55,7 +69,6 @@ const FeatureToggleListActions = ({ callback={handleSort} renderOptions={renderSortingOptions} title="Sort by" - className="" style={{ textTransform: 'lowercase', fontWeight: 'normal' }} data-loading /> @@ -78,14 +91,3 @@ const FeatureToggleListActions = ({
); }; - -FeatureToggleListActions.propTypes = { - filter: PropTypes.object, - setFilter: PropTypes.func, - sort: PropTypes.object, - setSort: PropTypes.func, - toggleMetrics: PropTypes.func, - loading: PropTypes.bool, -}; - -export default FeatureToggleListActions; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/index.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/index.jsx deleted file mode 100644 index 18b19140dd..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import FeatureToggleListActions from './FeatureToggleListActions'; - -export default FeatureToggleListActions; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListContainer.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListContainer.tsx index 13cbe7a3d6..83f0ea0ac6 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListContainer.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListContainer.tsx @@ -1,7 +1,7 @@ import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useFeaturesFilter } from 'hooks/useFeaturesFilter'; -import FeatureToggleList from './FeatureToggleList'; +import { FeatureToggleList } from './FeatureToggleList'; import { useFeaturesSort } from 'hooks/useFeaturesSort'; export const FeatureToggleListContainer = () => { diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx deleted file mode 100644 index 1093d1c78d..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { memo } from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { Link } from 'react-router-dom'; -import { Chip, ListItem, Tooltip } from '@material-ui/core'; -import { Undo } from '@material-ui/icons'; -import TimeAgo from 'react-timeago'; -import StatusChip from 'component/common/StatusChip/StatusChip'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; -import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import { styles as commonStyles } from 'component/common'; -import { useStyles } from './styles'; -import { getTogglePath } from 'utils/routePathHelpers'; -import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; -import FeatureType from 'component/feature/FeatureView/FeatureType/FeatureType'; -import useProjects from 'hooks/api/getters/useProjects/useProjects'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; - -const FeatureToggleListItem = ({ - feature, - revive, - hasAccess, - flags = {}, - ...rest -}) => { - const styles = useStyles(); - - const { projects } = useProjects(); - const isArchive = !!revive; - - const { name, description, type, stale, createdAt, project, lastSeenAt } = - feature; - - const projectExists = () => { - let projectExist = projects.find(proj => proj.id === project); - if (projectExist) { - return true; - } - return false; - }; - - const reviveFeature = () => { - if (projectExists()) { - revive(feature.name); - } - }; - return ( - - - - - - - - - - - - {name}  - - - - - - -
- - {description} - -
- - } - elseShow={ - <> - - - {name} {' '} - - - - - - -
- - {description} - -
- - } - /> -
- - - - - - - - - - } - /> -
- ); -}; - -FeatureToggleListItem.propTypes = { - feature: PropTypes.object, - revive: PropTypes.func, - hasAccess: PropTypes.func.isRequired, - flags: PropTypes.object, -}; - -export default memo(FeatureToggleListItem); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.test.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.test.jsx deleted file mode 100644 index 4a3cc68d61..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.test.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { ThemeProvider } from '@material-ui/core'; - -import FeatureToggleListItem from './index'; -import renderer from 'react-test-renderer'; - -import theme from 'themes/mainTheme'; - -test('renders correctly with one feature', () => { - const feature = { - name: 'Another', - description: "another's description", - enabled: false, - stale: false, - project: 'default', - strategies: [ - { - name: 'gradualRolloutRandom', - parameters: { - percentage: 50, - }, - }, - ], - createdAt: '2018-02-04T20:27:52.127Z', - }; - const tree = renderer.create( - - - true} - /> - - - ); - - expect(tree).toMatchSnapshot(); -}); - -test('renders correctly with one feature without permission', () => { - const feature = { - name: 'Another', - description: "another's description", - enabled: false, - stale: false, - strategies: [ - { - name: 'gradualRolloutRandom', - parameters: { - percentage: 50, - }, - }, - ], - createdAt: '2018-02-04T20:27:52.127Z', - }; - const tree = renderer.create( - - - true} - /> - - - ); - - expect(tree).toMatchSnapshot(); -}); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.tsx new file mode 100644 index 0000000000..97cab9af70 --- /dev/null +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.tsx @@ -0,0 +1,192 @@ +import { memo } from 'react'; +import classnames from 'classnames'; +import { Link } from 'react-router-dom'; +import { Chip, ListItem, Tooltip } from '@material-ui/core'; +import { Undo } from '@material-ui/icons'; +import TimeAgo from 'react-timeago'; +import { IAccessContext } from 'contexts/AccessContext'; +import StatusChip from 'component/common/StatusChip/StatusChip'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; +import { IFlags } from 'interfaces/uiConfig'; +import { getTogglePath } from 'utils/routePathHelpers'; +import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; +import FeatureType from 'component/feature/FeatureView/FeatureType/FeatureType'; +import useProjects from 'hooks/api/getters/useProjects/useProjects'; +import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; +import { styles as commonStyles } from 'component/common'; // FIXME: remove +import { FeatureSchema } from 'openapi'; +import { useStyles } from './styles'; // FIXME: cleanup + +interface IFeatureToggleListItemProps { + feature: FeatureSchema; + onRevive?: (id: string) => void; + hasAccess: IAccessContext['hasAccess']; + flags?: IFlags; + className?: string; +} + +export const FeatureToggleListItem = memo( + ({ feature, onRevive, hasAccess, flags = {}, className, ...rest }) => { + const styles = useStyles(); + + const { projects } = useProjects(); + const isArchive = Boolean(onRevive); + + const { + name, + description, + type, + stale, + createdAt, + project, + lastSeenAt, + } = feature; + + const projectExists = () => { + let projectExist = projects.find(proj => proj.id === project); + if (projectExist) { + return true; + } + return false; + }; + + const reviveFeature = () => { + if (projectExists() && onRevive) { + onRevive(feature.name); + } + }; + + return ( + + + ( + + )} + /> + + + + + + + + + {name}  + + + {/* */} + + + ( + + )} + /> + +
+ + {description} + +
+ + } + elseShow={ + <> + + + {name} {' '} + + + {/* */} + + + ( + + )} + /> + +
+ + {description} + +
+ + } + /> +
+ + + + + + + + + + } + /> +
+ ); + } +); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/__snapshots__/FeatureToggleListItem.test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/__snapshots__/FeatureToggleListItem.test.jsx.snap deleted file mode 100644 index 8c92d8cf7c..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/__snapshots__/FeatureToggleListItem.test.jsx.snap +++ /dev/null @@ -1,273 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly with one feature 1`] = ` -
  • - -
    - - ⊕ - -
    -
    - - - - - - - - - Another -   - - - - - -
    - - - another's description - - -
    -
    -
    - - -
    - - default - -
    -
    -
    -
  • -`; - -exports[`renders correctly with one feature without permission 1`] = ` -
  • - -
    - - ⊕ - -
    -
    - - - - - - - - - Another -   - - - - - -
    - - - another's description - - -
    -
    -
    - - -
    - -
    -
    -
    -
  • -`; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/index.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/index.jsx deleted file mode 100644 index 600ff78b9e..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import FeatureToggleListItem from './FeatureToggleListItem'; - -export default FeatureToggleListItem; diff --git a/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap deleted file mode 100644 index 1bc574e1d5..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap +++ /dev/null @@ -1,368 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly with one feature 1`] = ` -Array [ -
    -
    -
    -
    - - - -
    - -
    -
    -
    - - Archive - -
    -
    -
    -
    -
    -

    - Feature toggles -

    -
    -
    -
    -
    -

    - Sorted by: -

    - -
    -
    -
    -
    -
    -
    -
      - -
    -
    -
    -
    , -
    , -] -`; - -exports[`renders correctly with one feature without permissions 1`] = ` -Array [ -
    -
    -
    -
    - - - -
    - -
    -
    -
    - - Archive - -
    -
    -
    -
    -
    -

    - Feature toggles -

    -
    -
    -
    -
    -

    - Sorted by: -

    - -
    -
    -
    -
    -
    -
    -
      - -
    -
    -
    -
    , -
    - Navigated to Feature toggles -
    , -] -`; diff --git a/frontend/src/component/feature/FeatureToggleList/loadingFeatures.ts b/frontend/src/component/feature/FeatureToggleList/loadingFeatures.ts deleted file mode 100644 index 2c7bab5d95..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/loadingFeatures.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { IFeatureToggle } from 'interfaces/featureToggle'; - -const loadingFeatures: Partial[] = [ - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'one', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'two', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'three', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'four', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'five', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'six', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'seven', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'eight', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'nine', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, - { - createdAt: '2021-03-19T09:16:21.329Z', - description: '', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: 'ten', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - }, -]; - -export default loadingFeatures; diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx index 0eb3ffca28..52cf790cf1 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx @@ -14,7 +14,7 @@ import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState'; import { useQueryStringState } from 'hooks/useQueryStringState'; import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './FeatureMetrics.styles'; import { usePageTitle } from 'hooks/usePageTitle'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx index 7abf888e6c..14debab9f3 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx @@ -2,7 +2,7 @@ import { DialogContentText } from '@material-ui/core'; import { useParams } from 'react-router'; import React, { useState } from 'react'; import { IFeatureViewParams } from 'interfaces/params'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import Input from 'component/common/Input/Input'; import { useStyles } from './AddTagDialog.styles'; import { trim } from 'component/common/util'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index 31ea680e09..bf5113fe6c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -16,7 +16,7 @@ import { getFeatureStrategyIcon, formatStrategyName, } from 'utils/strategyNames'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import DisabledIndicator from 'component/common/DisabledIndicator/DisabledIndicator'; import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx index 4af9ce1c03..821c70344f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx @@ -1,6 +1,6 @@ import { useParams } from 'react-router-dom'; import { IFeatureViewParams } from 'interfaces/params'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import FeatureOverviewEnvironmentStrategies from '../FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies'; import { useStyles } from '../FeatureOverviewEnvironment.styles'; import { IFeatureEnvironment } from 'interfaces/featureToggle'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx index aa777845ad..ba851d1d46 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx @@ -1,6 +1,6 @@ import { Fragment } from 'react'; import { IConstraint, IFeatureStrategy, IParameter } from 'interfaces/strategy'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import FeatureOverviewExecutionChips from './FeatureOverviewExecutionChips/FeatureOverviewExecutionChips'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecutionChips/FeatureOverviewExecutionChips.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecutionChips/FeatureOverviewExecutionChips.tsx index ce5443e87c..0e91cc71f2 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecutionChips/FeatureOverviewExecutionChips.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecutionChips/FeatureOverviewExecutionChips.tsx @@ -1,5 +1,5 @@ import { Chip } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './FeatureOverviewExecutionChips.styles'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx index fcc93c3f45..039f6717da 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx @@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom'; import { Link } from 'react-router-dom'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './FeatureOverviewMetadata.styles'; import { Edit } from '@material-ui/icons'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx index a730490b93..5b2b023249 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx @@ -11,11 +11,11 @@ import webhookIcon from 'assets/icons/webhooks.svg'; import { formatAssetPath } from 'utils/formatPath'; import useTagTypes from 'hooks/api/getters/useTagTypes/useTagTypes'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { ITag } from 'interfaces/tags'; import useToast from 'hooks/useToast'; import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from 'contexts/AccessContext'; import { formatUnknownError } from 'utils/formatUnknownError'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx index f836bf4131..db25ff16fc 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx @@ -2,8 +2,8 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import { useParams } from 'react-router-dom'; import { IFeatureViewParams } from 'interfaces/params'; import { DialogContentText } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; -import Dialogue from 'component/common/Dialogue'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import React from 'react'; import useToast from 'hooks/useToast'; diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx index eadac85fda..caa6cf9fa8 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import PageContent from 'component/common/PageContent'; import { useStyles } from './FeatureSettings.styles'; import { List, ListItem } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import FeatureSettingsProject from './FeatureSettingsProject/FeatureSettingsProject'; import { useParams } from 'react-router-dom'; import { IFeatureViewParams } from 'interfaces/params'; diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx index 6a274acc2d..c07bb0cf82 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx @@ -10,7 +10,7 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { IFeatureViewParams } from 'interfaces/params'; import useToast from 'hooks/useToast'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { formatUnknownError } from 'utils/formatUnknownError'; const FeatureSettingsMetadata = () => { diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx index f250edab7a..6b991b526e 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx @@ -6,7 +6,7 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import useToast from 'hooks/useToast'; import { IFeatureViewParams } from 'interfaces/params'; import { MOVE_FEATURE_TOGGLE } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect'; import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm'; diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm.tsx index 4ace931301..807ec510a3 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm.tsx @@ -3,8 +3,8 @@ import { Check, Error, Cloud } from '@material-ui/icons'; import { useState, useEffect } from 'react'; import useProject from 'hooks/api/getters/useProject/useProject'; import { IFeatureEnvironment, IFeatureToggle } from 'interfaces/featureToggle'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import Dialogue from 'component/common/Dialogue'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { useStyles } from './FeatureSettingsProjectConfirm.styles'; interface IFeatureSettingsProjectConfirm { @@ -75,7 +75,6 @@ const FeatureSettingsProjectConfirm = ({ } elseShow={ - // @ts-expect-error >({}); const [payload, setPayload] = useState(EMPTY_PAYLOAD); - const [overrides, setOverrides] = useState([]); + const [overrides, overridesDispatch] = useOverrides([]); const [error, setError] = useState>({}); const commonStyles = useCommonStyles(); const { projectId, featureId } = useParams(); @@ -93,14 +93,17 @@ export const AddVariant = ({ setPayload(EMPTY_PAYLOAD); } if (editVariant.overrides) { - setOverrides(editVariant.overrides); + overridesDispatch({ + type: 'SET', + payload: editVariant.overrides, + }); } else { - setOverrides([]); + overridesDispatch({ type: 'CLEAR' }); } } else { setData({}); setPayload(EMPTY_PAYLOAD); - setOverrides([]); + overridesDispatch({ type: 'CLEAR' }); } setError({}); }; @@ -112,13 +115,11 @@ export const AddVariant = ({ if (feature) { setClonedVariants(feature.variants); } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [feature.variants]); + }, [feature.variants]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { clear(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [editVariant]); + }, [editVariant]); // eslint-disable-line react-hooks/exhaustive-deps const setVariantValue = ( e: React.ChangeEvent @@ -200,35 +201,12 @@ export const AddVariant = ({ closeDialog(); }; - const updateOverrideType = (index: number) => (value: string) => { - setOverrides( - produce(draft => { - draft[index].contextName = value; - }) - ); - }; - - const updateOverrideValues = (index: number, values: string[]) => { - setOverrides( - produce(draft => { - draft[index].values = values; - }) - ); - }; - - const removeOverride = (index: number) => (e: React.SyntheticEvent) => { - e.preventDefault(); - setOverrides(overrides.filter((o, i) => i !== index)); - }; - - const onAddOverride = (e: React.SyntheticEvent) => { - e.preventDefault(); - + const onAddOverride = () => { if (context.length > 0) { - setOverrides([ - ...overrides, - ...[{ contextName: context[0].name, values: [] }], - ]); + overridesDispatch({ + type: 'ADD', + payload: { contextName: context[0].name, values: [] }, + }); } }; @@ -381,9 +359,7 @@ export const AddVariant = ({ />
    - } - elseShow={ -
    - - updateConstraint(values, 'values') - } - helperText={error} - FormHelperTextProps={{ - classes: { - root: styles.helperText, - }, - }} - /> -
    - } - /> - - - - - - - - ); -}; -StrategyConstraintInputField.propTypes = { - id: PropTypes.string.isRequired, - constraint: PropTypes.object.isRequired, - updateConstraint: PropTypes.func.isRequired, - removeConstraint: PropTypes.func.isRequired, - contextFields: PropTypes.array.isRequired, -}; - -export default StrategyConstraintInputField; diff --git a/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/StrategyConstraintInputField.styles.ts b/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/StrategyConstraintInputField.styles.ts deleted file mode 100644 index 4b4c7628a6..0000000000 --- a/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/StrategyConstraintInputField.styles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles'; - -export const useStyles = makeStyles({ - contextField: { - minWidth: '140px', - }, - operator: { - minWidth: '105px', - }, - inputContainer: { - position: 'relative', - }, - inputError: { - position: 'absolute', - fontSize: '0.9rem', - color: 'red', - top: '10px', - left: '12px', - }, - tableCell: { - paddingBottom: '1.25rem', - }, - helperText: { - position: 'absolute', - top: '35px', - }, -}); diff --git a/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/index.jsx b/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/index.jsx deleted file mode 100644 index 172c8f5944..0000000000 --- a/frontend/src/component/feature/StrategyConstraints/StrategyConstraintInputField/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import StrategyConstraintInputField from './StrategyConstraintInputField'; - -export default StrategyConstraintInputField; diff --git a/frontend/src/component/feature/StrategyConstraints/StrategyConstraints.tsx b/frontend/src/component/feature/StrategyConstraints/StrategyConstraints.tsx deleted file mode 100644 index ec9f8a47a8..0000000000 --- a/frontend/src/component/feature/StrategyConstraints/StrategyConstraints.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Button, Tooltip, Typography } from '@material-ui/core'; -import { Info } from '@material-ui/icons'; -import { IConstraint } from 'interfaces/strategy'; -import { useCommonStyles } from 'themes/commonStyles'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { C } from 'component/common/flags'; -import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; -import StrategyConstraintInputField from './StrategyConstraintInputField'; -import React, { useEffect } from 'react'; -import cloneDeep from 'lodash.clonedeep'; - -interface IStrategyConstraintProps { - constraints: IConstraint[]; - updateConstraints: (constraints: IConstraint[]) => void; - constraintError: Record; - setConstraintError: React.Dispatch< - React.SetStateAction> - >; -} - -const StrategyConstraints: React.FC = ({ - constraints, - updateConstraints, - constraintError, - setConstraintError, -}) => { - const { uiConfig } = useUiConfig(); - const { context } = useUnleashContext(); - const commonStyles = useCommonStyles(); - - useEffect(() => { - if (constraints.length === 0) { - addConstraint(); - } - /* eslint-disable-next-line */ - }, []); - - const contextFields = context; - - const enabled = uiConfig.flags[C]; - const contextNames = contextFields.map(context => context.name); - - const onClick = (evt: React.SyntheticEvent) => { - evt.preventDefault(); - addConstraint(); - }; - - const addConstraint = () => { - const updatedConstraints = [...constraints]; - updatedConstraints.push(createConstraint()); - updateConstraints(updatedConstraints); - }; - - const createConstraint = (): IConstraint => { - return { - contextName: contextNames[0], - operator: 'IN', - values: [], - }; - }; - - const removeConstraint = (index: number) => (event: Event) => { - event.preventDefault(); - const updatedConstraints = [...constraints]; - updatedConstraints.splice(index, 1); - - updateConstraints(updatedConstraints); - }; - - // @ts-expect-error - const updateConstraint = (index: number) => (value, field) => { - const updatedConstraints = cloneDeep(constraints); - const constraint = updatedConstraints[index]; - constraint[field] = value; - updateConstraints(updatedConstraints); - }; - - if (!enabled) { - return null; - } - - return ( -
    - - Use context fields to constrain the activation strategy. - - } - > - - {'Constraints '} - - - - - - - {constraints.map((c, index) => ( - - ))} - -
    - - - -
    - ); -}; - -export default StrategyConstraints; diff --git a/frontend/src/component/feature/StrategyTypes/StrategyInputList/StrategyInputList.tsx b/frontend/src/component/feature/StrategyTypes/StrategyInputList/StrategyInputList.tsx index 4c08472e6f..3492c782d5 100644 --- a/frontend/src/component/feature/StrategyTypes/StrategyInputList/StrategyInputList.tsx +++ b/frontend/src/component/feature/StrategyTypes/StrategyInputList/StrategyInputList.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useState } from 'react'; import { Button, Chip, TextField, Typography } from '@material-ui/core'; import { Add } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ADD_TO_STRATEGY_INPUT_LIST, STRATEGY_INPUT_LIST } from 'utils/testIds'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; diff --git a/frontend/src/component/feedback/FeedbackNPS/FeedbackNPS.tsx b/frontend/src/component/feedback/FeedbackNPS/FeedbackNPS.tsx index 27e3ea6d20..80e5cad51b 100644 --- a/frontend/src/component/feedback/FeedbackNPS/FeedbackNPS.tsx +++ b/frontend/src/component/feedback/FeedbackNPS/FeedbackNPS.tsx @@ -5,7 +5,7 @@ import CloseIcon from '@material-ui/icons/Close'; import { ReactComponent as Logo } from 'assets/icons/logoPlain.svg'; import { useStyles } from 'component/feedback/FeedbackNPS/FeedbackNPS.styles'; import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useCommonStyles } from 'themes/commonStyles'; import UIContext from 'contexts/UIContext'; import { diff --git a/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx b/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx index f48d77e13f..5030958f0b 100644 --- a/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx +++ b/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from 'contexts/AccessContext'; import { EventHistory } from '../EventHistory/EventHistory'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; diff --git a/frontend/src/component/history/EventLog/EventCard/EventCard.jsx b/frontend/src/component/history/EventLog/EventCard/EventCard.jsx index 5f7bbd2a9f..57b0a9c8b7 100644 --- a/frontend/src/component/history/EventLog/EventCard/EventCard.jsx +++ b/frontend/src/component/history/EventLog/EventCard/EventCard.jsx @@ -1,7 +1,7 @@ import EventDiff from './EventDiff/EventDiff'; import { useStyles } from './EventCard.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; const EventCard = ({ entry, timeFormatted }) => { const styles = useStyles(); diff --git a/frontend/src/component/history/EventLog/EventLog.jsx b/frontend/src/component/history/EventLog/EventLog.jsx index cd2cc30d0a..efe2bd0eff 100644 --- a/frontend/src/component/history/EventLog/EventLog.jsx +++ b/frontend/src/component/history/EventLog/EventLog.jsx @@ -2,7 +2,7 @@ import { List, Switch, FormControlLabel } from '@material-ui/core'; import PropTypes from 'prop-types'; import EventJson from './EventJson/EventJson'; import PageContent from 'component/common/PageContent/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import EventCard from './EventCard/EventCard'; import { useStyles } from './EventLog.styles'; import { formatDateYMDHMS } from 'utils/formatDate'; diff --git a/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx b/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx index 4d7e9eb92b..c6cdb9fef1 100644 --- a/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx +++ b/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx @@ -1,4 +1,4 @@ -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { matchPath } from 'react-router'; import { useLocation } from 'react-router-dom'; import { MainLayout } from '../MainLayout/MainLayout'; diff --git a/frontend/src/component/menu/Footer/ApiDetails/ApiDetails.tsx b/frontend/src/component/menu/Footer/ApiDetails/ApiDetails.tsx index 9588928b05..f839957c9b 100644 --- a/frontend/src/component/menu/Footer/ApiDetails/ApiDetails.tsx +++ b/frontend/src/component/menu/Footer/ApiDetails/ApiDetails.tsx @@ -1,5 +1,5 @@ import { ReactElement } from 'react'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { formatCurrentVersion, formatUpdateNotification, diff --git a/frontend/src/component/menu/__tests__/footerTest.jsx b/frontend/src/component/menu/Footer/Footer.test.tsx similarity index 95% rename from frontend/src/component/menu/__tests__/footerTest.jsx rename to frontend/src/component/menu/Footer/Footer.test.tsx index 68fc32e20a..87e0bc4b1a 100644 --- a/frontend/src/component/menu/__tests__/footerTest.jsx +++ b/frontend/src/component/menu/Footer/Footer.test.tsx @@ -3,7 +3,7 @@ import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from '@material-ui/core'; -import Footer from '../Footer/Footer'; +import Footer from './Footer'; import theme from 'themes/mainTheme'; test('should render DrawerMenu', () => { diff --git a/frontend/src/component/menu/Footer/Footer.jsx b/frontend/src/component/menu/Footer/Footer.tsx similarity index 91% rename from frontend/src/component/menu/Footer/Footer.jsx rename to frontend/src/component/menu/Footer/Footer.tsx index e7ac677a08..8c7f849230 100644 --- a/frontend/src/component/menu/Footer/Footer.jsx +++ b/frontend/src/component/menu/Footer/Footer.tsx @@ -1,12 +1,13 @@ /* eslint-disable react/jsx-no-target-blank */ +import { VFC } from 'react'; import { List, ListItem, ListItemText, Grid } from '@material-ui/core'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { ApiDetails } from './ApiDetails/ApiDetails'; import { useStyles } from './Footer.styles'; import { FooterTitle } from './FooterTitle'; -export const Footer = () => { +export const Footer: VFC = () => { const styles = useStyles(); const { uiConfig } = useUiConfig(); @@ -32,7 +33,6 @@ export const Footer = () => { primary={ Node.js @@ -45,7 +45,6 @@ export const Footer = () => { primary={ Java @@ -58,7 +57,6 @@ export const Footer = () => { primary={ Go @@ -71,7 +69,6 @@ export const Footer = () => { primary={ Ruby @@ -84,7 +81,6 @@ export const Footer = () => { primary={ Python @@ -97,7 +93,6 @@ export const Footer = () => { primary={ .NET @@ -110,7 +105,6 @@ export const Footer = () => { primary={ PHP @@ -123,7 +117,6 @@ export const Footer = () => { primary={ All SDKs @@ -143,7 +136,6 @@ export const Footer = () => { primary={ Unleash Proxy @@ -156,7 +148,6 @@ export const Footer = () => { primary={ JavaScript SDK @@ -169,7 +160,6 @@ export const Footer = () => { primary={ React SDK @@ -182,7 +172,6 @@ export const Footer = () => { primary={ iOS SDK @@ -195,7 +184,6 @@ export const Footer = () => { primary={ Android SDK @@ -215,7 +203,6 @@ export const Footer = () => { primary={ getunleash.io @@ -228,7 +215,6 @@ export const Footer = () => { primary={ Twitter @@ -241,7 +227,6 @@ export const Footer = () => { primary={ LinkedIn @@ -254,7 +239,6 @@ export const Footer = () => { primary={ GitHub @@ -267,7 +251,6 @@ export const Footer = () => { primary={ Slack Community diff --git a/frontend/src/component/menu/__tests__/__snapshots__/footerTest.jsx.snap b/frontend/src/component/menu/Footer/__snapshots__/Footer.test.tsx.snap similarity index 100% rename from frontend/src/component/menu/__tests__/__snapshots__/footerTest.jsx.snap rename to frontend/src/component/menu/Footer/__snapshots__/Footer.test.tsx.snap diff --git a/frontend/src/component/menu/drawer.module.scss b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.module.scss similarity index 100% rename from frontend/src/component/menu/drawer.module.scss rename to frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.module.scss diff --git a/frontend/src/component/menu/drawer.jsx b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx similarity index 81% rename from frontend/src/component/menu/drawer.jsx rename to frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx index c8cd15421c..e5d0f620dc 100644 --- a/frontend/src/component/menu/drawer.jsx +++ b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx @@ -1,23 +1,43 @@ -import React from 'react'; +import React, { ReactNode, VFC } from 'react'; +import { Link } from 'react-router-dom'; import { Divider, Drawer, List } from '@material-ui/core'; -import PropTypes from 'prop-types'; import GitHubIcon from '@material-ui/icons/GitHub'; import LibraryBooksIcon from '@material-ui/icons/LibraryBooks'; import ExitToApp from '@material-ui/icons/ExitToApp'; -import { Link } from 'react-router-dom'; -import styles from './drawer.module.scss'; import { ReactComponent as LogoIcon } from 'assets/icons/logoBg.svg'; -import NavigationLink from './Header/NavigationLink/NavigationLink'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import NavigationLink from '../NavigationLink/NavigationLink'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { getBasePath } from 'utils/formatPath'; +import { IFlags } from 'interfaces/uiConfig'; +import { IRoute } from 'interfaces/route'; +import styles from './DrawerMenu.module.scss'; // FIXME: useStyle - theme -export const DrawerMenu = ({ +interface IDrawerMenuProps { + title?: string; + open?: boolean; + toggleDrawer: () => void; + admin?: boolean; + links: Array<{ + value: string; + icon: string | ReactNode; + href: string; + title: string; + }>; + flags?: IFlags; + routes: { + mainNavRoutes: IRoute[]; + mobileRoutes: IRoute[]; + adminRoutes: IRoute[]; + }; +} + +export const DrawerMenu: VFC = ({ links = [], title = 'Unleash', flags = {}, open = false, toggleDrawer, - admin, + admin = false, routes, }) => { const renderLinks = () => { @@ -111,11 +131,3 @@ export const DrawerMenu = ({ ); }; - -DrawerMenu.propTypes = { - links: PropTypes.array, - title: PropTypes.string, - flags: PropTypes.object, - open: PropTypes.bool, - toggleDrawer: PropTypes.func, -}; diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index b5aeab14d1..29de2364d8 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -1,17 +1,16 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, VFC } from 'react'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; import { Link } from 'react-router-dom'; import { AppBar, Container, IconButton, Tooltip } from '@material-ui/core'; -import { DrawerMenu } from '../drawer'; import MenuIcon from '@material-ui/icons/Menu'; import SettingsIcon from '@material-ui/icons/Settings'; import UserProfile from 'component/user/UserProfile'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import MenuBookIcon from '@material-ui/icons/MenuBook'; import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg'; -import { useStyles } from './Header.styles'; +import { DrawerMenu } from './DrawerMenu/DrawerMenu'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useCommonStyles } from 'themes/commonStyles'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; @@ -21,8 +20,9 @@ import { getRoutes } from 'component/menu/routes'; import { KeyboardArrowDown } from '@material-ui/icons'; import { filterByFlags } from 'component/common/util'; import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions'; +import { useStyles } from './Header.styles'; -const Header = () => { +const Header: VFC = () => { const theme = useTheme(); const [anchorEl, setAnchorEl] = useState(null); const [anchorElAdvanced, setAnchorElAdvanced] = @@ -31,7 +31,9 @@ const Header = () => { const [admin, setAdmin] = useState(false); const { permissions } = useAuthPermissions(); const commonStyles = useCommonStyles(); - const { uiConfig } = useUiConfig(); + const { + uiConfig: { links, name, flags }, + } = useUiConfig(); const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); const styles = useStyles(); const [openDrawer, setOpenDrawer] = useState(false); @@ -50,7 +52,6 @@ const Header = () => { } }, [permissions]); - const { links, name, flags } = uiConfig; const routes = getRoutes(); const filteredMainRoutes = { diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap similarity index 100% rename from frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap rename to frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap diff --git a/frontend/src/component/menu/__tests__/routesTest.jsx b/frontend/src/component/menu/__tests__/routes.test.tsx similarity index 81% rename from frontend/src/component/menu/__tests__/routesTest.jsx rename to frontend/src/component/menu/__tests__/routes.test.tsx index 7522ed9e1b..f0d7f11330 100644 --- a/frontend/src/component/menu/__tests__/routesTest.jsx +++ b/frontend/src/component/menu/__tests__/routes.test.tsx @@ -6,5 +6,5 @@ test('returns all baseRoutes', () => { test('getRoute() returns named route', () => { const featuresRoute = getRoute('/features'); - expect(featuresRoute.path).toEqual('/features'); + expect(featuresRoute?.path).toEqual('/features'); }); diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index 2cfff01807..31d5b4571a 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -2,7 +2,7 @@ import { useHistory, useParams } from 'react-router'; import useProject from 'hooks/api/getters/useProject/useProject'; import useLoading from 'hooks/useLoading'; import ApiError from 'component/common/ApiError/ApiError'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './Project.styles'; import { Tab, Tabs } from '@material-ui/core'; import { Edit } from '@material-ui/icons'; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 155d469324..fe80ae3d79 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -6,9 +6,9 @@ import { useParams } from 'react-router'; import { Link, useHistory } from 'react-router-dom'; import AccessContext from 'contexts/AccessContext'; import { SearchField } from 'component/common/SearchField/SearchField'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { PROJECTFILTERING } from 'component/common/flags'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PageContent from 'component/common/PageContent'; import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import FeatureToggleListNew from 'component/feature/FeatureToggleListNew/FeatureToggleListNew'; diff --git a/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx b/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx index 8f4a17357c..dc56e425de 100644 --- a/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx +++ b/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx @@ -1,7 +1,7 @@ import { useHealthReport } from 'hooks/api/getters/useHealthReport/useHealthReport'; import ApiError from 'component/common/ApiError/ApiError'; -import ConditionallyRender from 'component/common/ConditionallyRender'; -import ReportToggleList from 'component/Reporting/ReportToggleList/ReportToggleList'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ReportToggleList } from 'component/Reporting/ReportToggleList/ReportToggleList'; import { ReportCard } from 'component/Reporting/ReportCard/ReportCard'; import { usePageTitle } from 'hooks/usePageTitle'; diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx index e4bf8829fe..92491f750d 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx @@ -8,7 +8,7 @@ import { useCommonStyles } from 'themes/commonStyles'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Accordion, AccordionActions, diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx index f308602efa..fc6a1bc907 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx @@ -10,12 +10,12 @@ import { IProjectViewParams } from 'interfaces/params'; import usePagination from 'hooks/usePagination'; import PaginateUI from 'component/common/PaginateUI/PaginateUI'; import useToast from 'hooks/useToast'; -import ConfirmDialogue from 'component/common/Dialogue'; +import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue'; import useProjectAccess, { IProjectAccessUser, } from 'hooks/api/getters/useProjectAccess/useProjectAccess'; import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { ProjectAccessList } from './ProjectAccessList/ProjectAccessList'; export const ProjectAccess = () => { diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx index edf620a96b..9882d30e72 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx @@ -17,7 +17,7 @@ import useProjectAccess, { IProjectAccessUser, } from 'hooks/api/getters/useProjectAccess/useProjectAccess'; import { IProjectRole } from 'interfaces/role'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IProjectAccessAddUserProps { roles: IProjectRole[]; diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.tsx b/frontend/src/component/project/ProjectCard/ProjectCard.tsx index 5ee4b06bf0..b2e258b4db 100644 --- a/frontend/src/component/project/ProjectCard/ProjectCard.tsx +++ b/frontend/src/component/project/ProjectCard/ProjectCard.tsx @@ -4,7 +4,7 @@ import MoreVertIcon from '@material-ui/icons/MoreVert'; import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg'; import { useState } from 'react'; import { useHistory } from 'react-router-dom'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { Delete, Edit } from '@material-ui/icons'; diff --git a/frontend/src/component/project/ProjectEnvironment/EnvironmentDisableConfirm/EnvironmentDisableConfirm.tsx b/frontend/src/component/project/ProjectEnvironment/EnvironmentDisableConfirm/EnvironmentDisableConfirm.tsx index 5a6ba71c5a..400973e9fe 100644 --- a/frontend/src/component/project/ProjectEnvironment/EnvironmentDisableConfirm/EnvironmentDisableConfirm.tsx +++ b/frontend/src/component/project/ProjectEnvironment/EnvironmentDisableConfirm/EnvironmentDisableConfirm.tsx @@ -1,6 +1,6 @@ import { Alert } from '@material-ui/lab'; import React from 'react'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import Input from 'component/common/Input/Input'; import { useStyles } from './EnvironmentDisableConfirm.styles'; import { IProjectEnvironment } from 'interfaces/environments'; diff --git a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx index 7f88387054..021a4a54d1 100644 --- a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx +++ b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './ProjectEnvironment.styles'; import useLoading from 'hooks/useLoading'; import PageContent from 'component/common/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; import ApiError from 'component/common/ApiError/ApiError'; @@ -214,7 +214,7 @@ const ProjectEnvironmentList = ({ /> { - const strategy = { - name: 'Another', - description: "another's description", - }; - const tree = renderer.create( - - - - - - - - - - - - ); - - expect(tree).toMatchSnapshot(); -}); - -test('renders correctly with one strategy without permissions', () => { - const strategy = { - name: 'Another', - description: "another's description", - }; - const tree = renderer.create( - - - - - - - - - - - - ); - - expect(tree).toMatchSnapshot(); -}); diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx index 7eec56a2ee..8bfdaf35a3 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx @@ -22,12 +22,12 @@ import { DELETE_STRATEGY, UPDATE_STRATEGY, } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PageContent from 'component/common/PageContent/PageContent'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import { useStyles } from './StrategiesList.styles'; import AccessContext from 'contexts/AccessContext'; -import Dialogue from 'component/common/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { ADD_NEW_STRATEGY_ID } from 'utils/testIds'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; diff --git a/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap b/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap deleted file mode 100644 index eab31b8055..0000000000 --- a/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap +++ /dev/null @@ -1,563 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly with one strategy 1`] = ` -Array [ -
    -
    -
    -
    -

    - Strategies -

    -
    -
    - - - -
    -
    -
    -
    -
      -
    • -
      - - - -
      -
      - - - - Gradual rollout - - - -

      - Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit. -

      -
      -
      - - - -
      -
      - -
      -
      - -
      -
    • -
    -
    -
    , -
    - Navigated to Strategies -
    , -] -`; - -exports[`renders correctly with one strategy without permissions 1`] = ` -Array [ -
    -
    -
    -
    -

    - Strategies -

    -
    -
    - - - -
    -
    -
    -
    -
      -
    • -
      - - - -
      -
      - - - - Gradual rollout - - - -

      - Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit. -

      -
      -
      - - - -
      -
      - -
      -
      - -
      -
    • -
    -
    -
    , -
    - Navigated to Strategies -
    , -] -`; diff --git a/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.tsx b/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.tsx index 1e250cfc88..7b4ff98ec6 100644 --- a/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.tsx +++ b/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.tsx @@ -8,7 +8,7 @@ import { Delete } from '@material-ui/icons'; import { useStyles } from './StrategyParameter.styles'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import Input from 'component/common/Input/Input'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import React from 'react'; import { ICustomStrategyParameter } from 'interfaces/strategy'; diff --git a/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx b/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx index dfdd01827e..028aeaaae1 100644 --- a/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx +++ b/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx @@ -8,7 +8,7 @@ import { } from '@material-ui/core'; import { Add, RadioButtonChecked } from '@material-ui/icons'; import { AppsLinkList } from 'component/common'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import styles from '../../strategies.module.scss'; import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList'; import { IParameter, IStrategy } from 'interfaces/strategy'; diff --git a/frontend/src/component/strategies/StrategyView/StrategyView.test.jsx b/frontend/src/component/strategies/StrategyView/StrategyView.test.jsx deleted file mode 100644 index cbe999105d..0000000000 --- a/frontend/src/component/strategies/StrategyView/StrategyView.test.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { ThemeProvider } from '@material-ui/core'; -import { StrategyView } from './StrategyView'; -import renderer from 'react-test-renderer'; -import { MemoryRouter } from 'react-router-dom'; -import theme from 'themes/mainTheme'; -import AccessProvider from 'component/providers/AccessProvider/AccessProvider'; - -test('renders correctly with one strategy', () => { - const strategy = { - name: 'Another', - description: "another's description", - editable: true, - parameters: [ - { - name: 'customParam', - type: 'list', - description: 'customList', - required: true, - }, - ], - }; - const applications = [ - { - appName: 'appA', - description: 'app description', - }, - ]; - const toggles = [ - { - name: 'toggleA', - description: 'toggle description', - }, - ]; - const tree = renderer.create( - - - - - - - - ); - - expect(tree).toMatchSnapshot(); -}); diff --git a/frontend/src/component/strategies/StrategyView/StrategyView.tsx b/frontend/src/component/strategies/StrategyView/StrategyView.tsx index e18488c9f2..d5cebe280c 100644 --- a/frontend/src/component/strategies/StrategyView/StrategyView.tsx +++ b/frontend/src/component/strategies/StrategyView/StrategyView.tsx @@ -6,10 +6,10 @@ import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures'; import useApplications from 'hooks/api/getters/useApplications/useApplications'; import { StrategyDetails } from './StrategyDetails/StrategyDetails'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { Edit } from '@material-ui/icons'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; export const StrategyView = () => { const { name } = useParams<{ name: string }>(); diff --git a/frontend/src/component/strategies/StrategyView/__snapshots__/StrategyView.test.jsx.snap b/frontend/src/component/strategies/StrategyView/__snapshots__/StrategyView.test.jsx.snap deleted file mode 100644 index 3d1274c249..0000000000 --- a/frontend/src/component/strategies/StrategyView/__snapshots__/StrategyView.test.jsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly with one strategy 1`] = `null`; diff --git a/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx index 9b9ec45b2b..ad902c71fd 100644 --- a/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx +++ b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx @@ -8,7 +8,7 @@ import { import { Pause, PlayArrow } from '@material-ui/icons'; import styles from 'component/common/common.module.scss'; import { Link } from 'react-router-dom'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeatureSchema } from 'openapi'; interface ITogglesLinkListProps { diff --git a/frontend/src/component/tags/TagTypeList/TagTypeList.tsx b/frontend/src/component/tags/TagTypeList/TagTypeList.tsx index 9c6ea8199a..4094551bbb 100644 --- a/frontend/src/component/tags/TagTypeList/TagTypeList.tsx +++ b/frontend/src/component/tags/TagTypeList/TagTypeList.tsx @@ -10,14 +10,14 @@ import { Tooltip, } from '@material-ui/core'; import { Add, Delete, Edit, Label } from '@material-ui/icons'; -import HeaderTitle from 'component/common/HeaderTitle'; +import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PageContent from 'component/common/PageContent/PageContent'; -import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { DELETE_TAG_TYPE, UPDATE_TAG_TYPE, } from 'component/providers/AccessProvider/permissions'; -import Dialogue from 'component/common/Dialogue/Dialogue'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import styles from './TagTypeList.module.scss'; import AccessContext from 'contexts/AccessContext'; diff --git a/frontend/src/component/user/Authentication/Authentication.tsx b/frontend/src/component/user/Authentication/Authentication.tsx index 5596a468c3..8f9ea343f3 100644 --- a/frontend/src/component/user/Authentication/Authentication.tsx +++ b/frontend/src/component/user/Authentication/Authentication.tsx @@ -1,5 +1,5 @@ import SimpleAuth from '../SimpleAuth/SimpleAuth'; -import AuthenticationCustomComponent from 'component/user/AuthenticationCustomComponent'; +import { AuthenticationCustomComponent } from 'component/user/AuthenticationCustomComponent'; import PasswordAuth from '../PasswordAuth/PasswordAuth'; import HostedAuth from '../HostedAuth/HostedAuth'; import DemoAuth from '../DemoAuth/DemoAuth'; @@ -11,7 +11,7 @@ import { } from 'constants/authTypes'; import SecondaryLoginActions from '../common/SecondaryLoginActions/SecondaryLoginActions'; import useQueryParams from 'hooks/useQueryParams'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Alert } from '@material-ui/lab'; import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails'; import { AUTH_PAGE_ID } from 'utils/testIds'; diff --git a/frontend/src/component/user/AuthenticationCustomComponent.jsx b/frontend/src/component/user/AuthenticationCustomComponent.jsx deleted file mode 100644 index d98addfe0a..0000000000 --- a/frontend/src/component/user/AuthenticationCustomComponent.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { CardActions, Button } from '@material-ui/core'; - -class AuthenticationCustomComponent extends React.Component { - static propTypes = { - authDetails: PropTypes.object.isRequired, - }; - - render() { - const authDetails = this.props.authDetails; - return ( -
    -

    {authDetails.message}

    - - - - - -
    - ); - } -} - -export default AuthenticationCustomComponent; diff --git a/frontend/src/component/user/AuthenticationCustomComponent.tsx b/frontend/src/component/user/AuthenticationCustomComponent.tsx new file mode 100644 index 0000000000..5117bc3c77 --- /dev/null +++ b/frontend/src/component/user/AuthenticationCustomComponent.tsx @@ -0,0 +1,26 @@ +import { VFC } from 'react'; +import { CardActions, Button } from '@material-ui/core'; +import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint'; + +interface IAuthenticationCustomComponentProps { + authDetails: IAuthEndpointDetailsResponse; +} + +export const AuthenticationCustomComponent: VFC< + IAuthenticationCustomComponentProps +> = ({ authDetails }) => ( +
    +

    {authDetails.message}

    + + + + + +
    +); diff --git a/frontend/src/component/user/DemoAuth/DemoAuth.jsx b/frontend/src/component/user/DemoAuth/DemoAuth.tsx similarity index 86% rename from frontend/src/component/user/DemoAuth/DemoAuth.jsx rename to frontend/src/component/user/DemoAuth/DemoAuth.tsx index 6dbf3724f0..90eb8d5fbc 100644 --- a/frontend/src/component/user/DemoAuth/DemoAuth.jsx +++ b/frontend/src/component/user/DemoAuth/DemoAuth.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; +import { ChangeEventHandler, FormEventHandler, useState, VFC } from 'react'; import { Button, TextField } from '@material-ui/core'; import styles from './DemoAuth.module.scss'; import { ReactComponent as Logo } from 'assets/img/logo.svg'; @@ -9,15 +8,21 @@ import { useAuthApi } from 'hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint'; -const DemoAuth = ({ authDetails, redirect }) => { +interface IDemoAuthProps { + authDetails: IAuthEndpointDetailsResponse; + redirect: string; +} + +const DemoAuth: VFC = ({ authDetails, redirect }) => { const [email, setEmail] = useState(''); const history = useHistory(); const { refetchUser } = useAuthUser(); const { emailAuth } = useAuthApi(); const { setToastApiError } = useToast(); - const handleSubmit = async evt => { + const handleSubmit: FormEventHandler = async evt => { evt.preventDefault(); try { @@ -29,7 +34,7 @@ const DemoAuth = ({ authDetails, redirect }) => { } }; - const handleChange = e => { + const handleChange: ChangeEventHandler = e => { const value = e.target.value; setEmail(value); }; @@ -90,9 +95,4 @@ const DemoAuth = ({ authDetails, redirect }) => { ); }; -DemoAuth.propTypes = { - authDetails: PropTypes.object.isRequired, - redirect: PropTypes.string.isRequired, -}; - export default DemoAuth; diff --git a/frontend/src/component/user/ForgottenPassword/ForgottenPassword.tsx b/frontend/src/component/user/ForgottenPassword/ForgottenPassword.tsx index 6b9b97119d..f99da31597 100644 --- a/frontend/src/component/user/ForgottenPassword/ForgottenPassword.tsx +++ b/frontend/src/component/user/ForgottenPassword/ForgottenPassword.tsx @@ -7,7 +7,7 @@ import { useCommonStyles } from 'themes/commonStyles'; import useLoading from 'hooks/useLoading'; import { FORGOTTEN_PASSWORD_FIELD } from 'utils/testIds'; import { formatApiPath } from 'utils/formatPath'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import DividerText from 'component/common/DividerText/DividerText'; import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout'; import { useStyles } from './ForgottenPassword.styles'; diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.jsx b/frontend/src/component/user/HostedAuth/HostedAuth.tsx similarity index 83% rename from frontend/src/component/user/HostedAuth/HostedAuth.jsx rename to frontend/src/component/user/HostedAuth/HostedAuth.tsx index ba3fbf14ca..d048dbaac3 100644 --- a/frontend/src/component/user/HostedAuth/HostedAuth.jsx +++ b/frontend/src/component/user/HostedAuth/HostedAuth.tsx @@ -1,6 +1,5 @@ -import { useState } from 'react'; +import { FormEventHandler, useState, VFC } from 'react'; import classnames from 'classnames'; -import PropTypes from 'prop-types'; import { Button, Grid, TextField, Typography } from '@material-ui/core'; import { useHistory } from 'react-router'; import { useCommonStyles } from 'themes/commonStyles'; @@ -8,13 +7,20 @@ import { useStyles } from './HostedAuth.styles'; import useQueryParams from 'hooks/useQueryParams'; import AuthOptions from '../common/AuthOptions/AuthOptions'; import DividerText from 'component/common/DividerText/DividerText'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import PasswordField from 'component/common/PasswordField/PasswordField'; import { useAuthApi } from 'hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds'; +import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint'; +import { BadRequestError, NotFoundError } from 'utils/apiUtils'; -const HostedAuth = ({ authDetails, redirect }) => { +interface IHostedAuthProps { + authDetails: IAuthEndpointDetailsResponse; + redirect: string; +} + +const HostedAuth: VFC = ({ authDetails, redirect }) => { const commonStyles = useCommonStyles(); const styles = useStyles(); const { refetchUser } = useAuthUser(); @@ -23,12 +29,13 @@ const HostedAuth = ({ authDetails, redirect }) => { const { passwordAuth } = useAuthApi(); const [username, setUsername] = useState(params.get('email') || ''); const [password, setPassword] = useState(''); - const [errors, setErrors] = useState({ - usernameError: '', - passwordError: '', - }); + const [errors, setErrors] = useState<{ + usernameError?: string; + passwordError?: string; + apiError?: string; + }>({}); - const handleSubmit = async evt => { + const handleSubmit: FormEventHandler = async evt => { evt.preventDefault(); if (!username) { @@ -52,8 +59,11 @@ const HostedAuth = ({ authDetails, redirect }) => { await passwordAuth(authDetails.path, username, password); refetchUser(); history.push(redirect); - } catch (error) { - if (error.statusCode === 404 || error.statusCode === 400) { + } catch (error: any) { + if ( + error instanceof NotFoundError || + error instanceof BadRequestError + ) { setErrors(prev => ({ ...prev, apiError: 'Invalid login details', @@ -106,7 +116,7 @@ const HostedAuth = ({ authDetails, redirect }) => { type="string" onChange={evt => setUsername(evt.target.value)} value={username} - error={!!usernameError} + error={Boolean(usernameError)} helperText={usernameError} variant="outlined" size="small" @@ -118,7 +128,7 @@ const HostedAuth = ({ authDetails, redirect }) => { name="password" id="password" value={password} - error={!!passwordError} + error={Boolean(passwordError)} helperText={passwordError} data-testid={LOGIN_PASSWORD_ID} /> @@ -141,9 +151,4 @@ const HostedAuth = ({ authDetails, redirect }) => { ); }; -HostedAuth.propTypes = { - authDetails: PropTypes.object.isRequired, - redirect: PropTypes.string.isRequired, -}; - export default HostedAuth; diff --git a/frontend/src/component/user/Login/Login.tsx b/frontend/src/component/user/Login/Login.tsx index ab4cfbaa38..abf1b98b22 100644 --- a/frontend/src/component/user/Login/Login.tsx +++ b/frontend/src/component/user/Login/Login.tsx @@ -1,4 +1,4 @@ -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from 'component/user/Login/Login.styles'; import useQueryParams from 'hooks/useQueryParams'; import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess'; diff --git a/frontend/src/component/user/NewUser/NewUser.tsx b/frontend/src/component/user/NewUser/NewUser.tsx index 82770e4272..2334a26949 100644 --- a/frontend/src/component/user/NewUser/NewUser.tsx +++ b/frontend/src/component/user/NewUser/NewUser.tsx @@ -5,7 +5,7 @@ import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDe import { useStyles } from './NewUser.styles'; import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword'; import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import InvalidToken from '../common/InvalidToken/InvalidToken'; import AuthOptions from '../common/AuthOptions/AuthOptions'; import DividerText from 'component/common/DividerText/DividerText'; diff --git a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx b/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx similarity index 76% rename from frontend/src/component/user/PasswordAuth/PasswordAuth.jsx rename to frontend/src/component/user/PasswordAuth/PasswordAuth.tsx index acab7a7c8e..7ef05e4e37 100644 --- a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx +++ b/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx @@ -1,8 +1,7 @@ -import { useState } from 'react'; +import { FormEventHandler, useState, VFC } from 'react'; import classnames from 'classnames'; -import PropTypes from 'prop-types'; import { Button, TextField } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useHistory } from 'react-router'; import { useCommonStyles } from 'themes/commonStyles'; import { useStyles } from './PasswordAuth.styles'; @@ -14,8 +13,19 @@ import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds'; import PasswordField from 'component/common/PasswordField/PasswordField'; import { useAuthApi } from 'hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; +import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint'; +import { + AuthenticationError, + BadRequestError, + NotFoundError, +} from 'utils/apiUtils'; -const PasswordAuth = ({ authDetails, redirect }) => { +interface IPasswordAuthProps { + authDetails: IAuthEndpointDetailsResponse; + redirect: string; +} + +const PasswordAuth: VFC = ({ authDetails, redirect }) => { const commonStyles = useCommonStyles(); const styles = useStyles(); const history = useHistory(); @@ -24,12 +34,13 @@ const PasswordAuth = ({ authDetails, redirect }) => { const [username, setUsername] = useState(params.get('email') || ''); const [password, setPassword] = useState(''); const { passwordAuth } = useAuthApi(); - const [errors, setErrors] = useState({ - usernameError: '', - passwordError: '', - }); + const [errors, setErrors] = useState<{ + usernameError?: string; + passwordError?: string; + apiError?: string; + }>({}); - const handleSubmit = async evt => { + const handleSubmit: FormEventHandler = async evt => { evt.preventDefault(); if (!username) { @@ -53,15 +64,18 @@ const PasswordAuth = ({ authDetails, redirect }) => { await passwordAuth(authDetails.path, username, password); refetchUser(); history.push(redirect); - } catch (error) { - if (error.statusCode === 404 || error.statusCode === 400) { + } catch (error: any) { + if ( + error instanceof NotFoundError || + error instanceof BadRequestError + ) { setErrors(prev => ({ ...prev, apiError: 'Invalid login details', })); setPassword(''); setUsername(''); - } else if (error.statusCode === 401) { + } else if (error instanceof AuthenticationError) { setErrors({ apiError: 'Invalid password and username combination.', }); @@ -82,7 +96,7 @@ const PasswordAuth = ({ authDetails, redirect }) => { show={
    { type="string" onChange={evt => setUsername(evt.target.value)} value={username} - error={!!usernameError} + error={Boolean(usernameError)} helperText={usernameError} autoComplete="true" data-testid={LOGIN_EMAIL_ID} @@ -120,7 +134,7 @@ const PasswordAuth = ({ authDetails, redirect }) => { name="password" id="password" value={password} - error={!!passwordError} + error={Boolean(passwordError)} helperText={passwordError} autoComplete="true" data-testid={LOGIN_PASSWORD_ID} @@ -141,33 +155,28 @@ const PasswordAuth = ({ authDetails, redirect }) => { ); }; - const renderWithOptions = options => ( - <> - - } - /> - {renderLoginForm()} - - ); - const { options = [] } = authDetails; return ( <> 0} - show={renderWithOptions(options)} + show={ + <> + + + } + /> + {renderLoginForm()} + + } elseShow={renderLoginForm()} /> ); }; -PasswordAuth.propTypes = { - authDetails: PropTypes.object.isRequired, - redirect: PropTypes.string.isRequired, -}; - export default PasswordAuth; diff --git a/frontend/src/component/user/ResetPassword/ResetPassword.tsx b/frontend/src/component/user/ResetPassword/ResetPassword.tsx index caa248d86c..95948ee7f0 100644 --- a/frontend/src/component/user/ResetPassword/ResetPassword.tsx +++ b/frontend/src/component/user/ResetPassword/ResetPassword.tsx @@ -4,7 +4,7 @@ import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDe import { useStyles } from './ResetPassword.styles'; import { Typography } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import InvalidToken from '../common/InvalidToken/InvalidToken'; import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword'; import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout'; diff --git a/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx b/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx similarity index 84% rename from frontend/src/component/user/SimpleAuth/SimpleAuth.jsx rename to frontend/src/component/user/SimpleAuth/SimpleAuth.tsx index f09aed537b..321b37d1a1 100644 --- a/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx +++ b/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; +import { ChangeEventHandler, FormEventHandler, useState, VFC } from 'react'; import { Button, TextField } from '@material-ui/core'; import styles from './SimpleAuth.module.scss'; import { useHistory } from 'react-router-dom'; @@ -8,15 +7,21 @@ import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import { LOGIN_BUTTON, LOGIN_EMAIL_ID } from 'utils/testIds'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint'; -const SimpleAuth = ({ authDetails, redirect }) => { +interface ISimpleAuthProps { + authDetails: IAuthEndpointDetailsResponse; + redirect: string; +} + +const SimpleAuth: VFC = ({ authDetails, redirect }) => { const [email, setEmail] = useState(''); const { refetchUser } = useAuthUser(); const { emailAuth } = useAuthApi(); const history = useHistory(); const { setToastApiError } = useToast(); - const handleSubmit = async evt => { + const handleSubmit: FormEventHandler = async evt => { evt.preventDefault(); try { @@ -28,7 +33,7 @@ const SimpleAuth = ({ authDetails, redirect }) => { } }; - const handleChange = e => { + const handleChange: ChangeEventHandler = e => { const value = e.target.value; setEmail(value); }; @@ -80,9 +85,4 @@ const SimpleAuth = ({ authDetails, redirect }) => { ); }; -SimpleAuth.propTypes = { - authDetails: PropTypes.object.isRequired, - redirect: PropTypes.string.isRequired, -}; - export default SimpleAuth; diff --git a/frontend/src/component/user/StandaloneBanner/StandaloneBanner.tsx b/frontend/src/component/user/StandaloneBanner/StandaloneBanner.tsx index b7cd782067..067a391a41 100644 --- a/frontend/src/component/user/StandaloneBanner/StandaloneBanner.tsx +++ b/frontend/src/component/user/StandaloneBanner/StandaloneBanner.tsx @@ -4,7 +4,7 @@ import Gradient from 'component/common/Gradient/Gradient'; import { ReactComponent as Logo } from 'assets/icons/logoWhiteBg.svg'; import { ReactComponent as LogoWithText } from 'assets/img/logoWhiteTransparentHorizontal.svg'; import { useStyles } from './StandaloneBanner.styles'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IStandaloneBannerProps { title: string; diff --git a/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx b/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx index 751ab06de0..d9271821b7 100644 --- a/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx +++ b/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx @@ -6,7 +6,7 @@ import { useCommonStyles } from 'themes/commonStyles'; import PasswordChecker from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker'; import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher'; import { Alert } from '@material-ui/lab'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import useLoading from 'hooks/useLoading'; import { BAD_REQUEST, diff --git a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx index 9cce505c70..16f4c20b40 100644 --- a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx +++ b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Avatar, Button, diff --git a/frontend/src/component/user/common/AuthOptions/AuthOptions.tsx b/frontend/src/component/user/common/AuthOptions/AuthOptions.tsx index daff2bcece..3aae12e3ea 100644 --- a/frontend/src/component/user/common/AuthOptions/AuthOptions.tsx +++ b/frontend/src/component/user/common/AuthOptions/AuthOptions.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames'; import { useCommonStyles } from 'themes/commonStyles'; import { ReactComponent as GoogleSvg } from 'assets/icons/google.svg'; import LockRounded from '@material-ui/icons/LockRounded'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { IAuthOptions } from 'hooks/api/getters/useAuth/useAuthEndpoint'; import { SSO_LOGIN_BUTTON } from 'utils/testIds'; diff --git a/frontend/src/component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker.tsx b/frontend/src/component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker.tsx index dbccbd8f08..dc467302f6 100644 --- a/frontend/src/component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker.tsx +++ b/frontend/src/component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker.tsx @@ -6,8 +6,8 @@ import { useStyles } from './PasswordChecker.styles'; import { useCallback } from 'react'; import { formatApiPath } from 'utils/formatPath'; import { Alert } from '@material-ui/lab'; -import ConditionallyRender from 'component/common/ConditionallyRender'; import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; interface IPasswordCheckerProps { password: string; diff --git a/frontend/src/component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher.tsx b/frontend/src/component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher.tsx index 7f8994a41e..b3d5d61e9f 100644 --- a/frontend/src/component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher.tsx +++ b/frontend/src/component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher.tsx @@ -1,5 +1,5 @@ import { Typography } from '@material-ui/core'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import classnames from 'classnames'; import CheckIcon from '@material-ui/icons/Check'; diff --git a/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx b/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx index 5c5846aff4..513512b667 100644 --- a/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx +++ b/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx @@ -11,7 +11,7 @@ import React, { import { useHistory } from 'react-router'; import { useCommonStyles } from 'themes/commonStyles'; import { OK } from 'constants/statusCodes'; -import ConditionallyRender from 'component/common/ConditionallyRender'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import ResetPasswordError from '../ResetPasswordError/ResetPasswordError'; import PasswordChecker from './PasswordChecker/PasswordChecker'; import PasswordMatcher from './PasswordMatcher/PasswordMatcher'; diff --git a/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts b/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts index 3e0e493dc0..f3f3c0f639 100644 --- a/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts +++ b/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts @@ -10,7 +10,7 @@ const useFeatureApi = () => { propagateErrors: true, }); - const validateFeatureToggleName = async (name: string) => { + const validateFeatureToggleName = async (name: string | undefined) => { const path = `api/admin/features/validate`; const req = createRequest(path, { method: 'POST', diff --git a/frontend/src/hooks/api/getters/useAddons/useAddons.ts b/frontend/src/hooks/api/getters/useAddons/useAddons.ts index 619794e004..c9c58805b2 100644 --- a/frontend/src/hooks/api/getters/useAddons/useAddons.ts +++ b/frontend/src/hooks/api/getters/useAddons/useAddons.ts @@ -2,6 +2,12 @@ import useSWR, { mutate, SWRConfiguration } from 'swr'; import { useState, useEffect } from 'react'; import { formatApiPath } from 'utils/formatPath'; import handleErrorResponses from '../httpErrorResponseHandler'; +import { IAddon, IAddonProvider } from 'interfaces/addons'; + +interface IAddonsResponse { + addons: IAddon[]; + providers: IAddonProvider[]; +} const useAddons = (options: SWRConfiguration = {}) => { const fetcher = async () => { @@ -14,7 +20,7 @@ const useAddons = (options: SWRConfiguration = {}) => { const KEY = `api/admin/addons`; - const { data, error } = useSWR(KEY, fetcher, options); + const { data, error } = useSWR(KEY, fetcher, options); const [loading, setLoading] = useState(!error && !data); const refetchAddons = () => { diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts index cdfb00e346..604393561b 100644 --- a/frontend/src/hooks/useFeaturesSort.ts +++ b/frontend/src/hooks/useFeaturesSort.ts @@ -8,7 +8,7 @@ import { } from 'component/Reporting/utils'; import { FeatureSchema } from 'openapi'; -type FeaturesSortType = +export type FeaturesSortType = | 'name' | 'expired' | 'type' @@ -19,7 +19,7 @@ type FeaturesSortType = | 'status' | 'project'; -interface IFeaturesSort { +export interface IFeaturesSort { type: FeaturesSortType; desc?: boolean; } diff --git a/frontend/src/hooks/useSort.ts b/frontend/src/hooks/useSort.ts index 060c556801..9a7f2134ac 100644 --- a/frontend/src/hooks/useSort.ts +++ b/frontend/src/hooks/useSort.ts @@ -12,19 +12,15 @@ import { sortFeaturesByStatusDescending, } from 'component/Reporting/utils'; -import { - LAST_SEEN, - NAME, - CREATED, - EXPIRED, - STATUS, - REPORT, -} from 'component/Reporting/constants'; +import { ReportingSortType } from 'component/Reporting/constants'; import { IFeatureToggleListItem } from 'interfaces/featureToggle'; const useSort = () => { - const [sortData, setSortData] = useState({ - sortKey: NAME, + const [sortData, setSortData] = useState<{ + sortKey: ReportingSortType; + ascending: boolean; + }>({ + sortKey: 'name', ascending: true, }); @@ -66,16 +62,16 @@ const useSort = () => { const sort = (features: IFeatureToggleListItem[]) => { switch (sortData.sortKey) { - case NAME: + case 'name': return handleSortName(features); - case LAST_SEEN: + case 'last-seen': return handleSortLastSeen(features); - case CREATED: + case 'created': return handleSortCreatedAt(features); - case EXPIRED: - case REPORT: + case 'expired': + case 'report': return handleSortExpiredAt(features); - case STATUS: + case 'status': return handleSortStatus(features); default: return features; diff --git a/frontend/src/interfaces/addons.ts b/frontend/src/interfaces/addons.ts index 158fb3c18a..585ebcfba4 100644 --- a/frontend/src/interfaces/addons.ts +++ b/frontend/src/interfaces/addons.ts @@ -1,12 +1,12 @@ import { ITagType } from './tags'; export interface IAddon { - id: number; provider: string; - description: string; - enabled: boolean; + parameters: Record; + id: number; events: string[]; - parameters: object; + enabled: boolean; + description: string; } export interface IAddonProvider { @@ -30,10 +30,10 @@ export interface IAddonProviderParams { } export interface IAddonConfig { - description: string; - enabled: boolean; - events: string[]; - id: number; - parameters: Record; provider: string; + parameters: Record; + id: number; + events: string[]; + enabled: boolean; + description: string; } diff --git a/frontend/src/interfaces/route.ts b/frontend/src/interfaces/route.ts index ffc8937da7..55377e6742 100644 --- a/frontend/src/interfaces/route.ts +++ b/frontend/src/interfaces/route.ts @@ -1,4 +1,4 @@ -import { FunctionComponent } from 'react'; +import { VoidFunctionComponent } from 'react'; export interface IRoute { path: string; @@ -8,7 +8,7 @@ export interface IRoute { parent?: string; flag?: string; hidden?: boolean; - component: FunctionComponent; + component: VoidFunctionComponent; menu: IRouteMenu; } diff --git a/frontend/src/utils/apiUtils.ts b/frontend/src/utils/apiUtils.ts index 00cc787ca1..9155cf69c3 100644 --- a/frontend/src/utils/apiUtils.ts +++ b/frontend/src/utils/apiUtils.ts @@ -1,3 +1,10 @@ +import { + BAD_REQUEST, + FORBIDDEN, + NOT_FOUND, + UNAUTHORIZED, +} from 'constants/statusCodes'; + export interface IErrorBody { details?: { message: string }[]; } @@ -5,7 +12,7 @@ export interface IErrorBody { export class AuthenticationError extends Error { statusCode: number; - constructor(statusCode: number) { + constructor(statusCode: number = UNAUTHORIZED) { super('Authentication required'); this.name = 'AuthenticationError'; this.statusCode = statusCode; @@ -16,7 +23,7 @@ export class ForbiddenError extends Error { statusCode: number; body: IErrorBody; - constructor(statusCode: number, body: IErrorBody = {}) { + constructor(statusCode: number = FORBIDDEN, body: IErrorBody = {}) { super( body.details?.length ? body.details[0].message @@ -32,7 +39,7 @@ export class BadRequestError extends Error { statusCode: number; body: IErrorBody; - constructor(statusCode: number, body: IErrorBody = {}) { + constructor(statusCode: number = BAD_REQUEST, body: IErrorBody = {}) { super(body.details?.length ? body.details[0].message : 'Bad request'); this.name = 'BadRequestError'; this.statusCode = statusCode; @@ -43,7 +50,7 @@ export class BadRequestError extends Error { export class NotFoundError extends Error { statusCode: number; - constructor(statusCode: number) { + constructor(statusCode: number = NOT_FOUND) { super( 'The requested resource could not be found but may be available in the future' ); diff --git a/frontend/src/utils/testRenderer.tsx b/frontend/src/utils/testRenderer.tsx index 960c992f68..fe1771e3b9 100644 --- a/frontend/src/utils/testRenderer.tsx +++ b/frontend/src/utils/testRenderer.tsx @@ -4,30 +4,36 @@ import { render as rtlRender, RenderOptions } from '@testing-library/react'; import { SWRConfig } from 'swr'; import { MainThemeProvider } from 'themes/MainThemeProvider'; import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; +import { IPermission } from 'interfaces/user'; +import AccessProvider from 'component/providers/AccessProvider/AccessProvider'; export const render = ( ui: JSX.Element, { route = '/', + permissions = [], ...renderOptions - }: { route?: string } & Omit = {} + }: { route?: string; permissions?: IPermission[] } & Omit< + RenderOptions, + 'queries' + > = {} ) => { window.history.pushState({}, 'Test page', route); + const Wrapper: FC = ({ children }) => ( + new Map() }}> + + + + {children} + + + + + ); + return rtlRender(ui, { wrapper: Wrapper, ...renderOptions, }); }; - -const Wrapper: FC = ({ children }) => { - return ( - new Map() }}> - - - {children} - - - - ); -};