mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
feat: simple project view (#295)
Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
960801b8aa
commit
e1034a458b
@ -18,6 +18,7 @@ import LogoutFeatures from '../../page/user/logout';
|
|||||||
import ListProjects from '../../page/project';
|
import ListProjects from '../../page/project';
|
||||||
import CreateProject from '../../page/project/create';
|
import CreateProject from '../../page/project/create';
|
||||||
import EditProject from '../../page/project/edit';
|
import EditProject from '../../page/project/edit';
|
||||||
|
import ViewProject from '../../page/project/view';
|
||||||
import EditProjectAccess from '../../page/project/access';
|
import EditProjectAccess from '../../page/project/access';
|
||||||
import ListTagTypes from '../../page/tag-types';
|
import ListTagTypes from '../../page/tag-types';
|
||||||
import CreateTagType from '../../page/tag-types/create';
|
import CreateTagType from '../../page/tag-types/create';
|
||||||
@ -197,6 +198,14 @@ export const routes = [
|
|||||||
type: 'protected',
|
type: 'protected',
|
||||||
layout: 'main',
|
layout: 'main',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/view/:id',
|
||||||
|
parent: '/projects',
|
||||||
|
title: ':id',
|
||||||
|
component: ViewProject,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/projects/:id/access',
|
path: '/projects/:id/access',
|
||||||
parent: '/projects',
|
parent: '/projects',
|
||||||
|
@ -48,7 +48,7 @@ const ProjectList = ({ projects, fetchProjects, removeProject, history }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const projectLink = ({ id, name }) => (
|
const projectLink = ({ id, name }) => (
|
||||||
<Link to={`/projects/edit/${id}`}>
|
<Link to={`/projects/view/${id}`}>
|
||||||
<strong>{name}</strong>
|
<strong>{name}</strong>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
90
frontend/src/component/project/ViewProject/ViewProject.js
Normal file
90
frontend/src/component/project/ViewProject/ViewProject.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { Typography, Button, List } from '@material-ui/core';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
|
import PageContent from '../../common/PageContent';
|
||||||
|
|
||||||
|
import FeatureToggleListItem from '../../feature/FeatureToggleList/FeatureToggleListItem';
|
||||||
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
|
||||||
|
const ViewProject = ({
|
||||||
|
project,
|
||||||
|
features,
|
||||||
|
settings,
|
||||||
|
toggleFeature,
|
||||||
|
featureMetrics,
|
||||||
|
revive,
|
||||||
|
fetchFeatureToggles,
|
||||||
|
}) => {
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFeatureToggles();
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderProjectFeatures = () => {
|
||||||
|
return features.map(feature => {
|
||||||
|
return (
|
||||||
|
<FeatureToggleListItem
|
||||||
|
key={feature.name}
|
||||||
|
settings={settings}
|
||||||
|
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||||
|
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||||
|
feature={feature}
|
||||||
|
toggleFeature={toggleFeature}
|
||||||
|
revive={revive}
|
||||||
|
hasAccess={hasAccess}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageContent
|
||||||
|
headerContent={
|
||||||
|
<HeaderTitle
|
||||||
|
title={`${project.name}`}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={`/projects/edit/${project.id}`}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={`/projects/${project.id}/access`}
|
||||||
|
>
|
||||||
|
Manage access
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={project.description}
|
||||||
|
show={
|
||||||
|
<div style={{ marginBottom: '2rem' }}>
|
||||||
|
<Typography variant="subtitle2">
|
||||||
|
Description
|
||||||
|
</Typography>
|
||||||
|
<Typography>{project.description}</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography variant="subtitle2">
|
||||||
|
Feature toggles in this project
|
||||||
|
</Typography>
|
||||||
|
<List>{renderProjectFeatures()}</List>
|
||||||
|
</PageContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewProject;
|
39
frontend/src/component/project/ViewProject/index.js
Normal file
39
frontend/src/component/project/ViewProject/index.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
fetchFeatureToggles,
|
||||||
|
toggleFeature,
|
||||||
|
} from '../../../store/feature-toggle/actions';
|
||||||
|
import ViewProject from './ViewProject';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const projectBase = { id: '', name: '', description: '' };
|
||||||
|
const realProject = state.projects
|
||||||
|
.toJS()
|
||||||
|
.find(n => n.id === props.projectId);
|
||||||
|
const project = Object.assign(projectBase, realProject);
|
||||||
|
const features = state.features
|
||||||
|
.toJS()
|
||||||
|
.filter(feature => feature.project === project.id);
|
||||||
|
|
||||||
|
const settings = state.settings.toJS();
|
||||||
|
const featureMetrics = state.featureMetrics.toJS();
|
||||||
|
|
||||||
|
return {
|
||||||
|
project,
|
||||||
|
features,
|
||||||
|
settings,
|
||||||
|
featureMetrics,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
toggleFeature,
|
||||||
|
fetchFeatureToggles,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormAddContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ViewProject);
|
||||||
|
|
||||||
|
export default FormAddContainer;
|
@ -24,7 +24,9 @@ function AddUserComponent({ roles, addUserToRole }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (roles.length > 0) {
|
if (roles.length > 0) {
|
||||||
const regularRole = roles.find(r => r.name.toLowerCase() === 'regular');
|
const regularRole = roles.find(
|
||||||
|
r => r.name.toLowerCase() === 'regular'
|
||||||
|
);
|
||||||
setRole(regularRole || roles[0]);
|
setRole(regularRole || roles[0]);
|
||||||
}
|
}
|
||||||
}, [roles]);
|
}, [roles]);
|
||||||
@ -80,7 +82,9 @@ function AddUserComponent({ roles, addUserToRole }) {
|
|||||||
filterOptions={o => o}
|
filterOptions={o => o}
|
||||||
getOptionLabel={option => {
|
getOptionLabel={option => {
|
||||||
if (option) {
|
if (option) {
|
||||||
return `${option.name || '(Empty name)'} <${option.email || option.username}>`;
|
return `${option.name || '(Empty name)'} <${
|
||||||
|
option.email || option.username
|
||||||
|
}>`;
|
||||||
} else return '';
|
} else return '';
|
||||||
}}
|
}}
|
||||||
options={options}
|
options={options}
|
||||||
@ -100,7 +104,12 @@ function AddUserComponent({ roles, addUserToRole }) {
|
|||||||
),
|
),
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{loading ? <CircularProgress color="inherit" size={20} /> : null}
|
{loading ? (
|
||||||
|
<CircularProgress
|
||||||
|
color="inherit"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{params.InputProps.endAdornment}
|
{params.InputProps.endAdornment}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
@ -111,7 +120,9 @@ function AddUserComponent({ roles, addUserToRole }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InputLabel id="add-user-select-role-label">Role</InputLabel>
|
<InputLabel id="add-user-select-role-label">
|
||||||
|
Role
|
||||||
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId="add-user-select-role-label"
|
labelId="add-user-select-role-label"
|
||||||
id="add-user-select-role"
|
id="add-user-select-role"
|
||||||
@ -128,13 +139,19 @@ function AddUserComponent({ roles, addUserToRole }) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button variant="contained" color="primary" disabled={!user} onClick={handleSubmit}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={!user}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
Add user
|
Add user
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddUserComponent.propTypes = {
|
AddUserComponent.propTypes = {
|
||||||
roles: PropTypes.array.isRequired,
|
roles: PropTypes.array.isRequired,
|
||||||
addUserToRole: PropTypes.func.isRequired,
|
addUserToRole: PropTypes.func.isRequired,
|
||||||
|
14
frontend/src/page/project/view.js
Normal file
14
frontend/src/page/project/view.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ViewProject from '../../component/project/ViewProject';
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user