Add user type move portal query to header add services tooltip

This commit is contained in:
JUDIT GRESKOVITS 2017-05-24 18:09:10 +01:00
parent ba5ccea851
commit 845d9f4800
21 changed files with 193 additions and 40 deletions

View File

@ -18,6 +18,7 @@
"react-router-dom": "^4.1.1", "react-router-dom": "^4.1.1",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-actions": "^2.0.3", "redux-actions": "^2.0.3",
"redux-batched-actions": "^0.2.0",
"redux-form": "^6.7.0", "redux-form": "^6.7.0",
"reselect": "^3.0.1", "reselect": "^3.0.1",
"simple-statistics": "^4.1.0", "simple-statistics": "^4.1.0",

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
@ -8,6 +9,7 @@ import { colors } from '@ui/shared/constants';
import Logo from '@assets/triton_logo.png'; import Logo from '@assets/triton_logo.png';
import Row from '@ui/components/row'; import Row from '@ui/components/row';
import Column from '@ui/components/column'; import Column from '@ui/components/column';
import { P } from '@ui/components/base-elements';
const StyledHeader = styled.div` const StyledHeader = styled.div`
background-color: ${colors.base.primaryDarkBrand}; background-color: ${colors.base.primaryDarkBrand};
@ -19,14 +21,36 @@ const StyledLogo = styled.img`
height: ${remcalc(25)}; height: ${remcalc(25)};
`; `;
export default () => ( const StyledP = styled(P)`
color: ${colors.base.white};
font-weight: 600;
margin: ${unitcalc(0.5)} 0 0 0;
`;
const Header = ({
datacenter,
username
}) => (
<StyledHeader> <StyledHeader>
<Row> <Row>
<Column lg={12} xs={12}> <Column md={10} xs={12}>
<Link to='/'> <Link to='/'>
<StyledLogo src={Logo} /> <StyledLogo src={Logo} />
</Link> </Link>
</Column> </Column>
<Column md={1} xs={6}>
<StyledP>{datacenter}</StyledP>
</Column>
<Column md={1} xs={6}>
<StyledP>{username}</StyledP>
</Column>
</Row> </Row>
</StyledHeader> </StyledHeader>
); );
Header.propTypes = {
datacenter: PropTypes.string,
username: PropTypes.string
}
export default Header;

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import PortalQuery from '@graphql/Portal.gql';
import InstancesQuery from '@graphql/Instances.gql'; import InstancesQuery from '@graphql/Instances.gql';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
@ -58,8 +57,6 @@ class InstanceList extends Component {
} }
} }
const PortalGql = graphql(PortalQuery, {});
const InstanceListGql = graphql(InstancesQuery, { const InstanceListGql = graphql(InstancesQuery, {
options(props) { options(props) {
const params = props.match.params; const params = props.match.params;
@ -82,7 +79,6 @@ const InstanceListGql = graphql(InstancesQuery, {
}); });
const InstanceListWithData = compose( const InstanceListWithData = compose(
PortalGql,
InstanceListGql InstanceListGql
)(InstanceList); )(InstanceList);

View File

@ -0,0 +1,35 @@
import React from 'react';
import { graphql } from 'react-apollo';
import PortalQuery from '@graphql/Portal.gql';
import { Header as HeaderComponent } from '@components/navigation';
const Header = ({
portal = {
datacenter: {
region: ''
},
user: {
firstName: ''
}
},
loading,
error
}) => {
return (
<HeaderComponent
datacenter={portal.datacenter.region}
username={portal.user.firstName}
/>
);
};
const HeaderWithData = graphql(PortalQuery, {
props: ({ data: { portal, loading, error }}) => ({
portal,
loading,
error
})
})(Header);
export default HeaderWithData;

View File

@ -1,2 +1,3 @@
export { default as Header } from './header';
export { default as Breadcrumb } from './breadcrumb'; export { default as Breadcrumb } from './breadcrumb';
export { default as Menu } from './menu'; export { default as Menu } from './menu';

View File

@ -2,8 +2,6 @@ import React, { Component } from 'react';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
// import { Link } from 'react-router-dom';
import PortalQuery from '@graphql/Portal.gql';
import ServicesQuery from '@graphql/Services.gql'; import ServicesQuery from '@graphql/Services.gql';
import { processServices } from '@root/state/selectors'; import { processServices } from '@root/state/selectors';
@ -69,8 +67,6 @@ class ServiceList extends Component {
} }
} }
const PortalGql = graphql(PortalQuery, {});
const ServicesGql = graphql(ServicesQuery, { const ServicesGql = graphql(ServicesQuery, {
options(props) { options(props) {
return { return {
@ -89,7 +85,6 @@ const ServicesGql = graphql(ServicesQuery, {
}); });
const ServiceListWithData = compose( const ServiceListWithData = compose(
PortalGql,
ServicesGql ServicesGql
)(ServiceList); )(ServiceList);

View File

@ -2,10 +2,10 @@ import React from 'react';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import PortalQuery from '@graphql/Portal.gql';
import ServicesQuery from '@graphql/ServicesTopology.gql'; import ServicesQuery from '@graphql/ServicesTopology.gql';
import { processServices } from '@root/state/selectors'; import { processServices } from '@root/state/selectors';
import { toggleServicesQuickActions } from '@root/state/actions';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
import { Loader, ErrorMessage } from '@components/messaging'; import { Loader, ErrorMessage } from '@components/messaging';
@ -14,25 +14,26 @@ import { ServicesTooltip } from '@components/services';
import { colors } from '@ui/shared/constants'; import { colors } from '@ui/shared/constants';
import { unitcalc } from '@ui/shared/functions'; import { unitcalc } from '@ui/shared/functions';
import { TopologyGraph } from '@ui/components/topology'; import { TopologyGraph } from '@ui/components/topology';
/*import ServicesTooltip from '@components/services/tooltip';
import { toggleTooltip } from '@state/actions';*/
const StyledBackground = styled.div` const StyledBackground = styled.div`
background-color: ${colors.base.whiteActive}; background-color: ${colors.base.whiteActive};
padding: ${unitcalc(4)};
`; `;
const StyledContainer = styled.div` const StyledContainer = styled.div`
position: relative; position: relative;
padding: ${unitcalc(4)};
`; `;
const ServicesTopology = ({ const ServicesTopology = ({
url,
push, push,
services, services,
datacenter, datacenter,
loading, loading,
error error,
servicesQuickActions,
toggleServicesQuickActions
}) => { }) => {
if(loading) { if(loading) {
@ -52,18 +53,57 @@ const ServicesTopology = ({
) )
} }
const handleQuickActions = (evt, tooltipData) => {
toggleServicesQuickActions(tooltipData);
};
const handleTooltipBlur = (evt) => {
toggleServicesQuickActions({
show: false,
service: servicesQuickActions.service,
position: servicesQuickActions.position
});
}
const handleNodeTitleClick = (evt, { service }) => {
push(
`${url.split('/').slice(0, 3).join('/')}/services/${service.slug}`
);
};
return ( return (
<StyledBackground> <StyledBackground>
<StyledContainer> <StyledContainer>
<TopologyGraph <TopologyGraph
services={services} services={services}
onQuickActions={handleQuickActions}
onNodeTitleClick={handleNodeTitleClick}
/>
<ServicesTooltip
show={servicesQuickActions.show}
position={servicesQuickActions.position}
onBlur={handleTooltipBlur}
/> />
</StyledContainer> </StyledContainer>
</StyledBackground> </StyledBackground>
); );
} }
const PortalGql = graphql(PortalQuery, {}); const mapStateToProps = (state, ownProps) => ({
servicesQuickActions: state.ui.services.quickActions,
url: ownProps.match.url,
push: ownProps.history.push
})
const mapDispatchToProps = (dispatch) => ({
toggleServicesQuickActions: (data) =>
dispatch(toggleServicesQuickActions(data))
});
const UiConnect = connect(
mapStateToProps,
mapDispatchToProps
);
const ServicesGql = graphql(ServicesQuery, { const ServicesGql = graphql(ServicesQuery, {
options(props) { options(props) {
@ -82,8 +122,8 @@ const ServicesGql = graphql(ServicesQuery, {
}); });
const ServicesTopologyWithData = compose( const ServicesTopologyWithData = compose(
PortalGql, ServicesGql,
ServicesGql UiConnect
)(ServicesTopology); )(ServicesTopology);
export default ServicesTopologyWithData; export default ServicesTopologyWithData;

View File

@ -1,6 +1,8 @@
query Portal { query Portal {
portal { portal {
username user {
firstName
}
datacenter { datacenter {
uuid uuid
region region

View File

@ -6,9 +6,7 @@ import {
Switch Switch
} from 'react-router-dom'; } from 'react-router-dom';
import { Header } from '@components/navigation'; import { Header, Breadcrumb, Menu } from '@containers/navigation';
import { Breadcrumb, Menu } from '@containers/navigation';
import { DeploymentGroupList } from '@containers/deployment-groups'; import { DeploymentGroupList } from '@containers/deployment-groups';
import { ServiceList, ServicesTopology, ServicesMenu } from '@containers/services'; import { ServiceList, ServicesTopology, ServicesMenu } from '@containers/services';

View File

@ -5,5 +5,5 @@ const APP = constantCase(process.env['APP_NAME']);
/******************************* UI *******************************/ /******************************* UI *******************************/
export const addMemberToProject = export const toggleServicesQuickActions =
createAction(`${APP}/PROJECT_ADD_MEMBER`); createAction(`${APP}/TOGGLE_SERVICES_QUICK_ACTIONS`);

View File

@ -0,0 +1 @@
export { default as ui } from './ui';

View File

@ -0,0 +1,34 @@
import { handleActions } from 'redux-actions';
import { toggleServicesQuickActions } from '@state/actions';
export default handleActions({
[toggleServicesQuickActions.toString()]: (state, action) => {
const {
position,
service,
show
} = action.payload;
const s = show !== undefined ? show :
!state.services.quickActions.service ||
service.uuid !== state.services.quickActions.service.uuid;
const quickActions = s ? {
show: s,
position,
service
} : {
show: false
}
return {
...state,
services: {
...state.services,
quickActions
}
}
return state;
}
}, {});

View File

@ -15,6 +15,11 @@ const state = {
pathname: "instances", pathname: "instances",
name: "Instances" name: "Instances"
}] }]
},
services: {
quickActions: {
show: false
}
} }
} }
} }

View File

@ -1,6 +1,8 @@
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { enableBatching } from 'redux-batched-actions';
import { ApolloClient, createNetworkInterface } from 'react-apollo'; import { ApolloClient, createNetworkInterface } from 'react-apollo';
import state from './state'; import state from './state';
import { ui } from './reducers';
export const client = new ApolloClient({ export const client = new ApolloClient({
dataIdFromObject: o => { dataIdFromObject: o => {
@ -14,12 +16,10 @@ export const client = new ApolloClient({
export const store = createStore( export const store = createStore(
combineReducers({ combineReducers({
ui: (s) => { ui: ui,
return s ? s : state.ui
},
apollo: client.reducer(), apollo: client.reducer(),
}), }),
{}, // initial state state, // initial state
compose( compose(
applyMiddleware(client.middleware()), applyMiddleware(client.middleware()),
// If you are using the devToolsExtension, you can add it here also // If you are using the devToolsExtension, you can add it here also

View File

@ -5263,6 +5263,10 @@ redux-actions@^2.0.3:
lodash-es "^4.17.4" lodash-es "^4.17.4"
reduce-reducers "^0.1.0" reduce-reducers "^0.1.0"
redux-batched-actions@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/redux-batched-actions/-/redux-batched-actions-0.2.0.tgz#da0000c882b0e6c861a96d5823bd36adf5d9c0dd"
redux-form@^6.7.0: redux-form@^6.7.0:
version "6.7.0" version "6.7.0"
resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-6.7.0.tgz#9fb0769e76f14febf1dd7686e02c4ab2d6f953aa" resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-6.7.0.tgz#9fb0769e76f14febf1dd7686e02c4ab2d6f953aa"

View File

@ -7,7 +7,14 @@ const datacenter = {
uuid: 'datacenter-uuid', uuid: 'datacenter-uuid',
region: 'us-east-1' region: 'us-east-1'
}; };
const portal = { username: 'juditgreskovits', host: 'dockerhost', datacenter}; const user = {
uuid: 'unique-user-id',
firstName: 'Judit',
lastName: 'Greskovits',
email: 'name@email.com',
login: 'juditgreskovits'
}
const portal = { user, host: 'dockerhost', datacenter};
const deploymentGroups = data.projects.data.map(p => { const deploymentGroups = data.projects.data.map(p => {
p.slug = p.id; p.slug = p.id;
return p; return p;

View File

@ -3,11 +3,19 @@
scalar Object scalar Object
type Portal { type Portal {
username: String! user: User!
datacenter: Datacenter! datacenter: Datacenter!
deploymentGroups: [DeploymentGroup]! deploymentGroups: [DeploymentGroup]!
} }
type User {
uuid: ID!
firstName: String!
lastName: String!
email: String!
login: String!
}
type DeploymentGroup { type DeploymentGroup {
uuid: ID! uuid: ID!
name: String! name: String!
@ -163,6 +171,7 @@
# but this way we expose the entire db through gql # but this way we expose the entire db through gql
type Query { type Query {
portal: Portal portal: Portal
user: User
deploymentGroups(name: String, slug: String): [DeploymentGroup] deploymentGroups(name: String, slug: String): [DeploymentGroup]
deploymentGroup(uuid: ID, name: String, slug: String): DeploymentGroup deploymentGroup(uuid: ID, name: String, slug: String): DeploymentGroup
serviceScales(serviceName: String, versionUuid: ID): [ServiceScale] serviceScales(serviceName: String, versionUuid: ID): [ServiceScale]

View File

@ -39,6 +39,7 @@ const GraphNodeButton = ({
y2={height} y2={height}
connected={connected} connected={connected}
/> />
{buttonCircles}
<GraphButtonRect <GraphButtonRect
height={height} height={height}
onClick={onButtonClick} onClick={onButtonClick}
@ -47,7 +48,6 @@ const GraphNodeButton = ({
role='button' role='button'
tabIndex={100 + index} tabIndex={100 + index}
/> />
{buttonCircles}
</g> </g>
); );
}; };

View File

@ -40,7 +40,6 @@ const GraphNode = ({
} }
const onButtonClick = (evt) => { const onButtonClick = (evt) => {
const tooltipPosition = { const tooltipPosition = {
x: data.x + Constants.buttonRect.x + Constants.buttonRect.width/2, x: data.x + Constants.buttonRect.x + Constants.buttonRect.width/2,
y: data.y + Constants.buttonRect.y + Constants.buttonRect.height y: data.y + Constants.buttonRect.y + Constants.buttonRect.height
@ -52,18 +51,19 @@ const GraphNode = ({
} }
const d = { const d = {
service: data.uuid, service: data,
position: { position: {
left: tooltipPosition.x, left: tooltipPosition.x,
top: tooltipPosition.y top: tooltipPosition.y
} },
rect: Constants.buttonRect
}; };
onQuickActions(evt, d); onQuickActions(evt, d);
}; };
const onTitleClick = () => const onTitleClick = (evt) =>
onNodeTitleClick(data.uuid); onNodeTitleClick(evt, { service: data });
const onStart = (evt) => { const onStart = (evt) => {
evt.preventDefault(); evt.preventDefault();

View File

@ -27,6 +27,7 @@ export const GraphTitle = styled.text`
fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; fill: ${props => props.connected ? colors.base.white : colors.base.secondary};
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
cursor: pointer;
`; `;
export const GraphSubtitle = styled.text` export const GraphSubtitle = styled.text`

View File

@ -220,8 +220,8 @@ class TopologyGraph extends React.Component {
this.setDragInfo(false); this.setDragInfo(false);
}; };
const onTitleClick = (serviceUUID) => const onTitleClick = (evt, data) =>
this.props.onNodeTitleClick(serviceUUID); this.props.onNodeTitleClick(evt, data);
const renderedNode = (n, index) => ( const renderedNode = (n, index) => (
<GraphNode <GraphNode