aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/api/favorites.js (renamed from server/sonar-web/src/main/js/components/shared/issues-link.js)19
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js2
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js2
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/Favorites.js2
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/UserCard.js2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js4
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Measure.js4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js7
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/enhance.js9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js7
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js7
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/main.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/projects.js7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/search.js27
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/Search.js6
-rw-r--r--server/sonar-web/src/main/js/components/controls/Checkbox.js (renamed from server/sonar-web/src/main/js/components/shared/checkbox.js)54
-rw-r--r--server/sonar-web/src/main/js/components/controls/Favorite.js (renamed from server/sonar-web/src/main/js/components/shared/status-helper.js)30
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteBase.js92
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js (renamed from server/sonar-web/src/main/js/components/shared/assignee-helper.js)23
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js40
-rw-r--r--server/sonar-web/src/main/js/components/controls/ListFooter.js (renamed from server/sonar-web/src/main/js/components/shared/list-footer.js)58
-rw-r--r--server/sonar-web/src/main/js/components/controls/RadioToggle.js (renamed from server/sonar-web/src/main/js/components/shared/radio-toggle.js)55
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js75
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js70
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js61
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js60
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/comment-form-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/select-list/controls.js7
-rw-r--r--server/sonar-web/src/main/js/components/select-list/item.js6
-rw-r--r--server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js65
-rw-r--r--server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js65
-rw-r--r--server/sonar-web/src/main/js/components/shared/drilldown-link.js17
-rw-r--r--server/sonar-web/src/main/js/components/shared/favorite.js67
-rw-r--r--server/sonar-web/src/main/js/components/shared/status-icon.js30
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js2
-rw-r--r--server/sonar-web/src/main/js/components/ui/Avatar.js (renamed from server/sonar-web/src/main/js/components/shared/avatar.js)28
-rw-r--r--server/sonar-web/src/main/js/components/ui/Level.js (renamed from server/sonar-web/src/main/js/components/shared/rating.js)18
-rw-r--r--server/sonar-web/src/main/js/components/ui/Rating.js (renamed from server/sonar-web/src/main/js/components/shared/Level.js)28
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js58
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js (renamed from server/sonar-web/src/main/js/components/shared/quality-gate-link.js)15
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js (renamed from server/sonar-web/src/main/js/components/shared/quality-profile-link.js)22
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/item-view.js2
-rw-r--r--server/sonar-web/src/main/js/helpers/request.js13
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.js18
-rw-r--r--server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js6
-rw-r--r--server/sonar-web/src/main/js/main/nav/global/global-nav-user.js2
53 files changed, 770 insertions, 442 deletions
diff --git a/server/sonar-web/src/main/js/components/shared/issues-link.js b/server/sonar-web/src/main/js/api/favorites.js
index e550faa81d4..c9e61cd2ad4 100644
--- a/server/sonar-web/src/main/js/components/shared/issues-link.js
+++ b/server/sonar-web/src/main/js/api/favorites.js
@@ -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);
+}
diff --git a/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js b/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js
index 3eb59fcc076..87d5840c7e3 100644
--- a/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js
+++ b/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js
@@ -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 }) => (
diff --git a/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js b/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js
index c9998d0eaca..cc512ab1796 100644
--- a/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js
+++ b/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js
@@ -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 }) => (
diff --git a/server/sonar-web/src/main/js/apps/account/components/Favorites.js b/server/sonar-web/src/main/js/apps/account/components/Favorites.js
index 3cce3750a0b..6bfe92bd928 100644
--- a/server/sonar-web/src/main/js/apps/account/components/Favorites.js
+++ b/server/sonar-web/src/main/js/apps/account/components/Favorites.js
@@ -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';
diff --git a/server/sonar-web/src/main/js/apps/account/components/UserCard.js b/server/sonar-web/src/main/js/apps/account/components/UserCard.js
index 4c90d7fe255..a62526dc145 100644
--- a/server/sonar-web/src/main/js/apps/account/components/UserCard.js
+++ b/server/sonar-web/src/main/js/apps/account/components/UserCard.js
@@ -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 (
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
index be19157bb88..855646a4966 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
@@ -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
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.js b/server/sonar-web/src/main/js/apps/code/components/App.js
index 185f988c548..556bbb8e70c 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.js
+++ b/server/sonar-web/src/main/js/apps/code/components/App.js
@@ -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';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
index 7eec6eff935..a83267a0242 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
@@ -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';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
index 0f4ec641155..49a3635cf8d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
@@ -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 = {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
index 04875916ef2..e71b35e5e7a 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
@@ -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 () {
diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
index e0b3db05460..1704d7e0fa9 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
+++ b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.js b/server/sonar-web/src/main/js/apps/overview/main/enhance.js
index 7bb01d0d98c..4c11fa91011 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/enhance.js
+++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.js
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
index 3b26eb80623..f301184f798 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
index d889f815c2f..fd3fb30216c 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js b/server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js
index 7773b56ba31..3056cdc2994 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js
+++ b/server/sonar-web/src/main/js/apps/project-permissions/qualifier-filter.js
@@ -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 => {
diff --git a/server/sonar-web/src/main/js/apps/projects/main.js b/server/sonar-web/src/main/js/apps/projects/main.js
index 48bc0cd8a6a..adfc332c8d1 100644
--- a/server/sonar-web/src/main/js/apps/projects/main.js
+++ b/server/sonar-web/src/main/js/apps/projects/main.js
@@ -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: {
diff --git a/server/sonar-web/src/main/js/apps/projects/projects.js b/server/sonar-web/src/main/js/apps/projects/projects.js
index bf92990d726..a6c0949f642 100644
--- a/server/sonar-web/src/main/js/apps/projects/projects.js
+++ b/server/sonar-web/src/main/js/apps/projects/projects.js
@@ -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}/>
diff --git a/server/sonar-web/src/main/js/apps/projects/search.js b/server/sonar-web/src/main/js/apps/projects/search.js
index f6f0d126db2..24817c916f9 100644
--- a/server/sonar-web/src/main/js/apps/projects/search.js
+++ b/server/sonar-web/src/main/js/apps/projects/search.js
@@ -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">
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js
index 385bf06fc2e..4eeb48bcb8e 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
index a914b3ac6be..fd644b50c15 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
@@ -36,7 +36,7 @@ export default Marionette.LayoutView.extend({
},
modelEvents: {
- 'change': 'onChange',
+ 'change': 'handleChange',
'flashChangelog': 'flashChangelog'
},
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
index 09c5f751bcf..8090c4e2c3f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
@@ -32,7 +32,7 @@ export default Marionette.ItemView.extend({
},
events: {
- 'click': 'onClick'
+ 'click': 'handleClick'
},
onRender () {
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Search.js b/server/sonar-web/src/main/js/apps/web-api/components/Search.js
index 5f6caa881b0..0784a4a33ea 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/Search.js
+++ b/server/sonar-web/src/main/js/apps/web-api/components/Search.js
@@ -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
diff --git a/server/sonar-web/src/main/js/components/shared/checkbox.js b/server/sonar-web/src/main/js/components/controls/Checkbox.js
index 978e146e6cd..f417c875bf7 100644
--- a/server/sonar-web/src/main/js/components/shared/checkbox.js
+++ b/server/sonar-web/src/main/js/components/controls/Checkbox.js
@@ -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}/>
+ );
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/shared/status-helper.js b/server/sonar-web/src/main/js/components/controls/Favorite.js
index c3766f1c808..5e35ba2f1b2 100644
--- a/server/sonar-web/src/main/js/components/shared/status-helper.js
+++ b/server/sonar-web/src/main/js/components/controls/Favorite.js
@@ -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)}/>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js
new file mode 100644
index 00000000000..5d1f68361be
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js
@@ -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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/shared/assignee-helper.js b/server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
index 76bcbf30f3f..d4296548a1f 100644
--- a/server/sonar-web/src/main/js/components/shared/assignee-helper.js
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
@@ -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)}/>
+ );
}
}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js b/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js
new file mode 100644
index 00000000000..f4ee1236cc9
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js
@@ -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)}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/shared/list-footer.js b/server/sonar-web/src/main/js/components/controls/ListFooter.js
index f21757c4a3a..56e3b3ea6bd 100644
--- a/server/sonar-web/src/main/js/components/shared/list-footer.js
+++ b/server/sonar-web/src/main/js/components/controls/ListFooter.js
@@ -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>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/shared/radio-toggle.js b/server/sonar-web/src/main/js/components/controls/RadioToggle.js
index aff2443f12a..fe082e5a9e3 100644
--- a/server/sonar-web/src/main/js/components/shared/radio-toggle.js
+++ b/server/sonar-web/src/main/js/components/controls/RadioToggle.js
@@ -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>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js
new file mode 100644
index 00000000000..3f26878824a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js
@@ -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);
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
new file mode 100644
index 00000000000..6a8db197bc4
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
@@ -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;
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js
new file mode 100644
index 00000000000..5d02cf7ef36
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js
@@ -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;
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js
new file mode 100644
index 00000000000..8da0df6cca5
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js
@@ -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');
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js b/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
index 4773d372c20..55ca60f0620 100644
--- a/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
@@ -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',
diff --git a/server/sonar-web/src/main/js/components/select-list/controls.js b/server/sonar-web/src/main/js/components/select-list/controls.js
index 47877eae26e..b2e002f7236 100644
--- a/server/sonar-web/src/main/js/components/select-list/controls.js
+++ b/server/sonar-web/src/main/js/components/select-list/controls.js
@@ -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
diff --git a/server/sonar-web/src/main/js/components/select-list/item.js b/server/sonar-web/src/main/js/components/select-list/item.js
index 2e3d84289cf..7e57216b1e1 100644
--- a/server/sonar-web/src/main/js/components/select-list/item.js
+++ b/server/sonar-web/src/main/js/components/select-list/item.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js b/server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js
deleted file mode 100644
index e06c617f425..00000000000
--- a/server/sonar-web/src/main/js/components/shared/FavoriteIssueFilter.js
+++ /dev/null
@@ -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>;
- }
-});
diff --git a/server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js b/server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js
deleted file mode 100644
index c4bc895e610..00000000000
--- a/server/sonar-web/src/main/js/components/shared/FavoriteMeasureFilter.js
+++ /dev/null
@@ -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>;
- }
-});
diff --git a/server/sonar-web/src/main/js/components/shared/drilldown-link.js b/server/sonar-web/src/main/js/components/shared/drilldown-link.js
index c3ee5902ac7..2e2123a43a9 100644
--- a/server/sonar-web/src/main/js/components/shared/drilldown-link.js
+++ b/server/sonar-web/src/main/js/components/shared/drilldown-link.js
@@ -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() {
diff --git a/server/sonar-web/src/main/js/components/shared/favorite.js b/server/sonar-web/src/main/js/components/shared/favorite.js
deleted file mode 100644
index 20726776f26..00000000000
--- a/server/sonar-web/src/main/js/components/shared/favorite.js
+++ /dev/null
@@ -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>;
- }
-});
diff --git a/server/sonar-web/src/main/js/components/shared/status-icon.js b/server/sonar-web/src/main/js/components/shared/status-icon.js
deleted file mode 100644
index 2baec2cb5da..00000000000
--- a/server/sonar-web/src/main/js/components/shared/status-icon.js
+++ /dev/null
@@ -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>;
- }
-});
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
index 36f8e7da93d..d78a8daa4e4 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
@@ -24,7 +24,7 @@ export default Popup.extend({
template: Template,
events: {
- 'click': 'onClick'
+ 'click': 'handleClick'
},
onRender () {
diff --git a/server/sonar-web/src/main/js/components/shared/avatar.js b/server/sonar-web/src/main/js/components/ui/Avatar.js
index 5fc4dad8bf4..f7b78320c85 100644
--- a/server/sonar-web/src/main/js/components/shared/avatar.js
+++ b/server/sonar-web/src/main/js/components/ui/Avatar.js
@@ -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}/>
+ );
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/shared/rating.js b/server/sonar-web/src/main/js/components/ui/Level.js
index 51022ee6ede..7da8cf05d7b 100644
--- a/server/sonar-web/src/main/js/components/shared/rating.js
+++ b/server/sonar-web/src/main/js/components/ui/Level.js
@@ -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>;
}
-});
+}
diff --git a/server/sonar-web/src/main/js/components/shared/Level.js b/server/sonar-web/src/main/js/components/ui/Rating.js
index cd92ed80d0e..90b0c75cc5e 100644
--- a/server/sonar-web/src/main/js/components/shared/Level.js
+++ b/server/sonar-web/src/main/js/components/ui/Rating.js
@@ -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>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
new file mode 100644
index 00000000000..cbfd3e848b7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
@@ -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);
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/shared/quality-gate-link.js b/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js
index 069fb395dab..3d38503927b 100644
--- a/server/sonar-web/src/main/js/components/shared/quality-gate-link.js
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js
@@ -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);
+ });
});
diff --git a/server/sonar-web/src/main/js/components/shared/quality-profile-link.js b/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js
index a4deb7700c7..bcd89111e76 100644
--- a/server/sonar-web/src/main/js/components/shared/quality-profile-link.js
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js
@@ -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);
+ });
});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/item-view.js b/server/sonar-web/src/main/js/components/workspace/views/item-view.js
index 7ba8f4f7a92..adff2c005e4 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/item-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/item-view.js
@@ -32,7 +32,7 @@ export default Marionette.ItemView.extend({
},
events: {
- 'click': 'onClick',
+ 'click': 'handleClick',
'click .js-close': 'onCloseClick'
},
diff --git a/server/sonar-web/src/main/js/helpers/request.js b/server/sonar-web/src/main/js/helpers/request.js
index de67a630f8e..a63b07a8420 100644
--- a/server/sonar-web/src/main/js/helpers/request.js
+++ b/server/sonar-web/src/main/js/helpers/request.js
@@ -157,6 +157,19 @@ export function post (url, data) {
}
/**
+ * 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
* @returns {Promise}
diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js
index 645cf9adb8e..cf1a0fe57a4 100644
--- a/server/sonar-web/src/main/js/helpers/urls.js
+++ b/server/sonar-web/src/main/js/helpers/urls.js
@@ -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);
+}
diff --git a/server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js b/server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js
index ee159c26272..b3d68193b48 100644
--- a/server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js
+++ b/server/sonar-web/src/main/js/main/nav/component/component-nav-favorite.js
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/main/nav/global/global-nav-user.js b/server/sonar-web/src/main/js/main/nav/global/global-nav-user.js
index 1feb41770e1..52b0820c52e 100644
--- a/server/sonar-web/src/main/js/main/nav/global/global-nav-user.js
+++ b/server/sonar-web/src/main/js/main/nav/global/global-nav-user.js
@@ -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';