feat(cp-frontend): display metrics in service list
This commit is contained in:
parent
7f22dea0b8
commit
b89d1ad686
@ -83,6 +83,15 @@ const StyledCard = Card.extend`
|
||||
}
|
||||
`;
|
||||
|
||||
const StatusContainer = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
`;
|
||||
|
||||
const InstanceCard = ({
|
||||
instance,
|
||||
onHealthMouseOver = () => {},
|
||||
@ -117,22 +126,25 @@ const InstanceCard = ({
|
||||
<CardView>
|
||||
<CardTitle>{instance.name}</CardTitle>
|
||||
<CardDescription>
|
||||
<div onMouseOver={handleHealthMouseOver} onMouseOut={handleMouseOut}>
|
||||
<CardInfo
|
||||
icon={icon}
|
||||
iconPosition="left"
|
||||
label={label}
|
||||
color="dark"
|
||||
onMouseOver={handleHealthMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
/>
|
||||
</div>
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<div onMouseOver={handleStatusMouseOver} onMouseOut={handleMouseOut}>
|
||||
<StatusContainer
|
||||
onMouseOver={handleStatusMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<Label>
|
||||
<Dot {...statusProps} />
|
||||
{titleCase(instance.status)}
|
||||
</Label>
|
||||
</div>
|
||||
</StatusContainer>
|
||||
</CardDescription>
|
||||
</CardView>
|
||||
</StyledCard>
|
||||
|
@ -1,19 +1,50 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MetricGraph } from 'joyent-ui-toolkit';
|
||||
import styled from 'styled-components';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
import {
|
||||
MetricGraph,
|
||||
Card,
|
||||
CardView,
|
||||
CardTitle,
|
||||
CardSubTitle,
|
||||
CardDescription,
|
||||
CardGroupView,
|
||||
CardOptions,
|
||||
CardHeader,
|
||||
CardInfo,
|
||||
Anchor
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
const MetricView = styled(CardView)`
|
||||
padding-top: ${remcalc(48)};
|
||||
|
||||
& canvas {
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const ServiceMetrics = ({ metricsData, graphDurationSeconds }) => {
|
||||
// metricsData should prob be an array rather than an object
|
||||
const metricGraphs = Object.keys(metricsData).map(key => (
|
||||
// should also have a header, w metric name and number of instances (omit everything else from design for copilot)
|
||||
const metricGraphs = Object.keys(metricsData).map(key => (
|
||||
<Card key={key} headed active>
|
||||
<CardHeader>
|
||||
<CardTitle>{key}</CardTitle>
|
||||
</CardHeader>
|
||||
<MetricView>
|
||||
<MetricGraph
|
||||
key={key}
|
||||
metricsData={metricsData[key]}
|
||||
width={954}
|
||||
height={292}
|
||||
graphDurationSeconds={graphDurationSeconds}
|
||||
displayY
|
||||
displayX
|
||||
/>
|
||||
</MetricView>
|
||||
</Card>
|
||||
));
|
||||
|
||||
// This needs layout!!!
|
||||
return <div>{metricGraphs}</div>;
|
||||
};
|
||||
|
@ -4,11 +4,15 @@ import styled from 'styled-components';
|
||||
import forceArray from 'force-array';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import { isNot } from 'styled-is';
|
||||
import { Col, Row } from 'react-styled-flexboxgrid';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
import { InstancesIcon, HealthyIcon } from 'joyent-ui-toolkit';
|
||||
import Status from './status';
|
||||
|
||||
import {
|
||||
Small,
|
||||
MetricGraph,
|
||||
Card,
|
||||
CardView,
|
||||
CardTitle,
|
||||
@ -38,6 +42,73 @@ const StyledAnchor = styled(Anchor)`
|
||||
`};
|
||||
`;
|
||||
|
||||
const GraphsContainer = styled(Row)`
|
||||
background: #f6f7fe;
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const GraphContainer = styled(Col)`
|
||||
position: relative;
|
||||
border-left: ${remcalc(1)} solid #d8d8d8;
|
||||
padding-top: ${remcalc(20)};
|
||||
`;
|
||||
|
||||
const GraphLeftShaddow = styled.div`
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
margin-left: ${remcalc(-8)};
|
||||
margin-top: ${remcalc(-20)};
|
||||
width: ${remcalc(12)};
|
||||
height: 100%;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgba(213, 216, 231, 0.8),
|
||||
rgba(243, 244, 249, 0)
|
||||
);
|
||||
`;
|
||||
|
||||
const GraphTitle = Small.extend`
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: ${remcalc(20)};
|
||||
border-bottom: ${remcalc(1)} solid #d8d8d8;
|
||||
|
||||
font-size: ${remcalc(13)};
|
||||
text-align: center;
|
||||
color: #494949;
|
||||
`;
|
||||
|
||||
const ChildTitle = styled(CardTitle)`
|
||||
padding: 0;
|
||||
flex: 0 1 auto;
|
||||
align-self: stretch;
|
||||
`;
|
||||
|
||||
const ServiceView = styled(CardView)`
|
||||
height: ${remcalc(120)};
|
||||
`;
|
||||
|
||||
const StatusContainer = styled(CardDescription)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: stretch;
|
||||
`;
|
||||
|
||||
const HealthInfoContainer = styled.div`
|
||||
flex: 0 1 auto;
|
||||
align-self: flex-end;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
`;
|
||||
|
||||
const ServiceListItem = ({
|
||||
onQuickActionsClick = () => {},
|
||||
deploymentGroup = '',
|
||||
@ -68,7 +139,7 @@ const ServiceListItem = ({
|
||||
: null;
|
||||
|
||||
const title = isChild ? (
|
||||
<CardTitle>{service.name}</CardTitle>
|
||||
<ChildTitle>{service.name}</ChildTitle>
|
||||
) : (
|
||||
<CardTitle>
|
||||
<TitleInnerContainer>
|
||||
@ -79,13 +150,6 @@ const ServiceListItem = ({
|
||||
</CardTitle>
|
||||
);
|
||||
|
||||
const subtitle = (
|
||||
<CardSubTitle>
|
||||
{service.instances.length}{' '}
|
||||
{service.instances.length > 1 ? 'instances' : 'instance'}
|
||||
</CardSubTitle>
|
||||
);
|
||||
|
||||
const header = !isChild ? (
|
||||
<StyledCardHeader>
|
||||
{title}
|
||||
@ -115,17 +179,31 @@ const ServiceListItem = ({
|
||||
);
|
||||
}
|
||||
|
||||
const metrics = !children.length
|
||||
? Object.keys(service.metrics).map(key => (
|
||||
<GraphContainer xs={4}>
|
||||
<GraphLeftShaddow />
|
||||
<GraphTitle>{key}</GraphTitle>
|
||||
<MetricGraph
|
||||
key={key}
|
||||
metricsData={service.metrics[key]}
|
||||
graphDurationSeconds={90}
|
||||
/>
|
||||
</GraphContainer>
|
||||
))
|
||||
: null;
|
||||
|
||||
const view = children.length ? (
|
||||
<CardGroupView>{childrenItems}</CardGroupView>
|
||||
) : (
|
||||
<CardView>
|
||||
<ServiceView>
|
||||
<StatusContainer>
|
||||
{isChild && title}
|
||||
{isChild && subtitle}
|
||||
<CardDescription>
|
||||
<Status service={service} />
|
||||
{healthyInfo}
|
||||
</CardDescription>
|
||||
</CardView>
|
||||
<HealthInfoContainer>{healthyInfo}</HealthInfoContainer>
|
||||
</StatusContainer>
|
||||
<GraphsContainer>{metrics}</GraphsContainer>
|
||||
</ServiceView>
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -6,9 +6,10 @@ import { StatusLoader, P } from 'joyent-ui-toolkit';
|
||||
|
||||
const StyledStatusContainer = styled.div`
|
||||
display: inline-block;
|
||||
margin: 0 0 ${remcalc(15)} 0;
|
||||
height: ${remcalc(54)};
|
||||
width: ${remcalc(200)};
|
||||
margin: 0;
|
||||
|
||||
flex: 1 1 auto;
|
||||
align-self: stretch;
|
||||
`;
|
||||
|
||||
const StyledStatus = P.extend`
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import get from 'lodash.get';
|
||||
import moment from 'moment';
|
||||
|
||||
export const MetricNames = [
|
||||
@ -18,7 +19,13 @@ export const withServiceMetricsPolling = ({
|
||||
const { loading, error, service, fetchMoreMetrics } = this.props;
|
||||
|
||||
if (!loading && !error && service) {
|
||||
const previousEnd = service.instances[0].metrics[0].end;
|
||||
const previousEnd = get(
|
||||
service,
|
||||
'instances[0].metrics[0].end',
|
||||
moment()
|
||||
.utc()
|
||||
.format()
|
||||
);
|
||||
fetchMoreMetrics(previousEnd);
|
||||
}
|
||||
}, pollingInterval); // TODO this is the polling interval - think about amount is the todo I guess...
|
||||
@ -38,7 +45,9 @@ export const withServiceMetricsPolling = ({
|
||||
export const withServiceMetricsGql = ({
|
||||
gqlQuery,
|
||||
graphDurationSeconds,
|
||||
updateIntervalSeconds
|
||||
updateIntervalSeconds,
|
||||
variables = () => ({}),
|
||||
props = () => ({})
|
||||
}) => {
|
||||
const getPreviousMetrics = (
|
||||
previousResult,
|
||||
@ -81,7 +90,6 @@ export const withServiceMetricsGql = ({
|
||||
options(props) {
|
||||
const params = props.match.params;
|
||||
const deploymentGroupSlug = params.deploymentGroup;
|
||||
const serviceSlug = params.service;
|
||||
|
||||
// this is potentially prone to overfetching if we already have data within timeframe and we leave the page then come back to it
|
||||
const end = moment();
|
||||
@ -93,16 +101,14 @@ export const withServiceMetricsGql = ({
|
||||
return {
|
||||
variables: {
|
||||
deploymentGroupSlug,
|
||||
serviceSlug,
|
||||
metricNames: MetricNames,
|
||||
start: start.utc().format(),
|
||||
end: end.utc().format()
|
||||
end: end.utc().format(),
|
||||
...variables(props)
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ({
|
||||
data: { deploymentGroup, loading, error, variables, fetchMore }
|
||||
}) => {
|
||||
props: ({ data: { variables, fetchMore, ...rest } }) => {
|
||||
const fetchMoreMetrics = previousEnd => {
|
||||
fetchMore({
|
||||
variables: {
|
||||
@ -120,13 +126,10 @@ export const withServiceMetricsGql = ({
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
deploymentGroup,
|
||||
service:
|
||||
!loading && deploymentGroup ? deploymentGroup.services[0] : null,
|
||||
loading,
|
||||
error,
|
||||
fetchMoreMetrics
|
||||
fetchMoreMetrics,
|
||||
...props(rest)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -6,9 +6,12 @@ import moment from 'moment';
|
||||
import ServiceMetricsQuery from '@graphql/ServiceMetrics.gql';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
import { Title } from '@components/navigation';
|
||||
import { ServiceMetrics as ServiceMetricsComponent } from '@components/service';
|
||||
import { Button } from 'joyent-ui-toolkit';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { processInstancesMetrics } from '@state/selectors';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import {
|
||||
withServiceMetricsPolling,
|
||||
@ -20,9 +23,12 @@ import {
|
||||
const GraphDurationSeconds = 90;
|
||||
|
||||
const ServiceMetrics = ({ service, loading, error }) => {
|
||||
const _title = <Title>Metrics</Title>;
|
||||
|
||||
if (loading || !service) {
|
||||
return (
|
||||
<LayoutContainer center>
|
||||
{_title}
|
||||
<Loader />
|
||||
</LayoutContainer>
|
||||
);
|
||||
@ -31,6 +37,7 @@ const ServiceMetrics = ({ service, loading, error }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your metrics."
|
||||
@ -39,22 +46,11 @@ const ServiceMetrics = ({ service, loading, error }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// metricsData should prob be an array rather than an object
|
||||
const metricsData = service.instances.reduce((metrics, instance) => {
|
||||
// gather metrics of instances according to type
|
||||
instance.metrics.forEach(instanceMetrics => {
|
||||
if (!metrics[instanceMetrics.name]) {
|
||||
metrics[instanceMetrics.name] = [];
|
||||
}
|
||||
metrics[instanceMetrics.name].push(instanceMetrics);
|
||||
});
|
||||
return metrics;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ServiceMetricsComponent
|
||||
metricsData={metricsData}
|
||||
metricsData={processInstancesMetrics(service.instances)}
|
||||
graphDurationSeconds={GraphDurationSeconds}
|
||||
/>
|
||||
</LayoutContainer>
|
||||
@ -65,171 +61,15 @@ export default compose(
|
||||
withServiceMetricsGql({
|
||||
gqlQuery: ServiceMetricsQuery,
|
||||
graphDurationSeconds: GraphDurationSeconds,
|
||||
updateIntervalSeconds: 15
|
||||
updateIntervalSeconds: 15,
|
||||
variables: ({ match }) => ({ serviceSlug: match.params.service }),
|
||||
props: ({ deploymentGroup, loading, error }) => ({
|
||||
deploymentGroup,
|
||||
service: get(deploymentGroup || {}, 'services', [])[0],
|
||||
loading,
|
||||
error
|
||||
})
|
||||
}),
|
||||
withServiceMetricsPolling({ pollingInterval: 1000 }),
|
||||
withNotFound([GqlPaths.DEPLOYMENT_GROUP, GqlPaths.SERVICES])
|
||||
)(ServiceMetrics);
|
||||
|
||||
/*
|
||||
const metricNames = [
|
||||
'AVG_MEM_BYTES',
|
||||
'AVG_LOAD_PERCENT',
|
||||
'AGG_NETWORK_BYTES'
|
||||
];
|
||||
|
||||
class ServiceMetrics extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
this._poll = setInterval(() => {
|
||||
const {
|
||||
loading,
|
||||
deploymentGroup,
|
||||
service,
|
||||
fetchMoreMetrics
|
||||
} = this.props;
|
||||
|
||||
if(!loading && service) {
|
||||
const previousEnd = service.instances[0].metrics[0].end;
|
||||
fetchMoreMetrics(previousEnd);
|
||||
}
|
||||
|
||||
}, 1000); // TODO this is the polling interval - think about amount is the todo I guess...
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this._poll);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
const {
|
||||
service,
|
||||
loading,
|
||||
error,
|
||||
fetchMoreMetrics
|
||||
} = this.props;
|
||||
|
||||
if (loading || !service) {
|
||||
return (
|
||||
<LayoutContainer center>
|
||||
<Loader />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your metrics."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
// metricsData should prob be an array rather than an object
|
||||
const metricsData = service.instances.reduce((metrics, instance) => {
|
||||
// gather metrics of instances according to type
|
||||
instance.metrics.forEach((instanceMetrics) => {
|
||||
if(!metrics[instanceMetrics.name]) {
|
||||
metrics[instanceMetrics.name] = [];
|
||||
}
|
||||
metrics[instanceMetrics.name].push(instanceMetrics);
|
||||
});
|
||||
return metrics;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<LayoutContainer>
|
||||
<ServiceMetricsComponent metricsData={metricsData} />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getPreviousMetrics = (previousResult, serviceId, instanceId, metricName) => {
|
||||
return previousResult.deploymentGroup.services
|
||||
.find(s => s.id === serviceId).instances
|
||||
.find(i => i.id === instanceId).metrics
|
||||
.find(m => m.name === metricName).metrics;
|
||||
}
|
||||
|
||||
const getNextResult = (previousResult, fetchNextResult) => {
|
||||
const deploymentGroup = fetchNextResult.deploymentGroup;
|
||||
const nextResult = {
|
||||
deploymentGroup: {
|
||||
...deploymentGroup,
|
||||
services: deploymentGroup.services.map(service => ({
|
||||
...service,
|
||||
instances: service.instances.map(instance => ({
|
||||
...instance,
|
||||
metrics: instance.metrics.map(metric => ({
|
||||
...metric,
|
||||
metrics: getPreviousMetrics(
|
||||
previousResult,
|
||||
service.id,
|
||||
instance.id,
|
||||
metric.name
|
||||
).concat(metric.metrics)
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
return nextResult;
|
||||
}
|
||||
|
||||
const ServiceMetricsGql = graphql(ServiceMetricsQuery, {
|
||||
options(props) {
|
||||
const params = props.match.params;
|
||||
const deploymentGroupSlug = params.deploymentGroup;
|
||||
const serviceSlug = params.service;
|
||||
|
||||
// this is potentially prone to overfetching if we already have data within timeframe and we leave the page then come back to it
|
||||
const end = moment();
|
||||
const start = moment(end).subtract(105, 'seconds'); // TODO initial amount of data we wanna get - should be the same as what we display + 15 secs
|
||||
|
||||
return {
|
||||
variables: {
|
||||
deploymentGroupSlug,
|
||||
serviceSlug,
|
||||
metricNames,
|
||||
start: start.utc().format(),
|
||||
end: end.utc().format()
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ({ data: { deploymentGroup, loading, error, variables, fetchMore }}) => {
|
||||
|
||||
const fetchMoreMetrics = (previousEnd) => {
|
||||
fetchMore({
|
||||
variables: {
|
||||
...variables,
|
||||
start: previousEnd,
|
||||
end: moment().utc().format()
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult, queryVariables }) => {
|
||||
return getNextResult(previousResult, fetchMoreResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
return ({
|
||||
deploymentGroup,
|
||||
service: !loading && deploymentGroup ? deploymentGroup.services[0] : null,
|
||||
loading,
|
||||
error,
|
||||
fetchMoreMetrics
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
export default compose(
|
||||
ServiceMetricsGql,
|
||||
withNotFound([
|
||||
GqlPaths.DEPLOYMENT_GROUP,
|
||||
GqlPaths.SERVICES
|
||||
])
|
||||
)(ServiceMetrics); */
|
||||
|
@ -8,13 +8,25 @@ import sortBy from 'lodash.sortby';
|
||||
|
||||
import ServicesQuery from '@graphql/Services.gql';
|
||||
|
||||
import { processServices } from '@root/state/selectors';
|
||||
import {
|
||||
processServices,
|
||||
processInstancesMetrics
|
||||
} from '@root/state/selectors';
|
||||
import { toggleServicesQuickActions } from '@root/state/actions';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { ServiceListItem } from '@components/services';
|
||||
|
||||
import {
|
||||
withServiceMetricsPolling,
|
||||
withServiceMetricsGql
|
||||
} from '@containers/metrics';
|
||||
|
||||
// 'width' of graph, i.e. total duration of time it'll display and truncate data to
|
||||
// amount of data we'll need to initially fetch
|
||||
const GraphDurationSeconds = 90;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
@ -107,16 +119,29 @@ export class ServiceList extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const serviceList = sortBy(services, ['slug']).map(service => {
|
||||
return (
|
||||
const serviceList = sortBy(services, ['slug'])
|
||||
.map(service =>
|
||||
Object.assign(service, {
|
||||
metrics: !service.children
|
||||
? processInstancesMetrics(service.instances)
|
||||
: null,
|
||||
children: service.children
|
||||
? service.children.map(children =>
|
||||
Object.assign(children, {
|
||||
metrics: processInstancesMetrics(children.instances)
|
||||
})
|
||||
)
|
||||
: null
|
||||
})
|
||||
)
|
||||
.map(service => (
|
||||
<ServiceListItem
|
||||
key={service.id}
|
||||
deploymentGroup={deploymentGroup.slug}
|
||||
service={service}
|
||||
onQuickActionsClick={handleQuickActionsClick}
|
||||
/>
|
||||
);
|
||||
});
|
||||
));
|
||||
|
||||
return (
|
||||
<LayoutContainer>
|
||||
@ -143,16 +168,12 @@ const mapDispatchToProps = dispatch => ({
|
||||
|
||||
const UiConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const ServicesGql = graphql(ServicesQuery, {
|
||||
options(props) {
|
||||
return {
|
||||
pollInterval: 1000,
|
||||
variables: {
|
||||
deploymentGroupSlug: props.match.params.deploymentGroup
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ({ data: { deploymentGroup, loading, error } }) => ({
|
||||
export default compose(
|
||||
withServiceMetricsGql({
|
||||
gqlQuery: ServicesQuery,
|
||||
graphDurationSeconds: GraphDurationSeconds,
|
||||
updateIntervalSeconds: 15,
|
||||
props: ({ deploymentGroup, loading, error }) => ({
|
||||
deploymentGroup,
|
||||
services: deploymentGroup
|
||||
? processServices(deploymentGroup.services, null)
|
||||
@ -160,12 +181,8 @@ const ServicesGql = graphql(ServicesQuery, {
|
||||
loading,
|
||||
error
|
||||
})
|
||||
});
|
||||
|
||||
const ServiceListWithData = compose(
|
||||
ServicesGql,
|
||||
}),
|
||||
withServiceMetricsPolling({ pollingInterval: 1000 }),
|
||||
UiConnect,
|
||||
withNotFound([GqlPaths.DEPLOYMENT_GROUP])
|
||||
)(ServiceList);
|
||||
|
||||
export default ServiceListWithData;
|
||||
|
@ -1,7 +1,12 @@
|
||||
#import "./DeploymentGroupInfo.gql"
|
||||
#import "./ServiceInfo.gql"
|
||||
|
||||
query Services($deploymentGroupSlug: String!) {
|
||||
query Services(
|
||||
$deploymentGroupSlug: String!
|
||||
$metricNames: [MetricName]!
|
||||
$start: String!
|
||||
$end: String!
|
||||
) {
|
||||
deploymentGroup(slug: $deploymentGroupSlug) {
|
||||
...DeploymentGroupInfo
|
||||
services {
|
||||
@ -13,6 +18,16 @@ query Services($deploymentGroupSlug: String!) {
|
||||
id
|
||||
status
|
||||
healthy
|
||||
metrics(names: $metricNames, start: $start, end: $end) {
|
||||
instance
|
||||
name
|
||||
start
|
||||
end
|
||||
metrics {
|
||||
time
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
connections
|
||||
@ -20,6 +35,16 @@ query Services($deploymentGroupSlug: String!) {
|
||||
id
|
||||
status
|
||||
healthy
|
||||
metrics(names: $metricNames, start: $start, end: $end) {
|
||||
instance
|
||||
name
|
||||
start
|
||||
end
|
||||
metrics {
|
||||
time
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,18 @@ const processServicesForTopology = services => {
|
||||
}));
|
||||
};
|
||||
|
||||
// metricsData should prob be an array rather than an object
|
||||
const processInstancesMetrics = instances =>
|
||||
forceArray(instances).reduce((metrics, instance) => {
|
||||
instance.metrics.forEach(instanceMetrics => {
|
||||
metrics[instanceMetrics.name] = forceArray(
|
||||
metrics[instanceMetrics.name]
|
||||
).concat([instanceMetrics]);
|
||||
});
|
||||
|
||||
return metrics;
|
||||
}, {});
|
||||
|
||||
/* ,
|
||||
instancesByServiceId */
|
||||
export {
|
||||
@ -184,5 +196,6 @@ export {
|
||||
getInstancesHealthy,
|
||||
getService,
|
||||
processServices,
|
||||
processServicesForTopology
|
||||
processServicesForTopology,
|
||||
processInstancesMetrics
|
||||
};
|
||||
|
@ -163,7 +163,7 @@ const StyledAnchor = A.extend`
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
display: inline-block;
|
||||
${style}
|
||||
${style};
|
||||
`;
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,8 @@ const StyledTitle = Title.extend`
|
||||
|
||||
const InnerDescription = styled.div`
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Description = ({ children, ...rest }) => {
|
||||
|
@ -23,16 +23,33 @@ const StyledIconContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const CardInfo = ({ label, icon, iconPosition = 'left', color = 'light' }) => {
|
||||
const CardInfoContainer = styled.div`
|
||||
height: 100%;
|
||||
float: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
`;
|
||||
|
||||
const CardInfo = ({
|
||||
label,
|
||||
icon,
|
||||
iconPosition = 'left',
|
||||
color = 'light',
|
||||
onMouseOver,
|
||||
onMouseOut
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<CardInfoContainer onMouseOver={onMouseOver} onMouseOut={onMouseOver}>
|
||||
<StyledIconContainer iconPosition={iconPosition} color={color}>
|
||||
{icon}
|
||||
</StyledIconContainer>
|
||||
<StyledLabel iconPosition={iconPosition} color={color}>
|
||||
{label}
|
||||
</StyledLabel>
|
||||
</div>
|
||||
</CardInfoContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -13,9 +13,5 @@ export default props => {
|
||||
return <StyledLabel {...props} htmlFor={id} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Subscriber channel="input-group">
|
||||
{render}
|
||||
</Subscriber>
|
||||
);
|
||||
return <Subscriber channel="input-group">{render}</Subscriber>;
|
||||
};
|
||||
|
@ -19,12 +19,13 @@ const chartColors = [
|
||||
class MetricGraph extends Component {
|
||||
componentDidMount() {
|
||||
const { xMin, xMax, datasets } = this.processProps(this.props);
|
||||
const { displayX = false, displayY = false } = this.props;
|
||||
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: { datasets },
|
||||
options: {
|
||||
responsive: false, // this needs to be played with
|
||||
responsive: true, // this needs to be played with
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
@ -34,7 +35,7 @@ class MetricGraph extends Component {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
display: true, // config for mini should be false
|
||||
display: displayX, // config for mini should be false
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
time: {
|
||||
@ -46,7 +47,7 @@ class MetricGraph extends Component {
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
display: true // needs min / max and measurement
|
||||
display: displayY // needs min / max and measurement
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -25,15 +25,16 @@ const StyledInnerContainer = styled.div`
|
||||
left: -50%;
|
||||
margin: 0;
|
||||
padding: ${unitcalc(2)} 0;
|
||||
background-color: ${props => props.secondary ? props.theme.secondary : props.theme.white};
|
||||
border: ${props => props.secondary ? border.secondary : border.unchecked};
|
||||
background-color: ${props =>
|
||||
props.secondary ? props.theme.secondary : props.theme.white};
|
||||
border: ${props => (props.secondary ? border.secondary : border.unchecked)};
|
||||
box-shadow: ${tooltipShadow};
|
||||
border-radius: ${borderRadius};
|
||||
z-index: 1000;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
@ -43,13 +44,15 @@ const StyledInnerContainer = styled.div`
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-bottom-color: ${props => props.secondary ? props.theme.secondary : theme.white};
|
||||
border-bottom-color: ${props =>
|
||||
props.secondary ? props.theme.secondary : theme.white};
|
||||
border-width: ${remcalc(3)};
|
||||
margin-left: ${remcalc(-3)};
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-bottom-color: ${props => props.secondary ? props.theme.secondaryActive : theme.grey};
|
||||
border-bottom-color: ${props =>
|
||||
props.secondary ? props.theme.secondaryActive : theme.grey};
|
||||
border-width: ${remcalc(5)};
|
||||
margin-left: ${remcalc(-5)};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user