diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2016-02-01 14:08:20 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2016-02-01 16:36:00 +0100 |
commit | ec156cb28a340cc394ec28ccf5d10033f80168b6 (patch) | |
tree | 20570c5f0f39abf1c7b3dd27cfa8cea5e12dd2bc /server/sonar-web/src/main/js/apps/account | |
parent | 1d52c906629322026b3ddaea26b37d7c0da38f7e (diff) | |
download | sonarqube-ec156cb28a340cc394ec28ccf5d10033f80168b6.tar.gz sonarqube-ec156cb28a340cc394ec28ccf5d10033f80168b6.zip |
SONAR-7252 display widgets to my issues on "My Account" page
Diffstat (limited to 'server/sonar-web/src/main/js/apps/account')
3 files changed, 231 insertions, 4 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/components/Home.js b/server/sonar-web/src/main/js/apps/account/components/Home.js index 2561d8864af..593618038db 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Home.js +++ b/server/sonar-web/src/main/js/apps/account/components/Home.js @@ -22,6 +22,7 @@ import React from 'react'; import Favorites from './Favorites'; import FavoriteIssueFilters from './FavoriteIssueFilters'; import FavoriteMeasureFilters from './FavoriteMeasureFilters'; +import IssueWidgets from './IssueWidgets'; import { translate } from '../../../helpers/l10n'; const Home = ({ user, favorites, issueFilters, measureFilters }) => ( @@ -34,10 +35,7 @@ const Home = ({ user, favorites, issueFilters, measureFilters }) => ( </div> <div className="column-third"> - <section> - <h2 className="spacer-bottom">{translate('issues.page')}</h2> - <p>Some cool issue widgets go here...</p> - </section> + <IssueWidgets/> </div> <div className="column-third"> diff --git a/server/sonar-web/src/main/js/apps/account/components/IssueWidgets.js b/server/sonar-web/src/main/js/apps/account/components/IssueWidgets.js new file mode 100644 index 00000000000..3fec0fe2cff --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/components/IssueWidgets.js @@ -0,0 +1,211 @@ +/* + * 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> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/account/styles/account.css b/server/sonar-web/src/main/js/apps/account/styles/account.css index 4105365b0cb..28eb45f4a5e 100644 --- a/server/sonar-web/src/main/js/apps/account/styles/account.css +++ b/server/sonar-web/src/main/js/apps/account/styles/account.css @@ -27,3 +27,21 @@ .account-page { padding-top: 102px; } + +.account-bar-chart .bar-chart-bar { + fill: #4b9fd5; +} + +.account-bar-chart .bar-chart-tick { + fill: #777; + font-size: 12px; + text-anchor: middle; +} + +.account-bar-chart .histogram-tick { + text-anchor: end; +} + +.account-bar-chart .histogram-value { + text-anchor: start; +} |