--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import _ from 'underscore';
+import moment from 'moment';
+import React, { Component } from 'react';
+
+import SeverityHelper from '../../../components/shared/severity-helper';
+import { BarChart } from '../../../components/charts/bar-chart';
+import { getFacets, getFacet } from '../../../api/issues';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+const BASE_QUERY = { resolved: false, assignees: '__me__' };
+
+
+function getTotalUrl () {
+ return window.baseUrl + '/account/issues#resolved=false';
+}
+
+function getSeverityUrl (severity) {
+ return window.baseUrl + '/account/issues#resolved=false|severities=' + severity;
+}
+
+function getProjectUrl (project) {
+ return window.baseUrl + '/account/issues#resolved=false|projectUuids=' + project;
+}
+
+function getPeriodUrl (createdAfter, createdBefore) {
+ return window.baseUrl + `/account/issues#resolved=false|createdAfter=${createdAfter}|createdBefore=${createdBefore}`;
+}
+
+
+export default class IssueWidgets extends Component {
+ state = {
+ loading: true
+ };
+
+ componentDidMount () {
+ this.fetchIssues();
+ }
+
+ fetchIssues () {
+ Promise.all([
+ this.fetchFacets(),
+ this.fetchByDate()
+ ]).then(responses => {
+ const facets = responses[0];
+ const byDate = responses[1];
+
+ this.setState({
+ loading: false,
+ total: facets.total,
+ severities: facets.severities,
+ projects: facets.projects,
+ byDate
+ });
+ });
+ }
+
+ fetchFacets () {
+ return getFacets(BASE_QUERY, ['severities', 'projectUuids']).then(r => {
+ const severities = _.sortBy(
+ _.findWhere(r.facets, { property: 'severities' }).values,
+ (facet) => window.severityComparator(facet.val)
+ );
+
+ const projects = _.findWhere(r.facets, { property: 'projectUuids' }).values.map(p => {
+ const base = _.findWhere(r.response.components, { uuid: p.val });
+ return Object.assign({}, p, base);
+ });
+
+ const total = r.response.total;
+
+ return { severities, projects, total };
+ });
+ }
+
+ fetchByDate () {
+ return getFacet(Object.assign({ createdInLast: '1w' }, BASE_QUERY), 'createdAt').then(r => r.facet);
+ }
+
+ handleByDateClick ({ value }) {
+ const created = moment(value);
+ const createdAfter = created.format('YYYY-MM-DD');
+ const createdBefore = created.add(1, 'days').format('YYYY-MM-DD');
+ window.location = getPeriodUrl(createdAfter, createdBefore);
+ }
+
+ renderByDate () {
+ const data = this.state.byDate.map((d, x) => {
+ return { x, y: d.count, value: d.val };
+ });
+ const xTicks = this.state.byDate.map(d => moment(d.val).format('dd'));
+ const xValues = this.state.byDate.map(d => d.count);
+
+ return (
+ <section className="abs-width-300 huge-spacer-top account-bar-chart">
+ <h4 className="spacer-bottom">
+ {translate('my_account.issue_widget.by_creation_date')}
+ </h4>
+ <BarChart
+ data={data}
+ xTicks={xTicks}
+ xValues={xValues}
+ barsWidth={20}
+ padding={[25, 0, 25, 0]}
+ height={80}
+ onBarClick={this.handleByDateClick.bind(this)}/>
+ </section>
+ );
+ }
+
+ render () {
+ return (
+ <section>
+ <h2 className="spacer-bottom">{translate('issues.page')}</h2>
+
+ {this.state.loading && (
+ <i className="spinner"/>
+ )}
+
+ {!this.state.loading && (
+ <section className="abs-width-300">
+ <table className="data zebra">
+ <tbody>
+ <tr>
+ <td>
+ <strong>{translate('total')}</strong>
+ </td>
+ <td className="thin nowrap text-right">
+ <a href={getTotalUrl()}>
+ {formatMeasure(this.state.total, 'SHORT_INT')}
+ </a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </section>
+ )}
+
+ {!this.state.loading && (
+ <section className="abs-width-300 huge-spacer-top">
+ <h4 className="spacer-bottom">
+ {translate('my_account.issue_widget.by_severity')}
+ </h4>
+ <table className="data zebra">
+ <tbody>
+ {this.state.severities.map(s => (
+ <tr key={s.val}>
+ <td>
+ <SeverityHelper severity={s.val}/>
+ </td>
+ <td className="thin nowrap text-right">
+ <a href={getSeverityUrl(s.val)}>
+ {formatMeasure(s.count, 'SHORT_INT')}
+ </a>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </section>
+ )}
+
+ {!this.state.loading && (
+ <section className="abs-width-300 huge-spacer-top">
+ <h4 className="spacer-bottom">
+ {translate('my_account.issue_widget.by_project')}
+ </h4>
+ <table className="data zebra">
+ <tbody>
+ {this.state.projects.map(p => (
+ <tr key={p.val}>
+ <td>
+ {p.name}
+ </td>
+ <td className="thin nowrap text-right">
+ <a href={getProjectUrl(p.val)}>
+ {formatMeasure(p.count, 'SHORT_INT')}
+ </a>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </section>
+ )}
+
+ {!this.state.loading && this.renderByDate()}
+ </section>
+ );
+ }
+}
xValues: React.PropTypes.arrayOf(React.PropTypes.any),
height: React.PropTypes.number,
padding: React.PropTypes.arrayOf(React.PropTypes.number),
- barsWidth: React.PropTypes.number
+ barsWidth: React.PropTypes.number,
+ onBarClick: React.PropTypes.func
},
mixins: [ResizeMixin, TooltipsMixin],
return { width: this.props.width, height: this.props.height };
},
+ handleClick(point) {
+ this.props.onBarClick(point);
+ },
+
renderXTicks (xScale, yScale) {
if (!this.props.xTicks.length) {
return null;
x={x}
y={y}
dy="1.5em"
+ onClick={this.props.onBarClick && this.handleClick.bind(this, point)}
+ style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}
{...tooltipAtts}>{tick}</text>;
});
return <g>{ticks}</g>;
x={x}
y={y}
dy="-1em"
+ onClick={this.props.onBarClick && this.handleClick.bind(this, point)}
+ style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}
{...tooltipAtts}>{value}</text>;
});
return <g>{ticks}</g>;
x={x}
y={y}
width={this.props.barsWidth}
- height={height}/>;
+ height={height}
+ onClick={this.props.onBarClick && this.handleClick.bind(this, d)}
+ style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}/>;
});
return <g>{bars}</g>;
},