mirror of https://github.com/yldio/copilot.git
203 lines
5.1 KiB
JavaScript
203 lines
5.1 KiB
JavaScript
import React, { Component } from 'react';
|
||
import PropTypes from 'prop-types';
|
||
import { compose } from 'react-apollo';
|
||
import { connect } from 'react-redux';
|
||
import styled from 'styled-components';
|
||
import forceArray from 'force-array';
|
||
import sortBy from 'lodash.sortby';
|
||
import get from 'lodash.get';
|
||
import moment from 'moment';
|
||
|
||
import ServicesQuery from '@graphql/Services.gql';
|
||
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';
|
||
|
||
import {
|
||
processServices,
|
||
processInstancesMetrics
|
||
} from '@root/state/selectors';
|
||
|
||
// '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;
|
||
`;
|
||
|
||
export class ServiceList extends Component {
|
||
constructor(props) {
|
||
super(props);
|
||
|
||
this.state = {
|
||
errors: {}
|
||
};
|
||
}
|
||
|
||
render() {
|
||
const {
|
||
deploymentGroup,
|
||
services,
|
||
loading,
|
||
error,
|
||
toggleServicesQuickActions
|
||
} = this.props;
|
||
|
||
if (loading && !forceArray(services).length) {
|
||
return (
|
||
<LayoutContainer center>
|
||
<Loader />
|
||
</LayoutContainer>
|
||
);
|
||
}
|
||
|
||
const _err = error ? (
|
||
<ErrorMessage
|
||
title="Ooops!"
|
||
message="An error occurred while loading your services."
|
||
/>
|
||
) : null;
|
||
|
||
if (
|
||
deploymentGroup &&
|
||
deploymentGroup.status === 'PROVISIONING' &&
|
||
!forceArray(services).length
|
||
) {
|
||
return (
|
||
<LayoutContainer center>
|
||
<Loader msg="Just a moment, we’re on it" />
|
||
</LayoutContainer>
|
||
);
|
||
}
|
||
|
||
const handleQuickActionsClick = (evt, service) => {
|
||
const button = evt.currentTarget;
|
||
const buttonRect = button.getBoundingClientRect();
|
||
|
||
const position = {
|
||
left: `${buttonRect.left +
|
||
window.scrollX +
|
||
(buttonRect.right - buttonRect.left) / 2}px`,
|
||
top: `${buttonRect.bottom + window.scrollY}px`
|
||
};
|
||
|
||
toggleServicesQuickActions({
|
||
service,
|
||
position
|
||
});
|
||
};
|
||
|
||
let renderedError = null;
|
||
|
||
if (
|
||
this.state.errors.stop ||
|
||
this.state.errors.start ||
|
||
this.state.errors.restart
|
||
) {
|
||
const message = this.state.errors.stop
|
||
? 'An error occurred while attempting to stop your service.'
|
||
: this.state.errors.start
|
||
? 'An error occurred while attempting to start your service.'
|
||
: this.state.errors.restart
|
||
? 'An error occurred while attempting to restart your service.'
|
||
: '';
|
||
|
||
renderedError = <ErrorMessage title="Ooops!" message={message} />;
|
||
}
|
||
|
||
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>
|
||
{renderedError}
|
||
{_err}
|
||
<StyledContainer>{serviceList}</StyledContainer>
|
||
</LayoutContainer>
|
||
);
|
||
}
|
||
}
|
||
|
||
ServiceList.propTypes = {
|
||
deploymentGroup: PropTypes.object,
|
||
services: PropTypes.array,
|
||
loading: PropTypes.bool,
|
||
error: PropTypes.bool,
|
||
toggleServicesQuickActions: PropTypes.func
|
||
};
|
||
|
||
const mapStateToProps = (state, ownProps) => ({});
|
||
|
||
const mapDispatchToProps = dispatch => ({
|
||
toggleServicesQuickActions: data => dispatch(toggleServicesQuickActions(data))
|
||
});
|
||
|
||
const UiConnect = connect(mapStateToProps, mapDispatchToProps);
|
||
|
||
export default compose(
|
||
withServiceMetricsGql({
|
||
gqlQuery: ServicesQuery,
|
||
graphDurationSeconds: GraphDurationSeconds,
|
||
updateIntervalSeconds: 15,
|
||
props: ({ deploymentGroup, loading, error }) => ({
|
||
deploymentGroup,
|
||
services: deploymentGroup
|
||
? processServices(deploymentGroup.services, null)
|
||
: null,
|
||
loading,
|
||
error
|
||
})
|
||
}),
|
||
withServiceMetricsPolling({
|
||
pollingInterval: 1000,
|
||
getPreviousEnd: ({ loading, services = [] }) => {
|
||
if (loading) {
|
||
return false;
|
||
}
|
||
|
||
const previousEnd = forceArray(services)
|
||
.map(service => get(service, 'instances[0].metrics[0].end', null))
|
||
.filter(Boolean)
|
||
.shift();
|
||
|
||
return (
|
||
previousEnd ||
|
||
moment()
|
||
.utc()
|
||
.format()
|
||
);
|
||
}
|
||
}),
|
||
UiConnect,
|
||
withNotFound([GqlPaths.DEPLOYMENT_GROUP])
|
||
)(ServiceList);
|