1
0
mirror of https://github.com/yldio/copilot.git synced 2024-11-14 07:10:05 +02:00

link individual Project (fixes #120)

This commit is contained in:
Sérgio Ramos 2016-12-20 19:06:02 +00:00
parent 5195a3133d
commit d1d45c7c61
17 changed files with 375 additions and 81 deletions

View 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;

View File

@ -1,16 +1,11 @@
const isEmpty = require('lodash.isempty'); const isEmpty = require('lodash.isempty');
const React = require('react'); const React = require('react');
const ReactIntl = require('react-intl');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactRouter = require('react-router'); 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 NotFound = require('@containers/not-found');
const Redirect = require('@components/redirect'); const Redirect = require('@components/redirect');
const selectors = require('@state/selectors');
const SectionComponents = { const SectionComponents = {
people: require('./people'), people: require('./people'),
@ -18,16 +13,11 @@ const SectionComponents = {
settings: require('./settings'), settings: require('./settings'),
}; };
const {
FormattedMessage
} = ReactIntl;
const { const {
connect connect
} = ReactRedux; } = ReactRedux;
const { const {
Link,
Match, Match,
Miss Miss
} = ReactRouter; } = ReactRouter;
@ -47,13 +37,9 @@ const Org = ({
); );
} }
const navLinks = sections.map((name) => ( const missMatch = !sections.length ? null : (
<Li key={name}> <Miss component={Redirect(`/${org.id}/${sections[0]}`)} />
<Link activeClassName='active' to={`/${org.id}/${name}`}> );
<FormattedMessage id={name} />
</Link>
</Li>
));
const navMatches = sections.map((name) => ( const navMatches = sections.map((name) => (
<Match <Match
@ -63,16 +49,8 @@ const Org = ({
/> />
)); ));
const missMatch = !sections.length ? null : (
<Miss component={Redirect(`/${org.id}/${sections[0]}`)} />
);
return ( return (
<div> <div>
<H1>{org.name}</H1>
<Ul>
{navLinks}
</Ul>
{navMatches} {navMatches}
{missMatch} {missMatch}
</div> </div>

View File

@ -3,6 +3,8 @@ const React = require('react');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
// const ReactRouter = require('react-router'); // const ReactRouter = require('react-router');
const Section = require('./section');
// const { // const {
// FormattedMessage // FormattedMessage
// } = ReactIntl; // } = ReactIntl;
@ -18,8 +20,12 @@ const {
// Redirect // Redirect
// } = ReactRouter; // } = ReactRouter;
const People = () => { const People = (props) => {
return <p>people</p>; return (
<Section {...props}>
<p>people</p>
</Section>
);
}; };
People.propTypes = {}; People.propTypes = {};

View File

@ -1,24 +1,32 @@
const Projects = require('@components/projects'); const React = require('react');
const ReactRedux = require('react-redux'); const ReactRouter = require('react-router');
const selectors = require('@state/selectors');
const Section = require('./section');
const Projects = require('@containers/projects');
const Project = require('@containers/project');
const { const {
connect Match
} = ReactRedux; } = ReactRouter;
const { module.exports = () => {
projectsByOrgIdSelector const list = (props) => (
} = selectors; <Section {...props}>
<Projects {...props} />
</Section>
);
return (
const mapStateToProps = (state, { <div>
params = {} <Match
}) => ({ component={list}
projects: projectsByOrgIdSelector(params.org)(state) exactly
}); pattern='/:org/projects'
/>
module.exports = connect( <Match
mapStateToProps component={Project}
)(Projects); pattern='/:org/projects/:projectId/:section?'
/>
module.exports.mapStateToProps = mapStateToProps; </div>
);
};

View 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);

View File

@ -3,6 +3,8 @@ const React = require('react');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
// const ReactRouter = require('react-router'); // const ReactRouter = require('react-router');
const Section = require('./section');
// const { // const {
// FormattedMessage // FormattedMessage
// } = ReactIntl; // } = ReactIntl;
@ -18,8 +20,12 @@ const {
// Redirect // Redirect
// } = ReactRouter; // } = ReactRouter;
const Settings = () => { const Settings = (props) => {
return <p>Settings</p>; return (
<Section {...props}>
<p>settings</p>
</Section>
);
}; };
Settings.propTypes = {}; Settings.propTypes = {};

View 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);

View File

@ -0,0 +1,5 @@
const React = require('react');
module.exports = () => (
<p>instances</p>
);

View File

@ -0,0 +1,5 @@
const React = require('react');
module.exports = () => (
<p>manifest</p>
);

View File

@ -0,0 +1,5 @@
const React = require('react');
module.exports = () => (
<p>people</p>
);

View File

@ -0,0 +1,5 @@
const React = require('react');
module.exports = () => (
<p>services</p>
);

View File

@ -0,0 +1,5 @@
const React = require('react');
module.exports = () => (
<p>settings</p>
);

View File

@ -1,23 +1,32 @@
const React = require('react'); const React = require('react');
const ReactIntl = require('react-intl'); 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 Button = require('@ui/components/button');
const Column = require('@ui/components/column');
const Row = require('@ui/components/row'); const Row = require('@ui/components/row');
const selectors = require('@state/selectors');
const {
connect
} = ReactRedux;
const { const {
FormattedMessage FormattedMessage
} = ReactIntl; } = ReactIntl;
// const { const {
// Link, orgByIdSelector,
// Match, projectsByOrgIdSelector
// Miss, } = selectors;
// Redirect
// } = ReactRouter; const {
Link
} = ReactRouter;
const Projects = ({ const Projects = ({
org = {},
projects = [] projects = []
}) => { }) => {
const empty = projects.length ? null : ( const empty = projects.length ? null : (
@ -32,8 +41,9 @@ const Projects = ({
const _projects = projects.map((project) => ( const _projects = projects.map((project) => (
<li key={project.id}> <li key={project.id}>
<input type='checkbox' /> <Link activeClassName='active' to={`/${org.id}/projects/${project.id}`}>
<span>{project.name} ({project.plan}) </span> {project.name}
</Link>
</li> </li>
)); ));
@ -57,11 +67,25 @@ const Projects = ({
}; };
Projects.propTypes = { Projects.propTypes = {
projects: React.PropTypes.arrayOf(React.PropTypes.shape({ org: React.PropTypes.shape({
id: React.PropTypes.string, id: React.PropTypes.string,
name: React.PropTypes.string, name: React.PropTypes.string
plan: 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);

View File

@ -84,14 +84,11 @@
"projects": { "projects": {
"ui": { "ui": {
"sections": [ "sections": [
"summary", "services",
"instances", "instances",
"metrics", "people",
"networks", "settings",
"tags-metadata", "manifest"
"activity-feed",
"service-manifest",
"firewall"
] ]
}, },
"data": [{ "data": [{
@ -140,6 +137,18 @@
}] }]
}, },
"services": { "services": {
"ui": {
"sections": [
"summary",
"instances",
"metrics",
"networks",
"tags-metadata",
"activity-feed",
"service-manifest",
"firewall"
]
},
"data": [{ "data": [{
"uuid": "081a792c-47e0-4439-924b-2efa9788ae9e", "uuid": "081a792c-47e0-4439-924b-2efa9788ae9e",
"name": "Nginx", "name": "Nginx",

View File

@ -9,9 +9,15 @@ const {
const account = (state) => get(state, 'account.data', {}); const account = (state) => get(state, 'account.data', {});
const orgUiSections = (state) => get(state, 'orgs.ui.sections', []); const orgUiSections = (state) => get(state, 'orgs.ui.sections', []);
const projectUiSections = (state) => get(state, 'projects.ui.sections', []);
const orgs = (state) => get(state, 'orgs.data', []); const orgs = (state) => get(state, 'orgs.data', []);
const projects = (state) => get(state, 'projects.data', []); const projects = (state) => get(state, 'projects.data', []);
const projectById= (id) => createSelector(
projects,
(projects) => find(projects, ['id', id])
);
const orgById = (id) => createSelector( const orgById = (id) => createSelector(
orgs, orgs,
(orgs) => find(orgs, ['id', id]) (orgs) => find(orgs, ['id', id])
@ -34,5 +40,7 @@ module.exports = {
orgByIdSelector: orgById, orgByIdSelector: orgById,
orgsSelector: orgs, orgsSelector: orgs,
orgSectionsSelector: orgSections, orgSectionsSelector: orgSections,
projectsByOrgIdSelector: projectsByOrgId projectSectionsSelector: projectUiSections,
projectsByOrgIdSelector: projectsByOrgId,
projectByIdSelector: projectById
}; };

View File

@ -8,14 +8,19 @@ const {
render render
} = enzyme; } = enzyme;
const {
withIntl,
withRouter
} = create;
test('renders <Projects> without exploding', (t) => { test('renders <Projects> without exploding', (t) => {
const Projects = require('@containers/org/projects').WrappedComponent; const Projects = require('@containers/projects').WrappedComponent;
const wrapper = render(create.withIntl(<Projects />)); const wrapper = render(withIntl(<Projects />));
t.deepEqual(wrapper.length, 1); t.deepEqual(wrapper.length, 1);
}); });
test('renders connected <Projects> without exploding', (t) => { test('renders connected <Projects> without exploding', (t) => {
const Projects = require('@containers/org/projects'); const Projects = require('@containers/projects');
const wrapper = render(create(<Projects />)); const wrapper = render(create(<Projects />));
t.deepEqual(wrapper.length, 1); t.deepEqual(wrapper.length, 1);
}); });
@ -35,8 +40,10 @@ test('renders <Projects>\'s list of projects ', (t) => {
plan: '100.17$ per day' plan: '100.17$ per day'
}]; }];
const Projects = require('@containers/org/projects').WrappedComponent; const Projects = require('@containers/projects').WrappedComponent;
const wrapper = render(create.withIntl(<Projects projects={projects} />)); const wrapper = render(withRouter(withIntl(
<Projects projects={projects} />
)));
const empty = wrapper.find('p[name=empty]'); const empty = wrapper.find('p[name=empty]');
const ul = wrapper.find('ul[name=projects]'); 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) => { test('renders <Projects>\'s empty <p> when no projects ', (t) => {
const Projects = require('@containers/org/projects').WrappedComponent; const Projects = require('@containers/projects').WrappedComponent;
const wrapper = render(create.withIntl(<Projects />)); const wrapper = render(withIntl(<Projects />));
const empty = wrapper.find('p[name=empty]'); const empty = wrapper.find('p[name=empty]');
const ul = wrapper.find('ul[name=projects]'); const ul = wrapper.find('ul[name=projects]');

View File

@ -1,6 +1,7 @@
const React = require('react'); const React = require('react');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactIntl = require('react-intl'); const ReactIntl = require('react-intl');
const ReactRouter = require('react-router');
const createStore = require('@state/store'); const createStore = require('@state/store');
const { const {
@ -12,6 +13,10 @@ const {
Provider Provider
} = ReactRedux; } = ReactRedux;
const {
BrowserRouter
} = ReactRouter;
const Messages = { const Messages = {
'en-us': require('../../locales/en-us.json'), 'en-us': require('../../locales/en-us.json'),
'pt-pt': require('../../locales/pt-pt.json') 'pt-pt': require('../../locales/pt-pt.json')
@ -56,6 +61,13 @@ const withIntl = (children, {
</IntlProvider> </IntlProvider>
); );
const withRouter = (children, props = {}) => (
<BrowserRouter>
{children}
</BrowserRouter>
);
module.exports = (children, props) => withRedux(withIntl(children), props); module.exports = (children, props) => withRedux(withIntl(children), props);
module.exports.withRedux = withRedux;
module.exports.withIntl = withIntl; module.exports.withIntl = withIntl;
module.exports.withRedux = withRedux;
module.exports.withRouter = withRouter;