Quellcode durchsuchen

refactor react components (#1033)

tags/6.0-RC1
Stas Vilchik vor 8 Jahren
Ursprung
Commit
c6c7bf08a0
55 geänderte Dateien mit 772 neuen und 442 gelöschten Zeilen
  1. 1
    0
      server/sonar-web/package.json
  2. 11
    8
      server/sonar-web/src/main/js/api/favorites.js
  3. 1
    1
      server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js
  4. 1
    1
      server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js
  5. 1
    1
      server/sonar-web/src/main/js/apps/account/components/Favorites.js
  6. 1
    1
      server/sonar-web/src/main/js/apps/account/components/UserCard.js
  7. 2
    2
      server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
  8. 1
    1
      server/sonar-web/src/main/js/apps/code/components/App.js
  9. 2
    2
      server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
  10. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
  11. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
  12. 3
    4
      server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
  13. 4
    5
      server/sonar-web/src/main/js/apps/overview/main/enhance.js
  14. 3
    4
      server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
  15. 3
    4
      server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
  16. 1
    1
      server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js
  17. 1
    1
      server/sonar-web/src/main/js/apps/projects/main.js
  18. 4
    3
      server/sonar-web/src/main/js/apps/projects/projects.js
  19. 19
    8
      server/sonar-web/src/main/js/apps/projects/search.js
  20. 2
    2
      server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js
  21. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
  22. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
  23. 3
    3
      server/sonar-web/src/main/js/apps/web-api/components/Search.js
  24. 27
    27
      server/sonar-web/src/main/js/components/controls/Checkbox.js
  25. 13
    17
      server/sonar-web/src/main/js/components/controls/Favorite.js
  26. 92
    0
      server/sonar-web/src/main/js/components/controls/FavoriteBase.js
  27. 16
    7
      server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
  28. 40
    0
      server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js
  29. 33
    25
      server/sonar-web/src/main/js/components/controls/ListFooter.js
  30. 33
    22
      server/sonar-web/src/main/js/components/controls/RadioToggle.js
  31. 75
    0
      server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js
  32. 70
    0
      server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
  33. 61
    0
      server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js
  34. 60
    0
      server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js
  35. 1
    1
      server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
  36. 2
    5
      server/sonar-web/src/main/js/components/select-list/controls.js
  37. 4
    2
      server/sonar-web/src/main/js/components/select-list/item.js
  38. 0
    65
      server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js
  39. 0
    65
      server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js
  40. 11
    6
      server/sonar-web/src/main/js/components/shared/drilldown-link.js
  41. 0
    67
      server/sonar-web/src/main/js/components/shared/favorite.js
  42. 0
    30
      server/sonar-web/src/main/js/components/shared/status-icon.js
  43. 1
    1
      server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
  44. 16
    12
      server/sonar-web/src/main/js/components/ui/Avatar.js
  45. 9
    9
      server/sonar-web/src/main/js/components/ui/Level.js
  46. 17
    11
      server/sonar-web/src/main/js/components/ui/Rating.js
  47. 58
    0
      server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
  48. 10
    5
      server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js
  49. 17
    5
      server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js
  50. 1
    1
      server/sonar-web/src/main/js/components/workspace/views/item-view.js
  51. 13
    0
      server/sonar-web/src/main/js/helpers/request.js
  52. 18
    0
      server/sonar-web/src/main/js/helpers/urls.js
  53. 4
    2
      server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js
  54. 1
    1
      server/sonar-web/src/main/js/main/nav/global/global-nav-user.js
  55. 1
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 0
server/sonar-web/package.json Datei anzeigen

@@ -51,6 +51,7 @@
"jsdom": "6.5.1",
"lodash": "4.6.1",
"mocha": "2.3.4",
"mockery": "1.7.0",
"moment": "2.10.6",
"numeral": "1.5.3",
"postcss-loader": "0.8.0",

server/sonar-web/src/main/js/components/shared/issues-link.js → server/sonar-web/src/main/js/api/favorites.js Datei anzeigen

@@ -17,13 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import { post, requestDelete } from '../helpers/request';

import { getComponentIssuesUrl } from '../../helpers/urls';
export function addFavorite (componentKey) {
const url = window.baseUrl + '/api/favourites';
const data = { key: componentKey };
return post(url, data);
}

export const IssuesLink = React.createClass({
render() {
const url = getComponentIssuesUrl(this.props.component, this.props.params);
return <a className={this.props.className} href={url}>{this.props.children}</a>;
}
});
export function removeFavorite (componentKey) {
const url = window.baseUrl +
'/api/favourites/' + encodeURIComponent(componentKey);
return requestDelete(url);
}

+ 1
- 1
server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js Datei anzeigen

@@ -19,7 +19,7 @@
*/
import React from 'react';

import FavoriteIssueFilter from '../../../components/shared/FavoriteIssueFilter';
import FavoriteIssueFilter from '../../../components/controls/FavoriteIssueFilter';
import { translate } from '../../../helpers/l10n';

const FavoriteIssueFilters = ({ issueFilters }) => (

+ 1
- 1
server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js Datei anzeigen

@@ -19,7 +19,7 @@
*/
import React from 'react';

import FavoriteMeasureFilter from '../../../components/shared/FavoriteMeasureFilter';
import FavoriteMeasureFilter from '../../../components/controls/FavoriteMeasureFilter';
import { translate } from '../../../helpers/l10n';

const FavoriteMeasureFilters = ({ measureFilters }) => (

+ 1
- 1
server/sonar-web/src/main/js/apps/account/components/Favorites.js Datei anzeigen

@@ -19,7 +19,7 @@
*/
import React from 'react';

import Favorite from '../../../components/shared/favorite';
import Favorite from '../../../components/controls/Favorite';
import QualifierIcon from '../../../components/shared/qualifier-icon';
import { translate } from '../../../helpers/l10n';
import { getComponentUrl } from '../../../helpers/urls';

+ 1
- 1
server/sonar-web/src/main/js/apps/account/components/UserCard.js Datei anzeigen

@@ -21,7 +21,7 @@ import React from 'react';
import { IndexLink } from 'react-router';

import UserExternalIdentity from './UserExternalIdentity';
import Avatar from '../../../components/shared/avatar';
import Avatar from '../../../components/ui/Avatar';

const UserCard = ({ user }) => {
return (

+ 2
- 2
server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js Datei anzeigen

@@ -19,7 +19,7 @@
*/
import React from 'react';

import Checkbox from '../../../components/shared/checkbox';
import Checkbox from '../../../components/controls/Checkbox';
import { CURRENTS } from '../constants';

const CurrentsFilter = ({ value, onChange }) => {
@@ -40,7 +40,7 @@ const CurrentsFilter = ({ value, onChange }) => {
return (
<div className="bt-search-form-field">
<Checkbox
initiallyChecked={checked}
checked={checked}
onCheck={handleChange}/>
&nbsp;
<label

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/App.js Datei anzeigen

@@ -24,7 +24,7 @@ import Components from './Components';
import Breadcrumbs from './Breadcrumbs';
import SourceViewer from './../../../components/source-viewer/SourceViewer';
import Search from './Search';
import ListFooter from '../../../components/shared/list-footer';
import ListFooter from '../../../components/controls/ListFooter';
import { retrieveComponentBase, retrieveComponent, loadMoreChildren, parseError } from '../utils';
import { addComponentBreadcrumbs } from '../bucket';
import { selectCoverageMetric } from '../../../helpers/measures';

+ 2
- 2
server/sonar-web/src/main/js/apps/component-measures/components/Measure.js Datei anzeigen

@@ -19,8 +19,8 @@
*/
import React from 'react';

import { Rating } from '../../../components/shared/rating';
import Level from '../../../components/shared/Level';
import Rating from '../../../components/ui/Rating';
import Level from '../../../components/ui/Level';
import { formatMeasure } from '../../../helpers/measures';
import { formatLeak, isDiffMetric } from '../utils';


+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js Datei anzeigen

@@ -24,7 +24,7 @@ import ComponentsList from './ComponentsList';
import ListHeader from './ListHeader';
import Spinner from '../../components/Spinner';
import SourceViewer from '../../../../components/source-viewer/SourceViewer';
import ListFooter from '../../../../components/shared/list-footer';
import ListFooter from '../../../../components/controls/ListFooter';

export default class ListView extends React.Component {
static contextTypes = {

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js Datei anzeigen

@@ -23,7 +23,7 @@ import ComponentsList from './ComponentsList';
import ListHeader from './ListHeader';
import Spinner from '../../components/Spinner';
import SourceViewer from '../../../../components/source-viewer/SourceViewer';
import ListFooter from '../../../../components/shared/list-footer';
import ListFooter from '../../../../components/controls/ListFooter';

export default class TreeView extends React.Component {
componentDidMount () {

+ 3
- 4
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js Datei anzeigen

@@ -19,12 +19,11 @@
*/
import moment from 'moment';
import React from 'react';

import enhance from './enhance';
import { IssuesLink } from '../../../components/shared/issues-link';
import { getMetricName } from '../helpers/metrics';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';

class CodeSmells extends React.Component {
renderHeader () {
@@ -47,11 +46,11 @@ class CodeSmells extends React.Component {
const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate);

return (
<IssuesLink component={component.key} params={params}>
<a href={getComponentIssuesUrl(component.key, params)}>
<span title={tooltip} data-toggle="tooltip">
{formatMeasure(value, 'SHORT_WORK_DUR')}
</span>
</IssuesLink>
</a>
);
}


+ 4
- 5
server/sonar-web/src/main/js/apps/overview/main/enhance.js Datei anzeigen

@@ -20,11 +20,9 @@
import React from 'react';
import moment from 'moment';
import shallowCompare from 'react-addons-shallow-compare';

import { DrilldownLink } from '../../../components/shared/drilldown-link';
import { IssuesLink } from '../../../components/shared/issues-link';
import { DonutChart } from '../../../components/charts/donut-chart';
import { Rating } from './../../../components/shared/rating';
import Rating from './../../../components/ui/Rating';
import Timeline from '../components/Timeline';
import {
formatMeasure,
@@ -35,6 +33,7 @@ import {
} from '../../../helpers/measures';
import { translateWithParameters } from '../../../helpers/l10n';
import { getPeriodDate } from '../../../helpers/periods';
import { getComponentIssuesUrl } from '../../../helpers/urls';

export default function enhance (ComposedComponent) {
return class extends React.Component {
@@ -163,11 +162,11 @@ export default function enhance (ComposedComponent) {
const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate);

return (
<IssuesLink component={component.key} params={params}>
<a href={getComponentIssuesUrl(component.key, params)}>
<span title={tooltip} data-toggle="tooltip">
{formatMeasure(value, 'SHORT_INT')}
</span>
</IssuesLink>
</a>
);
}


+ 3
- 4
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js Datei anzeigen

@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';

import { QualityGateLink } from '../../../components/shared/quality-gate-link';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';

const MetaQualityGate = ({ gate }) => {
return (
@@ -36,9 +35,9 @@ const MetaQualityGate = ({ gate }) => {
{'(' + translate('default') + ')'}
</span>
)}
<QualityGateLink gate={gate.key}>
<a href={getQualityGateUrl(gate.key)}>
{gate.name}
</QualityGateLink>
</a>
</li>
</ul>
</div>

+ 3
- 4
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js Datei anzeigen

@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';

import { QualityProfileLink } from '../../../components/shared/quality-profile-link';
import { translate } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';

const MetaQualityProfiles = ({ profiles }) => {
return (
@@ -35,9 +34,9 @@ const MetaQualityProfiles = ({ profiles }) => {
<span className="note spacer-right">
{'(' + profile.language + ')'}
</span>
<QualityProfileLink profile={profile.key}>
<a href={getQualityProfileUrl(profile.key)}>
{profile.name}
</QualityProfileLink>
</a>
</li>
))}
</ul>

+ 1
- 1
server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js Datei anzeigen

@@ -20,7 +20,7 @@
import React from 'react';
import { translate } from '../../helpers/l10n';

import RadioToggle from '../../components/shared/radio-toggle';
import RadioToggle from '../../components/controls/RadioToggle';

const rootQualifiersToOptions = (qualifiers) => {
return qualifiers.map(q => {

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/main.js Datei anzeigen

@@ -24,7 +24,7 @@ import Search from './search';
import Projects from './projects';
import { PAGE_SIZE, TYPE } from './constants';
import { getComponents, getProvisioned, getGhosts, deleteComponents } from '../../api/components';
import ListFooter from '../../components/shared/list-footer';
import ListFooter from '../../components/controls/ListFooter';

export default React.createClass({
propTypes: {

+ 4
- 3
server/sonar-web/src/main/js/apps/projects/projects.js Datei anzeigen

@@ -20,7 +20,7 @@
import classNames from 'classnames';
import React from 'react';
import { getComponentUrl } from '../../helpers/urls';
import Checkbox from '../../components/shared/checkbox';
import Checkbox from '../../components/controls/Checkbox';
import QualifierIcon from '../../components/shared/qualifier-icon';

export default React.createClass({
@@ -46,8 +46,9 @@ export default React.createClass({
return (
<tr key={project.id}>
<td className="thin">
<Checkbox onCheck={this.onProjectCheck.bind(this, project)}
initiallyChecked={this.isProjectSelected(project)}/>
<Checkbox
checked={this.isProjectSelected(project)}
onCheck={this.onProjectCheck.bind(this, project)}/>
</td>
<td className="thin">
<QualifierIcon qualifier={project.qualifier}/>

+ 19
- 8
server/sonar-web/src/main/js/apps/projects/search.js Datei anzeigen

@@ -21,8 +21,8 @@ import _ from 'underscore';
import React from 'react';
import { TYPE, QUALIFIERS_ORDER } from './constants';
import DeleteView from './delete-view';
import RadioToggle from '../../components/shared/radio-toggle';
import Checkbox from '../../components/shared/checkbox';
import RadioToggle from '../../components/controls/RadioToggle';
import Checkbox from '../../components/controls/Checkbox';
import { translate } from '../../helpers/l10n';

export default React.createClass({
@@ -63,8 +63,13 @@ export default React.createClass({
const thirdState = this.props.projects.length > 0 &&
this.props.selection.length > 0 &&
this.props.selection.length < this.props.projects.length;
const isChecked = isAllChecked || thirdState;
return <Checkbox onCheck={this.onCheck} initiallyChecked={isChecked} thirdState={thirdState}/>;
const checked = isAllChecked || thirdState;
return (
<Checkbox
checked={checked}
thirdState={thirdState}
onCheck={this.onCheck}/>
);
},

renderSpinner() {
@@ -99,8 +104,11 @@ export default React.createClass({
}
return (
<td className="thin nowrap text-middle">
<RadioToggle options={this.getQualifierOptions()} value={this.props.qualifiers}
name="projects-qualifier" onCheck={this.props.onQualifierChanged}/>
<RadioToggle
options={this.getQualifierOptions()}
value={this.props.qualifiers}
name="projects-qualifier"
onCheck={this.props.onQualifierChanged}/>
</td>
);
},
@@ -117,8 +125,11 @@ export default React.createClass({
</td>
{this.renderQualifierFilter()}
<td className="thin nowrap text-middle">
<RadioToggle options={this.getTypeOptions()} value={this.props.type}
name="projects-type" onCheck={this.props.onTypeChanged}/>
<RadioToggle
options={this.getTypeOptions()}
value={this.props.type}
name="projects-type"
onCheck={this.props.onTypeChanged}/>
</td>
<td className="text-middle">
<form onSubmit={this.onSubmit} className="search-box">

+ 2
- 2
server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js Datei anzeigen

@@ -21,7 +21,7 @@ import React, { Component } from 'react';
import Select from 'react-select';

import DeleteConditionView from '../views/gate-conditions-delete-view';
import Checkbox from '../../../components/shared/checkbox';
import Checkbox from '../../../components/controls/Checkbox';
import { createCondition, updateCondition } from '../../../api/quality-gates';
import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
@@ -173,7 +173,7 @@ export default class Condition extends Component {
<td className="thin text-middle nowrap">
{(edit && !isDiffMetric) ? (
<Checkbox
initiallyChecked={isLeakSelected}
checked={isLeakSelected}
onCheck={this.handlePeriodChange.bind(this)}/>
) : this.renderPeriodValue()}
</td>

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js Datei anzeigen

@@ -36,7 +36,7 @@ export default Marionette.LayoutView.extend({
},

modelEvents: {
'change': 'onChange',
'change': 'handleChange',
'flashChangelog': 'flashChangelog'
},


+ 1
- 1
server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js Datei anzeigen

@@ -32,7 +32,7 @@ export default Marionette.ItemView.extend({
},

events: {
'click': 'onClick'
'click': 'handleClick'
},

onRender () {

+ 3
- 3
server/sonar-web/src/main/js/apps/web-api/components/Search.js Datei anzeigen

@@ -20,7 +20,7 @@
import _ from 'underscore';
import React from 'react';

import Checkbox from '../../../components/shared/checkbox';
import Checkbox from '../../../components/controls/Checkbox';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { translate } from '../../../helpers/l10n';

@@ -60,7 +60,7 @@ export default class Search extends React.Component {
<TooltipsContainer>
<div className="big-spacer-top">
<Checkbox
initiallyChecked={showInternal}
checked={showInternal}
onCheck={onToggleInternal}/>
{' '}
<span
@@ -79,7 +79,7 @@ export default class Search extends React.Component {
<TooltipsContainer>
<div className="spacer-top">
<Checkbox
initiallyChecked={showOnlyDeprecated}
checked={showOnlyDeprecated}
onCheck={onToggleDeprecated}/>
{' '}
<span

server/sonar-web/src/main/js/components/shared/checkbox.js → server/sonar-web/src/main/js/components/controls/Checkbox.js Datei anzeigen

@@ -18,39 +18,39 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import classNames from 'classnames';

export default React.createClass({
propTypes: {
export default class Checkbox extends React.Component {
static propTypes = {
onCheck: React.PropTypes.func.isRequired,
initiallyChecked: React.PropTypes.bool,
checked: React.PropTypes.bool.isRequired,
thirdState: React.PropTypes.bool
},
};

getInitialState() {
return { checked: this.props.initiallyChecked || false };
},
static defaultProps = {
thirdState: false
};

componentWillReceiveProps(nextProps) {
if (nextProps.initiallyChecked != null) {
this.setState({ checked: nextProps.initiallyChecked });
}
},
componentWillMount () {
this.handleClick = this.handleClick.bind(this);
}

toggle(e) {
handleClick (e) {
e.preventDefault();
this.props.onCheck(!this.state.checked);
this.setState({ checked: !this.state.checked });
},
e.target.blur();
this.props.onCheck(!this.props.checked);
}

render () {
const className = classNames('icon-checkbox', {
'icon-checkbox-checked': this.props.checked,
'icon-checkbox-single': this.props.thirdState
});

render() {
const classNames = ['icon-checkbox'];
if (this.state.checked) {
classNames.push('icon-checkbox-checked');
}
if (this.props.thirdState) {
classNames.push('icon-checkbox-single');
}
const className = classNames.join(' ');
return <a onClick={this.toggle} className={className} href="#"/>;
return (
<a className={className}
href="#"
onClick={this.handleClick}/>
);
}
});
}

server/sonar-web/src/main/js/components/shared/status-helper.js → server/sonar-web/src/main/js/components/controls/Favorite.js Datei anzeigen

@@ -18,25 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import StatusIcon from './status-icon';
import { translate } from '../../helpers/l10n';
import FavoriteBase from './FavoriteBase';
import { addFavorite, removeFavorite } from '../../api/favorites';

export default class Favorite extends React.Component {
static propTypes = {
favorite: React.PropTypes.bool.isRequired,
component: React.PropTypes.string.isRequired
};

export default React.createClass({
render () {
if (!this.props.status) {
return null;
}
let resolution;
if (this.props.resolution) {
resolution = ' (' + translate('issue.resolution', this.props.resolution) + ')';
}
return (
<span>
<StatusIcon status={this.props.status}/>
&nbsp;
{translate('issue.status', this.props.status)}
{resolution}
</span>
<FavoriteBase
favorite={this.props.favorite}
addFavorite={() => addFavorite(this.props.component)}
removeFavorite={() => removeFavorite(this.props.component)}/>
);
}
});
}

+ 92
- 0
server/sonar-web/src/main/js/components/controls/FavoriteBase.js Datei anzeigen

@@ -0,0 +1,92 @@
/*
* 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 React from 'react';
import classNames from 'classnames';

export default class FavoriteBase extends React.Component {
static propTypes = {
favorite: React.PropTypes.bool.isRequired,
addFavorite: React.PropTypes.func.isRequired,
removeFavorite: React.PropTypes.func.isRequired
};

constructor (props) {
super(props);
this.state = { favorite: this.props.favorite };
}

componentWillMount () {
this.mounted = true;
this.toggleFavorite = this.toggleFavorite.bind(this);
}

componentWillUnmount () {
this.mounted = false;
}

toggleFavorite (e) {
e.preventDefault();
if (this.state.favorite) {
this.removeFavorite();
} else {
this.addFavorite();
}
}

addFavorite () {
this.props.addFavorite().then(() => {
if (this.mounted) {
this.setState({ favorite: true });
}
});
}

removeFavorite () {
this.props.removeFavorite().then(() => {
if (this.mounted) {
this.setState({ favorite: false });
}
});
}

renderSVG () {
/* jscs:disable maximumLineLength */
return (
<svg width="16" height="16">
<path
d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z"/>
</svg>
);
}

render () {
const className = classNames('icon-star', {
'icon-star-favorite': this.state.favorite
});

return (
<a className={className}
href="#"
onClick={this.toggleFavorite}>
{this.renderSVG()}
</a>
);
}
}

server/sonar-web/src/main/js/components/shared/assignee-helper.js → server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js Datei anzeigen

@@ -18,14 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import Avatar from './avatar';
import { translate } from '../../helpers/l10n';
import FavoriteBase from './FavoriteBase';
import { toggleIssueFilter } from '../../api/issue-filters';

export default class FavoriteIssueFilter extends React.Component {
static propTypes = {
favorite: React.PropTypes.bool.isRequired,
filter: React.PropTypes.shape({
id: React.PropTypes.string.isRequired
}).isRequired
};

export default class Assignee extends React.Component {
render () {
const avatar = this.props.user ?
<span className="spacer-right"><Avatar email={this.props.user.email} size={16}/></span> : null;
const name = this.props.user ? this.props.user.name : translate('unassigned');
return <span>{avatar}{name}</span>;
return (
<FavoriteBase
favorite={this.props.favorite}
addFavorite={() => toggleIssueFilter(this.props.filter.id)}
removeFavorite={() => toggleIssueFilter(this.props.filter.id)}/>
);
}
}

+ 40
- 0
server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js Datei anzeigen

@@ -0,0 +1,40 @@
/*
* 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 React from 'react';
import FavoriteBase from './FavoriteBase';
import { toggleMeasureFilter } from '../../api/measure-filters';

export default class FavoriteMeasureFilter extends React.Component {
static propTypes = {
favorite: React.PropTypes.bool.isRequired,
filter: React.PropTypes.shape({
id: React.PropTypes.number.isRequired
}).isRequired
};

render () {
return (
<FavoriteBase
favorite={this.props.favorite}
addFavorite={() => toggleMeasureFilter(this.props.filter.id)}
removeFavorite={() => toggleMeasureFilter(this.props.filter.id)}/>
);
}
}

server/sonar-web/src/main/js/components/shared/list-footer.js → server/sonar-web/src/main/js/components/controls/ListFooter.js Datei anzeigen

@@ -19,48 +19,56 @@
*/
import classNames from 'classnames';
import React from 'react';
import { translate } from '../../helpers/l10n';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';

export default React.createClass({
propTypes: {
export default class ListFooter extends React.Component {
static propTypes = {
count: React.PropTypes.number.isRequired,
total: React.PropTypes.number.isRequired,
loadMore: React.PropTypes.func,
ready: React.PropTypes.bool
},
};

getDefaultProps() {
return { ready: true };
},
static defaultProps = {
ready: true
};

canLoadMore() {
componentWillMount () {
this.handleLoadMore = this.handleLoadMore.bind(this);
}

canLoadMore () {
return typeof this.props.loadMore === 'function';
},
}

handleLoadMore(e) {
handleLoadMore (e) {
e.preventDefault();
e.target.blur();
if (this.canLoadMore()) {
this.props.loadMore();
}
},

renderLoading() {
return <footer className="spacer-top note text-center">
{translate('loading')}
</footer>;
},
this.props.loadMore();
}

render() {
render () {
const hasMore = this.props.total > this.props.count;
const loadMoreLink = <a onClick={this.handleLoadMore} className="spacer-left" href="#">show more</a>;
const className = classNames('spacer-top note text-center', { 'new-loading': !this.props.ready });
const loadMoreLink = (
<a className="spacer-left" href="#" onClick={this.handleLoadMore}>
{translate('show_more')}
</a>

);
const className = classNames('spacer-top note text-center', {
'new-loading': !this.props.ready
});

return (
<footer className={className}>
{formatMeasure(this.props.count, 'INT')} of {formatMeasure(this.props.total, 'INT')} shown
{translateWithParameters(
'x_of_y_shown',
formatMeasure(this.props.count, 'INT'),
formatMeasure(this.props.total, 'INT')
)}
{this.canLoadMore() && hasMore ? loadMoreLink : null}
</footer>
);
}
});
}

server/sonar-web/src/main/js/components/shared/radio-toggle.js → server/sonar-web/src/main/js/components/controls/RadioToggle.js Datei anzeigen

@@ -19,44 +19,55 @@
*/
import React from 'react';

export default React.createClass({
propTypes: {
export default class RadioToggle extends React.Component {
static propTypes = {
value: React.PropTypes.string,
options: React.PropTypes.array.isRequired,
options: React.PropTypes.arrayOf(React.PropTypes.shape({
value: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired
})).isRequired,
name: React.PropTypes.string.isRequired,
onCheck: React.PropTypes.func.isRequired
},
};

getDefaultProps () {
return { disabled: false, value: null };
},
static defaultProps = {
disabled: false,
value: null
};

onChange(e) {
componentWillMount () {
this.renderOption = this.renderOption.bind(this);
this.handleChange = this.handleChange.bind(this);
}

handleChange (e) {
const newValue = e.currentTarget.value;
this.props.onCheck(newValue);
},
}

renderOption(option) {
renderOption (option) {
const checked = option.value === this.props.value;
const htmlId = this.props.name + '__' + option.value;
return (
<li key={option.value}>
<input onChange={this.onChange}
type="radio"
name={this.props.name}
value={option.value}
id={htmlId}
checked={checked}
disabled={this.props.disabled}/>
<input
type="radio"
name={this.props.name}
value={option.value}
id={htmlId}
checked={checked}
onChange={this.handleChange}/>

<label htmlFor={htmlId}>{option.label}</label>
</li>
);
},
}

render() {
const options = this.props.options.map(this.renderOption);
render () {
return (
<ul className="radio-toggle">{options}</ul>
<ul className="radio-toggle">
{this.props.options.map(this.renderOption)}
</ul>
);
}
});
}

+ 75
- 0
server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js Datei anzeigen

@@ -0,0 +1,75 @@
/*
* 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 chai, { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import React from 'react';
import Checkbox from '../Checkbox';

chai.use(sinonChai);

function click (element) {
return element.simulate('click', {
target: { blur () {} },
preventDefault () {}
});
}

describe('Components :: Controls :: Checkbox', () => {
it('should render unchecked', () => {
const checkbox = shallow(
<Checkbox checked={false} onCheck={() => true}/>
);
expect(checkbox.is('.icon-checkbox-checked')).to.equal(false);
});

it('should render checked', () => {
const checkbox = shallow(
<Checkbox checked={true} onCheck={() => true}/>
);
expect(checkbox.is('.icon-checkbox-checked')).to.equal(true);
});

it('should render unchecked third state', () => {
const checkbox = shallow(
<Checkbox checked={false} thirdState={true} onCheck={() => true}/>
);
expect(checkbox.is('.icon-checkbox-single')).to.equal(true);
expect(checkbox.is('.icon-checkbox-checked')).to.equal(false);
});

it('should render checked third state', () => {
const checkbox = shallow(
<Checkbox checked={true} thirdState={true} onCheck={() => true}/>
);
expect(checkbox.is('.icon-checkbox-single')).to.equal(true);
expect(checkbox.is('.icon-checkbox-checked')).to.equal(true);
});

it('should call onCheck', () => {
const onCheck = sinon.spy();
const checkbox = shallow(
<Checkbox checked={false} onCheck={onCheck}/>
);
click(checkbox);
expect(onCheck).to.have.been.calledWith(true);
});
});

+ 70
- 0
server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js Datei anzeigen

@@ -0,0 +1,70 @@
/*
* 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 chai, { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import React from 'react';
import FavoriteBase from '../FavoriteBase';

chai.use(sinonChai);

function click (element) {
return element.simulate('click', {
target: { blur () {} },
preventDefault () {}
});
}

function renderFavoriteBase (props) {
return shallow(
<FavoriteBase
favorite={true}
addFavorite={sinon.stub().throws()}
removeFavorite={sinon.stub().throws()}
{...props}/>
);
}

describe('Components :: Controls :: FavoriteBase', () => {
it('should render favorite', () => {
const favorite = renderFavoriteBase({ favorite: true });
expect(favorite.is('.icon-star-favorite')).to.equal(true);
});

it('should render not favorite', () => {
const favorite = renderFavoriteBase({ favorite: false });
expect(favorite.is('.icon-star-favorite')).to.equal(false);
});

it('should add favorite', () => {
const addFavorite = sinon.stub().returns(Promise.resolve());
const favorite = renderFavoriteBase({ favorite: false, addFavorite });
click(favorite.find('a'));
expect(addFavorite).to.have.been.called;
});

it('should remove favorite', () => {
const removeFavorite = sinon.stub().returns(Promise.resolve());
const favorite = renderFavoriteBase({ favorite: true, removeFavorite });
click(favorite.find('a'));
expect(removeFavorite).to.have.been.called;
});
});

+ 61
- 0
server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js Datei anzeigen

@@ -0,0 +1,61 @@
/*
* 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 chai, { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import React from 'react';
import ListFooter from '../ListFooter';

chai.use(sinonChai);

function click (element) {
return element.simulate('click', {
target: { blur () {} },
preventDefault () {}
});
}

describe('Components :: Controls :: ListFooter', () => {
it('should render "3 of 5 shown"', () => {
const listFooter = shallow(
<ListFooter count={3} total={5}/>
);
expect(listFooter.text()).to.contain('x_of_y_shown.3.5');
});

it('should not render "show more"', () => {
const listFooter = shallow(
<ListFooter count={3} total={5}/>
);
expect(listFooter.find('a')).to.have.length(0);
});

it('should "show more"', () => {
const loadMore = sinon.spy();
const listFooter = shallow(
<ListFooter count={3} total={5} loadMore={loadMore}/>
);
const link = listFooter.find('a');
expect(link).to.have.length(1);
click(link);
expect(loadMore).to.have.been.called;
});
});

+ 60
- 0
server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js Datei anzeigen

@@ -0,0 +1,60 @@
/*
* 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 chai, { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import React from 'react';
import RadioToggle from '../RadioToggle';

chai.use(sinonChai);

function getSample (props) {
const options = [
{ value: 'one', label: 'first' },
{ value: 'two', label: 'second' }
];
return (
<RadioToggle
options={options}
name="sample"
onCheck={() => true}
{...props}/>
);
}

function change (element, value) {
return element.simulate('change', { currentTarget: { value } });
}

describe('Components :: Controls :: RadioToggle', () => {
it('should render', () => {
const radioToggle = shallow(getSample());
expect(radioToggle.find('input[type="radio"]')).to.have.length(2);
expect(radioToggle.find('label')).to.have.length(2);
});

it('should call onCheck', () => {
const onCheck = sinon.spy();
const radioToggle = shallow(getSample({ onCheck }));
change(radioToggle.find('input[value="two"]'), 'two');
expect(onCheck).to.have.been.calledWith('two');
});
});

+ 1
- 1
server/sonar-web/src/main/js/components/issue/views/comment-form-view.js Datei anzeigen

@@ -33,7 +33,7 @@ export default PopupView.extend({
},

events: {
'click': 'onClick',
'click': 'handleClick',
'keydown @ui.textarea': 'onKeydown',
'keyup @ui.textarea': 'toggleSubmit',
'click @ui.cancelButton': 'cancel',

+ 2
- 5
server/sonar-web/src/main/js/components/select-list/controls.js Datei anzeigen

@@ -19,7 +19,7 @@
*/
import _ from 'underscore';
import React from 'react';
import RadioToggle from '../shared/radio-toggle';
import RadioToggle from '../controls/RadioToggle';
import { translate } from '../../helpers/l10n';

export default React.createClass({
@@ -46,8 +46,6 @@ export default React.createClass({
},

render() {
const selectionDisabled = !!this.props.query;

const selectionOptions = [
{ value: 'selected', label: 'Selected' },
{ value: 'deselected', label: 'Not Selected' },
@@ -61,8 +59,7 @@ export default React.createClass({
name="select-list-selection"
options={selectionOptions}
onCheck={this.onCheck}
value={this.props.selection}
disabled={selectionDisabled}/>
value={this.props.selection}/>
</div>
<div className="pull-right">
<input

+ 4
- 2
server/sonar-web/src/main/js/components/select-list/item.js Datei anzeigen

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import Checkbox from '../shared/checkbox';
import Checkbox from '../controls/Checkbox';

export default React.createClass({
propTypes: {
@@ -41,7 +41,9 @@ export default React.createClass({
return (
<li className="panel panel-vertical">
<div className="display-inline-block text-middle spacer-right">
<Checkbox onCheck={this.onCheck} initiallyChecked={!!this.props.item.selected}/>
<Checkbox
checked={!!this.props.item.selected}
onCheck={this.onCheck}/>
</div>
<div className="display-inline-block text-middle" dangerouslySetInnerHTML={{ __html: renderedItem }}/>
</li>

+ 0
- 65
server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js Datei anzeigen

@@ -1,65 +0,0 @@
/*
* 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 React from 'react';

import { toggleIssueFilter } from '../../api/issue-filters';

export default React.createClass({
propTypes: {
filter: React.PropTypes.object.isRequired,
favorite: React.PropTypes.bool.isRequired
},

getInitialState() {
return { favorite: this.props.favorite };
},

toggleFavorite(e) {
e.preventDefault();
if (this.state.favorite) {
this.removeFavorite();
} else {
this.addFavorite();
}
},

addFavorite() {
toggleIssueFilter(this.props.filter.id).then(() => this.setState({ favorite: true }));
},

removeFavorite() {
toggleIssueFilter(this.props.filter.id).then(() => this.setState({ favorite: false }));
},

renderSVG() {
/* jscs:disable maximumLineLength */
return (
<svg width="16" height="16" style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 1.41421 }}>
<path d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z"
style={{ fillRule: 'nonzero' }}/>
</svg>
);
},

render() {
const className = this.state.favorite ? 'icon-star icon-star-favorite' : 'icon-star';
return <a onClick={this.toggleFavorite} className={className} href="#">{this.renderSVG()}</a>;
}
});

+ 0
- 65
server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js Datei anzeigen

@@ -1,65 +0,0 @@
/*
* 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 React from 'react';

import { toggleMeasureFilter } from '../../api/measure-filters';

export default React.createClass({
propTypes: {
filter: React.PropTypes.object.isRequired,
favorite: React.PropTypes.bool.isRequired
},

getInitialState() {
return { favorite: this.props.favorite };
},

toggleFavorite(e) {
e.preventDefault();
if (this.state.favorite) {
this.removeFavorite();
} else {
this.addFavorite();
}
},

addFavorite() {
toggleMeasureFilter(this.props.filter.id).then(() => this.setState({ favorite: true }));
},

removeFavorite() {
toggleMeasureFilter(this.props.filter.id).then(() => this.setState({ favorite: false }));
},

renderSVG() {
/* jscs:disable maximumLineLength */
return (
<svg width="16" height="16" style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 1.41421 }}>
<path d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z"
style={{ fillRule: 'nonzero' }}/>
</svg>
);
},

render() {
const className = this.state.favorite ? 'icon-star icon-star-favorite' : 'icon-star';
return <a onClick={this.toggleFavorite} className={className} href="#">{this.renderSVG()}</a>;
}
});

+ 11
- 6
server/sonar-web/src/main/js/components/shared/drilldown-link.js Datei anzeigen

@@ -20,9 +20,10 @@
import _ from 'underscore';
import moment from 'moment';
import React from 'react';

import { IssuesLink } from './issues-link';
import { getComponentDrilldownUrl } from '../../helpers/urls';
import {
getComponentDrilldownUrl,
getComponentIssuesUrl
} from '../../helpers/urls';

const ISSUE_MEASURES = [
'violations',
@@ -113,9 +114,13 @@ export const DrilldownLink = React.createClass({
},

renderIssuesLink() {
return <IssuesLink component={this.props.component} params={this.propsToIssueParams()}>
{this.props.children}
</IssuesLink>;
const url = getComponentIssuesUrl(
this.props.component,
this.propsToIssueParams());

return (
<a href={url}>{this.props.children}</a>
);
},

render() {

+ 0
- 67
server/sonar-web/src/main/js/components/shared/favorite.js Datei anzeigen

@@ -1,67 +0,0 @@
/*
* 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 'jquery';
import React from 'react';

export default React.createClass({
propTypes: {
component: React.PropTypes.string.isRequired,
favorite: React.PropTypes.bool.isRequired
},

getInitialState() {
return { favorite: this.props.favorite };
},

toggleFavorite(e) {
e.preventDefault();
if (this.state.favorite) {
this.removeFavorite();
} else {
this.addFavorite();
}
},

addFavorite() {
const url = window.baseUrl + '/api/favourites';
const data = { key: this.props.component };
$.ajax({ type: 'POST', url, data }).done(() => this.setState({ favorite: true }));
},

removeFavorite() {
const url = `${window.baseUrl}/api/favourites/${encodeURIComponent(this.props.component)}`;
$.ajax({ type: 'DELETE', url }).done(() => this.setState({ favorite: false }));
},

renderSVG() {
/* jscs:disable maximumLineLength */
return (
<svg width="16" height="16" style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 1.41421 }}>
<path d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z"
style={{ fillRule: 'nonzero' }}/>
</svg>
);
},

render() {
const className = this.state.favorite ? 'icon-star icon-star-favorite' : 'icon-star';
return <a onClick={this.toggleFavorite} className={className} href="#">{this.renderSVG()}</a>;
}
});

+ 0
- 30
server/sonar-web/src/main/js/components/shared/status-icon.js Datei anzeigen

@@ -1,30 +0,0 @@
/*
* 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 React from 'react';

export default React.createClass({
render() {
if (!this.props.status) {
return null;
}
const className = 'icon-status-' + this.props.status.toLowerCase();
return <i className={className}></i>;
}
});

+ 1
- 1
server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js Datei anzeigen

@@ -24,7 +24,7 @@ export default Popup.extend({
template: Template,

events: {
'click': 'onClick'
'click': 'handleClick'
},

onRender () {

server/sonar-web/src/main/js/components/shared/avatar.js → server/sonar-web/src/main/js/components/ui/Avatar.js Datei anzeigen

@@ -20,25 +20,29 @@
import React from 'react';
import md5 from 'blueimp-md5';

export default React.createClass({
propTypes: {
export default class Avatar extends React.Component {
static propTypes = {
email: React.PropTypes.string,
size: React.PropTypes.number.isRequired
},
};

render() {
render () {
const shouldShowAvatar = window.SS && window.SS.lf && window.SS.lf.enableGravatar;
if (!shouldShowAvatar) {
return null;
}

const emailHash = md5.md5(this.props.email || '').trim();
const url = ('' + window.SS.lf.gravatarServerUrl)
.replace('{EMAIL_MD5}', emailHash)
.replace('{SIZE}', this.props.size * 2);
return <img className="rounded"
src={url}
width={this.props.size}
height={this.props.size}
alt={this.props.email}/>;
.replace('{EMAIL_MD5}', emailHash)
.replace('{SIZE}', this.props.size * 2);

return (
<img className="rounded"
src={url}
width={this.props.size}
height={this.props.size}
alt={this.props.email}/>
);
}
});
}

server/sonar-web/src/main/js/components/shared/rating.js → server/sonar-web/src/main/js/components/ui/Level.js Datei anzeigen

@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';

import { formatMeasure } from '../../helpers/measures';

export const Rating = React.createClass({
render() {
if (this.props.value == null || isNaN(this.props.value)) {
return null;
}
const formatted = formatMeasure(this.props.value, 'RATING');
const className = 'rating rating-' + formatted;
export default class Level extends React.Component {
static propTypes = {
level: React.PropTypes.oneOf(['ERROR', 'WARN', 'OK']).isRequired
};

render () {
const formatted = formatMeasure(this.props.level, 'LEVEL');
const className = 'level level-' + this.props.level;
return <span className={className}>{formatted}</span>;
}
});
}

server/sonar-web/src/main/js/components/shared/Level.js → server/sonar-web/src/main/js/components/ui/Rating.js Datei anzeigen

@@ -18,17 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';

import { formatMeasure } from '../../helpers/measures';

const Level = ({ level }) => {
const formatted = formatMeasure(level, 'LEVEL');
const className = 'level level-' + level;
return (
<span className={className}>
{formatted}
</span>
);
};
export default class Rating extends React.Component {
static propTypes = {
value: (props, propName, componentName) => {
// allow both numbers and strings
const numberValue = Number(props[propName]);
if (numberValue < 1 || numberValue > 5) {
throw new Error(
`Invalid prop "${propName}" passed to "${componentName}".`);
}
}
};

export default Level;
render () {
const formatted = formatMeasure(this.props.value, 'RATING');
const className = 'rating rating-' + formatted;
return <span className={className}>{formatted}</span>;
}
}

+ 58
- 0
server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js Datei anzeigen

@@ -0,0 +1,58 @@
/*
* 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 { expect } from 'chai';
import { shallow } from 'enzyme';
import React from 'react';
import Avatar from '../Avatar';

describe('Components :: UI :: Avatar', () => {
beforeEach(() => {
window.SS = {
lf: {
enableGravatar: true,
gravatarServerUrl: 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}'
}
};
});

afterEach(() => {
window.SS = undefined;
});

it('should render', () => {
const avatar = shallow(
<Avatar email="mail@example.com" size={20}/>
);
expect(avatar.is('img')).to.equal(true);
expect(avatar.prop('width')).to.equal(20);
expect(avatar.prop('height')).to.equal(20);
expect(avatar.prop('alt')).to.equal('mail@example.com');
expect(avatar.prop('src')).to.equal(
'http://example.com/7daf6c79d4802916d83f6266e24850af.jpg?s=40');
});

it('should not render', () => {
window.SS.lf.enableGravatar = false;
const avatar = shallow(
<Avatar email="mail@example.com" size={20}/>
);
expect(avatar.is('img')).to.equal(false);
});
});

server/sonar-web/src/main/js/components/shared/quality-gate-link.js → server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js Datei anzeigen

@@ -17,11 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { expect } from 'chai';
import { shallow } from 'enzyme';
import React from 'react';
import Level from '../Level';

export const QualityGateLink = React.createClass({
render() {
const url = `${window.baseUrl}/quality_gates/show/${this.props.gate}`;
return <a href={url}>{this.props.children}</a>;
}
describe('Components :: UI :: Level', () => {
it('should render', () => {
const rating = shallow(
<Level level="ERROR"/>
);
expect(rating.is('.level-ERROR')).to.equal(true);
});
});

server/sonar-web/src/main/js/components/shared/quality-profile-link.js → server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js Datei anzeigen

@@ -17,11 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { expect } from 'chai';
import { shallow } from 'enzyme';
import React from 'react';
import Rating from '../Rating';

export const QualityProfileLink = React.createClass({
render() {
const url = `${window.baseUrl}/profiles/show?key=${encodeURIComponent(this.props.profile)}`;
return <a href={url}>{this.props.children}</a>;
}
describe('Components :: UI :: Rating', () => {
it('should render with numeric value', () => {
const rating = shallow(
<Rating value={2}/>
);
expect(rating.is('.rating-B')).to.equal(true);
});

it('should render with string value', () => {
const rating = shallow(
<Rating value="2.0"/>
);
expect(rating.is('.rating-B')).to.equal(true);
});
});

+ 1
- 1
server/sonar-web/src/main/js/components/workspace/views/item-view.js Datei anzeigen

@@ -32,7 +32,7 @@ export default Marionette.ItemView.extend({
},

events: {
'click': 'onClick',
'click': 'handleClick',
'click .js-close': 'onCloseClick'
},


+ 13
- 0
server/sonar-web/src/main/js/helpers/request.js Datei anzeigen

@@ -156,6 +156,19 @@ export function post (url, data) {
.then(checkStatus);
}

/**
* Shortcut to do a POST request and return response json
* @param url
* @param data
*/
export function requestDelete (url, data) {
return request(url)
.setMethod('DELETE')
.setData(data)
.submit()
.then(checkStatus);
}

/**
* Delay promise for testing purposes
* @param response

+ 18
- 0
server/sonar-web/src/main/js/helpers/urls.js Datei anzeigen

@@ -83,3 +83,21 @@ export function getComponentFixedDashboardUrl (componentKey, dashboardKey) {
export function getComponentDashboardManagementUrl (componentKey) {
return window.baseUrl + '/dashboards?resource=' + encodeURIComponent(componentKey);
}

/**
* Generate URL for a quality profile
* @param {string} key
* @returns {string}
*/
export function getQualityProfileUrl (key) {
return window.baseUrl + '/profiles/show?key=' + encodeURIComponent(key);
}

/**
* Generate URL for a quality gate
* @param {string} key
* @returns {string}
*/
export function getQualityGateUrl (key) {
return window.baseUrl + '/quality_gates/show/' + encodeURIComponent(key);
}

+ 4
- 2
server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js Datei anzeigen

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import Favorite from '../../../components/shared/favorite';
import Favorite from '../../../components/controls/Favorite';

export default React.createClass({
render() {
@@ -27,7 +27,9 @@ export default React.createClass({
}
return (
<div className="navbar-context-favorite">
<Favorite component={this.props.component} favorite={this.props.favorite}/>
<Favorite
component={this.props.component}
favorite={this.props.favorite}/>
</div>
);
}

+ 1
- 1
server/sonar-web/src/main/js/main/nav/global/global-nav-user.js Datei anzeigen

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import Avatar from '../../../components/shared/avatar';
import Avatar from '../../../components/ui/Avatar';
import RecentHistory from '../component/RecentHistory';
import { translate } from '../../../helpers/l10n';


+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Datei anzeigen

@@ -149,6 +149,7 @@ severity=Severity
severity_abbreviated=Se.
shared=Shared
show_verb=Show
x_of_y_shown={0} of {1} shown
size=Size
status=Status
status_abbreviated=St.

Laden…
Abbrechen
Speichern