diff --git a/frontend/src/components/metrics-row/index.js b/frontend/src/components/metrics-row/index.js new file mode 100644 index 00000000..ff842e52 --- /dev/null +++ b/frontend/src/components/metrics-row/index.js @@ -0,0 +1,56 @@ +const React = require('react'); +const Styled = require('styled-components'); + +const Column = require('@ui/components/column'); +const MiniMetric = require('@ui/components/mini-metric'); +const PropTypes = require('@root/prop-types'); +const Row = require('@ui/components/row'); + +const { + default: styled +} = Styled; + +const { + MiniMetricGraph, + MiniMetricMeta, + MiniMetricTitle, + MiniMetricSubtitle, + MiniMetricView +} = MiniMetric; + +const StyledRow = styled(Row)` + margin: 0; + + & > div { + padding-left: 0; + padding-right: 0; + } +`; + +const MetricsRow = ({ + datasets = [] +}) => { + const _datasets = datasets.map((metric, i) => ( + + + + Memory: 54% + (1280/3000 MB) + + + + + )); + + return ( + + {_datasets} + + ); +}; + +MetricsRow.propTypes = { + datasets: React.PropTypes.arrayOf(PropTypes.dataset) +}; + +module.exports = MetricsRow; diff --git a/frontend/src/components/service-item/index.js b/frontend/src/components/service-item/index.js index c3e6e528..3fe83741 100644 --- a/frontend/src/components/service-item/index.js +++ b/frontend/src/components/service-item/index.js @@ -1,23 +1,16 @@ const forceArray = require('force-array'); const React = require('react'); const ReactRouter = require('react-router'); -const Styled = require('styled-components'); const Anchor = require('@ui/components/anchor'); -const Column = require('@ui/components/column'); const List = require('@ui/components/list'); -const MiniMetric = require('@ui/components/mini-metric'); +const MetricsRow = require('@components/metrics-row'); const PropTypes = require('@root/prop-types'); -const Row = require('@ui/components/row'); const { Link } = ReactRouter; -const { - default: styled -} = Styled; - const { ListItem, ListItemView, @@ -31,23 +24,6 @@ const { ListItemHeader } = List; -const { - MiniMetricGraph, - MiniMetricMeta, - MiniMetricTitle, - MiniMetricSubtitle, - MiniMetricView -} = MiniMetric; - -const MetricsRow = styled(Row)` - margin: 0; - - & > div { - padding-left: 0; - padding-right: 0; - } -`; - const ServiceItem = ({ org = '', project = '', @@ -99,18 +75,6 @@ const ServiceItem = ({ ); - const metrics = service.metrics.map((metric, i) => ( - - - - Memory: 54% - (1280/3000 MB) - - - - - )); - const view = childs.length ? ( {childs} @@ -123,9 +87,7 @@ const ServiceItem = ({ {description} - - {metrics} - + ); diff --git a/frontend/src/containers/service/instances.js b/frontend/src/containers/service/instances.js index c062a507..6572c529 100644 --- a/frontend/src/containers/service/instances.js +++ b/frontend/src/containers/service/instances.js @@ -1,11 +1,17 @@ const React = require('react'); const ReactRedux = require('react-redux'); +const actions = require('@state/actions'); const EmptyInstances = require('@components/empty/instances'); const PropTypes = require('@root/prop-types'); const List = require('@ui/components/list'); +const DatasetsRow = require('@components/metrics-row'); const selectors = require('@state/selectors'); +const { + toggleInstanceCollapsed +} = actions; + const { connect } = ReactRedux; @@ -19,22 +25,29 @@ const { ListItemView, ListItemMeta, ListItemTitle, - ListItemOptions + ListItemOptions, + ListItemOutlet } = List; const Instances = ({ - instances = [] + instances = [], + toggleCollapsed = () => null }) => { + const onClick = (uuid) => () => toggleCollapsed(uuid); + const empty = instances.length ? null : ( ); - const instanceList = instances.map((service) => ( - + const instanceList = instances.map((instance) => ( + - - {service.name} + + {instance.name} + + + … @@ -51,7 +64,8 @@ const Instances = ({ }; Instances.propTypes = { - instances: React.PropTypes.arrayOf(PropTypes.instance) + instances: React.PropTypes.arrayOf(PropTypes.instance), + toggleCollapsed: React.PropTypes.func }; const mapStateToProps = (state, { @@ -60,6 +74,11 @@ const mapStateToProps = (state, { instances: instancesByServiceIdSelector(params.serviceId)(state) }); +const mapDispatchToProps = (dispatch) => ({ + toggleCollapsed: (uuid) => dispatch(toggleInstanceCollapsed(uuid)) +}); + module.exports = connect( - mapStateToProps + mapStateToProps, + mapDispatchToProps )(Instances); diff --git a/frontend/src/mock-state.json b/frontend/src/mock-state.json index 49e5f94c..48be8b2d 100644 --- a/frontend/src/mock-state.json +++ b/frontend/src/mock-state.json @@ -378,72 +378,130 @@ }] }, "instances": { + "ui": { + "collapsed": [] + }, "data": [{ "uuid": "309ecd9f-ac03-474b-aff7-4bd2e743296c", "name": "wordpress_01", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "be227788-74f1-4e5b-a85f-b5c71cbae8d8", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "0db6db53-de6f-4378-839e-5d5b452fbaf2", "name": "nfs_01", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6a0eee76-c019-413b-9d5f-44712b55b993", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "250c8a6c-7d02-49a9-8abd-e1c22773041d", "name": "consul", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "97c68055-db88-45c9-ad49-f26da4264777", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "2c921f3a-8bc3-4f57-9cd7-789ebae72061", "name": "memcache_01", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "68d3046e-8e34-4f5d-a0e5-db3795a250fd", "name": "memcache_02", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "2ea99763-3b44-4179-8393-d66d94961051", "name": "memcache_03", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "25f6bc62-63b8-4959-908e-1f6d7ff6341d", "name": "memcache_04", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "8be01042-0281-4a77-a357-25979e87bf3d", "name": "memcache_05", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "3d652e9d-73e8-4a6f-8171-84fa83740662", "name": "nginx", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "081a792c-47e0-4439-924b-2efa9788ae9e", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "c3ec7633-a02b-4615-86a0-9e6faeaae94b", "name": "percona-primary", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "4ee4103e-1a52-4099-a48e-01588f597c70", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }, { "uuid": "c2b5fec2-31e2-41a7-b7fc-cd0bb1822e76", "name": "percona-secundary", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "4ee4103e-1a52-4099-a48e-01588f597c70", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "metrics": [ + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", + "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + ] }] } } diff --git a/frontend/src/prop-types.js b/frontend/src/prop-types.js index 611097f0..c3422655 100644 --- a/frontend/src/prop-types.js +++ b/frontend/src/prop-types.js @@ -39,6 +39,20 @@ const Metric = React.PropTypes.shape({ ...BaseObject }); +const Dataset = React.PropTypes.shape({ + uuid: React.PropTypes.string, + type: React.PropTypes.string, + data: React.PropTypes.arrayOf( + React.PropTypes.shape({ + firstQuartile: React.PropTypes.string, + thirdQuartile: React.PropTypes.string, + median: React.PropTypes.string, + max: React.PropTypes.string, + min: React.PropTypes.string + }) + ) +}); + const Sections = React.PropTypes.arrayOf( React.PropTypes.string ); @@ -58,5 +72,6 @@ module.exports = { instance: Instance, metric: Metric, // consinder renaming this to 'Types' as it could be used for any - metricTypes: MetricTypes + metricTypes: MetricTypes, + dataset: Dataset }; diff --git a/frontend/src/state/actions.js b/frontend/src/state/actions.js index 739bc96a..35e0629d 100644 --- a/frontend/src/state/actions.js +++ b/frontend/src/state/actions.js @@ -11,5 +11,6 @@ module.exports = { ...require('@state/thunks'), updateRouter: createAction(`${APP}/APP/UPDATE_ROUTER`), toggleHeaderTooltip: createAction(`${APP}/APP/TOGGLE_HEADER_TOOLTIP`), - toggleServiceCollapsed: createAction(`${APP}/APP/TOGGLE_SERVICE_COLLAPSED`) + toggleServiceCollapsed: createAction(`${APP}/APP/TOGGLE_SERVICE_COLLAPSED`), + toggleInstanceCollapsed: createAction(`${APP}/APP/TOGGLE_INSTANCE_COLLAPSED`) }; diff --git a/frontend/src/state/reducers/common.js b/frontend/src/state/reducers/common.js new file mode 100644 index 00000000..d192085c --- /dev/null +++ b/frontend/src/state/reducers/common.js @@ -0,0 +1,25 @@ +const toggleCollapsed = (state, action) => { + const { + ui + } = state; + + const { + collapsed = [] + } = ui; + + const _collapsed = collapsed.indexOf(action.payload) >= 0 + ? collapsed.filter((uuid) => uuid !== action.payload) + : [...collapsed, action.payload]; + + return { + ...state, + ui: { + ...ui, + collapsed: _collapsed + } + }; +}; + +module.exports = { + toggleCollapsed +}; diff --git a/frontend/src/state/reducers/instances.js b/frontend/src/state/reducers/instances.js index 7fec06fb..4f04e2f4 100644 --- a/frontend/src/state/reducers/instances.js +++ b/frontend/src/state/reducers/instances.js @@ -1,9 +1,20 @@ const ReduxActions = require('redux-actions'); +const actions = require('@state/actions'); +const common = require('@state/reducers/common'); + const { handleActions } = ReduxActions; +const { + toggleInstanceCollapsed +} = actions; + +const { + toggleCollapsed +} = common; + module.exports = handleActions({ - 'x': (state) => state // somehow handleActions needs at least one reducer + [toggleInstanceCollapsed.toString()]: toggleCollapsed }, {}); diff --git a/frontend/src/state/reducers/services.js b/frontend/src/state/reducers/services.js index 4a6311db..d67ffb59 100644 --- a/frontend/src/state/reducers/services.js +++ b/frontend/src/state/reducers/services.js @@ -1,6 +1,7 @@ const ReduxActions = require('redux-actions'); const actions = require('@state/actions'); +const common = require('@state/reducers/common'); const { handleActions @@ -10,14 +11,10 @@ const { toggleServiceCollapsed } = actions; +const { + toggleCollapsed +} = common; + module.exports = handleActions({ - [toggleServiceCollapsed.toString()]: (state, action) => ({ - ...state, - ui: { - ...state.ui, - collapsed: state.ui.collapsed.indexOf(action.payload) >= 0 - ? state.ui.collapsed.filter((uuid) => uuid !== action.payload) - : [...state.ui.collapsed, action.payload] - } - }) + [toggleServiceCollapsed.toString()]: toggleCollapsed }, {}); diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js index 0e2d9225..4ef35fbd 100644 --- a/frontend/src/state/selectors.js +++ b/frontend/src/state/selectors.js @@ -16,6 +16,7 @@ const orgs = (state) => get(state, 'orgs.data', []); const projects = (state) => get(state, 'projects.data', []); const services = (state) => get(state, 'services.data', []); const collapsedServices = (state) => get(state, 'services.ui.collapsed', []); +const collapsedInstances = (state) => get(state, 'instances.ui.collapsed', []); const instances = (state) => get(state, 'instances.data', []); const metricDatasets = (state) => get(state, 'metrics.data.datasets', []); @@ -46,6 +47,13 @@ const orgSections = (orgId) => createSelector( ) ); +const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0; + +const datasets = (metrics, uuids) => uuids.map((uuid) => find(metrics, [ + 'uuid', + uuid +])); + const servicesByProjectId = (projectId) => createSelector( [services, projectById(projectId), collapsedServices, metricDatasets], (services, project, collapsed, metrics) => @@ -55,31 +63,27 @@ const servicesByProjectId = (projectId) => createSelector( services: services.filter((s) => s.parent === service.uuid) })) .filter((s) => !s.parent) - .map((service) => { - const isCollapsed = (uuid) => collapsed.indexOf(uuid) >= 0; - - const datasets = (uuids) => uuids.map((uuid) => find(metrics, [ - 'uuid', - uuid - ])); - - return { + .map((service) => ({ + ...service, + metrics: datasets(metrics, service.metrics), + collapsed: isCollapsed(collapsed, service.uuid), + services: service.services.map((service) => ({ ...service, - metrics: datasets(service.metrics), - collapsed: isCollapsed(service.uuid), - services: service.services.map((service) => ({ - ...service, - metrics: datasets(service.metrics), - collapsed: isCollapsed(service.uuid) - })) - }; - }) + metrics: datasets(metrics, service.metrics), + collapsed: isCollapsed(collapsed, service.uuid) + })) + })) ); const instancesByServiceId = (serviceId) => createSelector( - [instances, serviceById(serviceId)], - (instances, service) => + [instances, serviceById(serviceId), collapsedInstances, metricDatasets], + (instances, service, collapsed, metrics) => instances.filter((i) => i.service === service.uuid) + .map((instance) => ({ + ...instance, + metrics: datasets(metrics, instance.metrics), + collapsed: isCollapsed(collapsed, instance.uuid) + })) );