Skip to content

Commit

Permalink
Merge pull request #504 from zalando-stups/499-Improve-UX-of-applicat…
Browse files Browse the repository at this point in the history
…ion-lifecycle

499 improve ux of application lifecycle
  • Loading branch information
neremic authored Sep 14, 2016
2 parents 0e6cf5d + f7279b7 commit f1810b1
Show file tree
Hide file tree
Showing 18 changed files with 368 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import moment from 'moment';
import { bindActionCreators } from 'redux';

import * as AliceActions from 'common/src/data/alice/alice-action';
import * as KioActions from 'common/src/data/kio/kio-actions';

import ApplicationLifeCycle from './application-lifecycle.jsx'

Expand All @@ -22,8 +23,7 @@ class ApplicationLifecycleHandler extends React.Component {

this.handleVersionsSelect = this.handleVersionsSelect.bind(this);
this.handleVersionReset = this.handleVersionReset.bind(this);
this.handleStartDatePicked = this.handleStartDatePicked.bind(this);
this.handleEndDatePicked = this.handleEndDatePicked.bind(this);
this.handleDateChanged = this.handleDateChanged.bind(this);
this.handleBrushChanged = this.handleBrushChanged.bind(this);
this.handleRemoveVersion = this.handleRemoveVersion.bind(this);
}
Expand All @@ -32,6 +32,7 @@ class ApplicationLifecycleHandler extends React.Component {

componentDidMount() {
this.props.aliceActions.fetchInstanceCount(this.props.params.applicationId, this.state.startDate, this.state.endDate);
this.props.kioActions.fetchApplication(this.props.params.applicationId);
}

componentWillReceiveProps(nextProps) {
Expand All @@ -44,15 +45,13 @@ class ApplicationLifecycleHandler extends React.Component {

// handler functions

handleStartDatePicked(date) {
this.handleDateChanged(moment(date).startOf('day').toDate(), this.state.endDate);
}

handleEndDatePicked(date) {
this.handleDateChanged(this.state.startDate, moment(date).endOf('day').toDate());
}
handleDateChanged(range) {
if (!range || range.length < 1) {
return;
}

handleDateChanged(startDate, endDate) {
const startDate = range[0].toDate();
const endDate = range[1].toDate();
this.setState({
startDate,
endDate,
Expand All @@ -75,7 +74,8 @@ class ApplicationLifecycleHandler extends React.Component {
}

handleVersionsSelect(param) {
this.setState({selectedVersions: param});
const selectedVersions = this.state.versions.filter( v => param.indexOf(v) > -1);
this.setState({selectedVersions});
}

handleVersionReset() {
Expand All @@ -92,8 +92,7 @@ class ApplicationLifecycleHandler extends React.Component {
selectedVersions = {this.state.selectedVersions}
onVersionsSelect = {this.handleVersionsSelect}
onVersionReset = {this.handleVersionReset}
onStartDatePicked = {this.handleStartDatePicked}
onEndDatePicked = {this.handleEndDatePicked}
onDateChanged = {this.handleDateChanged}
onBrushChanged = {this.handleBrushChanged}
onRemoveVersion = {this.handleRemoveVersion}
startDate = {this.state.startDate}
Expand All @@ -111,6 +110,9 @@ ApplicationLifecycleHandler.propTypes = {
aliceActions: React.PropTypes.shape({
fetchInstanceCount: React.PropTypes.func
}).isRequired,
kioActions: React.PropTypes.shape({
fetchApplication: React.PropTypes.func
}).isRequired,
params: React.PropTypes.shape({
applicationId: React.PropTypes.string
}).isRequired
Expand All @@ -119,13 +121,15 @@ ApplicationLifecycleHandler.propTypes = {
function mapStateToProps(state) {
return {
aliceStore: state.alice,
applications: state.kio.applications
applications: state.kio.applications,
kio: state.kio
};
}

function mapDispatchToProps(dispatch) {
return {
aliceActions: bindActionCreators(AliceActions, dispatch)
aliceActions: bindActionCreators(AliceActions, dispatch),
kioActions: bindActionCreators(KioActions, dispatch)
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ class ApplicationLifeCycle extends React.Component {
linkParams={LINK_PARAMS}
application={application.name || applicationId}
/>
<Toolbar
brushExtentEndDate={props.brushExtentEndDate}
brushExtentStartDate={props.brushExtentStartDate}
brushWidth={chartsWidth}
endDate={props.endDate}
startDate={props.startDate}
onBrushChanged={props.onBrushChanged}
onDateChanged={props.onDateChanged}
/>
<div>
<ComboBox
value={props.selectedVersions}
Expand All @@ -44,20 +53,11 @@ class ApplicationLifeCycle extends React.Component {
title='Select Versions'
/>
</div>
<Toolbar
brushExtentEndDate={props.brushExtentEndDate}
brushExtentStartDate={props.brushExtentStartDate}
brushWidth={chartsWidth}
endDate={props.endDate}
startDate={props.startDate}
onBrushChanged={props.onBrushChanged}
onEndDatePicked={props.onEndDatePicked}
onStartDatePicked={props.onStartDatePicked}
/>
{aliceStore.isLoading ?
<Loading />
:
<Charts
application ={application}
applicationId={applicationId}
onDeselect={props.onRemoveVersion}
versions={props.selectedVersions}
Expand Down Expand Up @@ -85,9 +85,8 @@ ApplicationLifeCycle.propTypes = {
brushExtentStartDate: React.PropTypes.instanceOf(Date),
endDate: React.PropTypes.instanceOf(Date),
onBrushChanged: React.PropTypes.func.isRequired,
onEndDatePicked: React.PropTypes.func.isRequired,
onDateChanged: React.PropTypes.func.isRequired,
onRemoveVersion: React.PropTypes.func.isRequired,
onStartDatePicked: React.PropTypes.func.isRequired,
onVersionReset: React.PropTypes.func.isRequired,
onVersionsSelect: React.PropTypes.func.isRequired,
selectedVersions: React.PropTypes.arrayOf(React.PropTypes.shape({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,39 @@ import { Link } from 'react-router';
import * as Routes from 'application/src/routes';
import Chart from 'common/src/components/pure/Chart.jsx';
import Icon from 'react-fa';
import TitleWithButton from 'common/src/components/pure/TitleWithButton.jsx';
import ThreeColumns from 'common/src/components/pure/ThreeColumns.jsx';
import * as utils from './charts_utils.jsx';


const CHART_HEIGHT = 200;

const Charts = (props) => {
const arrayOfChartComponets = props.versions.map((version, index) => {
const versionDataSet = props.versionDataSets.find(e => e.version_id == version.id);

const titleWithButton = <TitleWithButton
title = {version.id}
onClick = {() => props.onDeselect(version.id)}
/>;
const leftElements =
<div style={{display: 'flex', flexDirection: 'column', justifyContent: 'flex-end'}}>
<div>
<Link
to={Routes.verApproval({applicationId: props.applicationId, versionId: version.id})}
className='btn btn-default btn-small'>
<Icon fixedWidth name='check' />
</Link> Approvals
</div>
<div>
<utils.ScmShortCut {...props} />
</div>
<div>
<utils.ServiceShortCut {...props} />
</div>
</div>;

const rightElements = <div
className = 'btn btn-danger btn-small'
onClick = {() => props.onDeselect(version.id)}>
<Icon size='2x' name='remove' />
</div>;


const chart = <Chart
height = {CHART_HEIGHT}
Expand All @@ -26,18 +46,18 @@ const Charts = (props) => {
dataSet = {versionDataSet}
/>;

const links = <Link
to={Routes.verApproval({applicationId: props.applicationId, versionId: version.id})}
className='btn btn-default btn-small'>
<Icon name='check' />
</Link>;

return (
<ThreeColumns key = {index}
leftChildren = {titleWithButton}
middleChildren = {chart}
rightChildren = {links}
/>);
<div key = {index}>
<ThreeColumns
middleChildren = {<h3>{version.id}</h3>}
/>
<ThreeColumns
leftChildren = {leftElements}
middleChildren = {chart}
rightChildren = {rightElements}
/>
</div>
);
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react';
import moment from 'moment';

import Brush from 'common/src/components/pure/Brush.jsx';
import DateSelector, {STYLE_RIGHT} from 'common/src/components/functional/DateSelector.jsx';
import ThreeColumns from 'common/src/components/pure/ThreeColumns.jsx';
import config from 'common/src/config';
import DateDropdown from 'common/src/components/functional/date-dropdown.jsx';

const BRUSH_HEIGHT = 50;
const BRUSH_HEIGHT = 80;

const Toolbar = (props) => {
const brush = <Brush
Expand All @@ -19,26 +17,14 @@ const Toolbar = (props) => {
onChange = {props.onBrushChanged}
/>;

const startDateSelector = <DateSelector
onDatePicked = {props.onStartDatePicked}
title = {moment(props.startDate).format(config.DATE_FORMAT_DAY_OF_MONTH_WITH_YEAR)}
defaultValue = {props.startDate}
maxDate = {props.endDate}
/>;

const endDateSelector = <DateSelector
onDatePicked = {props.onEndDatePicked}
title = {moment(props.endDate).format(config.DATE_FORMAT_DAY_OF_MONTH_WITH_YEAR)}
alignStyle = {STYLE_RIGHT}
defaultValue = {props.endDate}
minDate = {props.startDate}
maxDate = {moment().endOf('day').toDate()}
/>;
const dateSelector = <DateDropdown
onUpdate={props.onDateChanged}
range={[props.startDate, props.endDate]}
title='Date range' />;

return (
<ThreeColumns leftChildren = {startDateSelector}
<ThreeColumns leftChildren = {dateSelector}
middleChildren = {brush}
rightChildren = {endDateSelector}
/>
);
};
Expand All @@ -51,8 +37,7 @@ Toolbar.propTypes = {
brushWidth: React.PropTypes.number.isRequired,
endDate: React.PropTypes.instanceOf(Date).isRequired,
onBrushChanged: React.PropTypes.func.isRequired,
onEndDatePicked: React.PropTypes.func.isRequired,
onStartDatePicked: React.PropTypes.func.isRequired,
onDateChanged: React.PropTypes.func.isRequired,
startDate: React.PropTypes.instanceOf(Date).isRequired
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import Icon from 'react-fa';
import Spinner from 'common/src/components/pure/Spinner.jsx';

import Conditional from 'common/src/components/pure/ConditionalHOC.jsx';

// Functions
const iconNameByRepo = function (url) {
if (!url) {
return '';
}
if (url.includes('github.com')) {
return 'github';
}
if (url.includes('github')) {
return 'github-square';
}
return 'external-link-square';
};

const isKioLoading = props => props.application['status'] && props.application['status'] == 'PENDING';

// Simple Components

const ScmDisplay = props =>
<div>
<a className='btn btn-default btn-small'
href={props.application.scm_url}>
<Icon fixedWidth name={iconNameByRepo(props.application.scm_url)} />
</a> SCM
</div>;
ScmDisplay.displayName = 'ScmDisplay';
ScmDisplay.propTypes = {
application: React.PropTypes.shape({
scm_url: React.PropTypes.string
}).isRequired
};

const ServiceDisplay = props =>
<div>
<a className='btn btn-default btn-small'
href={props.application.service_url}>
<Icon fixedWidth name='external-link-square' />
</a> Service
</div>;

ServiceDisplay.displayName = 'ServiceDisplay';
ServiceDisplay.propTypes = {
application: React.PropTypes.shape({
service_url: React.PropTypes.string
}).isRequired
};

/*eslint-disable react/display-name */
const NotAvailable = displayString => () => <div><Icon name='icon-frown' />{displayString} n/a</div>;
const Loading = displayString => () => <div><Spinner />{displayString}</div>;
/*eslint-enable react/display-name */

// HOC'ified Components

const ScmAvailable = Conditional(props => !!props.application.scm_url,
ScmDisplay, NotAvailable('SCM'));
const ServiceAvailable = Conditional(props => !!props.application.service_url,
ServiceDisplay, NotAvailable('Service'));

const ScmShortCut = Conditional(props => isKioLoading(props), Loading('SCM'), ScmAvailable);
const ServiceShortCut = Conditional(props => isKioLoading(props), Loading('Service'), ServiceAvailable);


export {
ScmShortCut,
ServiceShortCut
}
Loading

0 comments on commit f1810b1

Please sign in to comment.