mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: created project header (#388)
* feat: created project header * fix: remove reporting from global menu * fix: add projects to global menu for oss also
This commit is contained in:
		
							parent
							
								
									2ef935cf42
								
							
						
					
					
						commit
						fe2a8311bf
					
				| @ -5,6 +5,7 @@ interface IApiErrorProps { | ||||
|     className?: string; | ||||
|     onClick: () => void; | ||||
|     text: string; | ||||
|     style?: React.CSSProperties; | ||||
| } | ||||
| 
 | ||||
| const ApiError: React.FC<IApiErrorProps> = ({ | ||||
|  | ||||
| @ -5,6 +5,7 @@ interface IResponsiveButtonProps { | ||||
|     Icon: React.ElementType; | ||||
|     onClick: () => void; | ||||
|     tooltip?: string; | ||||
|     disabled?: boolean; | ||||
|     maxWidth: string; | ||||
| } | ||||
| 
 | ||||
| @ -13,33 +14,37 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({ | ||||
|     onClick, | ||||
|     maxWidth, | ||||
|     tooltip, | ||||
|     disabled = false, | ||||
|     children, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const smallScreen = useMediaQuery(`(max-width:${maxWidth})`); | ||||
| 
 | ||||
|     return ( | ||||
|         <ConditionallyRender | ||||
|             condition={smallScreen} | ||||
|             show={ | ||||
|                 <Tooltip title={tooltip ? tooltip : ''}> | ||||
|                     <IconButton onClick={onClick} data-loading {...rest}> | ||||
|         <Tooltip title={tooltip ? tooltip : ''} arrow> | ||||
|             <span> | ||||
|             <ConditionallyRender | ||||
|                 condition={smallScreen} | ||||
|                 show={ | ||||
|                     <IconButton disabled={disabled} onClick={onClick} data-loading {...rest}> | ||||
|                         <Icon /> | ||||
|                     </IconButton> | ||||
|                 </Tooltip> | ||||
|             } | ||||
|             elseShow={ | ||||
|                 <Button | ||||
|                     onClick={onClick} | ||||
|                     color="primary" | ||||
|                     variant="contained" | ||||
|                     data-loading | ||||
|                     {...rest} | ||||
|                 > | ||||
|                     {children} | ||||
|                 </Button> | ||||
|             } | ||||
|         /> | ||||
|                 } | ||||
|                 elseShow={ | ||||
|                     <Button | ||||
|                         onClick={onClick} | ||||
|                         color="primary" | ||||
|                         variant="contained" | ||||
|                         disabled={disabled} | ||||
|                         data-loading | ||||
|                         {...rest} | ||||
|                     > | ||||
|                         {children} | ||||
|                     </Button> | ||||
|                 } | ||||
|             /> | ||||
|             </span> | ||||
|         </Tooltip> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,7 @@ import { IEnvironments } from '../../../../interfaces/featureToggle'; | ||||
| import ConditionallyRender from '../../../common/ConditionallyRender'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import { getTogglePath } from '../../../../utils/route-path-helpers'; | ||||
| import { SyntheticEvent } from 'react-router/node_modules/@types/react'; | ||||
| 
 | ||||
| interface IFeatureToggleListNewItemProps { | ||||
|     name: string; | ||||
| @ -40,13 +41,13 @@ const FeatureToggleListNewItem = ({ | ||||
|     const history = useHistory(); | ||||
|     const ref = useRef(null); | ||||
| 
 | ||||
|     const onClick = (e: Event) => { | ||||
|     const onClick = (e: SyntheticEvent) => { | ||||
|         if (!ref.current?.contains(e.target)) { | ||||
|             history.push(getTogglePath(projectId, name)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const handleToggle = (env: IEnvironments) => { | ||||
|     const handleToggle = (env: IEnvironments, e: SyntheticEvent) => { | ||||
|         toggleFeatureByEnvironment(env.name, env.enabled) | ||||
|             .then(() => { | ||||
|                 setToastData({ | ||||
| @ -68,14 +69,14 @@ const FeatureToggleListNewItem = ({ | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <TableRow onClick={onClick} className={styles.tableRow}> | ||||
|                 <TableCell className={styles.tableCell} align="left"> | ||||
|             <TableRow className={styles.tableRow}> | ||||
|                 <TableCell className={styles.tableCell} align="left" onClick={onClick}> | ||||
|                     <span data-loading>{name}</span> | ||||
|                 </TableCell> | ||||
|                 <ConditionallyRender | ||||
|                     condition={!smallScreen} | ||||
|                     show={ | ||||
|                         <TableCell className={styles.tableCell} align="left"> | ||||
|                         <TableCell className={styles.tableCell} align="left" onClick={onClick}> | ||||
|                             <div className={styles.tableCellType}> | ||||
|                                 <IconComponent | ||||
|                                     data-loading | ||||
| @ -98,7 +99,7 @@ const FeatureToggleListNewItem = ({ | ||||
|                                 <Switch | ||||
|                                     checked={env.enabled} | ||||
|                                     ref={ref} | ||||
|                                     onClick={() => handleToggle(env)} | ||||
|                                     onClick={handleToggle.bind(this, env)} | ||||
|                                 /> | ||||
|                             </span> | ||||
|                         </TableCell> | ||||
|  | ||||
| @ -91,12 +91,8 @@ const Header = () => { | ||||
|                         condition={!smallScreen} | ||||
|                         show={ | ||||
|                             <div className={styles.links}> | ||||
|                                 <ConditionallyRender | ||||
|                                     condition={flags?.P} | ||||
|                                     show={<Link to="/projects">Projects</Link>} | ||||
|                                 /> | ||||
|                                 <Link to="/projects">Projects</Link> | ||||
|                                 <Link to="/features">Feature toggles</Link> | ||||
|                                 <Link to="/reporting">Reporting</Link> | ||||
| 
 | ||||
|                                 <button | ||||
|                                     className={styles.advancedNavButton} | ||||
|  | ||||
| @ -166,33 +166,6 @@ Array [ | ||||
|     "title": "Create", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object {}, | ||||
|     "parent": "/projects", | ||||
|     "path": "/projects/:id/edit", | ||||
|     "title": ":id", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object {}, | ||||
|     "parent": "/projects", | ||||
|     "path": "/projects/:id/access", | ||||
|     "title": ":id", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object {}, | ||||
|     "parent": "/projects", | ||||
|     "path": "/projects/:id/environments", | ||||
|     "title": "Environments", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
| @ -239,6 +212,16 @@ Array [ | ||||
|     "title": "Create", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "flag": "P", | ||||
|     "layout": "main", | ||||
|     "menu": Object {}, | ||||
|     "parent": "/projects", | ||||
|     "path": "/projects/:id/:activeTab", | ||||
|     "title": ":id", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "flag": "P", | ||||
| @ -328,16 +311,6 @@ Array [ | ||||
|     "title": "Addons", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object { | ||||
|       "mobile": true, | ||||
|     }, | ||||
|     "path": "/reporting", | ||||
|     "title": "Reporting", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { baseRoutes, getRoute } from '../routes'; | ||||
| 
 | ||||
| test('returns all baseRoutes', () => { | ||||
|     expect(baseRoutes).toHaveLength(39); | ||||
|     expect(baseRoutes).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -15,9 +15,6 @@ import ContextFields from '../../page/context'; | ||||
| import CreateContextField from '../../page/context/create'; | ||||
| import EditContextField from '../../page/context/edit'; | ||||
| import CreateProject from '../../page/project/create'; | ||||
| import EditProject from '../../page/project/edit'; | ||||
| import EditProjectAccess from '../../page/project/access'; | ||||
| import EditProjectEnvironment from '../../page/project/environment'; | ||||
| import ListTagTypes from '../../page/tag-types'; | ||||
| import CreateTagType from '../../page/tag-types/create'; | ||||
| import EditTagType from '../../page/tag-types/edit'; | ||||
| @ -31,7 +28,6 @@ import AdminApi from '../../page/admin/api'; | ||||
| import AdminUsers from '../../page/admin/users'; | ||||
| import AdminInvoice from '../../page/admin/invoice'; | ||||
| import AdminAuth from '../../page/admin/auth'; | ||||
| import Reporting from '../../page/reporting'; | ||||
| import Login from '../user/Login'; | ||||
| import { P, C, E } from '../common/flags'; | ||||
| import NewUser from '../user/NewUser'; | ||||
| @ -207,33 +203,6 @@ export const routes = [ | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id/edit', | ||||
|         parent: '/projects', | ||||
|         title: ':id', | ||||
|         component: EditProject, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id/access', | ||||
|         parent: '/projects', | ||||
|         title: ':id', | ||||
|         component: EditProjectAccess, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id/environments', | ||||
|         parent: '/projects', | ||||
|         title: 'Environments', | ||||
|         component: EditProjectEnvironment, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id/archived', | ||||
|         title: ':name', | ||||
| @ -280,6 +249,16 @@ export const routes = [ | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id/:activeTab', | ||||
|         parent: '/projects', | ||||
|         title: ':id', | ||||
|         component: Project, | ||||
|         flag: P, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:id', | ||||
|         parent: '/projects', | ||||
| @ -374,14 +353,6 @@ export const routes = [ | ||||
|         layout: 'main', | ||||
|         menu: { mobile: true, advanced: true }, | ||||
|     }, | ||||
|     { | ||||
|         path: '/reporting', | ||||
|         title: 'Reporting', | ||||
|         component: Reporting, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: { mobile: true }, | ||||
|     }, | ||||
|     // Admin
 | ||||
|     { | ||||
|         path: '/admin/api', | ||||
|  | ||||
| @ -8,4 +8,23 @@ export const useStyles = makeStyles(theme => ({ | ||||
|             flexDirection: 'column', | ||||
|         }, | ||||
|     }, | ||||
|     header: { | ||||
|         backgroundColor: '#fff', | ||||
|         borderRadius: '10px', | ||||
|         marginBottom: '1rem', | ||||
|     }, | ||||
|     innerContainer: { padding: '2rem' }, | ||||
|     separator: { | ||||
|         width: '100%', | ||||
|         backgroundColor: theme.palette.grey[200], | ||||
|         height: '1px', | ||||
|     }, | ||||
|     tabContainer: { | ||||
|         padding: '1rem 2rem', | ||||
|     }, | ||||
|     tabButton: { | ||||
|         textTransform: 'none', | ||||
|         width: 'auto', | ||||
|         fontSize: '1rem', | ||||
|     }, | ||||
| })); | ||||
|  | ||||
| @ -1,29 +1,37 @@ | ||||
| import { useParams } from 'react-router'; | ||||
| 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 useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import ApiError from '../../common/ApiError/ApiError'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import ProjectFeatureToggles from './ProjectFeatureToggles/ProjectFeatureToggles'; | ||||
| import ProjectInfo from './ProjectInfo/ProjectInfo'; | ||||
| import { useStyles } from './Project.styles'; | ||||
| import { IconButton } from '@material-ui/core'; | ||||
| import { Edit } from '@material-ui/icons'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Tab, Tabs } from '@material-ui/core'; | ||||
| import useToast from '../../../hooks/useToast'; | ||||
| import useQueryParams from '../../../hooks/useQueryParams'; | ||||
| import { useEffect } from 'react'; | ||||
| import { getProjectEditPath } from '../../../utils/route-path-helpers'; | ||||
| import useTabs from '../../../hooks/useTabs'; | ||||
| import TabPanel from '../../common/TabNav/TabPanel'; | ||||
| import ProjectAccess from '../access-container'; | ||||
| import EditProject from '../edit-project-container'; | ||||
| import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment'; | ||||
| import ProjectOverview from './ProjectOverview'; | ||||
| import ProjectHealth from './ProjectHealth/ProjectHealth'; | ||||
| 
 | ||||
| const Project = () => { | ||||
|     const { id } = useParams<{ id: string }>(); | ||||
|     const { id, activeTab } = useParams<{ id: string, activeTab: string }>(); | ||||
|     const params = useQueryParams(); | ||||
|     const { project, error, loading, refetch } = useProject(id); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const ref = useLoading(loading); | ||||
|     const { toast, setToastData } = useToast(); | ||||
|     const { members, features, health } = project; | ||||
|     const commonStyles = useCommonStyles(); | ||||
|     const styles = useStyles(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     const { a11yProps, activeTabIdx, setActiveTab } = useTabs(0); | ||||
| 
 | ||||
|     const basePath = `/projects/${id}`; | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         const created = params.get('created'); | ||||
| @ -40,34 +48,116 @@ const Project = () => { | ||||
|         /* eslint-disable-next-line */ | ||||
|     }, []); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         const tabIdx = tabData.findIndex(tab => tab.name === activeTab); | ||||
|         if(tabIdx > 0) { | ||||
|             setActiveTab(tabIdx); | ||||
|         } else { | ||||
|             setActiveTab(0); | ||||
|         } | ||||
|          | ||||
|         /* eslint-disable-next-line */ | ||||
|     }, []); | ||||
| 
 | ||||
|     const tabData = [ | ||||
|         { | ||||
|             title: 'Overview', | ||||
|             component: <ProjectOverview projectId={id} />, | ||||
|             path: basePath, | ||||
|             name: 'overview', | ||||
|         }, | ||||
|         { | ||||
|             title: 'Health', | ||||
|             component: <ProjectHealth projectId={id} />, | ||||
|             path: `${basePath}/health`, | ||||
|             name: 'health', | ||||
|         }, | ||||
|         { | ||||
|             title: 'Access', | ||||
|             component: <ProjectAccess projectId={id} />, | ||||
|             path: `${basePath}/access`, | ||||
|             name: 'access', | ||||
|         }, | ||||
|         { | ||||
|             title: 'Environments', | ||||
|             component: <ProjectEnvironment projectId={id}  />, | ||||
|             path: `${basePath}/environments`, | ||||
|             name: 'environments', | ||||
|             disabled: !uiConfig.flags.E | ||||
|         }, | ||||
|         { | ||||
|             title: 'Settings', | ||||
|             // @ts-ignore (fix later)
 | ||||
|             component: <EditProject projectId={id} history={history} title="Edit project" />, | ||||
|             path: `${basePath}/settings`, | ||||
|             name: 'settings', | ||||
|         }, | ||||
|     ].filter(tab => !tab.disabled); | ||||
| 
 | ||||
| 
 | ||||
|     const renderTabs = () => { | ||||
|         return tabData.map((tab, index) => { | ||||
|             return ( | ||||
|                 <Tab | ||||
|                     key={tab.title} | ||||
|                     label={tab.title} | ||||
|                     {...a11yProps(index)} | ||||
|                     onClick={() => { | ||||
|                         setActiveTab(index); | ||||
|                         history.push(tab.path); | ||||
|                     }} | ||||
|                     className={styles.tabButton} | ||||
|                 /> | ||||
|             ); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const renderTabContent = () => { | ||||
|         return tabData.map((tab, index) => { | ||||
|             return ( | ||||
|                 <TabPanel value={activeTabIdx} index={index} key={tab.path}> | ||||
|                     {tab.component} | ||||
|                 </TabPanel> | ||||
|             ); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     return ( | ||||
|         <div ref={ref}> | ||||
|             <h1 data-loading className={commonStyles.title}> | ||||
|                 {project?.name}{' '} | ||||
|                 <IconButton component={Link} to={getProjectEditPath(id)}> | ||||
|                     <Edit /> | ||||
|                 </IconButton> | ||||
|             </h1> | ||||
|             <ConditionallyRender | ||||
|                 condition={error} | ||||
|                 show={ | ||||
|                     <ApiError | ||||
|                         data-loading | ||||
|                         style={{ maxWidth: '400px', marginTop: '1rem' }} | ||||
|                         onClick={refetch} | ||||
|                         text="Could not fetch project" | ||||
|                     /> | ||||
|                 } | ||||
|             /> | ||||
|             <div className={styles.containerStyles}> | ||||
|                 <ProjectInfo | ||||
|                     id={id} | ||||
|                     memberCount={members} | ||||
|                     health={health} | ||||
|                     featureCount={features?.length} | ||||
|             <div className={styles.header}> | ||||
|                 <div className={styles.innerContainer}> | ||||
|                     <h2 data-loading className={commonStyles.title}> | ||||
|                         Project: {project?.name}{' '} | ||||
|                     </h2> | ||||
|                     <p>{project?.description}</p> | ||||
|                 </div> | ||||
|                 <ConditionallyRender | ||||
|                     condition={error} | ||||
|                     show={ | ||||
|                         <ApiError | ||||
|                             data-loading | ||||
|                             style={{ maxWidth: '400px', marginTop: '1rem' }} | ||||
|                             onClick={refetch} | ||||
|                             text="Could not fetch project" | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|                 <ProjectFeatureToggles features={features} loading={loading} /> | ||||
|                 <div className={styles.separator} /> | ||||
|                 <div className={styles.tabContainer}> | ||||
|                     <Tabs | ||||
|                         value={activeTabIdx} | ||||
|                         onChange={(_, tabId) => { | ||||
|                             setActiveTab(tabId); | ||||
|                         }} | ||||
|                         indicatorColor="primary" | ||||
|                         textColor="primary" | ||||
|                     > | ||||
|                         {renderTabs()} | ||||
|                     </Tabs> | ||||
|                 </div> | ||||
|             </div> | ||||
|             {renderTabContent()} | ||||
|             {toast} | ||||
|         </div> | ||||
|     ); | ||||
|  | ||||
| @ -0,0 +1,42 @@ | ||||
| import useHealthReport from '../../../../hooks/api/getters/useHealthReport/useHealthReport'; | ||||
| import ApiError from '../../../common/ApiError/ApiError'; | ||||
| import ConditionallyRender from '../../../common/ConditionallyRender'; | ||||
| import ReportCardContainer from '../../../Reporting/ReportCard/ReportCardContainer' | ||||
| import ReportToggleList from '../../../Reporting/ReportToggleList/ReportToggleList' | ||||
| 
 | ||||
| interface ProjectHealthProps { | ||||
|     projectId: string; | ||||
| } | ||||
| 
 | ||||
| const ProjectHealth = ({ projectId }: ProjectHealthProps) => { | ||||
|     const { project, error, refetch } = useHealthReport(projectId); | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <ConditionallyRender | ||||
|                 condition={error} | ||||
|                 show={ | ||||
|                     <ApiError | ||||
|                         data-loading | ||||
|                         style={{ maxWidth: '500px', marginTop: '1rem' }} | ||||
|                         onClick={refetch} | ||||
|                         text={`Could not fetch health rating for ${projectId}`} | ||||
|                     /> | ||||
|                 } | ||||
|             /> | ||||
|             <ReportCardContainer | ||||
|                     health={project?.health} | ||||
|                     staleCount={project?.staleCount} | ||||
|                     activeCount={project?.activeCount} | ||||
|                     potentiallyStaleCount={project?.potentiallyStaleCount} | ||||
|                     selectedProject={project.name} | ||||
|             /> | ||||
|             <ReportToggleList | ||||
|                 features={project.features} | ||||
|                 selectedProject={projectId} | ||||
|             /> | ||||
|         </div> | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| export default ProjectHealth; | ||||
							
								
								
									
										31
									
								
								frontend/src/component/project/Project/ProjectOverview.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/src/component/project/Project/ProjectOverview.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| import useProject from '../../../hooks/api/getters/useProject/useProject'; | ||||
| import ProjectFeatureToggles from './ProjectFeatureToggles/ProjectFeatureToggles'; | ||||
| import ProjectInfo from './ProjectInfo/ProjectInfo'; | ||||
| import { useStyles } from './Project.styles'; | ||||
| 
 | ||||
| interface ProjectOverviewProps { | ||||
|     projectId: string; | ||||
| } | ||||
| 
 | ||||
| const ProjectOverview = ({projectId}: ProjectOverviewProps) => { | ||||
|     const { project, loading } = useProject(projectId); | ||||
|     const { members, features, health } = project; | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <div className={styles.containerStyles}> | ||||
|                 <ProjectInfo | ||||
|                     id={projectId} | ||||
|                     memberCount={members} | ||||
|                     health={health} | ||||
|                     featureCount={features?.length} | ||||
|                 /> | ||||
|                 <ProjectFeatureToggles features={features} loading={loading} /> | ||||
|             </div> | ||||
|              | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default ProjectOverview; | ||||
| @ -1,5 +1,4 @@ | ||||
| import { useContext, useState } from 'react'; | ||||
| import { useParams } from 'react-router-dom'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import { useStyles } from './ProjectEnvironment.styles'; | ||||
| 
 | ||||
| @ -23,15 +22,18 @@ export interface ProjectEnvironment { | ||||
|     enabled: boolean; | ||||
| } | ||||
| 
 | ||||
| const ProjectEnvironmentList = () => { | ||||
|     const { id } = useParams<{id: string}>(); | ||||
| interface ProjectEnvironmentListProps { | ||||
|     projectId: string; | ||||
| } | ||||
| 
 | ||||
| const ProjectEnvironmentList = ({projectId}: ProjectEnvironmentListProps) => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     // api state
 | ||||
|     const { toast, setToastData } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { environments, loading, error, refetch: refetchEnvs } = useEnvironments(); | ||||
|     const { project, refetch: refetchProject } = useProject(id); | ||||
|     const { project, refetch: refetchProject } = useProject(projectId); | ||||
|     const { removeEnvironmentFromProject, addEnvironmentToProject } = useProjectApi(); | ||||
|      | ||||
|     // local state
 | ||||
| @ -64,7 +66,7 @@ const ProjectEnvironmentList = () => { | ||||
|             setSelectedEnv(env); | ||||
|         } else { | ||||
|             try { | ||||
|                 await addEnvironmentToProject(id, env.name); | ||||
|                 await addEnvironmentToProject(projectId, env.name); | ||||
|                 setToastData({ text: 'Environment successfully enabled.', type: 'success', show: true}); | ||||
|             } catch (error) { | ||||
|                 setToastData({text: errorMsg(true), type: 'error', show: true}); | ||||
| @ -76,7 +78,7 @@ const ProjectEnvironmentList = () => { | ||||
|     const handleDisableEnvironment = async () => { | ||||
|         if(selectedEnv && confirmName===selectedEnv.name) { | ||||
|             try { | ||||
|                 await removeEnvironmentFromProject(id, selectedEnv.name); | ||||
|                 await removeEnvironmentFromProject(projectId, selectedEnv.name); | ||||
|                 setSelectedEnv(undefined); | ||||
|                 setConfirmName(''); | ||||
|                 setToastData({ text: 'Environment successfully disabled.', type: 'success', show: true}); | ||||
| @ -98,7 +100,7 @@ const ProjectEnvironmentList = () => { | ||||
|         enabled: (project?.environments).includes(e.name), | ||||
|     })); | ||||
| 
 | ||||
|     const hasPermission = hasAccess(UPDATE_PROJECT, id); | ||||
|     const hasPermission = hasAccess(UPDATE_PROJECT, projectId); | ||||
| 
 | ||||
|     const genLabel = (env: ProjectEnvironment) => ( | ||||
|         <> | ||||
|  | ||||
| @ -25,6 +25,25 @@ type projectMap = { | ||||
|     [index: string]: boolean; | ||||
| }; | ||||
| 
 | ||||
| function resolveCreateButtonData(isOss: boolean, hasAccess: boolean) { | ||||
|     if(isOss) { | ||||
|         return { | ||||
|             title: 'You must be on a paid subscription to create new projects', | ||||
|             disabled: true | ||||
|         } | ||||
|     } else if (!hasAccess) { | ||||
|         return { | ||||
|             title: 'You do not have permissions create new projects', | ||||
|             disabled: true | ||||
|         } | ||||
|     } else { | ||||
|         return { | ||||
|             title: 'Click to create a new project', | ||||
|             disabled: false | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const ProjectListNew = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const history = useHistory(); | ||||
| @ -33,7 +52,7 @@ const ProjectListNew = () => { | ||||
|     const { projects, loading, error, refetch } = useProjects(); | ||||
|     const [fetchedProjects, setFetchedProjects] = useState<projectMap>({}); | ||||
|     const ref = useLoading(loading); | ||||
|     const { loading: configLoading, isOss } = useUiConfig(); | ||||
|     const { isOss } = useUiConfig(); | ||||
| 
 | ||||
|     const handleHover = (projectId: string) => { | ||||
|         if (fetchedProjects[projectId]) { | ||||
| @ -45,6 +64,8 @@ const ProjectListNew = () => { | ||||
|         setFetchedProjects(prev => ({ ...prev, [projectId]: true })); | ||||
|     }; | ||||
| 
 | ||||
|     const createButtonData = resolveCreateButtonData(isOss(), hasAccess(CREATE_PROJECT)); | ||||
| 
 | ||||
|     const renderError = () => { | ||||
|         return ( | ||||
|             <ApiError | ||||
| @ -104,12 +125,6 @@ const ProjectListNew = () => { | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     if (!configLoading) { | ||||
|         if (isOss()) { | ||||
|             history.push('projects/default'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <div ref={ref}> | ||||
|             <PageContent | ||||
| @ -126,7 +141,8 @@ const ProjectListNew = () => { | ||||
|                                             history.push('/projects/create') | ||||
|                                         } | ||||
|                                         maxWidth="700px" | ||||
|                                         tooltip="Add new project" | ||||
|                                         tooltip={createButtonData.title} | ||||
|                                         disabled={createButtonData.disabled} | ||||
|                                     > | ||||
|                                         Add new project | ||||
|                                     </ResponsiveButton> | ||||
|  | ||||
| @ -25,14 +25,11 @@ import AddUserComponent from './access-add-user'; | ||||
| 
 | ||||
| import projectApi from '../../store/project/api'; | ||||
| import PageContent from '../common/PageContent'; | ||||
| import HeaderTitle from '../common/HeaderTitle'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| 
 | ||||
| function AccessComponent({ projectId, project }) { | ||||
|     const [roles, setRoles] = useState([]); | ||||
|     const [users, setUsers] = useState([]); | ||||
|     const [error, setError] = useState(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     const fetchAccess = async () => { | ||||
|         try { | ||||
| @ -97,20 +94,6 @@ function AccessComponent({ projectId, project }) { | ||||
|     return ( | ||||
|         <PageContent | ||||
|             style={{ minHeight: '400px' }} | ||||
|             headerContent={ | ||||
|                 <HeaderTitle | ||||
|                     title={`Manage Access for project "${project.name}"`} | ||||
|                     actions={ | ||||
|                         <Button | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                             onClick={() => history.goBack()} | ||||
|                         > | ||||
|                             Back | ||||
|                         </Button> | ||||
|                     } | ||||
|                 /> | ||||
|             } | ||||
|         > | ||||
|             <AddUserComponent roles={roles} addUserToRole={addUser} /> | ||||
|             <Dialog | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { TextField, Typography, Button } from '@material-ui/core'; | ||||
| import { TextField, Typography } from '@material-ui/core'; | ||||
| 
 | ||||
| import styles from './Project.module.scss'; | ||||
| import classnames from 'classnames'; | ||||
| @ -112,37 +112,6 @@ class ProjectFormComponent extends Component { | ||||
|                 headerContent={ | ||||
|                     <HeaderTitle | ||||
|                         title={`${submitText} Project`} | ||||
|                         actions={ | ||||
|                             <ConditionallyRender | ||||
|                                 condition={ | ||||
|                                     hasAccess(CREATE_PROJECT) && editMode | ||||
|                                 } | ||||
|                                 show={ | ||||
|                                     <> | ||||
|                                     <Button | ||||
|                                         color="primary" | ||||
|                                         onClick={() => | ||||
|                                             this.props.history.push( | ||||
|                                                 `/projects/${project.id}/environments` | ||||
|                                             ) | ||||
|                                         } | ||||
|                                     > | ||||
|                                         Environments | ||||
|                                     </Button> | ||||
|                                     <Button | ||||
|                                         color="primary" | ||||
|                                         onClick={() => | ||||
|                                             this.props.history.push( | ||||
|                                                 `/projects/${project.id}/access` | ||||
|                                             ) | ||||
|                                         } | ||||
|                                     > | ||||
|                                         Manage access | ||||
|                                     </Button> | ||||
|                                     </> | ||||
|                                 } | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                 } | ||||
|             > | ||||
|  | ||||
| @ -1,14 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ProjectAccess from '../../component/project/access-container.js'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const render = ({ match: { params }, history }) => ( | ||||
|     <ProjectAccess projectId={params.id} title="Edit project Access" history={history} /> | ||||
| ); | ||||
| 
 | ||||
| render.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
|     history: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default render; | ||||
| @ -1,6 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import ReportingContainer from '../../component/Reporting/ReportingContainer'; | ||||
| 
 | ||||
| const render = () => <ReportingContainer />; | ||||
| 
 | ||||
| export default render; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user