diff options
Diffstat (limited to 'server')
54 files changed, 771 insertions, 442 deletions
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 3dd9cbad49b..c16172f1192 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -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", 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}/> <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}/> - - {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'; |