Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,35 @@ const client = mozaik => {
};
})
;
}
},
projectsMergeRequests({ projects }) {
const reqs = projects.map((project) => {
return buildApiRequest(`/projects/${encodeURIComponent(project)}/merge_requests`);
});
return Promise.props({
mergeRequests: Promise.all(reqs).then((data) => {
return data.map((item) => item.body);
})
});
},
groupMergeRequests({ groups, query={} }) {
const reqs = groups.map((group) => {
return buildApiRequest(`/groups/${encodeURIComponent(group)}/merge_requests`, query);
});
return Promise.props({
mergeRequests: Promise.all(reqs).then((data) => {
return data.map((item) => item.body);
})
});
},
projectPipelines({ project, query={} }) {
return Promise.props({
project: operations.project({ project }),
pipelines: buildApiRequest(`/projects/${encodeURIComponent(project)}/pipelines`, query).then((res) => {
return res.body;
})
});
},
};

return operations;
Expand Down
55 changes: 55 additions & 0 deletions src/components/MergeRequest.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { Component, PropTypes } from 'react';
import moment from 'moment';


class MergeRequestItem extends Component {
render() {
const { mergeRequest } = this.props;

const cssClasses = `list__item`;

return (
<div className={cssClasses}>
<a href={`${''}/builds/${mergeRequest.id}`} target="_blank">
#{mergeRequest.title}
</a>
<br />

<span className="label__group">
<span className="label__addon">by</span>
<span className="label">{mergeRequest.author.name}</span>
</span>
<span className="label__group">
<span className="label__addon">upvotes</span>
<span className="label">{mergeRequest.upvotes}</span>
</span>
&nbsp;
<br />
<time className="list__item__time">
<i className="fa fa-clock-o" />&nbsp;
{moment(mergeRequest.updated_at).format('MMMM Do YYYY, h:mm:ss a')}
</time>

</div>
);
}
}

MergeRequestItem.displayName = 'MergeRequestItem';

MergeRequestItem.propTypes = {
project: PropTypes.shape({
web_url: PropTypes.string.isRequired
}).isRequired,
build: PropTypes.shape({
id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
finished_at: PropTypes.string,
commit: PropTypes.shape({
message: PropTypes.string.isRequired
})
}).isRequired
};


export default MergeRequestItem;
105 changes: 105 additions & 0 deletions src/components/MergeRequestGaugeGroups.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { Component, PropTypes } from 'react';
import reactMixin from 'react-mixin';
import { ListenerMixin } from 'reflux';
import _ from 'lodash';
import Mozaik from 'mozaik/browser';
const { Gauge } = Mozaik.Component;


class MergeRequestsGaugeGroups extends Component {
constructor(props) {
super(props);

this.state = { mergeRequestCount: 0 };
}

getApiRequest() {
const { groups } = this.props;

return {
id: `gitlab.groupMergeRequests.${ groups }`,
params: {
groups,
query: {
state: 'opened'
}
}
};
}

onApiData({ mergeRequests }) {
const flattenArray = (arr) => [].concat.apply([], arr);
this.setState({ mergeRequestCount: flattenArray(mergeRequests).length });
}

render() {
const { thresholds } = this.props;
const { mergeRequestCount } = this.state;

const cappedValue = Math.min(mergeRequestCount, _.max(thresholds.map(threshold => threshold.threshold)));
let message = null;
const normThresholds = thresholds.map(threshold => {
if (message === null && cappedValue <= threshold.threshold) {
message = threshold.message;
}

return {
upperBound: threshold.threshold,
color: threshold.color
};
});

return (
<div>
<div className="widget__header">
Pending Merge Requests
<span className="widget__header__count">
{mergeRequestCount}
</span>
<i className="fa fa-dashboard"/>
</div>
<div className="widget__body">
<div className="gitlab__merge-requests_gauge_chart">
<Gauge
donutRatio={0.65}
spacing={{ top: 45, right: 45, left: 45 }}
ranges={normThresholds}
value={mergeRequestCount}
/>
</div>
<div className="gitlab__merge-requests_gauge_message">
{message}
</div>
</div>
</div>
);
}
}

MergeRequestsGaugeGroups.displayName = 'MergeRequestsGaugeGroups';

MergeRequestsGaugeGroups.propTypes = {
project: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
thresholds: PropTypes.arrayOf(PropTypes.shape({
threshold: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
message: PropTypes.string.isRequired
})).isRequired
};

MergeRequestsGaugeGroups.defaultProps = {
thresholds: [
{ threshold: 3, color: '#85e985', message: 'good job!' },
{ threshold: 5, color: '#ecc265', message: 'you should consider reviewing' },
{ threshold: 10, color: '#f26a3f', message: 'merge requests overflow' }
]
};

