link individual Project (fixes #120)
This commit is contained in:
parent
5195a3133d
commit
d1d45c7c61
52
frontend/src/components/section/index.js
Normal file
52
frontend/src/components/section/index.js
Normal file
@ -0,0 +1,52 @@
|
||||
const React = require('react');
|
||||
const ReactIntl = require('react-intl');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const H1 = require('@ui/components/h1');
|
||||
const Li = require('@ui/components/horizontal-list/li');
|
||||
const Ul = require('@ui/components/horizontal-list/ul');
|
||||
|
||||
const {
|
||||
FormattedMessage
|
||||
} = ReactIntl;
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
const Section = ({
|
||||
children,
|
||||
links = [],
|
||||
name = ''
|
||||
}) => {
|
||||
const navLinks = links.map((link) => (
|
||||
<Li key={link.name}>
|
||||
<Link activeClassName='active' to={link.pathname}>
|
||||
<FormattedMessage id={link.name} />
|
||||
</Link>
|
||||
</Li>
|
||||
));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<H1>{name}</H1>
|
||||
<Ul>
|
||||
{navLinks}
|
||||
</Ul>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Section.propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
links: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
name: React.PropTypes.string,
|
||||
pathname: React.PropTypes.string
|
||||
})
|
||||
),
|
||||
name: React.PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Section;
|
@ -1,16 +1,11 @@
|
||||
const isEmpty = require('lodash.isempty');
|
||||
const React = require('react');
|
||||
const ReactIntl = require('react-intl');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const selectors = require('@state/selectors');
|
||||
|
||||
const H1 = require('@ui/components/h1');
|
||||
const Li = require('@ui/components/horizontal-list/li');
|
||||
const Ul = require('@ui/components/horizontal-list/ul');
|
||||
const NotFound = require('@containers/not-found');
|
||||
const Redirect = require('@components/redirect');
|
||||
const selectors = require('@state/selectors');
|
||||
|
||||
const SectionComponents = {
|
||||
people: require('./people'),
|
||||
@ -18,16 +13,11 @@ const SectionComponents = {
|
||||
settings: require('./settings'),
|
||||
};
|
||||
|
||||
const {
|
||||
FormattedMessage
|
||||
} = ReactIntl;
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
Link,
|
||||
Match,
|
||||
Miss
|
||||
} = ReactRouter;
|
||||
@ -47,13 +37,9 @@ const Org = ({
|
||||
);
|
||||
}
|
||||
|
||||
const navLinks = sections.map((name) => (
|
||||
<Li key={name}>
|
||||
<Link activeClassName='active' to={`/${org.id}/${name}`}>
|
||||
<FormattedMessage id={name} />
|
||||
</Link>
|
||||
</Li>
|
||||
));
|
||||
const missMatch = !sections.length ? null : (
|
||||
<Miss component={Redirect(`/${org.id}/${sections[0]}`)} />
|
||||
);
|
||||
|
||||
const navMatches = sections.map((name) => (
|
||||
<Match
|
||||
@ -63,16 +49,8 @@ const Org = ({
|
||||
/>
|
||||
));
|
||||
|
||||
const missMatch = !sections.length ? null : (
|
||||
<Miss component={Redirect(`/${org.id}/${sections[0]}`)} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<H1>{org.name}</H1>
|
||||
<Ul>
|
||||
{navLinks}
|
||||
</Ul>
|
||||
{navMatches}
|
||||
{missMatch}
|
||||
</div>
|
||||
|
@ -3,6 +3,8 @@ const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
// const ReactRouter = require('react-router');
|
||||
|
||||
const Section = require('./section');
|
||||
|
||||
// const {
|
||||
// FormattedMessage
|
||||
// } = ReactIntl;
|
||||
@ -18,8 +20,12 @@ const {
|
||||
// Redirect
|
||||
// } = ReactRouter;
|
||||
|
||||
const People = () => {
|
||||
return <p>people</p>;
|
||||
const People = (props) => {
|
||||
return (
|
||||
<Section {...props}>
|
||||
<p>people</p>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
People.propTypes = {};
|
||||
|
@ -1,24 +1,32 @@
|
||||
const Projects = require('@components/projects');
|
||||
const ReactRedux = require('react-redux');
|
||||
const selectors = require('@state/selectors');
|
||||
const React = require('react');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const Section = require('./section');
|
||||
const Projects = require('@containers/projects');
|
||||
const Project = require('@containers/project');
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
Match
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
projectsByOrgIdSelector
|
||||
} = selectors;
|
||||
module.exports = () => {
|
||||
const list = (props) => (
|
||||
<Section {...props}>
|
||||
<Projects {...props} />
|
||||
</Section>
|
||||
);
|
||||
|
||||
|
||||
const mapStateToProps = (state, {
|
||||
params = {}
|
||||
}) => ({
|
||||
projects: projectsByOrgIdSelector(params.org)(state)
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps
|
||||
)(Projects);
|
||||
|
||||
module.exports.mapStateToProps = mapStateToProps;
|
||||
return (
|
||||
<div>
|
||||
<Match
|
||||
component={list}
|
||||
exactly
|
||||
pattern='/:org/projects'
|
||||
/>
|
||||
<Match
|
||||
component={Project}
|
||||
pattern='/:org/projects/:projectId/:section?'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
49
frontend/src/containers/org/section.js
Normal file
49
frontend/src/containers/org/section.js
Normal file
@ -0,0 +1,49 @@
|
||||
const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
|
||||
const selectors = require('@state/selectors');
|
||||
const Section = require('@components/section');
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
orgByIdSelector,
|
||||
orgSectionsSelector
|
||||
} = selectors;
|
||||
|
||||
const OrgSection = ({
|
||||
children,
|
||||
org = {},
|
||||
sections = []
|
||||
}) => {
|
||||
const links = sections.map((name) => ({
|
||||
pathname: `/${org.id}/${name}`,
|
||||
name
|
||||
}));
|
||||
|
||||
return (
|
||||
<Section links={links} name={org.name}>
|
||||
{children}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
OrgSection.propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
org: React.PropTypes.shape({
|
||||
id: React.PropTypes.string,
|
||||
name: React.PropTypes.string
|
||||
}),
|
||||
sections: React.PropTypes.arrayOf(
|
||||
React.PropTypes.string
|
||||
)
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
org: orgByIdSelector(ownProps.params.org)(state),
|
||||
sections: orgSectionsSelector(ownProps.params.org)(state)
|
||||
});
|
||||
|
||||
module.exports = connect(mapStateToProps)(OrgSection);
|
@ -3,6 +3,8 @@ const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
// const ReactRouter = require('react-router');
|
||||
|
||||
const Section = require('./section');
|
||||
|
||||
// const {
|
||||
// FormattedMessage
|
||||
// } = ReactIntl;
|
||||
@ -18,8 +20,12 @@ const {
|
||||
// Redirect
|
||||
// } = ReactRouter;
|
||||
|
||||
const Settings = () => {
|
||||
return <p>Settings</p>;
|
||||
const Settings = (props) => {
|
||||
return (
|
||||
<Section {...props}>
|
||||
<p>settings</p>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
Settings.propTypes = {};
|
||||
|
110
frontend/src/containers/project/index.js
Normal file
110
frontend/src/containers/project/index.js
Normal file
@ -0,0 +1,110 @@
|
||||
const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const Redirect = require('@components/redirect');
|
||||
const Section = require('@components/section');
|
||||
const selectors = require('@state/selectors');
|
||||
|
||||
const SectionComponents = {
|
||||
services: require('./services'),
|
||||
instances: require('./instances'),
|
||||
people: require('./people'),
|
||||
settings: require('./settings'),
|
||||
manifest: require('./manifest')
|
||||
};
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
Match,
|
||||
Miss
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
orgByIdSelector,
|
||||
projectSectionsSelector,
|
||||
projectByIdSelector
|
||||
} = selectors;
|
||||
|
||||
const Project = ({
|
||||
org = {},
|
||||
project = {},
|
||||
sections = []
|
||||
}) => {
|
||||
const pathname = (props) => (
|
||||
`/${props.org}/projects/${props.project}/${props.section}`
|
||||
);
|
||||
|
||||
const name = `${org.name} / ${project.name}`;
|
||||
|
||||
const links = sections.map((name) => ({
|
||||
pathname: pathname({
|
||||
org: org.id,
|
||||
project: project.id,
|
||||
section: name
|
||||
}),
|
||||
name
|
||||
}));
|
||||
|
||||
const navMatches = sections.map((name) => {
|
||||
const pattern = pathname({
|
||||
org: org.id,
|
||||
project: project.id,
|
||||
section: name
|
||||
});
|
||||
|
||||
return (
|
||||
<Match
|
||||
component={SectionComponents[name]}
|
||||
key={name}
|
||||
pattern={pattern}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const missPathname = pathname({
|
||||
org: org.id,
|
||||
project: project.id,
|
||||
section: sections[0]
|
||||
});
|
||||
|
||||
const missMatch = !sections.length ? null : (
|
||||
<Miss component={Redirect(missPathname)} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Section links={links} name={name}>
|
||||
{navMatches}
|
||||
{missMatch}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
Project.propTypes = {
|
||||
org: React.PropTypes.shape({
|
||||
id: React.PropTypes.string,
|
||||
name: React.PropTypes.string
|
||||
}),
|
||||
project: React.PropTypes.shape({
|
||||
id: React.PropTypes.string,
|
||||
name: React.PropTypes.string
|
||||
}),
|
||||
sections: React.PropTypes.arrayOf(
|
||||
React.PropTypes.string
|
||||
)
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, {
|
||||
params = {}
|
||||
}) => ({
|
||||
org: orgByIdSelector(params.org)(state),
|
||||
project: projectByIdSelector(params.projectId)(state),
|
||||
sections: projectSectionsSelector(state)
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps
|
||||
)(Project);
|
5
frontend/src/containers/project/instances.js
Normal file
5
frontend/src/containers/project/instances.js
Normal file
@ -0,0 +1,5 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => (
|
||||
<p>instances</p>
|
||||
);
|
5
frontend/src/containers/project/manifest.js
Normal file
5
frontend/src/containers/project/manifest.js
Normal file
@ -0,0 +1,5 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => (
|
||||
<p>manifest</p>
|
||||
);
|
5
frontend/src/containers/project/people.js
Normal file
5
frontend/src/containers/project/people.js
Normal file
@ -0,0 +1,5 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => (
|
||||
<p>people</p>
|
||||
);
|
5
frontend/src/containers/project/services.js
Normal file
5
frontend/src/containers/project/services.js
Normal file
@ -0,0 +1,5 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => (
|
||||
<p>services</p>
|
||||
);
|
5
frontend/src/containers/project/settings.js
Normal file
5
frontend/src/containers/project/settings.js
Normal file
@ -0,0 +1,5 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => (
|
||||
<p>settings</p>
|
||||
);
|
@ -1,23 +1,32 @@
|
||||
const React = require('react');
|
||||
const ReactIntl = require('react-intl');
|
||||
// const ReactRouter = require('react-router');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const Column = require('@ui/components/column');
|
||||
const Button = require('@ui/components/button');
|
||||
const Column = require('@ui/components/column');
|
||||
const Row = require('@ui/components/row');
|
||||
const selectors = require('@state/selectors');
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
FormattedMessage
|
||||
} = ReactIntl;
|
||||
|
||||
// const {
|
||||
// Link,
|
||||
// Match,
|
||||
// Miss,
|
||||
// Redirect
|
||||
// } = ReactRouter;
|
||||
const {
|
||||
orgByIdSelector,
|
||||
projectsByOrgIdSelector
|
||||
} = selectors;
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
const Projects = ({
|
||||
org = {},
|
||||
projects = []
|
||||
}) => {
|
||||
const empty = projects.length ? null : (
|
||||
@ -32,8 +41,9 @@ const Projects = ({
|
||||
|
||||
const _projects = projects.map((project) => (
|
||||
<li key={project.id}>
|
||||
<input type='checkbox' />
|
||||
<span>{project.name} ({project.plan}) ⚙️</span>
|
||||
<Link activeClassName='active' to={`/${org.id}/projects/${project.id}`}>
|
||||
{project.name}
|
||||
</Link>
|
||||
</li>
|
||||
));
|
||||
|
||||
@ -57,11 +67,25 @@ const Projects = ({
|
||||
};
|
||||
|
||||
Projects.propTypes = {
|
||||
projects: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
org: React.PropTypes.shape({
|
||||
id: React.PropTypes.string,
|
||||
name: React.PropTypes.string,
|
||||
plan: React.PropTypes.string
|
||||
}))
|
||||
name: React.PropTypes.string
|
||||
}),
|
||||
projects: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
id: React.PropTypes.string,
|
||||
name: React.PropTypes.string
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
module.exports = Projects;
|
||||
const mapStateToProps = (state, {
|
||||
params = {}
|
||||
}) => ({
|
||||
org: orgByIdSelector(params.org)(state),
|
||||
projects: projectsByOrgIdSelector(params.org)(state)
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps
|
||||
)(Projects);
|
@ -84,14 +84,11 @@
|
||||
"projects": {
|
||||
"ui": {
|
||||
"sections": [
|
||||
"summary",
|
||||
"services",
|
||||
"instances",
|
||||
"metrics",
|
||||
"networks",
|
||||
"tags-metadata",
|
||||
"activity-feed",
|
||||
"service-manifest",
|
||||
"firewall"
|
||||
"people",
|
||||
"settings",
|
||||
"manifest"
|
||||
]
|
||||
},
|
||||
"data": [{
|
||||
@ -140,6 +137,18 @@
|
||||
}]
|
||||
},
|
||||
"services": {
|
||||
"ui": {
|
||||
"sections": [
|
||||
"summary",
|
||||
"instances",
|
||||
"metrics",
|
||||
"networks",
|
||||
"tags-metadata",
|
||||
"activity-feed",
|
||||
"service-manifest",
|
||||
"firewall"
|
||||
]
|
||||
},
|
||||
"data": [{
|
||||
"uuid": "081a792c-47e0-4439-924b-2efa9788ae9e",
|
||||
"name": "Nginx",
|
||||
|
@ -9,9 +9,15 @@ const {
|
||||
|
||||
const account = (state) => get(state, 'account.data', {});
|
||||
const orgUiSections = (state) => get(state, 'orgs.ui.sections', []);
|
||||
const projectUiSections = (state) => get(state, 'projects.ui.sections', []);
|
||||
const orgs = (state) => get(state, 'orgs.data', []);
|
||||
const projects = (state) => get(state, 'projects.data', []);
|
||||
|
||||
const projectById= (id) => createSelector(
|
||||
projects,
|
||||
(projects) => find(projects, ['id', id])
|
||||
);
|
||||
|
||||
const orgById = (id) => createSelector(
|
||||
orgs,
|
||||
(orgs) => find(orgs, ['id', id])
|
||||
@ -34,5 +40,7 @@ module.exports = {
|
||||
orgByIdSelector: orgById,
|
||||
orgsSelector: orgs,
|
||||
orgSectionsSelector: orgSections,
|
||||
projectsByOrgIdSelector: projectsByOrgId
|
||||
projectSectionsSelector: projectUiSections,
|
||||
projectsByOrgIdSelector: projectsByOrgId,
|
||||
projectByIdSelector: projectById
|
||||
};
|
||||
|
@ -8,14 +8,19 @@ const {
|
||||
render
|
||||
} = enzyme;
|
||||
|
||||
const {
|
||||
withIntl,
|
||||
withRouter
|
||||
} = create;
|
||||
|
||||
test('renders <Projects> without exploding', (t) => {
|
||||
const Projects = require('@containers/org/projects').WrappedComponent;
|
||||
const wrapper = render(create.withIntl(<Projects />));
|
||||
const Projects = require('@containers/projects').WrappedComponent;
|
||||
const wrapper = render(withIntl(<Projects />));
|
||||
t.deepEqual(wrapper.length, 1);
|
||||
});
|
||||
|
||||
test('renders connected <Projects> without exploding', (t) => {
|
||||
const Projects = require('@containers/org/projects');
|
||||
const Projects = require('@containers/projects');
|
||||
const wrapper = render(create(<Projects />));
|
||||
t.deepEqual(wrapper.length, 1);
|
||||
});
|
||||
@ -35,8 +40,10 @@ test('renders <Projects>\'s list of projects ', (t) => {
|
||||
plan: '100.17$ per day'
|
||||
}];
|
||||
|
||||
const Projects = require('@containers/org/projects').WrappedComponent;
|
||||
const wrapper = render(create.withIntl(<Projects projects={projects} />));
|
||||
const Projects = require('@containers/projects').WrappedComponent;
|
||||
const wrapper = render(withRouter(withIntl(
|
||||
<Projects projects={projects} />
|
||||
)));
|
||||
|
||||
const empty = wrapper.find('p[name=empty]');
|
||||
const ul = wrapper.find('ul[name=projects]');
|
||||
@ -48,8 +55,8 @@ test('renders <Projects>\'s list of projects ', (t) => {
|
||||
});
|
||||
|
||||
test('renders <Projects>\'s empty <p> when no projects ', (t) => {
|
||||
const Projects = require('@containers/org/projects').WrappedComponent;
|
||||
const wrapper = render(create.withIntl(<Projects />));
|
||||
const Projects = require('@containers/projects').WrappedComponent;
|
||||
const wrapper = render(withIntl(<Projects />));
|
||||
|
||||
const empty = wrapper.find('p[name=empty]');
|
||||
const ul = wrapper.find('ul[name=projects]');
|
||||
|
@ -1,6 +1,7 @@
|
||||
const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactIntl = require('react-intl');
|
||||
const ReactRouter = require('react-router');
|
||||
const createStore = require('@state/store');
|
||||
|
||||
const {
|
||||
@ -12,6 +13,10 @@ const {
|
||||
Provider
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
BrowserRouter
|
||||
} = ReactRouter;
|
||||
|
||||
const Messages = {
|
||||
'en-us': require('../../locales/en-us.json'),
|
||||
'pt-pt': require('../../locales/pt-pt.json')
|
||||
@ -56,6 +61,13 @@ const withIntl = (children, {
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
const withRouter = (children, props = {}) => (
|
||||
<BrowserRouter>
|
||||
{children}
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
module.exports = (children, props) => withRedux(withIntl(children), props);
|
||||
module.exports.withRedux = withRedux;
|
||||
module.exports.withIntl = withIntl;
|
||||
module.exports.withRedux = withRedux;
|
||||
module.exports.withRouter = withRouter;
|
||||
|
Loading…
Reference in New Issue
Block a user