diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2017-03-27 14:00:14 +0200 |
---|---|---|
committer | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-04-03 10:38:52 +0200 |
commit | 32a73efa05cb12056a93f08b9124e647213f1f02 (patch) | |
tree | 89c545631a613d2041383b5143afb0e040c327e0 /server/sonar-web/src | |
parent | be8738ea1e0322d81e238c4462c7ec6f22d2177c (diff) | |
download | sonarqube-32a73efa05cb12056a93f08b9124e647213f1f02.tar.gz sonarqube-32a73efa05cb12056a93f08b9124e647213f1f02.zip |
SONAR-9008 support quality profiles for organizations
Diffstat (limited to 'server/sonar-web/src')
58 files changed, 917 insertions, 512 deletions
diff --git a/server/sonar-web/src/main/js/api/quality-profiles.js b/server/sonar-web/src/main/js/api/quality-profiles.js index 6f482bd3a9a..2f178014559 100644 --- a/server/sonar-web/src/main/js/api/quality-profiles.js +++ b/server/sonar-web/src/main/js/api/quality-profiles.js @@ -17,14 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import { request, checkStatus, parseJSON, getJSON, post, postJSON } from '../helpers/request'; -export function getQualityProfiles(data) { +export function getQualityProfiles(data: { organization?: string, projectKey?: string }) { const url = '/api/qualityprofiles/search'; return getJSON(url, data).then(r => r.profiles); } -export function createQualityProfile(data) { +export function createQualityProfile(data: Object) { return request('/api/qualityprofiles/create') .setMethod('post') .setData(data) @@ -33,7 +34,7 @@ export function createQualityProfile(data) { .then(parseJSON); } -export function restoreQualityProfile(data) { +export function restoreQualityProfile(data: Object) { return request('/api/qualityprofiles/restore') .setMethod('post') .setData(data) @@ -42,128 +43,80 @@ export function restoreQualityProfile(data) { .then(parseJSON); } -export function getProfileProjects(data) { +export function getProfileProjects(data: Object) { const url = '/api/qualityprofiles/projects'; return getJSON(url, data); } -export function getProfileInheritance(profileKey) { +export function getProfileInheritance(profileKey: string) { const url = '/api/qualityprofiles/inheritance'; const data = { profileKey }; return getJSON(url, data); } -export function setDefaultProfile(profileKey) { +export function setDefaultProfile(profileKey: string) { const url = '/api/qualityprofiles/set_default'; const data = { profileKey }; return post(url, data); } -/** - * Rename profile - * @param {string} key - * @param {string} name - * @returns {Promise} - */ -export function renameProfile(key, name) { +export function renameProfile(key: string, name: string) { const url = '/api/qualityprofiles/rename'; const data = { key, name }; return post(url, data); } -/** - * Copy profile - * @param {string} fromKey - * @param {string} toName - * @returns {Promise} - */ -export function copyProfile(fromKey, toName) { +export function copyProfile(fromKey: string, toName: string) { const url = '/api/qualityprofiles/copy'; const data = { fromKey, toName }; return postJSON(url, data); } -/** - * Delete profile - * @param {string} profileKey - * @returns {Promise} - */ -export function deleteProfile(profileKey) { +export function deleteProfile(profileKey: string) { const url = '/api/qualityprofiles/delete'; const data = { profileKey }; return post(url, data); } -/** - * Change profile parent - * @param {string} profileKey - * @param {string} parentKey - * @returns {Promise} - */ -export function changeProfileParent(profileKey, parentKey) { +export function changeProfileParent(profileKey: string, parentKey: string) { const url = '/api/qualityprofiles/change_parent'; const data = { profileKey, parentKey }; return post(url, data); } -/** - * Get list of available importers - * @returns {Promise} - */ export function getImporters() { const url = '/api/qualityprofiles/importers'; return getJSON(url).then(r => r.importers); } -/** - * Get list of available exporters - * @returns {Promise} - */ export function getExporters() { const url = '/api/qualityprofiles/exporters'; return getJSON(url).then(r => r.exporters); } -/** - * Restore built-in profiles - * @param {string} languageKey - * @returns {Promise} - */ -export function restoreBuiltInProfiles(languageKey) { +export function restoreBuiltInProfiles(data: Object) { const url = '/api/qualityprofiles/restore_built_in'; - const data = { language: languageKey }; return post(url, data); } -/** - * Get changelog of a quality profile - * @param {Object} data API parameters - * @returns {Promise} - */ -export function getProfileChangelog(data) { +export function getProfileChangelog(data: Object) { const url = '/api/qualityprofiles/changelog'; return getJSON(url, data); } -/** - * Compare two profiles - * @param {string} leftKey - * @param {string} rightKey - * @returns {Promise} - */ -export function compareProfiles(leftKey, rightKey) { +export function compareProfiles(leftKey: string, rightKey: string) { const url = '/api/qualityprofiles/compare'; const data = { leftKey, rightKey }; return getJSON(url, data); } -export function associateProject(profileKey, projectKey) { +export function associateProject(profileKey: string, projectKey: string) { const url = '/api/qualityprofiles/add_project'; const data = { profileKey, projectKey }; return post(url, data); } -export function dissociateProject(profileKey, projectKey) { +export function dissociateProject(profileKey: string, projectKey: string) { const url = '/api/qualityprofiles/remove_project'; const data = { profileKey, projectKey }; return post(url, data); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js index 7eebc3d0393..90249a0e7e2 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js @@ -143,6 +143,7 @@ export default class GlobalNavMenu extends React.Component { render() { const governanceInstalled = this.props.appState.qualifiers.includes('VW'); + const { organizationsEnabled } = this.props.appState; return ( <ul className="nav navbar-nav"> @@ -150,7 +151,7 @@ export default class GlobalNavMenu extends React.Component { {governanceInstalled && this.renderPortfolios()} {this.renderIssuesLink()} {this.renderRulesLink()} - {this.renderProfilesLink()} + {!organizationsEnabled && this.renderProfilesLink()} {this.renderQualityGatesLink()} {this.renderAdministrationLink()} {this.renderMore()} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js index a38d9829abd..fdf72a5009e 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js @@ -154,6 +154,13 @@ export default class OrganizationNavigation extends React.Component { {translate('organization.members.page')} </Link> </li> + <li> + <Link + to={`/organizations/${organization.key}/quality_profiles`} + activeClassName="active"> + {translate('quality_profiles.page')} + </Link> + </li> {organization.canAdmin && this.renderAdministration()} </ul> </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap index 8fbbcd9b6a3..46358abc847 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap @@ -44,6 +44,15 @@ exports[`test admin 1`] = ` organization.members.page </Link> </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/quality_profiles"> + quality_profiles.page + </Link> + </li> <li className=""> <a @@ -165,6 +174,15 @@ exports[`test regular user 1`] = ` organization.members.page </Link> </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/quality_profiles"> + quality_profiles.page + </Link> + </li> </ul> </div> </div> @@ -217,6 +235,15 @@ exports[`test undeletable org 1`] = ` organization.members.page </Link> </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/quality_profiles"> + quality_profiles.page + </Link> + </li> <li className=""> <a diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.js b/server/sonar-web/src/main/js/apps/organizations/routes.js index eae57052d3e..1b7f81eda3d 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.js +++ b/server/sonar-web/src/main/js/apps/organizations/routes.js @@ -28,6 +28,7 @@ import OrganizationPermissions from './components/OrganizationPermissions'; import OrganizationPermissionTemplates from './components/OrganizationPermissionTemplates'; import OrganizationProjectsManagement from './components/OrganizationProjectsManagement'; import OrganizationDelete from './components/OrganizationDelete'; +import qualityProfilesRoutes from '../quality-profiles/routes'; const routes = [ { @@ -55,6 +56,10 @@ const routes = [ component: OrganizationMembersContainer }, { + path: 'quality_profiles', + childRoutes: qualityProfilesRoutes + }, + { component: OrganizationAdmin, childRoutes: [ { path: 'delete', component: OrganizationDelete }, diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js index 9f67196b368..a18e1385f1b 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js @@ -57,7 +57,12 @@ const Meta = ({ component, measures, areThereCustomOrganizations }) => { {shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />} - {shouldShowQualityProfiles && <MetaQualityProfiles profiles={qualityProfiles} />} + {shouldShowQualityProfiles && + <MetaQualityProfiles + component={component} + customOrganizations={areThereCustomOrganizations} + profiles={qualityProfiles} + />} <MetaLinks component={component} /> 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 ff4c39b3db0..fb5c176df85 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 @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router'; @@ -27,6 +28,15 @@ import { searchRules } from '../../../api/rules'; import { getLanguages } from '../../../store/rootReducer'; class MetaQualityProfiles extends React.Component { + mounted: boolean; + + props: { + component: { organization: string }, + customOrganizations: boolean, + languages: { [string]: { name: string } }, + profiles: Array<{ key: string, language: string, name: string }> + }; + state = { deprecatedByKey: {} }; @@ -74,12 +84,16 @@ class MetaQualityProfiles extends React.Component { const languageFromStore = this.props.languages[profile.language]; const languageName = languageFromStore ? languageFromStore.name : profile.language; + const path = this.props.customOrganizations + ? getQualityProfileUrl(profile.key, this.props.component.organization) + : getQualityProfileUrl(profile.key); + const inner = ( <div className="text-ellipsis"> <span className="note spacer-right"> {'(' + languageName + ')'} </span> - <Link to={getQualityProfileUrl(profile.key)}> + <Link to={path}> {profile.name} </Link> </div> diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js index 88d4c79c5c7..ee3d6a0e774 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js @@ -17,36 +17,42 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { connect } from 'react-redux'; -import shallowCompare from 'react-addons-shallow-compare'; import Header from './Header'; import Table from './Table'; import { fetchProjectProfiles, setProjectProfile } from '../store/actions'; import { + areThereCustomOrganizations, getProjectAdminAllProfiles, getProjectAdminProjectProfiles, getComponent } from '../../../store/rootReducer'; -class QualityProfiles extends React.Component { - static propTypes = { - component: React.PropTypes.object.isRequired, - allProfiles: React.PropTypes.array, - profiles: React.PropTypes.array - }; +type Props = { + allProfiles: Array<{}>, + component: { key: string, organization: string }, + customOrganizations: boolean, + fetchProjectProfiles: (componentKey: string, organization?: string) => void, + profiles: Array<{}>, + setProjectProfile: (string, string, string) => void +}; - componentDidMount() { - this.props.fetchProjectProfiles(this.props.component.key); - } +class QualityProfiles extends React.PureComponent { + props: Props; - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(this, nextProps, nextState); + componentDidMount() { + if (this.props.customOrganizations) { + this.props.fetchProjectProfiles(this.props.component.key, this.props.component.organization); + } else { + this.props.fetchProjectProfiles(this.props.component.key); + } } - handleChangeProfile(oldKey, newKey) { + handleChangeProfile = (oldKey, newKey) => { this.props.setProjectProfile(this.props.component.key, oldKey, newKey); - } + }; render() { const { allProfiles, profiles } = this.props; @@ -59,7 +65,7 @@ class QualityProfiles extends React.Component { ? <Table allProfiles={allProfiles} profiles={profiles} - onChangeProfile={this.handleChangeProfile.bind(this)} + onChangeProfile={this.handleChangeProfile} /> : <i className="spinner" />} </div> @@ -69,10 +75,11 @@ class QualityProfiles extends React.Component { const mapStateToProps = (state, ownProps) => ({ component: getComponent(state, ownProps.location.query.id), + customOrganizations: areThereCustomOrganizations(state), allProfiles: getProjectAdminAllProfiles(state), profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id) }); -export default connect(mapStateToProps, { fetchProjectProfiles, setProjectProfile })( - QualityProfiles -); +const mapDispatchToProps = { fetchProjectProfiles, setProjectProfile }; + +export default connect(mapStateToProps, mapDispatchToProps)(QualityProfiles); diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js index 2f96a8bf723..d3f66e9af6c 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js @@ -47,9 +47,14 @@ export const receiveProjectProfiles = (projectKey, profiles) => ({ profiles }); -export const fetchProjectProfiles = projectKey => +export const fetchProjectProfiles = (projectKey, organization) => dispatch => { - Promise.all([getQualityProfiles(), getQualityProfiles({ projectKey })]).then(responses => { + Promise.all([ + organization ? getQualityProfiles({ organization }) : getQualityProfiles(), + organization + ? getQualityProfiles({ organization, projectKey }) + : getQualityProfiles({ projectKey }) + ]).then(responses => { const [allProfiles, projectProfiles] = responses; dispatch(receiveProfiles(allProfiles)); dispatch(receiveProjectProfiles(projectKey, projectProfiles)); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js index cd70278214d..2e84e64d2a3 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import moment from 'moment'; @@ -24,10 +25,19 @@ import ChangesList from './ChangesList'; import { translate } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; -export default class Changelog extends React.Component { - static propTypes = { - events: React.PropTypes.array.isRequired - }; +type Props = { + events: Array<{ + action: string, + authorName: string, + date: string, + params?: {}, + ruleKey: string, + ruleName: string + }> +}; + +export default class Changelog extends React.PureComponent { + props: Props; render() { let isEvenRow = false; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js index 8de4383ca42..c7ea2d89572 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js @@ -17,40 +17,52 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import Changelog from './Changelog'; import ChangelogSearch from './ChangelogSearch'; import ChangelogEmpty from './ChangelogEmpty'; import { getProfileChangelog } from '../../../api/quality-profiles'; -import { ProfileType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; - -export default class ChangelogContainer extends React.Component { - static propTypes = { - location: React.PropTypes.object.isRequired, - profile: ProfileType - }; +import { getProfileChangelogPath } from '../utils'; +import type { Profile } from '../propTypes'; + +type Props = { + location: { + query: { + since?: string, + to?: string + } + }, + organization: ?string, + profile: Profile +}; + +type State = { + events?: Array<*>, + loading: boolean, + page?: number, + total?: number +}; + +export default class ChangelogContainer extends React.PureComponent { + mounted: boolean; + props: Props; static contextTypes = { router: React.PropTypes.object }; - state = { + state: State = { loading: true }; - componentWillMount() { - this.handleFromDateChange = this.handleFromDateChange.bind(this); - this.handleToDateChange = this.handleToDateChange.bind(this); - this.handleReset = this.handleReset.bind(this); - } - componentDidMount() { this.mounted = true; this.loadChangelog(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.location !== this.props.location) { this.loadChangelog(); } @@ -63,7 +75,7 @@ export default class ChangelogContainer extends React.Component { loadChangelog() { this.setState({ loading: true }); const { query } = this.props.location; - const data = { profileKey: this.props.profile.key }; + const data: Object = { profileKey: this.props.profile.key }; if (query.since) { data.since = query.since; } @@ -83,13 +95,13 @@ export default class ChangelogContainer extends React.Component { }); } - loadMore(e) { + loadMore(e: SyntheticInputEvent) { e.preventDefault(); e.target.blur(); this.setState({ loading: true }); const { query } = this.props.location; - const data = { + const data: Object = { profileKey: this.props.profile.key, p: this.state.page + 1 }; @@ -101,7 +113,7 @@ export default class ChangelogContainer extends React.Component { } getProfileChangelog(data).then(r => { - if (this.mounted) { + if (this.mounted && this.state.events) { this.setState({ events: [...this.state.events, ...r.events], total: r.total, @@ -112,25 +124,32 @@ export default class ChangelogContainer extends React.Component { }); } - handleFromDateChange(fromDate) { - const query = { ...this.props.location.query, since: fromDate }; - this.context.router.push({ pathname: '/profiles/changelog', query }); - } + handleFromDateChange = (fromDate?: string) => { + const path = getProfileChangelogPath(this.props.profile.key, this.props.organization, { + since: fromDate, + to: this.props.location.query.to + }); + this.context.router.push(path); + }; - handleToDateChange(toDate) { - const query = { ...this.props.location.query, to: toDate }; - this.context.router.push({ pathname: '/profiles/changelog', query }); - } + handleToDateChange = (toDate?: string) => { + const path = getProfileChangelogPath(this.props.profile.key, this.props.organization, { + since: this.props.location.query.since, + to: toDate + }); + this.context.router.push(path); + }; - handleReset() { - const query = { key: this.props.profile.key }; - this.context.router.push({ pathname: '/profiles/changelog', query }); - } + handleReset = () => { + const path = getProfileChangelogPath(this.props.profile.key, this.props.organization); + this.context.router.push(path); + }; render() { const { query } = this.props.location; const shouldDisplayFooter = this.state.events != null && + this.state.total != null && this.state.events.length < this.state.total; return ( diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js index 1baf1d73e4f..96db0d8c8eb 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { translate } from '../../../helpers/l10n'; -export default class ChangelogEmpty extends React.Component { +export default class ChangelogEmpty extends React.PureComponent { render() { return ( <div className="big-spacer-top"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js index 1106ba94b34..89d2099a229 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js @@ -17,20 +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. */ +// @flow import React from 'react'; import DateInput from '../../../components/controls/DateInput'; import { translate } from '../../../helpers/l10n'; -export default class ChangelogSearch extends React.Component { - static propTypes = { - fromDate: React.PropTypes.string, - toDate: React.PropTypes.string, - onFromDateChange: React.PropTypes.func.isRequired, - onToDateChange: React.PropTypes.func.isRequired, - onReset: React.PropTypes.func.isRequired - }; +type Props = { + fromDate?: string, + toDate?: string, + onFromDateChange: () => void, + onReset: () => void, + onToDateChange: () => void +}; - handleResetClick(e) { +export default class ChangelogSearch extends React.PureComponent { + props: Props; + + handleResetClick(e: SyntheticInputEvent) { e.preventDefault(); e.target.blur(); this.props.onReset(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js index 9ec1807227a..6377b4f3799 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import SeverityChange from './SeverityChange'; import ParameterChange from './ParameterChange'; -export default class ChangesList extends React.Component { - static propTypes = { - changes: React.PropTypes.object.isRequired - }; +type Props = { + changes: { [string]: ?string } +}; + +export default class ChangesList extends React.PureComponent { + props: Props; render() { const { changes } = this.props; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js index 7b4e0168979..d4e01d55b72 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { translateWithParameters } from '../../../helpers/l10n'; -export default class ParameterChange extends React.Component { - static propTypes = { - name: React.PropTypes.string.isRequired, - value: React.PropTypes.any - }; +type Props = { + name: string, + value: ?string +}; + +export default class ParameterChange extends React.PureComponent { + props: Props; render() { const { name, value } = this.props; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js index c6e20846d83..5497d9ad0fb 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import SeverityHelper from '../../../components/shared/severity-helper'; import { translate } from '../../../helpers/l10n'; -export default class SeverityChange extends React.Component { - static propTypes = { - severity: React.PropTypes.string.isRequired - }; +type Props = { + severity: ?string +}; + +export default class SeverityChange extends React.PureComponent { + props: Props; render() { return ( diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js index a41bc66c8ad..cdc70a050ed 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js @@ -17,28 +17,42 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import ComparisonForm from './ComparisonForm'; import ComparisonResults from './ComparisonResults'; -import { ProfileType, ProfilesListType } from '../propTypes'; import { compareProfiles } from '../../../api/quality-profiles'; +import { getProfileComparePath } from '../utils'; +import type { Profile } from '../propTypes'; -export default class ComparisonContainer extends React.Component { - static propTypes = { - profile: ProfileType, - profiles: ProfilesListType - }; +type Props = { + location: { query: { withKey?: string } }, + organization: ?string, + profile: Profile, + profiles: Array<Profile> +}; + +type State = { + loading: boolean, + left?: { name: string }, + right?: { name: string }, + inLeft?: Array<*>, + inRight?: Array<*>, + modified?: Array<*> +}; + +export default class ComparisonContainer extends React.PureComponent { + mounted: boolean; + props: Props; + state: State; static contextTypes = { router: React.PropTypes.object }; - state = { - loading: false - }; - - componentWillMount() { - this.handleCompare = this.handleCompare.bind(this); + constructor(props: Props) { + super(props); + this.state = { loading: false }; } componentDidMount() { @@ -46,7 +60,7 @@ export default class ComparisonContainer extends React.Component { this.loadResults(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) { this.loadResults(); } @@ -59,7 +73,7 @@ export default class ComparisonContainer extends React.Component { loadResults() { const { withKey } = this.props.location.query; if (!withKey) { - this.setState({ left: null, loading: false }); + this.setState({ left: undefined, loading: false }); return; } @@ -78,15 +92,10 @@ export default class ComparisonContainer extends React.Component { }); } - handleCompare(withKey) { - this.context.router.push({ - pathname: '/profiles/compare', - query: { - key: this.props.profile.key, - withKey - } - }); - } + handleCompare = (withKey: string) => { + const path = getProfileComparePath(this.props.profile.key, this.props.organization, withKey); + this.context.router.push(path); + }; render() { const { profile, profiles, location } = this.props; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js index 7d7298dc761..b3a19a37f5c 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { translate } from '../../../helpers/l10n'; -export default class ComparisonEmpty extends React.Component { +export default class ComparisonEmpty extends React.PureComponent { render() { return ( <div className="big-spacer-top"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js index f686b415b5a..a99f64b2186 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js @@ -17,19 +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. */ +// @flow import React from 'react'; import Select from 'react-select'; -import { ProfileType, ProfilesListType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; +import type { Profile } from '../propTypes'; -export default class ComparisonForm extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - profiles: ProfilesListType.isRequired, - onCompare: React.PropTypes.func.isRequired - }; +type Props = { + profile: Profile, + profiles: Array<Profile>, + onCompare: (string) => void, + withKey: string +}; - handleChange(option) { +export default class ComparisonForm extends React.PureComponent { + props: Props; + + handleChange(option: { value: string }) { this.props.onCompare(option.value); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js index bd8793490d3..c6acf738546 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import ComparisonEmpty from './ComparisonEmpty'; @@ -24,20 +25,20 @@ import SeverityIcon from '../../../components/shared/severity-icon'; import { translateWithParameters } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; -export default class ComparisonResults extends React.Component { - static propTypes = { - left: React.PropTypes.shape({ - name: React.PropTypes.string.isRequired - }).isRequired, - right: React.PropTypes.shape({ - name: React.PropTypes.string.isRequired - }).isRequired, - inLeft: React.PropTypes.array.isRequired, - inRight: React.PropTypes.array.isRequired, - modified: React.PropTypes.array.isRequired - }; +type Params = { [string]: string }; - renderRule(rule, severity) { +type Props = { + left: { name: string }, + right: { name: string }, + inLeft: Array<*>, + inRight: Array<*>, + modified: Array<*> +}; + +export default class ComparisonResults extends React.PureComponent { + props: Props; + + renderRule(rule: { key: string, name: string }, severity: string) { return ( <div> <SeverityIcon severity={severity} /> @@ -49,7 +50,7 @@ export default class ComparisonResults extends React.Component { ); } - renderParameters(params) { + renderParameters(params: Params) { if (!params) { return null; } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js index f8fd41c66c5..982553d7993 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js @@ -17,17 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { getQualityProfiles, getExporters } from '../../../api/quality-profiles'; import { sortProfiles } from '../utils'; +import type { Exporter } from '../propTypes'; import '../styles.css'; -export default class App extends React.Component { - state = { loading: true }; +type Props = { + children: React.Element<*>, + currentUser: { permissions: { global: Array<string> } }, + languages: Array<*>, + organization: { canAdmin?: boolean, key: string } | null +}; + +type State = { + loading: boolean, + exporters?: Array<Exporter>, + profiles?: Array<*> +}; + +export default class App extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { loading: true }; componentWillMount() { - document.querySelector('html').classList.add('dashboard-page'); - this.updateProfiles = this.updateProfiles.bind(this); + const html = document.querySelector('html'); + if (html) { + html.classList.add('dashboard-page'); + } } componentDidMount() { @@ -37,12 +56,21 @@ export default class App extends React.Component { componentWillUnmount() { this.mounted = false; - document.querySelector('html').classList.remove('dashboard-page'); + const html = document.querySelector('html'); + if (html) { + html.classList.remove('dashboard-page'); + } + } + + fetchProfiles() { + const { organization } = this.props; + const data = organization ? { organization: organization.key } : {}; + return getQualityProfiles(data); } loadData() { this.setState({ loading: true }); - Promise.all([getExporters(), getQualityProfiles()]).then(responses => { + Promise.all([getExporters(), this.fetchProfiles()]).then(responses => { if (this.mounted) { const [exporters, profiles] = responses; this.setState({ @@ -54,13 +82,13 @@ export default class App extends React.Component { }); } - updateProfiles() { - return getQualityProfiles().then(profiles => { + updateProfiles = () => { + return this.fetchProfiles().then(profiles => { if (this.mounted) { this.setState({ profiles: sortProfiles(profiles) }); } }); - } + }; renderChild() { if (this.state.loading) { @@ -69,13 +97,16 @@ export default class App extends React.Component { const finalLanguages = Object.values(this.props.languages); - const canAdmin = this.props.currentUser.permissions.global.includes('profileadmin'); + const canAdmin = this.props.organization + ? this.props.organization.canAdmin + : this.props.currentUser.permissions.global.includes('profileadmin'); return React.cloneElement(this.props.children, { profiles: this.state.profiles, languages: finalLanguages, exporters: this.state.exporters, updateProfiles: this.updateProfiles, + organization: this.props.organization ? this.props.organization.key : null, canAdmin }); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js index a19ddff98b6..b5ff40a9ace 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js @@ -19,9 +19,14 @@ */ import { connect } from 'react-redux'; import App from './App'; -import { getLanguages, getCurrentUser } from '../../../store/rootReducer'; +import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; -export default connect(state => ({ +const mapStateToProps = (state, ownProps) => ({ currentUser: getCurrentUser(state), - languages: getLanguages(state) -}))(App); + languages: getLanguages(state), + organization: ownProps.params.organizationKey + ? getOrganizationByKey(state, ownProps.params.organizationKey) + : null +}); + +export default connect(mapStateToProps)(App); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js index 1616c0936c8..1101c1560ce 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js @@ -17,78 +17,74 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import RenameProfileView from '../views/RenameProfileView'; import CopyProfileView from '../views/CopyProfileView'; import DeleteProfileView from '../views/DeleteProfileView'; import { translate } from '../../../helpers/l10n'; -import { ProfileType } from '../propTypes'; import { getRulesUrl } from '../../../helpers/urls'; import { setDefaultProfile } from '../../../api/quality-profiles'; +import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils'; +import type { Profile } from '../propTypes'; -export default class ProfileActions extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - canAdmin: React.PropTypes.bool.isRequired, - updateProfiles: React.PropTypes.func.isRequired - }; +type Props = { + canAdmin: boolean, + organization: ?string, + profile: Profile, + updateProfiles: () => Promise<*> +}; + +export default class ProfileActions extends React.PureComponent { + props: Props; static contextTypes = { router: React.PropTypes.object }; - handleRenameClick(e) { + handleRenameClick = (e: SyntheticInputEvent) => { e.preventDefault(); - new RenameProfileView({ - profile: this.props.profile - }) - .on('done', () => { - this.props.updateProfiles(); - }) + new RenameProfileView({ profile: this.props.profile }) + .on('done', () => this.props.updateProfiles()) .render(); - } + }; - handleCopyClick(e) { + handleCopyClick = (e: SyntheticInputEvent) => { e.preventDefault(); - new CopyProfileView({ - profile: this.props.profile - }) + new CopyProfileView({ profile: this.props.profile }) .on('done', profile => { this.props.updateProfiles().then(() => { - this.context.router.push({ - pathname: '/profiles/show', - query: { key: profile.key } - }); + this.context.router.push(getProfilePath(profile.key, this.props.organization)); }); }) .render(); - } + }; - handleSetDefaultClick(e) { + handleSetDefaultClick = (e: SyntheticInputEvent) => { e.preventDefault(); setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles); - } + }; - handleDeleteClick(e) { + handleDeleteClick = (e: SyntheticInputEvent) => { e.preventDefault(); - new DeleteProfileView({ - profile: this.props.profile - }) + new DeleteProfileView({ profile: this.props.profile }) .on('done', () => { - this.context.router.replace('/profiles'); + this.context.router.replace(getProfilesPath(this.props.organization)); this.props.updateProfiles(); }) .render(); - } + }; render() { const { profile, canAdmin } = this.props; + // FIXME use org, name and lang const backupUrl = window.baseUrl + '/api/qualityprofiles/backup?profileKey=' + encodeURIComponent(profile.key); + // FIXME getRulesUrl const activateMoreUrl = getRulesUrl({ qprofile: profile.key, activation: 'false' @@ -109,37 +105,34 @@ export default class ProfileActions extends React.Component { </li> <li> <Link - to={{ pathname: '/profiles/compare', query: { key: profile.key } }} + to={getProfileComparePath(profile.key, this.props.organization)} id="quality-profile-compare"> {translate('compare')} </Link> </li> {canAdmin && <li> - <a id="quality-profile-copy" href="#" onClick={this.handleCopyClick.bind(this)}> + <a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}> {translate('copy')} </a> </li>} {canAdmin && <li> - <a id="quality-profile-rename" href="#" onClick={this.handleRenameClick.bind(this)}> + <a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}> {translate('rename')} </a> </li>} {canAdmin && !profile.isDefault && <li> - <a - id="quality-profile-set-as-default" - href="#" - onClick={this.handleSetDefaultClick.bind(this)}> + <a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}> {translate('set_as_default')} </a> </li>} {canAdmin && !profile.isDefault && <li> - <a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick.bind(this)}> + <a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick}> {translate('delete')} </a> </li>} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js index e11cbe0968c..d55f57dd8fe 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js @@ -17,31 +17,41 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import Helmet from 'react-helmet'; import ProfileNotFound from './ProfileNotFound'; import ProfileHeader from '../details/ProfileHeader'; import { translate } from '../../../helpers/l10n'; -import { ProfilesListType } from '../propTypes'; +import type { Profile } from '../propTypes'; -export default class ProfileContainer extends React.Component { - static propTypes = { - location: React.PropTypes.object, - profiles: ProfilesListType, - canAdmin: React.PropTypes.bool, - updateProfiles: React.PropTypes.func - }; +type Props = { + canAdmin: boolean, + children: React.Element<*>, + location: { query: { key: string } }, + organization: ?string, + profiles: Array<Profile>, + updateProfiles: () => Promise<*> +}; + +export default class ProfileContainer extends React.PureComponent { + props: Props; render() { - const { profiles, location, ...other } = this.props; + const { organization, profiles, location, ...other } = this.props; const { key } = location.query; const profile = profiles.find(profile => profile.key === key); if (!profile) { - return <ProfileNotFound />; + return <ProfileNotFound organization={organization} />; } - const child = React.cloneElement(this.props.children, { profile, profiles, ...other }); + const child = React.cloneElement(this.props.children, { + organization, + profile, + profiles, + ...other + }); const title = translate('quality_profiles.page') + ' - ' + profile.name; @@ -50,8 +60,9 @@ export default class ProfileContainer extends React.Component { <Helmet title={title} titleTemplate="SonarQube - %s" /> <ProfileHeader - profile={profile} canAdmin={this.props.canAdmin} + organization={organization} + profile={profile} updateProfiles={this.props.updateProfiles} /> {child} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js index 369a6a067fa..158e6b9955b 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js @@ -17,18 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import moment from 'moment'; import shallowCompare from 'react-addons-shallow-compare'; import { translate } from '../../../helpers/l10n'; -export default class ProfileDate extends React.Component { - static propTypes = { - date: React.PropTypes.string - }; +type Props = { + date?: string +}; - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(this, nextProps, nextState); +export default class ProfileDate extends React.PureComponent { + props: Props; + + shouldComponentUpdate(nextProps: Props) { + return shallowCompare(this, nextProps); } render() { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js index e6d332735b7..a60c37ab9b1 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js @@ -17,20 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; +import { getProfilePath } from '../utils'; -export default class ProfileLink extends React.Component { - static propTypes = { - profileKey: React.PropTypes.string.isRequired - }; +type Props = { + children?: React.Element<*>, + organization: ?string, + profileKey: string +}; + +export default class ProfileLink extends React.PureComponent { + props: Props; render() { - const { profileKey, children, ...other } = this.props; - const query = { key: profileKey }; + const { profileKey, organization, children, ...other } = this.props; return ( <Link - to={{ pathname: '/profiles/show', query }} + to={getProfilePath(profileKey, organization)} activeClassName="link-no-underline" {...other}> {children} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js index 2dff3d655e0..7401007841d 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js @@ -17,16 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { IndexLink } from 'react-router'; import { translate } from '../../../helpers/l10n'; +import { getProfilesPath } from '../utils'; + +type Props = { + organization: ?string +}; + +export default class ProfileNotFound extends React.PureComponent { + props: Props; -export default class ProfileNotFound extends React.Component { render() { return ( <div className="quality-profile-not-found"> <div className="note spacer-bottom"> - <IndexLink to="/profiles/" className="text-muted"> + <IndexLink to={getProfilesPath(this.props.organization)} className="text-muted"> {translate('quality_profiles.page')} </IndexLink> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js index b6deaa8ddc7..7b7a296536c 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js @@ -17,19 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import ProfileRules from './ProfileRules'; import ProfileProjects from './ProfileProjects'; import ProfileInheritance from './ProfileInheritance'; import ProfileExporters from './ProfileExporters'; -import { ProfileType } from '../propTypes'; +import type { Profile, Exporter } from '../propTypes'; -export default class ProfileDetails extends React.Component { - static propTypes = { - profile: ProfileType, - canAdmin: React.PropTypes.bool, - updateProfiles: React.PropTypes.func - }; +type Props = { + canAdmin: boolean, + exporters: Array<Exporter>, + organization: ?string, + profile: Profile, + profiles: Array<Profile>, + updateProfiles: () => Promise<*> +}; + +export default class ProfileDetails extends React.PureComponent { + props: Props; render() { return ( diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js index 1a7e9df653f..be6702dd7ed 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js @@ -17,23 +17,34 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow +import { stringify } from 'querystring'; import React from 'react'; import { translate } from '../../../helpers/l10n'; +import type { Profile, Exporter } from '../propTypes'; -export default class ProfileExporters extends React.Component { - static propTypes = { - exporters: React.PropTypes.array.isRequired - }; +type Props = { + exporters: Array<Exporter>, + organization: ?string, + profile: Profile +}; - getExportUrl(exporter) { - return window.baseUrl + - '/api/qualityprofiles/export' + - '?exporterKey=' + - encodeURIComponent(exporter.key) + - '&language=' + - encodeURIComponent(this.props.profile.language) + - '&name=' + - encodeURIComponent(this.props.profile.name); +export default class ProfileExporters extends React.PureComponent { + props: Props; + + getExportUrl(exporter: Exporter) { + const { organization, profile } = this.props; + + const path = '/api/qualityprofiles/export'; + const parameters: { [string]: string } = { + exporterKey: exporter.key, + language: profile.language, + name: profile.name + }; + if (organization) { + Object.assign(parameters, { organization }); + } + return window.baseUrl + path + '?' + stringify(parameters); } render() { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js index a581dad1983..3a06c63e472 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js @@ -17,21 +17,30 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link, IndexLink } from 'react-router'; import ProfileLink from '../components/ProfileLink'; import ProfileActions from '../components/ProfileActions'; import ProfileDate from '../components/ProfileDate'; -import { ProfileType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; -import { isStagnant } from '../utils'; +import { + isStagnant, + getProfilesPath, + getProfilesForLanguagePath, + getProfileChangelogPath +} from '../utils'; +import type { Profile } from '../propTypes'; -export default class ProfileHeader extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - canAdmin: React.PropTypes.bool.isRequired, - updateProfiles: React.PropTypes.func.isRequired - }; +type Props = { + canAdmin: boolean, + organization: ?string, + profile: Profile, + updateProfiles: () => Promise<*> +}; + +export default class ProfileHeader extends React.PureComponent { + props: Props; renderUpdateDate() { const { profile } = this.props; @@ -81,25 +90,28 @@ export default class ProfileHeader extends React.Component { } render() { - const { profile } = this.props; + const { organization, profile } = this.props; return ( <header className="page-header quality-profile-header"> <div className="note spacer-bottom"> - <IndexLink to="/profiles/" className="text-muted"> + <IndexLink to={getProfilesPath(organization)} className="text-muted"> {translate('quality_profiles.page')} </IndexLink> {' / '} <Link - to={{ pathname: '/profiles/', query: { language: profile.language } }} + to={getProfilesForLanguagePath(profile.language, organization)} className="text-muted"> {profile.languageName} </Link> </div> <h1 className="page-title"> - <ProfileLink profileKey={profile.key} className="link-base-color"> - {profile.name} + <ProfileLink + organization={organization} + profileKey={profile.key} + className="link-base-color"> + <span>{profile.name}</span> </ProfileLink> </h1> @@ -108,9 +120,7 @@ export default class ProfileHeader extends React.Component { {this.renderUpdateDate()} {this.renderUsageDate()} <li> - <Link - to={{ pathname: '/profiles/changelog', query: { key: profile.key } }} - className="button"> + <Link to={getProfileChangelogPath(profile.key, organization)} className="button"> {translate('changelog')} </Link> </li> @@ -122,8 +132,9 @@ export default class ProfileHeader extends React.Component { <i className="icon-dropdown" /> </button> <ProfileActions - profile={profile} canAdmin={this.props.canAdmin} + organization={organization} + profile={profile} updateProfiles={this.props.updateProfiles} /> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js index d17198f89d5..f6760f674f5 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js @@ -17,34 +17,58 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import classNames from 'classnames'; import ProfileInheritanceBox from './ProfileInheritanceBox'; import ChangeParentView from '../views/ChangeParentView'; -import { ProfileType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; import { getProfileInheritance } from '../../../api/quality-profiles'; +import type { Profile } from '../propTypes'; -export default class ProfileInheritance extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - canAdmin: React.PropTypes.bool.isRequired - }; +type Props = { + canAdmin: boolean, + organization: ?string, + profile: Profile, + profiles: Array<Profile>, + updateProfiles: () => Promise<*> +}; + +type State = { + ancestors?: Array<{ + activeRuleCount: number, + key: string, + name: string, + overridingRuleCount?: number + }>, + children?: Array<{ + activeRuleCount: number, + key: string, + name: string, + overridingRuleCount?: number + }>, + loading: boolean, + profile?: { + activeRuleCount: number, + key: string, + name: string, + overridingRuleCount?: number + } +}; - state = { +export default class ProfileInheritance extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { loading: true }; - componentWillMount() { - this.handleChangeParent = this.handleChangeParent.bind(this); - } - componentDidMount() { this.mounted = true; this.loadData(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.profile !== this.props.profile) { this.loadData(); } @@ -68,20 +92,17 @@ export default class ProfileInheritance extends React.Component { }); } - handleChangeParent(e) { + handleChangeParent = (e: SyntheticInputEvent) => { e.preventDefault(); - new ChangeParentView({ - profile: this.props.profile, - profiles: this.props.profiles - }) - .on('done', () => { - this.props.updateProfiles(); - }) + new ChangeParentView({ profile: this.props.profile, profiles: this.props.profiles }) + .on('done', () => this.props.updateProfiles()) .render(); - } + }; render() { const highlightCurrent = !this.state.loading && + this.state.ancestors != null && + this.state.children != null && (this.state.ancestors.length > 0 || this.state.children.length > 0); const currentClassName = classNames('js-inheritance-current', { selected: highlightCurrent @@ -102,30 +123,35 @@ export default class ProfileInheritance extends React.Component { {!this.state.loading && <table className="data zebra"> <tbody> - {this.state.ancestors.map((ancestor, index) => ( - <ProfileInheritanceBox - key={ancestor.key} - profile={ancestor} - depth={index} - className="js-inheritance-ancestor" - /> - ))} + {this.state.ancestors != null && + this.state.ancestors.map((ancestor, index) => ( + <ProfileInheritanceBox + className="js-inheritance-ancestor" + depth={index} + key={ancestor.key} + organization={this.props.organization} + profile={ancestor} + /> + ))} <ProfileInheritanceBox - profile={this.state.profile} - depth={this.state.ancestors.length} - displayLink={false} className={currentClassName} + depth={this.state.ancestors ? this.state.ancestors.length : 0} + displayLink={false} + organization={this.props.organization} + profile={this.state.profile} /> - {this.state.children.map(child => ( - <ProfileInheritanceBox - key={child.key} - profile={child} - depth={this.state.ancestors.length + 1} - className="js-inheritance-child" - /> - ))} + {this.state.children != null && + this.state.children.map(child => ( + <ProfileInheritanceBox + className="js-inheritance-child" + depth={this.state.ancestors ? this.state.ancestors.length + 1 : 0} + key={child.key} + organization={this.props.organization} + profile={child} + /> + ))} </tbody> </table>} </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js index 7e8b464ad16..5af9d91b207 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js @@ -17,22 +17,26 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import ProfileLink from '../components/ProfileLink'; import { translateWithParameters } from '../../../helpers/l10n'; -export default class ProfileInheritanceBox extends React.Component { - static propTypes = { - profile: React.PropTypes.shape({ - key: React.PropTypes.string.isRequired, - name: React.PropTypes.string.isRequired, - activeRuleCount: React.PropTypes.number.isRequired, - overridingRuleCount: React.PropTypes.number - }).isRequired, - depth: React.PropTypes.number.isRequired, - displayLink: React.PropTypes.bool, - className: React.PropTypes.string - }; +type Props = { + className?: string, + depth: number, + displayLink?: boolean, + organization: ?string, + profile: { + activeRuleCount: number, + key: string, + name: string, + overridingRuleCount?: number + } +}; + +export default class ProfileInheritanceBox extends React.PureComponent { + props: Props; static defaultProps = { displayLink: true @@ -47,7 +51,7 @@ export default class ProfileInheritanceBox extends React.Component { <td> <div style={{ paddingLeft: offset }}> {this.props.displayLink - ? <ProfileLink profileKey={profile.key}> + ? <ProfileLink organization={this.props.organization} profileKey={profile.key}> {profile.name} </ProfileLink> : profile.name} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js index 650eae3746e..6d877d785fa 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js @@ -17,34 +17,42 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import ChangeProjectsView from '../views/ChangeProjectsView'; import QualifierIcon from '../../../components/shared/qualifier-icon'; -import { ProfileType } from '../propTypes'; import { getProfileProjects } from '../../../api/quality-profiles'; import { translate } from '../../../helpers/l10n'; - -export default class ProfileProjects extends React.Component { - static propTypes = { - profile: ProfileType, - canAdmin: React.PropTypes.bool.isRequired - }; - - state = { +import type { Profile } from '../propTypes'; + +type Props = { + canAdmin: boolean, + organization: ?string, + profile: Profile, + updateProfiles: () => Promise<*> +}; + +type State = { + loading: boolean, + more?: boolean, + projects: ?Array<*> +}; + +export default class ProfileProjects extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { + loading: true, projects: null }; - componentWillMount() { - this.loadProjects = this.loadProjects.bind(this); - } - componentDidMount() { this.mounted = true; this.loadProjects(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.profile !== this.props.profile) { this.loadProjects(); } @@ -71,12 +79,13 @@ export default class ProfileProjects extends React.Component { }); } - handleChange(e) { + handleChange(e: SyntheticInputEvent) { e.preventDefault(); e.target.blur(); new ChangeProjectsView({ - profile: this.props.profile, - loadProjects: this.props.updateProfiles + loadProjects: this.props.updateProfiles, + organization: this.props.organization, + profile: this.props.profile }).render(); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js index 2dee1b5680e..7a68617ab25 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js @@ -17,26 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import { keyBy } from 'lodash'; import ProfileRulesRow from './ProfileRulesRow'; -import { ProfileType } from '../propTypes'; import { searchRules, takeFacet } from '../../../api/rules'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { getRulesUrl, getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; +import type { Profile } from '../propTypes'; const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL']; -export default class ProfileRules extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - canAdmin: React.PropTypes.bool.isRequired - }; - - state = { +type Props = { + canAdmin: boolean, + profile: Profile +}; + +type State = { + total: ?number, + activatedTotal: ?number, + allByType?: { [string]: ?{ val: string, count: ?number } }, + activatedByType?: { [string]: ?{ val: string, count: ?number } } +}; + +export default class ProfileRules extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { total: null, activatedTotal: null, allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'), @@ -48,7 +58,7 @@ export default class ProfileRules extends React.Component { this.loadRules(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.profile !== this.props.profile) { this.loadRules(); } @@ -89,7 +99,7 @@ export default class ProfileRules extends React.Component { }); } - getTooltip(count, total) { + getTooltip(count: ?number, total: ?number) { if (count == null || total == null) { return ''; } @@ -101,10 +111,17 @@ export default class ProfileRules extends React.Component { ); } - getTooltipForType(type) { - const { count } = this.state.activatedByType[type]; - const total = this.state.allByType[type].count; - return this.getTooltip(count, total); + getTooltipForType(type: string) { + if ( + this.state.activatedByType && + this.state.activatedByType[type] && + this.state.allByType && + this.state.allByType[type] + ) { + const { count } = this.state.activatedByType[type]; + const total = this.state.allByType[type].count; + return this.getTooltip(count, total); + } } renderActiveTitle() { @@ -140,7 +157,7 @@ export default class ProfileRules extends React.Component { activation: 'false' }); - if (this.state.total == null) { + if (this.state.total == null || this.state.activatedTotal == null) { return null; } @@ -157,7 +174,7 @@ export default class ProfileRules extends React.Component { ); } - renderTitleForType(type) { + renderTitleForType(type: string) { return ( <span> <IssueTypeIcon query={type} className="little-spacer-right" /> @@ -166,14 +183,16 @@ export default class ProfileRules extends React.Component { ); } - renderCountForType(type) { + renderCountForType(type: string) { const rulesUrl = getRulesUrl({ qprofile: this.props.profile.key, activation: 'true', types: type }); - const { count } = this.state.activatedByType[type]; + const count = this.state.activatedByType && this.state.activatedByType[type] + ? this.state.activatedByType[type].count + : null; if (count == null) { return null; @@ -186,17 +205,22 @@ export default class ProfileRules extends React.Component { ); } - renderTotalForType(type) { + renderTotalForType(type: string) { const rulesUrl = getRulesUrl({ qprofile: this.props.profile.key, activation: 'false', types: type }); - const { count } = this.state.activatedByType[type]; - const { count: total } = this.state.allByType[type]; + const count = this.state.activatedByType && this.state.activatedByType[type] + ? this.state.activatedByType[type].count + : null; - if (count == null) { + const total = this.state.allByType && this.state.allByType[type] + ? this.state.allByType[type].count + : null; + + if (count == null || total == null) { return null; } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js index ae74c615802..9a7d140fa14 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; -export default class ProfileRulesRow extends React.Component { - static propTypes = { - renderTitle: React.PropTypes.func.isRequired, - renderCount: React.PropTypes.func.isRequired, - renderTotal: React.PropTypes.func.isRequired - }; +type Props = { + renderCount: () => ?React.Element<*>, + renderTitle: () => React.Element<*>, + renderTotal: () => ?React.Element<*> +}; + +export default class ProfileRulesRow extends React.PureComponent { + props: Props; render() { const { renderTitle, renderCount, renderTotal } = this.props; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js index b87f3ceb5df..23995d62e19 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js @@ -17,22 +17,28 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import EvolutionDeprecated from './EvolutionDeprecated'; import EvolutionStagnant from './EvolutionStagnant'; import EvolutionRules from './EvolutionRules'; -import { ProfilesListType } from '../propTypes'; +import type { Profile } from '../propTypes'; -export default class Evolution extends React.Component { - static propTypes = { - profiles: ProfilesListType.isRequired - }; +type Props = { + organization: ?string, + profiles: Array<Profile> +}; + +export default class Evolution extends React.PureComponent { + props: Props; render() { + const { organization, profiles } = this.props; + return ( <div className="quality-profiles-evolution"> - <EvolutionDeprecated profiles={this.props.profiles} /> - <EvolutionStagnant profiles={this.props.profiles} /> + <EvolutionDeprecated organization={organization} profiles={profiles} /> + <EvolutionStagnant organization={organization} profiles={profiles} /> <EvolutionRules /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js index 8c2a79cc697..ce8f4ddafbc 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js @@ -17,20 +17,26 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import { sortBy } from 'lodash'; import ProfileLink from '../components/ProfileLink'; import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; -import { ProfilesListType } from '../propTypes'; import { translateWithParameters, translate } from '../../../helpers/l10n'; +import type { Profile } from '../propTypes'; -export default class EvolutionDeprecated extends React.Component { - static propTypes = { - profiles: ProfilesListType.isRequired - }; +type Props = { + organization: ?string, + profiles: Array<Profile> +}; + +export default class EvolutionDeprecated extends React.PureComponent { + props: Props; render() { + // FIXME getDeprecatedActiveRulesUrl + const profilesWithDeprecations = this.props.profiles.filter( profile => profile.activeDeprecatedRuleCount > 0 ); @@ -56,7 +62,10 @@ export default class EvolutionDeprecated extends React.Component { {sortedProfiles.map(profile => ( <li key={profile.key} className="spacer-top"> <div className="text-ellipsis"> - <ProfileLink profileKey={profile.key} className="link-no-underline"> + <ProfileLink + organization={this.props.organization} + profileKey={profile.key} + className="link-no-underline"> {profile.name} </ProfileLink> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js index 224be2a3b28..553e6bdf62c 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import moment from 'moment'; @@ -38,7 +39,11 @@ function parseRules(r) { }); } -export default class EvolutionRules extends React.Component { +type Props = {}; + +export default class EvolutionRules extends React.PureComponent { + mounted: boolean; + props: Props; state = {}; componentDidMount() { @@ -70,6 +75,8 @@ export default class EvolutionRules extends React.Component { } render() { + // FIXME getRulesUrl + if (!this.state.latestRulesTotal) { return null; } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js index 833ec05fd33..29679aca1ae 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js @@ -17,17 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import moment from 'moment'; import ProfileLink from '../components/ProfileLink'; -import { ProfilesListType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; import { isStagnant } from '../utils'; +import type { Profile } from '../propTypes'; -export default class EvolutionStagnant extends React.Component { - static propTypes = { - profiles: ProfilesListType.isRequired - }; +type Props = { + organization: ?string, + profiles: Array<Profile> +}; + +export default class EvolutionStagnant extends React.PureComponent { + props: Props; render() { // TODO filter built-in out @@ -50,7 +54,10 @@ export default class EvolutionStagnant extends React.Component { {outdated.map(profile => ( <li key={profile.key} className="spacer-top"> <div className="text-ellipsis"> - <ProfileLink profileKey={profile.key} className="link-no-underline"> + <ProfileLink + organization={this.props.organization} + profileKey={profile.key} + className="link-no-underline"> {profile.name} </ProfileLink> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js index e5979422006..e675d6245f9 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js @@ -17,14 +17,27 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import Helmet from 'react-helmet'; import PageHeader from './PageHeader'; import Evolution from './Evolution'; import ProfilesList from './ProfilesList'; import { translate } from '../../../helpers/l10n'; +import type { Profile } from '../propTypes'; + +type Props = { + canAdmin: boolean, + languages: Array<{ key: string, name: string }>, + location: { query: { [string]: string } }, + organization?: string, + profiles: Array<Profile>, + updateProfiles: () => Promise<*> +}; + +export default class HomeContainer extends React.PureComponent { + props: Props; -export default class HomeContainer extends React.Component { render() { return ( <div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js index 9d4ca3dbefb..48ff0383758 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js @@ -17,17 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import CreateProfileView from '../views/CreateProfileView'; import RestoreProfileView from '../views/RestoreProfileView'; import RestoreBuiltInProfilesView from '../views/RestoreBuiltInProfilesView'; +import { getProfilePath } from '../utils'; import { translate } from '../../../helpers/l10n'; import { getImporters } from '../../../api/quality-profiles'; -export default class PageHeader extends React.Component { - static propTypes = { - canAdmin: React.PropTypes.bool.isRequired - }; +type Props = { + canAdmin: boolean, + languages: Array<{ key: string, name: string }>, + organization: ?string, + updateProfiles: () => Promise<*> +}; + +export default class PageHeader extends React.PureComponent { + mounted: boolean; + props: Props; static contextTypes = { router: React.PropTypes.object @@ -54,37 +62,42 @@ export default class PageHeader extends React.Component { } } - handleCreateClick(e) { + handleCreateClick = (e: SyntheticInputEvent) => { e.preventDefault(); e.target.blur(); this.retrieveImporters().then(importers => { new CreateProfileView({ languages: this.props.languages, + organization: this.props.organization, importers }) .on('done', profile => { this.props.updateProfiles().then(() => { - this.context.router.push({ - pathname: '/profiles/show', - query: { key: profile.key } - }); + this.context.router.push(getProfilePath(profile.key, this.props.organization)); }); }) .render(); }); - } + }; - handleRestoreClick(e) { + handleRestoreClick = (e: SyntheticInputEvent) => { e.preventDefault(); - new RestoreProfileView().on('done', this.props.updateProfiles).render(); - } + new RestoreProfileView({ + organization: this.props.organization + }) + .on('done', this.props.updateProfiles) + .render(); + }; - handleRestoreBuiltIn(e) { + handleRestoreBuiltIn = (e: SyntheticInputEvent) => { e.preventDefault(); - new RestoreBuiltInProfilesView({ languages: this.props.languages }) + new RestoreBuiltInProfilesView({ + languages: this.props.languages, + organization: this.props.organization + }) .on('done', this.props.updateProfiles) .render(); - } + }; render() { return ( @@ -95,7 +108,7 @@ export default class PageHeader extends React.Component { {this.props.canAdmin && <div className="page-actions button-group dropdown"> - <button id="quality-profiles-create" onClick={this.handleCreateClick.bind(this)}> + <button id="quality-profiles-create" onClick={this.handleCreateClick}> {translate('create')} </button> <button className="dropdown-toggle js-more-admin-actions" data-toggle="dropdown"> @@ -103,10 +116,7 @@ export default class PageHeader extends React.Component { </button> <ul className="dropdown-menu dropdown-menu-right"> <li> - <a - href="#" - id="quality-profiles-restore" - onClick={this.handleRestoreClick.bind(this)}> + <a href="#" id="quality-profiles-restore" onClick={this.handleRestoreClick}> {translate('quality_profiles.restore_profile')} </a> </li> @@ -115,7 +125,7 @@ export default class PageHeader extends React.Component { <a href="#" id="quality-profiles-restore-built-in" - onClick={this.handleRestoreBuiltIn.bind(this)}> + onClick={this.handleRestoreBuiltIn}> {translate('quality_profiles.restore_built_in_profiles')} </a> </li> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js index 34d0317107f..c2b0681f686 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js @@ -17,36 +17,46 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { groupBy, pick, sortBy } from 'lodash'; import ProfilesListRow from './ProfilesListRow'; import ProfilesListHeader from './ProfilesListHeader'; -import { ProfilesListType, LanguagesListType } from '../propTypes'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin'; +import type { Profile } from '../propTypes'; -export default class ProfilesList extends React.Component { - static propTypes = { - profiles: ProfilesListType, - languages: LanguagesListType, - location: React.PropTypes.object, - canAdmin: React.PropTypes.bool.isRequired, - updateProfiles: React.PropTypes.func.isRequired - }; +type Props = { + canAdmin: boolean, + languages: Array<{ key: string, name: string }>, + location: { query: { [string]: string } }, + organization: ?string, + profiles: Array<Profile>, + updateProfiles: () => Promise<*> +}; - renderProfiles(profiles) { +export default class ProfilesList extends React.PureComponent { + props: Props; + + renderProfiles(profiles: Array<Profile>) { return profiles.map(profile => ( <ProfilesListRow + canAdmin={this.props.canAdmin} key={profile.key} + organization={this.props.organization} profile={profile} - canAdmin={this.props.canAdmin} updateProfiles={this.props.updateProfiles} /> )); } - renderHeader(languageKey, profilesCount) { + renderHeader(languageKey: string, profilesCount: number) { const language = this.props.languages.find(l => l.key === languageKey); + + if (!language) { + return null; + } + return ( <thead> <tr> @@ -84,7 +94,11 @@ export default class ProfilesList extends React.Component { return ( <div> - <ProfilesListHeader languages={languages} currentFilter={language} /> + <ProfilesListHeader + currentFilter={language} + languages={languages} + organization={this.props.organization} + /> {Object.keys(profilesToShow).length === 0 && <div className="alert alert-warning spacer-top"> @@ -95,11 +109,13 @@ export default class ProfilesList extends React.Component { <div key={languageKey} className="quality-profile-box quality-profiles-table"> <table data-language={languageKey} className="data zebra zebra-hover"> - {this.renderHeader(languageKey, profilesToShow[languageKey].length)} + {profilesToShow[languageKey] != null && + this.renderHeader(languageKey, profilesToShow[languageKey].length)} <TooltipsContainer> <tbody> - {this.renderProfiles(profilesToShow[languageKey])} + {profilesToShow[languageKey] != null && + this.renderProfiles(profilesToShow[languageKey])} </tbody> </TooltipsContainer> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js index 3fd7c18d5ff..1d0c931aa04 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js @@ -17,22 +17,26 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { IndexLink } from 'react-router'; -import { LanguagesListType } from '../propTypes'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { getProfilesPath, getProfilesForLanguagePath } from '../utils'; -export default class ProfilesListHeader extends React.Component { - static propTypes = { - languages: LanguagesListType.isRequired, - currentFilter: React.PropTypes.string - }; +type Props = { + currentFilter?: string, + languages: Array<{ key: string, name: string }>, + organization: ?string +}; + +export default class ProfilesListHeader extends React.PureComponent { + props: Props; renderFilterToggle() { const { languages, currentFilter } = this.props; const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter); - const label = currentFilter + const label = currentLanguage ? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name) : translate('quality_profiles.all_profiles'); @@ -50,14 +54,14 @@ export default class ProfilesListHeader extends React.Component { return ( <ul className="dropdown-menu"> <li> - <IndexLink to="/profiles/"> + <IndexLink to={getProfilesPath(this.props.organization)}> {translate('quality_profiles.all_profiles')} </IndexLink> </li> {this.props.languages.map(language => ( <li key={language.key}> <IndexLink - to={{ pathname: '/profiles/', query: { language: language.key } }} + to={getProfilesForLanguagePath(language.key, this.props.organization)} className="js-language-filter-option" data-language={language.key}> {language.name} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js index eca33432e52..3a53ee66e19 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js @@ -17,26 +17,30 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import shallowCompare from 'react-addons-shallow-compare'; import ProfileLink from '../components/ProfileLink'; import ProfileDate from '../components/ProfileDate'; import ProfileActions from '../components/ProfileActions'; -import { ProfileType } from '../propTypes'; import { translate } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; import { isStagnant } from '../utils'; +import type { Profile } from '../propTypes'; -export default class ProfilesListRow extends React.Component { - static propTypes = { - profile: ProfileType.isRequired, - canAdmin: React.PropTypes.bool.isRequired, - updateProfiles: React.PropTypes.func.isRequired - }; +type Props = { + canAdmin: boolean, + organization: ?string, + profile: Profile, + updateProfiles: () => Promise<*> +}; - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(this, nextProps, nextState); +export default class ProfilesListRow extends React.PureComponent { + props: Props; + + shouldComponentUpdate(nextProps: Props) { + return shallowCompare(this, nextProps); } renderName() { @@ -44,7 +48,7 @@ export default class ProfilesListRow extends React.Component { const offset = 25 * (profile.depth - 1); return ( <div style={{ paddingLeft: offset }}> - <ProfileLink profileKey={profile.key}> + <ProfileLink organization={this.props.organization} profileKey={profile.key}> {profile.name} </ProfileLink> </div> @@ -70,6 +74,8 @@ export default class ProfilesListRow extends React.Component { } renderRules() { + // FIXME getRulesUrl + const { profile } = this.props; const activeRulesUrl = getRulesUrl({ @@ -150,8 +156,9 @@ export default class ProfilesListRow extends React.Component { <i className="icon-dropdown" /> </button> <ProfileActions - profile={this.props.profile} canAdmin={this.props.canAdmin} + organization={this.props.organization} + profile={this.props.profile} updateProfiles={this.props.updateProfiles} /> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js b/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js index 5ba29f3a37e..f8438011916 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js @@ -17,10 +17,35 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import { PropTypes } from 'react'; const { shape, string, number, bool, arrayOf } = PropTypes; +export type Profile = { + key: string, + name: string, + isDefault: boolean, + isInherited: boolean, + language: string, + languageName: string, + activeRuleCount: number, + activeDeprecatedRuleCount: number, + projectCount?: number, + parentKey?: string, + parentName?: string, + userUpdatedAt?: string, + lastUsed?: string, + rulesUpdatedAt: string, + depth: number +}; + +export type Exporter = { + key: string, + name: string, + languages: Array<string> +}; + export const ProfileType = shape({ key: string.isRequired, name: string.isRequired, diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.js b/server/sonar-web/src/main/js/apps/quality-profiles/routes.js index ac8f4c22894..57c5ca1baad 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/routes.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/routes.js @@ -19,9 +19,15 @@ */ const routes = [ { - getComponent(_, callback) { + getComponent(state, callback) { require.ensure([], require => { - callback(null, require('./components/AppContainer').default); + const AppContainer = require('./components/AppContainer').default; + if (state.params.organizationKey) { + callback(null, AppContainer); + } else { + const forSingleOrganization = require('../organizations/forSingleOrganization').default; + callback(null, forSingleOrganization(AppContainer)); + } }); }, getIndexRoute(_, callback) { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs index 845e4843ef6..acdc7f93805 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs +++ b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs @@ -1,14 +1,16 @@ -<form id="create-profile-form" action="{{link '/api/qualityprofiles/create'}}" enctype="multipart/form-data" - method="POST"> +<form id="create-profile-form" action="{{link '/api/qualityprofiles/create'}}" enctype="multipart/form-data" method="POST"> <div class="modal-head"> <h2>{{t 'quality_profiles.new_profile'}}</h2> </div> <div class="modal-body"> <div class="js-modal-messages"></div> + {{#if organization}} + <input type="hidden" name="organization" value="{{organization}}"> + {{/if}} <div class="modal-field"> - <label for="create-profile-name">{{t 'name'}}<em class="mandatory">*</em></label> - <input id="create-profile-name" name="name" type="text" size="50" maxlength="100" required> - </div> + <label for="create-profile-name">{{t 'name'}}<em class="mandatory">*</em></label> + <input id="create-profile-name" name="name" type="text" size="50" maxlength="100" required> + </div> <div class="modal-field spacer-bottom"> <label for="create-profile-language">{{t 'language'}}<em class="mandatory">*</em></label> <select id="create-profile-language" name="language" required> @@ -18,15 +20,15 @@ </select> </div> {{#each importers}} - <div class="modal-field spacer-bottom js-importer" data-key="{{key}}"> - <label for="create-profile-form-backup-{{key}}">{{name}}</label> - <input id="create-profile-form-backup-{{key}}" name="backup_{{key}}" type="file"> - <p class="note">{{t 'quality_profiles.optional_configuration_file'}}</p> - </div> + <div class="modal-field spacer-bottom js-importer" data-key="{{key}}"> + <label for="create-profile-form-backup-{{key}}">{{name}}</label> + <input id="create-profile-form-backup-{{key}}" name="backup_{{key}}" type="file"> + <p class="note">{{t 'quality_profiles.optional_configuration_file'}}</p> + </div> {{/each}} </div> <div class="modal-foot"> <button id="create-profile-submit">{{t 'create'}}</button> <a href="#" class="js-modal-close" id="create-profile-cancel">{{t 'cancel'}}</a> </div> -</form> +</form>
\ No newline at end of file diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs index 9a8b0182717..7c22c83aab5 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs +++ b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs @@ -5,6 +5,9 @@ <div class="modal-body"> <div class="js-modal-messages"></div> + {{#if organization}} + <input type="hidden" name="organization" value="{{organization}}"> + {{/if}} {{#if profile}} {{#if ruleFailures}} <div class="alert alert-warning"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/utils.js b/server/sonar-web/src/main/js/apps/quality-profiles/utils.js index d5ea476bab8..64bb0ea07d6 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/utils.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/utils.js @@ -20,14 +20,9 @@ // @flow import { sortBy } from 'lodash'; import moment from 'moment'; +import type { Profile } from './propTypes'; -type Profiles = Array<{ - key: string, - name: string, - parentKey?: string -}>; - -export function sortProfiles(profiles: Profiles) { +export function sortProfiles(profiles: Array<Profile>) { const result = []; const sorted = sortBy(profiles, 'name'); @@ -67,6 +62,56 @@ export function createFakeProfile(overrides: {}) { }; } -export function isStagnant(profile: { userUpdatedAt: string }) { +export function isStagnant(profile: Profile) { return moment().diff(moment(profile.userUpdatedAt), 'years') >= 1; } + +export const getProfilesPath = (organization: ?string) => + organization ? `/organizations/${organization}/quality_profiles` : '/profiles'; + +export const getProfilesForLanguagePath = (language: string, organization: ?string) => ({ + pathname: organization ? `/organizations/${organization}/quality_profiles` : '/profiles', + query: { language } +}); + +export const getProfilePath = (profile: string, organization: ?string) => ({ + pathname: organization + ? `/organizations/${organization}/quality_profiles/show` + : '/profiles/show', + query: { key: profile } +}); + +export const getProfileComparePath = (profile: string, organization: ?string, withKey?: string) => { + const query: Object = { key: profile }; + if (withKey) { + Object.assign(query, { withKey }); + } + return { + pathname: organization + ? `/organizations/${organization}/quality_profiles/compare` + : '/profiles/compare', + query + }; +}; + +export const getProfileChangelogPath = ( + profile: string, + organization: ?string, + filter?: { since?: string, to?: string } +) => { + const query: Object = { key: profile }; + if (filter) { + if (filter.since) { + Object.assign(query, { since: filter.since }); + } + if (filter.to) { + Object.assign(query, { to: filter.to }); + } + } + return { + pathname: organization + ? `/organizations/${organization}/quality_profiles/changelog` + : '/profiles/changelog', + query + }; +}; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js index 01c83a69ee1..912aecb10cb 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-change-profile-parent.hbs'; import { changeProfileParent } from '../../../api/quality-profiles'; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js index df5a7facd0c..c08cc80a8cf 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import escapeHtml from 'escape-html'; import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-change-projects.hbs'; @@ -27,6 +28,8 @@ export default ModalFormView.extend({ template: Template, onRender() { + // TODO remove uuid usage + ModalFormView.prototype.onRender.apply(this, arguments); const { key } = this.options.profile; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js index c16397a652f..711946b8bfa 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-copy-profile.hbs'; import { copyProfile } from '../../../api/quality-profiles'; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js index 8e12b9668a1..fb74811ec69 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import $ from 'jquery'; import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-create-profile.hbs'; @@ -87,7 +88,8 @@ export default ModalFormView.extend({ return { ...ModalFormView.prototype.serializeData.apply(this, arguments), languages: this.options.languages, - importers: this.options.importers + importers: this.options.importers, + organization: this.options.organization }; } }); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js index 0c5d8a61732..ec4c5f878a4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-delete-profile.hbs'; import { deleteProfile } from '../../../api/quality-profiles'; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js index daf8db1ff04..308efa7c143 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-rename-profile.hbs'; import { renameProfile } from '../../../api/quality-profiles'; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js index 39d0c720887..2edf05044b4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-restore-built-in-profiles.hbs'; import TemplateSuccess from '../templates/quality-profiles-restore-built-in-profiles-success.hbs'; @@ -47,7 +48,10 @@ export default ModalFormView.extend({ sendRequest() { const language = this.$('#restore-built-in-profiles-language').val(); this.selectedLanguage = this.options.languages.find(l => l.key === language).name; - restoreBuiltInProfiles(language) + const data = this.options.organization + ? { language, organization: this.options.organization } + : { language }; + restoreBuiltInProfiles(data) .then(() => { this.done = true; this.render(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js index ebd1fabe870..d2393e5053d 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import ModalFormView from '../../../components/common/modal-form'; import Template from '../templates/quality-profiles-restore-profile.hbs'; import { restoreQualityProfile } from '../../../api/quality-profiles'; @@ -47,6 +48,7 @@ export default ModalFormView.extend({ serializeData() { return { ...ModalFormView.prototype.serializeData.apply(this, arguments), + organization: this.options.organization, profile: this.profile, ruleSuccesses: this.ruleSuccesses, ruleFailures: this.ruleFailures diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js index 571d7a6069e..de25aa553ee 100644 --- a/server/sonar-web/src/main/js/helpers/urls.js +++ b/server/sonar-web/src/main/js/helpers/urls.js @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { getProfilePath } from '../apps/quality-profiles/utils'; + /** * Generate URL for a component's home page * @param {string} componentKey @@ -92,11 +94,8 @@ export function getComponentPermissionsUrl(componentKey) { * @param {string} key * @returns {Object} */ -export function getQualityProfileUrl(key) { - return { - pathname: '/profiles/show', - query: { key } - }; +export function getQualityProfileUrl(key, organization) { + return getProfilePath(key, organization); } /** |