reactMixin(MergeRequestsGaugeGroups.prototype, ListenerMixin);
reactMixin(MergeRequestsGaugeGroups.prototype, Mozaik.Mixin.ApiConsumer);


export default MergeRequestsGaugeGroups;
64 changes: 64 additions & 0 deletions src/components/MergeRequests.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { Component, PropTypes } from 'react';
import reactMixin from 'react-mixin';
import { ListenerMixin } from 'reflux';
import Mozaik from 'mozaik/browser';
import MergeRequestItem from './MergeRequest';


class MergeRequests extends Component {
constructor(props) {
super(props);

this.state = {
mergeRequests: [],
groups: [],
query: {}
};
}

getApiRequest() {
const { groups, query } = this.props;

return {
id: `gitlab.groupMergeRequests.${ groups }`,
params: { groups, query }
};
}

onApiData({ groups, mergeRequests }) {
const flattenArray = (arr) => [].concat.apply([], arr);
this.setState({ groups, mergeRequests: flattenArray(mergeRequests) });
}

render() {
const { groups, mergeRequests } = this.state;

return (
<div>
<div className="widget__header">
MergeRequests
<span className="widget__header__count">
{mergeRequests.length}
</span>
<i className="fa fa-code-fork" />
</div>
<div className="widget__body">
{mergeRequests.map(mr => (<MergeRequestItem mergeRequest={mr}/>))}
</div>
</div>
);
}
}

MergeRequests.propTypes = {
groups: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired
};

reactMixin(MergeRequests.prototype, ListenerMixin);
reactMixin(MergeRequests.prototype, Mozaik.Mixin.ApiConsumer);


export default MergeRequests;
59 changes: 59 additions & 0 deletions src/components/PipelinesHistory.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { Component, PropTypes } from 'react';
import reactMixin from 'react-mixin';
import { ListenerMixin } from 'reflux';
import Mozaik from 'mozaik/browser';
import PipelineHistoryItem from './PipelinesHistoryItem.jsx';

class PipelinesHistory extends Component {
constructor(props) {
super(props);

this.state = {
project: null,
pipelines: []
};
}

getApiRequest() {
const { project, query } = this.props;

return {
id: `gitlab.projectPipelines.${project}`,
params: { project, query }
};
}

onApiData(data) {
const { project, pipelines } = data;
this.setState({ project, pipelines });
}

render() {
const { project, pipelines } = this.state;

return (
<div>
<div className="widget__header">
pipelines history
<i className="fa fa-bars" />
</div>
<div className="widget__body">
{pipelines.map((pipeline) => (
<PipelineHistoryItem key={pipeline.id} project={project} pipeline={pipeline} />
))}
</div>
</div>
);
}
}

PipelinesHistory.displayName = 'PipelinesHistory';

PipelinesHistory.propTypes = {
project: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]).isRequired
};

reactMixin(PipelinesHistory.prototype, ListenerMixin);
reactMixin(PipelinesHistory.prototype, Mozaik.Mixin.ApiConsumer);

export default PipelinesHistory;
36 changes: 36 additions & 0 deletions src/components/PipelinesHistoryItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { Component, PropTypes } from 'react';
class PipelineHistoryItem extends Component {
render() {
const { project, pipeline } = this.props;

const cssClasses = `list__item list__item--with-status list__item--with-status--${pipeline.status}`;

return (
<div className={cssClasses}>
<a href={`${project.web_url}/pipelines/${pipeline.id}`} target="_blank">
#{pipeline.id}
</a>
<span className="label__group">
<span className="label__addon">ref</span>
<span className="label">{pipeline.ref}</span>
</span>
&nbsp;
<br />
</div>
);
}
}

PipelineHistoryItem.displayName = 'PipelineHistoryItem';

PipelineHistoryItem.propTypes = {
project: PropTypes.shape({
web_url: PropTypes.string.isRequired
}).isRequired,
pipeline: PropTypes.shape({
id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
}).isRequired
};

export default PipelineHistoryItem;
9 changes: 7 additions & 2 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import BuildHistory from './BuildHistory.jsx';
import BuildHistogram from './BuildHistogram.jsx';
import Branches from './Branches.jsx';
import MergeRequestsGauge from './MergeRequestsGauge.jsx';

import PipelinesHistory from './PipelinesHistory.jsx';
import MergeRequests from './MergeRequests.jsx';
import MergeRequestsGaugeGroups from './MergeRequestGaugeGroups.jsx';

export default {
Project,
Expand All @@ -14,5 +16,8 @@ export default {
BuildHistory,
BuildHistogram,
Branches,
MergeRequestsGauge
MergeRequestsGauge,
PipelinesHistory,
MergeRequests,
MergeRequestsGaugeGroups
};