diff --git a/frontend/package.json b/frontend/package.json index c374607f..467054c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "react-router-dom": "^4.1.1", "redux": "^3.6.0", "redux-actions": "^2.0.3", + "redux-batched-actions": "^0.2.0", "redux-form": "^6.7.0", "reselect": "^3.0.1", "simple-statistics": "^4.1.0", diff --git a/frontend/src/components/navigation/header.js b/frontend/src/components/navigation/header.js index b83567b1..c1513de6 100644 --- a/frontend/src/components/navigation/header.js +++ b/frontend/src/components/navigation/header.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; @@ -8,6 +9,7 @@ import { colors } from '@ui/shared/constants'; import Logo from '@assets/triton_logo.png'; import Row from '@ui/components/row'; import Column from '@ui/components/column'; +import { P } from '@ui/components/base-elements'; const StyledHeader = styled.div` background-color: ${colors.base.primaryDarkBrand}; @@ -19,14 +21,36 @@ const StyledLogo = styled.img` 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 +}) => ( - + + + {datacenter} + + + {username} + ); + +Header.propTypes = { + datacenter: PropTypes.string, + username: PropTypes.string +} + +export default Header; diff --git a/frontend/src/containers/instances/list.js b/frontend/src/containers/instances/list.js index 5996570a..917674cc 100644 --- a/frontend/src/containers/instances/list.js +++ b/frontend/src/containers/instances/list.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { compose, graphql } from 'react-apollo'; -import PortalQuery from '@graphql/Portal.gql'; import InstancesQuery from '@graphql/Instances.gql'; import { LayoutContainer } from '@components/layout'; @@ -58,8 +57,6 @@ class InstanceList extends Component { } } -const PortalGql = graphql(PortalQuery, {}); - const InstanceListGql = graphql(InstancesQuery, { options(props) { const params = props.match.params; @@ -82,7 +79,6 @@ const InstanceListGql = graphql(InstancesQuery, { }); const InstanceListWithData = compose( - PortalGql, InstanceListGql )(InstanceList); diff --git a/frontend/src/containers/navigation/header.js b/frontend/src/containers/navigation/header.js new file mode 100644 index 00000000..ad36b1e6 --- /dev/null +++ b/frontend/src/containers/navigation/header.js @@ -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 ( + + ); +}; + +const HeaderWithData = graphql(PortalQuery, { + props: ({ data: { portal, loading, error }}) => ({ + portal, + loading, + error + }) +})(Header); + +export default HeaderWithData; diff --git a/frontend/src/containers/navigation/index.js b/frontend/src/containers/navigation/index.js index 4e5c76e1..570ccc4d 100644 --- a/frontend/src/containers/navigation/index.js +++ b/frontend/src/containers/navigation/index.js @@ -1,2 +1,3 @@ +export { default as Header } from './header'; export { default as Breadcrumb } from './breadcrumb'; export { default as Menu } from './menu'; diff --git a/frontend/src/containers/services/list.js b/frontend/src/containers/services/list.js index 83f7efc4..dccafa9c 100644 --- a/frontend/src/containers/services/list.js +++ b/frontend/src/containers/services/list.js @@ -2,8 +2,6 @@ import React, { Component } from 'react'; import { compose, graphql } from 'react-apollo'; import { connect } from 'react-redux'; import styled from 'styled-components'; -// import { Link } from 'react-router-dom'; -import PortalQuery from '@graphql/Portal.gql'; import ServicesQuery from '@graphql/Services.gql'; import { processServices } from '@root/state/selectors'; @@ -69,8 +67,6 @@ class ServiceList extends Component { } } -const PortalGql = graphql(PortalQuery, {}); - const ServicesGql = graphql(ServicesQuery, { options(props) { return { @@ -89,7 +85,6 @@ const ServicesGql = graphql(ServicesQuery, { }); const ServiceListWithData = compose( - PortalGql, ServicesGql )(ServiceList); diff --git a/frontend/src/containers/services/topology.js b/frontend/src/containers/services/topology.js index 3dcf9250..b958cf02 100644 --- a/frontend/src/containers/services/topology.js +++ b/frontend/src/containers/services/topology.js @@ -2,10 +2,10 @@ import React from 'react'; import { compose, graphql } from 'react-apollo'; import { connect } from 'react-redux'; import styled from 'styled-components'; -import PortalQuery from '@graphql/Portal.gql'; import ServicesQuery from '@graphql/ServicesTopology.gql'; import { processServices } from '@root/state/selectors'; +import { toggleServicesQuickActions } from '@root/state/actions'; import { LayoutContainer } from '@components/layout'; import { Loader, ErrorMessage } from '@components/messaging'; @@ -14,25 +14,26 @@ import { ServicesTooltip } from '@components/services'; import { colors } from '@ui/shared/constants'; import { unitcalc } from '@ui/shared/functions'; import { TopologyGraph } from '@ui/components/topology'; -/*import ServicesTooltip from '@components/services/tooltip'; - -import { toggleTooltip } from '@state/actions';*/ const StyledBackground = styled.div` + background-color: ${colors.base.whiteActive}; + padding: ${unitcalc(4)}; `; const StyledContainer = styled.div` position: relative; - padding: ${unitcalc(4)}; `; const ServicesTopology = ({ + url, push, services, datacenter, loading, - error + error, + servicesQuickActions, + toggleServicesQuickActions }) => { 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 ( + ); } -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, { options(props) { @@ -82,8 +122,8 @@ const ServicesGql = graphql(ServicesQuery, { }); const ServicesTopologyWithData = compose( - PortalGql, - ServicesGql + ServicesGql, + UiConnect )(ServicesTopology); export default ServicesTopologyWithData; diff --git a/frontend/src/graphql/Portal.gql b/frontend/src/graphql/Portal.gql index 9918426c..4d5e20c9 100644 --- a/frontend/src/graphql/Portal.gql +++ b/frontend/src/graphql/Portal.gql @@ -1,6 +1,8 @@ query Portal { portal { - username + user { + firstName + } datacenter { uuid region diff --git a/frontend/src/router.js b/frontend/src/router.js index e7aa63a2..a6c8713f 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -6,9 +6,7 @@ import { Switch } from 'react-router-dom'; -import { Header } from '@components/navigation'; - -import { Breadcrumb, Menu } from '@containers/navigation'; +import { Header, Breadcrumb, Menu } from '@containers/navigation'; import { DeploymentGroupList } from '@containers/deployment-groups'; import { ServiceList, ServicesTopology, ServicesMenu } from '@containers/services'; diff --git a/frontend/src/state/actions.js b/frontend/src/state/actions.js index fa428d71..58ce94aa 100644 --- a/frontend/src/state/actions.js +++ b/frontend/src/state/actions.js @@ -5,5 +5,5 @@ const APP = constantCase(process.env['APP_NAME']); /******************************* UI *******************************/ -export const addMemberToProject = - createAction(`${APP}/PROJECT_ADD_MEMBER`); +export const toggleServicesQuickActions = + createAction(`${APP}/TOGGLE_SERVICES_QUICK_ACTIONS`); diff --git a/frontend/src/state/reducers/index.js b/frontend/src/state/reducers/index.js index e69de29b..570fa5d0 100644 --- a/frontend/src/state/reducers/index.js +++ b/frontend/src/state/reducers/index.js @@ -0,0 +1 @@ +export { default as ui } from './ui'; diff --git a/frontend/src/state/reducers/ui.js b/frontend/src/state/reducers/ui.js new file mode 100644 index 00000000..95ad051e --- /dev/null +++ b/frontend/src/state/reducers/ui.js @@ -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; + } +}, {}); diff --git a/frontend/src/state/state.js b/frontend/src/state/state.js index 02e5421a..32472fe0 100644 --- a/frontend/src/state/state.js +++ b/frontend/src/state/state.js @@ -15,6 +15,11 @@ const state = { pathname: "instances", name: "Instances" }] + }, + services: { + quickActions: { + show: false + } } } } diff --git a/frontend/src/state/store.js b/frontend/src/state/store.js index 1b54bd0c..3e6ec108 100644 --- a/frontend/src/state/store.js +++ b/frontend/src/state/store.js @@ -1,6 +1,8 @@ import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; +import { enableBatching } from 'redux-batched-actions'; import { ApolloClient, createNetworkInterface } from 'react-apollo'; import state from './state'; +import { ui } from './reducers'; export const client = new ApolloClient({ dataIdFromObject: o => { @@ -14,12 +16,10 @@ export const client = new ApolloClient({ export const store = createStore( combineReducers({ - ui: (s) => { - return s ? s : state.ui - }, + ui: ui, apollo: client.reducer(), }), - {}, // initial state + state, // initial state compose( applyMiddleware(client.middleware()), // If you are using the devToolsExtension, you can add it here also diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 509fe11b..8bd6e3c2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -5263,6 +5263,10 @@ redux-actions@^2.0.3: lodash-es "^4.17.4" 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: version "6.7.0" resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-6.7.0.tgz#9fb0769e76f14febf1dd7686e02c4ab2d6f953aa" diff --git a/spikes/graphql/graphql-server/src/resolvers.js b/spikes/graphql/graphql-server/src/resolvers.js index 5c751c6b..d35d67fd 100644 --- a/spikes/graphql/graphql-server/src/resolvers.js +++ b/spikes/graphql/graphql-server/src/resolvers.js @@ -7,7 +7,14 @@ const datacenter = { uuid: 'datacenter-uuid', 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 => { p.slug = p.id; return p; diff --git a/spikes/graphql/graphql-server/src/schema.gql b/spikes/graphql/graphql-server/src/schema.gql index 73cf8ef0..82f28374 100644 --- a/spikes/graphql/graphql-server/src/schema.gql +++ b/spikes/graphql/graphql-server/src/schema.gql @@ -3,11 +3,19 @@ scalar Object type Portal { - username: String! + user: User! datacenter: Datacenter! deploymentGroups: [DeploymentGroup]! } + type User { + uuid: ID! + firstName: String! + lastName: String! + email: String! + login: String! + } + type DeploymentGroup { uuid: ID! name: String! @@ -163,6 +171,7 @@ # but this way we expose the entire db through gql type Query { portal: Portal + user: User deploymentGroups(name: String, slug: String): [DeploymentGroup] deploymentGroup(uuid: ID, name: String, slug: String): DeploymentGroup serviceScales(serviceName: String, versionUuid: ID): [ServiceScale] diff --git a/ui/src/components/topology/graph-node/button.js b/ui/src/components/topology/graph-node/button.js index 52e2d799..9b1a78e7 100644 --- a/ui/src/components/topology/graph-node/button.js +++ b/ui/src/components/topology/graph-node/button.js @@ -39,6 +39,7 @@ const GraphNodeButton = ({ y2={height} connected={connected} /> + {buttonCircles} - {buttonCircles} ); }; diff --git a/ui/src/components/topology/graph-node/index.js b/ui/src/components/topology/graph-node/index.js index 2c406867..f4334e3e 100644 --- a/ui/src/components/topology/graph-node/index.js +++ b/ui/src/components/topology/graph-node/index.js @@ -40,7 +40,6 @@ const GraphNode = ({ } const onButtonClick = (evt) => { - const tooltipPosition = { x: data.x + Constants.buttonRect.x + Constants.buttonRect.width/2, y: data.y + Constants.buttonRect.y + Constants.buttonRect.height @@ -52,18 +51,19 @@ const GraphNode = ({ } const d = { - service: data.uuid, + service: data, position: { left: tooltipPosition.x, top: tooltipPosition.y - } + }, + rect: Constants.buttonRect }; onQuickActions(evt, d); }; - const onTitleClick = () => - onNodeTitleClick(data.uuid); + const onTitleClick = (evt) => + onNodeTitleClick(evt, { service: data }); const onStart = (evt) => { evt.preventDefault(); diff --git a/ui/src/components/topology/graph-node/shapes.js b/ui/src/components/topology/graph-node/shapes.js index 8ad486c7..5e4890f8 100644 --- a/ui/src/components/topology/graph-node/shapes.js +++ b/ui/src/components/topology/graph-node/shapes.js @@ -27,6 +27,7 @@ export const GraphTitle = styled.text` fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; font-size: 16px; font-weight: 600; + cursor: pointer; `; export const GraphSubtitle = styled.text` diff --git a/ui/src/components/topology/topology-graph.js b/ui/src/components/topology/topology-graph.js index 2c60d00b..70751d52 100644 --- a/ui/src/components/topology/topology-graph.js +++ b/ui/src/components/topology/topology-graph.js @@ -220,8 +220,8 @@ class TopologyGraph extends React.Component { this.setDragInfo(false); }; - const onTitleClick = (serviceUUID) => - this.props.onNodeTitleClick(serviceUUID); + const onTitleClick = (evt, data) => + this.props.onNodeTitleClick(evt, data); const renderedNode = (n, index) => (