mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge branch 'main' into fix/api-token-copy
This commit is contained in:
		
						commit
						def7dbf963
					
				| @ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "unleash-frontend", | ||||
|   "description": "unleash your features", | ||||
|   "version": "4.8.0-beta.1", | ||||
|   "version": "4.8.0-beta.5", | ||||
|   "keywords": [ | ||||
|     "unleash", | ||||
|     "feature toggle", | ||||
| @ -46,7 +46,7 @@ | ||||
|     "@types/debounce": "1.2.1", | ||||
|     "@types/deep-diff": "1.0.1", | ||||
|     "@types/jest": "27.4.0", | ||||
|     "@types/node": "14.18.10", | ||||
|     "@types/node": "14.18.12", | ||||
|     "@types/react": "17.0.39", | ||||
|     "@types/react-dom": "17.0.11", | ||||
|     "@types/react-outside-click-handler": "1.3.1", | ||||
| @ -63,7 +63,7 @@ | ||||
|     "debounce": "1.2.1", | ||||
|     "deep-diff": "1.0.2", | ||||
|     "fast-json-patch": "3.1.0", | ||||
|     "http-proxy-middleware": "2.0.2", | ||||
|     "http-proxy-middleware": "2.0.3", | ||||
|     "@types/lodash.clonedeep": "4.5.6", | ||||
|     "lodash.clonedeep": "4.5.0", | ||||
|     "lodash.flow": "3.5.0", | ||||
|  | ||||
| @ -81,7 +81,7 @@ export const App = () => { | ||||
|                                 <EnvironmentSplash onFinish={refetchSplash} /> | ||||
|                             } | ||||
|                             elseShow={ | ||||
|                                 <LayoutPicker location={location}> | ||||
|                                 <LayoutPicker> | ||||
|                                     <Switch> | ||||
|                                         <ProtectedRoute | ||||
|                                             exact | ||||
|  | ||||
| @ -5,13 +5,13 @@ import { | ||||
|     ListItemSecondaryAction, | ||||
|     ListItemText, | ||||
| } from '@material-ui/core'; | ||||
| import { Visibility, VisibilityOff, Delete } from '@material-ui/icons'; | ||||
| import { Delete, Edit, Visibility, VisibilityOff } from '@material-ui/icons'; | ||||
| import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     DELETE_ADDON, | ||||
|     UPDATE_ADDON, | ||||
| } from '../../../providers/AccessProvider/permissions'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Link, useHistory } from 'react-router-dom'; | ||||
| import PageContent from '../../../common/PageContent/PageContent'; | ||||
| import useAddons from '../../../../hooks/api/getters/useAddons/useAddons'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| @ -31,6 +31,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { | ||||
|     const { updateAddon, removeAddon } = useAddonsApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const history = useHistory(); | ||||
|     const [showDelete, setShowDelete] = useState(false); | ||||
|     const [deletedAddon, setDeletedAddon] = useState<IAddon>({ | ||||
|         id: 0, | ||||
| @ -115,10 +116,19 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { | ||||
|                 > | ||||
|                     <ConditionallyRender | ||||
|                         condition={addon.enabled} | ||||
|                         show={<Visibility />} | ||||
|                         elseShow={<VisibilityOff />} | ||||
|                         show={<Visibility titleAccess="Disable addon" />} | ||||
|                         elseShow={<VisibilityOff titleAccess="Enable addon" />} | ||||
|                     /> | ||||
|                 </PermissionIconButton> | ||||
|                 <PermissionIconButton | ||||
|                     permission={UPDATE_ADDON} | ||||
|                     tooltip={'Edit Addon'} | ||||
|                     onClick={() => { | ||||
|                         history.push(`/addons/edit/${addon.id}`); | ||||
|                     }} | ||||
|                 > | ||||
|                     <Edit titleAccess="Edit Addon" /> | ||||
|                 </PermissionIconButton> | ||||
|                 <PermissionIconButton | ||||
|                     permission={DELETE_ADDON} | ||||
|                     tooltip={'Remove Addon'} | ||||
| @ -127,7 +137,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { | ||||
|                         setShowDelete(true); | ||||
|                     }} | ||||
|                 > | ||||
|                     <Delete /> | ||||
|                     <Delete titleAccess="Remove Addon" /> | ||||
|                 </PermissionIconButton> | ||||
|             </ListItemSecondaryAction> | ||||
|         </ListItem> | ||||
|  | ||||
| @ -1,27 +1,21 @@ | ||||
| import PropTypes from 'prop-types'; | ||||
| import { ApiTokenList } from '../api-token/ApiTokenList/ApiTokenList'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { useContext } from 'react'; | ||||
| 
 | ||||
| const ApiPage = ({ history }) => { | ||||
| const ApiPage = () => { | ||||
|     const { isAdmin } = useContext(AccessContext); | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <ConditionallyRender | ||||
|                 condition={isAdmin} | ||||
|                 show={<AdminMenu history={history} />} | ||||
|                 show={<AdminMenu />} | ||||
|             /> | ||||
|             <ApiTokenList /> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ApiPage.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default ApiPage; | ||||
|  | ||||
| @ -18,7 +18,7 @@ const ProjectRoles = () => { | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <AdminMenu history={history} /> | ||||
|             <AdminMenu /> | ||||
|             <PageContent | ||||
|                 bodyClass={styles.rolesListBody} | ||||
|                 headerContent={ | ||||
|  | ||||
| @ -38,7 +38,6 @@ const EditUser = () => { | ||||
|     } = useAddUserForm( | ||||
|         user?.name, | ||||
|         user?.email, | ||||
|         user?.sendEmail, | ||||
|         user?.rootRole | ||||
|     ); | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ const UsersAdmin = () => { | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <AdminMenu history={history} /> | ||||
|             <AdminMenu /> | ||||
|             <PageContent | ||||
|                 bodyClass={styles.userListBody} | ||||
|                 headerContent={ | ||||
|  | ||||
| @ -114,8 +114,6 @@ const ChangePassword = ({ | ||||
|                     password={data.password} | ||||
|                     callback={setValidPassword} | ||||
|                 /> | ||||
| 
 | ||||
|                 <p style={{ color: 'red' }}>{error.general}</p> | ||||
|                 <TextField | ||||
|                     label="New password" | ||||
|                     name="password" | ||||
|  | ||||
| @ -15,7 +15,6 @@ import { | ||||
|     FlagRounded, | ||||
|     SvgIconComponent, | ||||
| } from '@material-ui/icons'; | ||||
| import { shorten } from '../../common'; | ||||
| import { | ||||
|     CREATE_FEATURE, | ||||
|     CREATE_STRATEGY, | ||||
| @ -87,9 +86,14 @@ export const ApplicationView = () => { | ||||
|             </ListItemAvatar> | ||||
|             <ListItemText | ||||
|                 primary={ | ||||
|                     <Link to={`${viewUrl}/${name}`}>{shorten(name, 50)}</Link> | ||||
|                     <Link | ||||
|                         to={`${viewUrl}/${name}`} | ||||
|                         style={{ wordBreak: 'break-all' }} | ||||
|                     > | ||||
|                         {name} | ||||
|                     </Link> | ||||
|                 } | ||||
|                 secondary={shorten(description, 60)} | ||||
|                 secondary={description} | ||||
|             /> | ||||
|         </ListItem> | ||||
|     ); | ||||
|  | ||||
| @ -18,7 +18,6 @@ test('renders correctly if no application', () => { | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 locationSettings={{ locale: 'en-GB' }} | ||||
|                             /> | ||||
|                         </MemoryRouter> | ||||
| @ -42,7 +41,6 @@ test('renders correctly without permission', () => { | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 application={{ | ||||
|                                     appName: 'test-app', | ||||
|                                     instances: [ | ||||
| @ -104,7 +102,6 @@ test('renders correctly with permissions', () => { | ||||
|                             <ApplicationEdit | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 application={{ | ||||
|                                     appName: 'test-app', | ||||
|  | ||||
| @ -18,7 +18,6 @@ test('renders correctly if no application', () => { | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 locationSettings={{ locale: 'en-GB' }} | ||||
|                             /> | ||||
|                         </MemoryRouter> | ||||
| @ -42,7 +41,6 @@ test('renders correctly without permission', () => { | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 application={{ | ||||
|                                     appName: 'test-app', | ||||
|                                     instances: [ | ||||
| @ -104,7 +102,6 @@ test('renders correctly with permissions', () => { | ||||
|                             <ApplicationEdit | ||||
|                                 fetchApplication={() => Promise.resolve({})} | ||||
|                                 storeApplicationMetaData={jest.fn()} | ||||
|                                 history={{}} | ||||
|                                 deleteApplication={jest.fn()} | ||||
|                                 application={{ | ||||
|                                     appName: 'test-app', | ||||
|  | ||||
| @ -7,7 +7,7 @@ import useToast from '../../hooks/useToast'; | ||||
| import { useFeaturesSort } from '../../hooks/useFeaturesSort'; | ||||
| 
 | ||||
| export const ArchiveListContainer = () => { | ||||
|     const { setToastApiError } = useToast(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { reviveFeature } = useFeatureArchiveApi(); | ||||
|     const { archivedFeatures, loading, refetchArchived } = useFeaturesArchive(); | ||||
| @ -17,6 +17,14 @@ export const ArchiveListContainer = () => { | ||||
|     const revive = (feature: string) => { | ||||
|         reviveFeature(feature) | ||||
|             .then(refetchArchived) | ||||
|             .then(() => | ||||
|                 setToastData({ | ||||
|                     type: 'success', | ||||
|                     title: "And we're back!", | ||||
|                     text: 'The feature toggle has been revived.', | ||||
|                     confetti: true, | ||||
|                 }) | ||||
|             ) | ||||
|             .catch(e => setToastApiError(e.toString())); | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -5,8 +5,16 @@ export const useStyles = makeStyles(theme => ({ | ||||
|         position: 'absolute', | ||||
|         top: '4px', | ||||
|     }, | ||||
|     breadcrumbNavParagraph: { color: 'inherit' }, | ||||
|     breadcrumbNavParagraph: { | ||||
|         color: 'inherit', | ||||
|         '& > *': { | ||||
|             verticalAlign: 'middle', | ||||
|         }, | ||||
|     }, | ||||
|     breadcrumbLink: { | ||||
|         textDecoration: 'none', | ||||
|         '& > *': { | ||||
|             verticalAlign: 'middle', | ||||
|         }, | ||||
|     }, | ||||
| })); | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { useStyles } from './BreadcrumbNav.styles'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { useContext } from 'react'; | ||||
| import StringTruncator from '../StringTruncator/StringTruncator'; | ||||
| 
 | ||||
| const BreadcrumbNav = () => { | ||||
|     const { isAdmin } = useContext(AccessContext); | ||||
| @ -51,7 +52,7 @@ const BreadcrumbNav = () => { | ||||
|                                                 styles.breadcrumbNavParagraph | ||||
|                                             } | ||||
|                                         > | ||||
|                                             {path.substring(0, 30)} | ||||
|                                             <StringTruncator text={path} maxWidth="200" /> | ||||
|                                         </p> | ||||
|                                     ); | ||||
|                                 } | ||||
| @ -72,7 +73,7 @@ const BreadcrumbNav = () => { | ||||
|                                         className={styles.breadcrumbLink} | ||||
|                                         to={link} | ||||
|                                     > | ||||
|                                         {path.substring(0, 30)} | ||||
|                                         <StringTruncator text={path} maxWidth="200" /> | ||||
|                                     </Link> | ||||
|                                 ); | ||||
|                             })} | ||||
|  | ||||
| @ -9,7 +9,7 @@ interface IPermissionIconButtonProps | ||||
|     > { | ||||
|     permission: string; | ||||
|     Icon?: React.ElementType; | ||||
|     tooltip: string; | ||||
|     tooltip?: string; | ||||
|     onClick?: (e: any) => void; | ||||
|     projectId?: string; | ||||
|     environmentId?: string; | ||||
|  | ||||
| @ -72,8 +72,6 @@ const ProjectSelect = ({ currentProjectId, updateCurrentProject, ...rest }) => { | ||||
| }; | ||||
| 
 | ||||
| ProjectSelect.propTypes = { | ||||
|     projects: PropTypes.array.isRequired, | ||||
|     fetchProjects: PropTypes.func.isRequired, | ||||
|     currentProjectId: PropTypes.string.isRequired, | ||||
|     updateCurrentProject: PropTypes.func.isRequired, | ||||
| }; | ||||
|  | ||||
| @ -38,6 +38,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({ | ||||
|                     permission={permission} | ||||
|                     projectId={projectId} | ||||
|                     environmentId={environmentId} | ||||
|                     tooltip={tooltip} | ||||
|                     data-loading | ||||
|                     {...rest} | ||||
|                 > | ||||
| @ -53,6 +54,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({ | ||||
|                     variant="contained" | ||||
|                     disabled={disabled} | ||||
|                     environmentId={environmentId} | ||||
|                     tooltip={tooltip} | ||||
|                     data-loading | ||||
|                     {...rest} | ||||
|                 > | ||||
|  | ||||
| @ -19,8 +19,6 @@ import ConditionallyRender from './ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| export { styles }; | ||||
| 
 | ||||
| export const shorten = (str, len = 50) => | ||||
|     str && str.length > len ? `${str.substring(0, len)}...` : str; | ||||
| export const AppsLinkList = ({ apps }) => ( | ||||
|     <List> | ||||
|         <ConditionallyRender | ||||
|  | ||||
| @ -112,11 +112,11 @@ const ContextForm: React.FC<IContextForm> = ({ | ||||
|                     autoFocus | ||||
|                 /> | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What is this context for? | ||||
|                      What is this context for? | ||||
|                 </p> | ||||
|                 <TextField | ||||
|                     className={styles.input} | ||||
|                     label="Context description" | ||||
|                     label="Context description (optional)" | ||||
|                     variant="outlined" | ||||
|                     multiline | ||||
|                     maxRows={4} | ||||
| @ -139,7 +139,7 @@ const ContextForm: React.FC<IContextForm> = ({ | ||||
|                 })} | ||||
|                 <div className={styles.tagContainer}> | ||||
|                     <TextField | ||||
|                         label="Value" | ||||
|                         label="Value (optional)" | ||||
|                         name="value" | ||||
|                         className={styles.tagInput} | ||||
|                         value={value} | ||||
|  | ||||
| @ -159,7 +159,6 @@ const EnvironmentListItem = ({ | ||||
|                     <Tooltip title={`${tooltipText} environment`}> | ||||
|                         <IconButton | ||||
|                             aria-label="disable" | ||||
|                             disabled={env.protected} | ||||
|                             onClick={() => { | ||||
|                                 setSelectedEnv(env); | ||||
|                                 setToggleDialog(prev => !prev); | ||||
|  | ||||
| @ -55,7 +55,6 @@ const EditFeature = () => { | ||||
|             history.push(`/projects/${project}/features/${name}`); | ||||
|             setToastData({ | ||||
|                 title: 'Toggle updated successfully', | ||||
|                 text: 'Now you can start using your toggle.', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (e: any) { | ||||
|  | ||||
| @ -5,6 +5,9 @@ import { DialogContentText } from '@material-ui/core'; | ||||
| import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender'; | ||||
| import Dialogue from '../../../../common/Dialogue'; | ||||
| import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature'; | ||||
| import React from 'react'; | ||||
| import useToast from '../../../../../hooks/useToast'; | ||||
| import { formatUnknownError } from '../../../../../utils/format-unknown-error'; | ||||
| 
 | ||||
| interface IStaleDialogProps { | ||||
|     open: boolean; | ||||
| @ -13,6 +16,7 @@ interface IStaleDialogProps { | ||||
| } | ||||
| 
 | ||||
| const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => { | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { projectId, featureId } = useParams<IFeatureViewParams>(); | ||||
|     const { patchFeatureToggle } = useFeatureApi(); | ||||
|     const { refetch } = useFeature(projectId, featureId); | ||||
| @ -30,12 +34,31 @@ const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => { | ||||
| 
 | ||||
|     const toggleActionText = stale ? 'active' : 'stale'; | ||||
| 
 | ||||
|     const onSubmit = async e => { | ||||
|         e.stopPropagation(); | ||||
|         const patch = [{ op: 'replace', path: '/stale', value: !stale }]; | ||||
|         await patchFeatureToggle(projectId, featureId, patch); | ||||
|         refetch(); | ||||
|         setOpen(false); | ||||
|     const onSubmit = async (event: React.SyntheticEvent) => { | ||||
|         event.stopPropagation(); | ||||
| 
 | ||||
|         try { | ||||
|             const patch = [{ op: 'replace', path: '/stale', value: !stale }]; | ||||
|             await patchFeatureToggle(projectId, featureId, patch); | ||||
|             refetch(); | ||||
|             setOpen(false); | ||||
|         } catch (err: unknown) { | ||||
|             setToastApiError(formatUnknownError(err)); | ||||
|         } | ||||
| 
 | ||||
|         if (stale) { | ||||
|             setToastData({ | ||||
|                 type: 'success', | ||||
|                 title: "And we're back!", | ||||
|                 text: 'The toggle is no longer marked as stale.', | ||||
|             }); | ||||
|         } else { | ||||
|             setToastData({ | ||||
|                 type: 'success', | ||||
|                 title: 'A job well done.', | ||||
|                 text: 'The toggle has been marked as stale.', | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const onCancel = () => { | ||||
|  | ||||
| @ -44,4 +44,7 @@ export const useStyles = makeStyles(theme => ({ | ||||
|             flexDirection: 'column', | ||||
|         }, | ||||
|     }, | ||||
|     featureId: { | ||||
|         wordBreak: 'break-all' | ||||
|     } | ||||
| })); | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Tab, Tabs, useMediaQuery } from '@material-ui/core'; | ||||
| import { useState } from 'react'; | ||||
| import { WatchLater, Archive, FileCopy, Label } from '@material-ui/icons'; | ||||
| import { Archive, FileCopy, Label, WatchLater } from '@material-ui/icons'; | ||||
| import { Link, Route, useHistory, useParams } from 'react-router-dom'; | ||||
| import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useFeature from '../../../hooks/api/getters/useFeature/useFeature'; | ||||
| @ -115,7 +115,8 @@ const FeatureView = () => { | ||||
|         return ( | ||||
|             <div> | ||||
|                 <p> | ||||
|                     The feature <strong>{featureId.substring(0, 30)}</strong>{' '} | ||||
|                     The feature{' '} | ||||
|                     <strong className={styles.featureId}>{featureId}</strong>{' '} | ||||
|                     does not exist. Do you want to   | ||||
|                     <Link | ||||
|                         to={getCreateTogglePath(projectId, uiConfig.flags.E, { | ||||
|  | ||||
| @ -29,9 +29,10 @@ const useFeatureForm = ( | ||||
|     }, [initialType]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (!toggleQueryName) setName(initialName); | ||||
|         else setName(toggleQueryName); | ||||
|     }, [initialName, toggleQueryName]); | ||||
|         if (!name) { | ||||
|             setName(toggleQueryName || initialName); | ||||
|         } | ||||
|     }, [name, initialName, toggleQueryName]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (!projectId) setProject(initialProject); | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import React, { useContext } from 'react'; | ||||
| import { ADMIN } from '../../component/providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from '../../component/common/ConditionallyRender'; | ||||
| import { EventHistory } from '../../component/history/EventHistory/EventHistory'; | ||||
| import AccessContext from '../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { EventHistory } from '../EventHistory/EventHistory'; | ||||
| 
 | ||||
| const HistoryPage = () => { | ||||
| export const EventHistoryPage = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     return ( | ||||
| @ -20,5 +20,3 @@ const HistoryPage = () => { | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default HistoryPage; | ||||
| @ -0,0 +1,9 @@ | ||||
| import React from 'react'; | ||||
| import { useParams } from 'react-router-dom'; | ||||
| import { FeatureEventHistory } from '../FeatureEventHistory/FeatureEventHistory'; | ||||
| 
 | ||||
| export const FeatureEventHistoryPage = () => { | ||||
|     const { toggleName } = useParams<{ toggleName: string }>(); | ||||
| 
 | ||||
|     return <FeatureEventHistory toggleName={toggleName} />; | ||||
| }; | ||||
| @ -1,8 +1,10 @@ | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import { matchPath } from 'react-router'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
| import { MainLayout } from '../MainLayout/MainLayout'; | ||||
| 
 | ||||
| const LayoutPicker = ({ children, location }) => { | ||||
| const LayoutPicker = ({ children }) => { | ||||
|     const location = useLocation(); | ||||
|     const standalonePages = () => { | ||||
|         const { pathname } = location; | ||||
|         const isLoginPage = matchPath(pathname, { path: '/login' }); | ||||
|  | ||||
| @ -27,7 +27,7 @@ const Header = () => { | ||||
|     const [anchorEl, setAnchorEl] = useState(); | ||||
|     const [anchorElAdvanced, setAnchorElAdvanced] = useState(); | ||||
|     const [admin, setAdmin] = useState(false); | ||||
|     const { permissions } = useAuthPermissions() | ||||
|     const { permissions } = useAuthPermissions(); | ||||
|     const commonStyles = useCommonStyles(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
| @ -68,12 +68,19 @@ const Header = () => { | ||||
|                                 className={styles.drawerButton} | ||||
|                                 onClick={toggleDrawer} | ||||
|                             > | ||||
|                                 <MenuIcon /> | ||||
|                                 <MenuIcon titleAccess="Menu" /> | ||||
|                             </IconButton> | ||||
|                         } | ||||
|                         elseShow={ | ||||
|                             <Link to="/" className={commonStyles.flexRow}> | ||||
|                                 <UnleashLogo className={styles.logo} />{' '} | ||||
|                             <Link | ||||
|                                 to="/" | ||||
|                                 className={commonStyles.flexRow} | ||||
|                                 aria-label="Home" | ||||
|                             > | ||||
|                                 <UnleashLogo | ||||
|                                     className={styles.logo} | ||||
|                                     aria-label="Unleash logo" | ||||
|                                 /> | ||||
|                             </Link> | ||||
|                         } | ||||
|                     /> | ||||
| @ -127,6 +134,7 @@ const Header = () => { | ||||
|                                         > | ||||
|                                             <MenuBookIcon | ||||
|                                                 className={styles.docsIcon} | ||||
|                                                 titleAccess="Documentation" | ||||
|                                             /> | ||||
|                                         </a> | ||||
|                                     </Tooltip> | ||||
| @ -140,6 +148,7 @@ const Header = () => { | ||||
|                                             > | ||||
|                                                 <SettingsIcon | ||||
|                                                     className={styles.docsIcon} | ||||
|                                                     titleAccess="Settings" | ||||
|                                                 /> | ||||
|                                             </IconButton> | ||||
|                                         } | ||||
|  | ||||
| @ -1,9 +1,7 @@ | ||||
| import { FeatureToggleListContainer } from '../feature/FeatureToggleList/FeatureToggleListContainer'; | ||||
| import { StrategyForm } from '../strategies/StrategyForm/StrategyForm'; | ||||
| import { StrategyView } from '../../component/strategies/StrategyView/StrategyView'; | ||||
| import { StrategyView } from '../strategies/StrategyView/StrategyView'; | ||||
| import { StrategiesList } from '../strategies/StrategiesList/StrategiesList'; | ||||
| import HistoryPage from '../../page/history'; | ||||
| import HistoryTogglePage from '../../page/history/toggle'; | ||||
| import { ArchiveListContainer } from '../archive/ArchiveListContainer'; | ||||
| import { TagTypeList } from '../tags/TagTypeList/TagTypeList'; | ||||
| import { AddonList } from '../addons/AddonList/AddonList'; | ||||
| @ -45,6 +43,8 @@ import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureV | ||||
| import { CreateAddon } from '../addons/CreateAddon/CreateAddon'; | ||||
| import { EditAddon } from '../addons/EditAddon/EditAddon'; | ||||
| import { CopyFeatureToggle } from '../feature/CopyFeature/CopyFeature'; | ||||
| import { EventHistoryPage } from '../history/EventHistoryPage/EventHistoryPage'; | ||||
| import { FeatureEventHistoryPage } from '../history/FeatureEventHistoryPage/FeatureEventHistoryPage'; | ||||
| 
 | ||||
| export const routes = [ | ||||
|     // Project
 | ||||
| @ -354,7 +354,7 @@ export const routes = [ | ||||
|         path: '/history/:toggleName', | ||||
|         title: ':toggleName', | ||||
|         parent: '/history', | ||||
|         component: HistoryTogglePage, | ||||
|         component: FeatureEventHistoryPage, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
| @ -362,7 +362,7 @@ export const routes = [ | ||||
|     { | ||||
|         path: '/history', | ||||
|         title: 'Event History', | ||||
|         component: HistoryPage, | ||||
|         component: EventHistoryPage, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: { adminSettings: true }, | ||||
|  | ||||
| @ -32,4 +32,18 @@ export const useStyles = makeStyles(theme => ({ | ||||
|         width: 'auto', | ||||
|         fontSize: '1rem', | ||||
|     }, | ||||
|     title: { | ||||
|         fontSize: theme.fontSizes.mainHeader, | ||||
|         fontWeight: 'bold', | ||||
|         marginBottom: '0.5rem', | ||||
|         display: 'grid', | ||||
|         gridTemplateColumns: '1fr auto', | ||||
|         alignItems: 'center', | ||||
|         gridGap: '1rem', | ||||
|     }, | ||||
|     titleText: { | ||||
|         overflow: 'hidden', | ||||
|         textOverflow: 'ellipsis', | ||||
|         whiteSpace: 'nowrap', | ||||
|     }, | ||||
| })); | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| import { useHistory, useParams } from 'react-router'; | ||||
| import { useCommonStyles } from '../../../common.styles'; | ||||
| import useProject from '../../../hooks/api/getters/useProject/useProject'; | ||||
| import useLoading from '../../../hooks/useLoading'; | ||||
| import ApiError from '../../common/ApiError/ApiError'; | ||||
| @ -25,7 +24,6 @@ const Project = () => { | ||||
|     const { project, error, loading, refetch } = useProject(id); | ||||
|     const ref = useLoading(loading); | ||||
|     const { setToastData } = useToast(); | ||||
|     const commonStyles = useCommonStyles(); | ||||
|     const styles = useStyles(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
| @ -121,10 +119,10 @@ const Project = () => { | ||||
|                 <div className={styles.innerContainer}> | ||||
|                     <h2 | ||||
|                         data-loading | ||||
|                         className={commonStyles.title} | ||||
|                         className={styles.title} | ||||
|                         style={{ margin: 0 }} | ||||
|                     > | ||||
|                         Project: {project?.name}{' '} | ||||
|                         <div className={styles.titleText}>{project?.name}</div> | ||||
|                         <PermissionIconButton | ||||
|                             permission={UPDATE_PROJECT} | ||||
|                             tooltip="Edit" | ||||
|  | ||||
| @ -23,6 +23,11 @@ export const useStyles = makeStyles(theme => ({ | ||||
|     title: { | ||||
|         fontWeight: 'normal', | ||||
|         fontSize: '1rem', | ||||
|         lineClamp: 2, | ||||
|         display: '-webkit-box', | ||||
|         boxOrient: 'vertical', | ||||
|         textOverflow: 'ellipsis', | ||||
|         overflow: 'hidden' | ||||
|     }, | ||||
| 
 | ||||
|     projectIcon: { | ||||
|  | ||||
| @ -46,7 +46,7 @@ const ProjectCard = ({ | ||||
|     return ( | ||||
|         <Card className={styles.projectCard} onMouseEnter={onHover}> | ||||
|             <div className={styles.header} data-loading> | ||||
|                 <h2 className={styles.title}>{name}</h2> | ||||
|                 <div className={styles.title}>{name}</div> | ||||
| 
 | ||||
|                 <PermissionIconButton | ||||
|                     permission={UPDATE_PROJECT} | ||||
|  | ||||
| @ -11,6 +11,7 @@ import useStrategiesApi from '../../../hooks/api/actions/useStrategiesApi/useStr | ||||
| import { IStrategy } from '../../../interfaces/strategy'; | ||||
| import useToast from '../../../hooks/useToast'; | ||||
| import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies'; | ||||
| import { formatUnknownError } from '../../../utils/format-unknown-error'; | ||||
| 
 | ||||
| interface ICustomStrategyParams { | ||||
|     name?: string; | ||||
| @ -78,7 +79,7 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => { | ||||
|         setParams(prev => [...parameters]); | ||||
|         if (editMode) { | ||||
|             try { | ||||
|                 await updateStrategy({ name, description, parameters: params }); | ||||
|                 await updateStrategy({ name, description, parameters }); | ||||
|                 history.push(`/strategies/view/${name}`); | ||||
|                 setToastData({ | ||||
|                     type: 'success', | ||||
| @ -86,12 +87,12 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => { | ||||
|                     text: 'Successfully updated strategy', | ||||
|                 }); | ||||
|                 refetchStrategies(); | ||||
|             } catch (e: any) { | ||||
|                 setToastApiError(e.toString()); | ||||
|             } catch (error: unknown) { | ||||
|                 setToastApiError(formatUnknownError(error)); | ||||
|             } | ||||
|         } else { | ||||
|             try { | ||||
|                 await createStrategy({ name, description, parameters: params }); | ||||
|                 await createStrategy({ name, description, parameters }); | ||||
|                 history.push(`/strategies`); | ||||
|                 setToastData({ | ||||
|                     type: 'success', | ||||
| @ -99,13 +100,8 @@ export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => { | ||||
|                     text: 'Successfully created new strategy', | ||||
|                 }); | ||||
|                 refetchStrategies(); | ||||
|             } catch (e: any) { | ||||
|                 const STRATEGY_EXIST_ERROR = 'Error: Strategy with name'; | ||||
|                 if (e.toString().includes(STRATEGY_EXIST_ERROR)) { | ||||
|                     setErrors({ | ||||
|                         name: 'A strategy with this name already exists', | ||||
|                     }); | ||||
|                 } | ||||
|             } catch (error: unknown) { | ||||
|                 setToastApiError(formatUnknownError(error)); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| @ -23,7 +23,6 @@ test('renders correctly with one strategy', () => { | ||||
|                             removeStrategy={jest.fn()} | ||||
|                             deprecateStrategy={jest.fn()} | ||||
|                             reactivateStrategy={jest.fn()} | ||||
|                             history={{}} | ||||
|                         /> | ||||
|                     </AccessProvider> | ||||
|                 </UIProvider> | ||||
| @ -50,7 +49,6 @@ test('renders correctly with one strategy without permissions', () => { | ||||
|                             removeStrategy={jest.fn()} | ||||
|                             deprecateStrategy={jest.fn()} | ||||
|                             reactivateStrategy={jest.fn()} | ||||
|                             history={{}} | ||||
|                         /> | ||||
|                     </AccessProvider> | ||||
|                 </UIProvider> | ||||
|  | ||||
| @ -45,7 +45,6 @@ test('renders correctly with one strategy', () => { | ||||
|                         fetchStrategies={jest.fn()} | ||||
|                         fetchApplications={jest.fn()} | ||||
|                         fetchFeatureToggles={jest.fn()} | ||||
|                         history={{}} | ||||
|                     /> | ||||
|                 </ThemeProvider> | ||||
|             </AccessProvider> | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi'; | ||||
| import { formatUnknownError } from '../../../utils/format-unknown-error'; | ||||
| 
 | ||||
| const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => { | ||||
|     const [tagName, setTagName] = useState(initialTagName); | ||||
| @ -22,8 +23,6 @@ const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => { | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     const NAME_EXISTS_ERROR = | ||||
|         'There already exists a tag-type with the name simple'; | ||||
|     const validateNameUniqueness = async () => { | ||||
|         if (tagName.length === 0) { | ||||
|             setErrors(prev => ({ ...prev, name: 'Name can not be empty.' })); | ||||
| @ -39,14 +38,12 @@ const useTagTypeForm = (initialTagName = '', initialTagDesc = '') => { | ||||
|         try { | ||||
|             await validateTagName(tagName); | ||||
|             return true; | ||||
|         } catch (e: any) { | ||||
|             if (e.toString().includes(NAME_EXISTS_ERROR)) { | ||||
|                 setErrors(prev => ({ | ||||
|                     ...prev, | ||||
|                     name: NAME_EXISTS_ERROR, | ||||
|                 })); | ||||
|                 return false; | ||||
|             } | ||||
|         } catch (err: unknown) { | ||||
|             setErrors(prev => ({ | ||||
|                 ...prev, | ||||
|                 name: formatUnknownError(err) | ||||
|             })); | ||||
|             return false; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -22,7 +22,6 @@ test('renders an empty list correctly', () => { | ||||
|                             tagTypes={[]} | ||||
|                             fetchTagTypes={jest.fn()} | ||||
|                             removeTagType={jest.fn()} | ||||
|                             history={{}} | ||||
|                         /> | ||||
|                     </AccessProvider> | ||||
|                 </UIProvider> | ||||
| @ -53,7 +52,6 @@ test('renders a list with elements correctly', () => { | ||||
|                             ]} | ||||
|                             fetchTagTypes={jest.fn()} | ||||
|                             removeTagType={jest.fn()} | ||||
|                             history={{}} | ||||
|                         /> | ||||
|                     </AccessProvider> | ||||
|                 </UIProvider> | ||||
|  | ||||
| @ -10,8 +10,13 @@ import AuthOptions from '../common/AuthOptions/AuthOptions'; | ||||
| import DividerText from '../../common/DividerText/DividerText'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import PasswordField from '../../common/PasswordField/PasswordField'; | ||||
| import { useAuthApi } from "../../../hooks/api/actions/useAuthApi/useAuthApi"; | ||||
| 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 '../../../testIds'; | ||||
| 
 | ||||
| const HostedAuth = ({ authDetails }) => { | ||||
|     const commonStyles = useCommonStyles(); | ||||
| @ -19,7 +24,7 @@ const HostedAuth = ({ authDetails }) => { | ||||
|     const { refetchUser } = useAuthUser(); | ||||
|     const history = useHistory(); | ||||
|     const params = useQueryParams(); | ||||
|     const { passwordAuth } = useAuthApi() | ||||
|     const { passwordAuth } = useAuthApi(); | ||||
|     const [username, setUsername] = useState(params.get('email') || ''); | ||||
|     const [password, setPassword] = useState(''); | ||||
|     const [errors, setErrors] = useState({ | ||||
| @ -108,6 +113,7 @@ const HostedAuth = ({ authDetails }) => { | ||||
|                                 helperText={usernameError} | ||||
|                                 variant="outlined" | ||||
|                                 size="small" | ||||
|                                 data-test={LOGIN_EMAIL_ID} | ||||
|                             /> | ||||
|                             <PasswordField | ||||
|                                 label="Password" | ||||
| @ -116,6 +122,7 @@ const HostedAuth = ({ authDetails }) => { | ||||
|                                 value={password} | ||||
|                                 error={!!passwordError} | ||||
|                                 helperText={passwordError} | ||||
|                                 data-test={LOGIN_PASSWORD_ID} | ||||
|                             /> | ||||
|                             <Grid container> | ||||
|                                 <Button | ||||
| @ -123,6 +130,7 @@ const HostedAuth = ({ authDetails }) => { | ||||
|                                     color="primary" | ||||
|                                     type="submit" | ||||
|                                     className={styles.button} | ||||
|                                     data-test={LOGIN_BUTTON} | ||||
|                                 > | ||||
|                                     Sign in | ||||
|                                 </Button> | ||||
|  | ||||
| @ -14,10 +14,11 @@ export const handleBadRequest = async ( | ||||
|     if (!setErrors || !requestId) return; | ||||
|     if (res) { | ||||
|         const data = await res.json(); | ||||
|         const message = data.isJoi ? data.details[0].message : data[0].msg; | ||||
| 
 | ||||
|         setErrors(prev => ({ | ||||
|             ...prev, | ||||
|             [requestId]: data[0].msg, | ||||
|             [requestId]: message, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
| @ -47,10 +48,11 @@ export const handleUnauthorized = async ( | ||||
|     if (!setErrors || !requestId) return; | ||||
|     if (res) { | ||||
|         const data = await res.json(); | ||||
|         const message = data.isJoi ? data.details[0].message : data[0].msg; | ||||
| 
 | ||||
|         setErrors(prev => ({ | ||||
|             ...prev, | ||||
|             [requestId]: data[0].msg, | ||||
|             [requestId]: message, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
| @ -65,7 +67,6 @@ export const handleForbidden = async ( | ||||
|     if (!setErrors || !requestId) return; | ||||
|     if (res) { | ||||
|         const data = await res.json(); | ||||
| 
 | ||||
|         const message = data.isJoi ? data.details[0].message : data[0].msg; | ||||
| 
 | ||||
|         setErrors(prev => ({ | ||||
|  | ||||
| @ -58,7 +58,3 @@ export const useAuthApi = (): IUseAuthApiOutput => { | ||||
|         loading, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| const ensureRelativePath = (path: string): string => { | ||||
|     return path.replace(/^\//, ''); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ClientInstance from '../../component/client-instance/client-instance-container'; | ||||
| 
 | ||||
| const render = () => <ClientInstance />; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,13 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import CreateFeature from '../../component/feature/create/CreateFeature'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ history }) => ( | ||||
|     <CreateFeature title="Create feature toggle" history={history} /> | ||||
| ); | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,13 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { FeatureEventHistory } from '../../component/history/FeatureEventHistory/FeatureEventHistory'; | ||||
| 
 | ||||
| const render = ({ match: { params } }) => ( | ||||
|     <FeatureEventHistory toggleName={params.toggleName} /> | ||||
| ); | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,6 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import Metrics from '../../component/metrics/metrics-container'; | ||||
| 
 | ||||
| const render = () => <Metrics />; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,11 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import CreateProject from '../../component/project/create-project-container'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ history }) => <CreateProject title="Create Project" history={history} />; | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,14 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import EditProject from '../../component/project/edit-project-container'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ match: { params }, history }) => ( | ||||
|     <EditProject projectId={params.id} title="Edit project" history={history} /> | ||||
| ); | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,6 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ProjectEnvironment from '../../component/project/ProjectEnvironment/ProjectEnvironment'; | ||||
| 
 | ||||
| const ProjectEnvironmentConfigPage = () => <ProjectEnvironment />; | ||||
| 
 | ||||
| export default ProjectEnvironmentConfigPage; | ||||
| @ -1,11 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ProjectList from '../../component/project/ProjectList/ProjectList'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ history }) => <ProjectList history={history} />; | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,14 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ViewProject from '../../component/project/ProjectView'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ match: { params }, history }) => ( | ||||
|     <ViewProject projectId={params.id} title="View project" history={history} /> | ||||
| ); | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -2091,10 +2091,10 @@ | ||||
|   resolved "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz" | ||||
|   integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== | ||||
| 
 | ||||
| "@types/node@14.18.10": | ||||
|   version "14.18.10" | ||||
|   resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.10.tgz#774f43868964f3cfe4ced1f5417fe15818a4eaea" | ||||
|   integrity sha512-6iihJ/Pp5fsFJ/aEDGyvT4pHGmCpq7ToQ/yf4bl5SbVAvwpspYJ+v3jO7n8UyjhQVHTy+KNszOozDdv+O6sovQ== | ||||
| "@types/node@14.18.12": | ||||
|   version "14.18.12" | ||||
|   resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.12.tgz#0d4557fd3b94497d793efd4e7d92df2f83b4ef24" | ||||
|   integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== | ||||
| 
 | ||||
| "@types/node@^14.14.31": | ||||
|   version "14.17.19" | ||||
| @ -6426,10 +6426,10 @@ http-proxy-middleware@0.19.1: | ||||
|     lodash "^4.17.11" | ||||
|     micromatch "^3.1.10" | ||||
| 
 | ||||
| http-proxy-middleware@2.0.2: | ||||
|   version "2.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.2.tgz#94d7593790aad6b3de48164f13792262f656c332" | ||||
|   integrity sha512-XtmDN5w+vdFTBZaYhdJAbMqn0DP/EhkUaAeo963mojwpKMMbw6nivtFKw07D7DDOH745L5k0VL0P8KRYNEVF/g== | ||||
| http-proxy-middleware@2.0.3: | ||||
|   version "2.0.3" | ||||
|   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz#5df04f69a89f530c2284cd71eeaa51ba52243289" | ||||
|   integrity sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA== | ||||
|   dependencies: | ||||
|     "@types/http-proxy" "^1.17.8" | ||||
|     http-proxy "^1.18.1" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user