From: Grégoire Aubert Date: Thu, 20 Jul 2017 11:27:32 +0000 (+0200) Subject: SONAR-9565 Move the Quality Gates link to organization level X-Git-Tag: 6.6-RC1~828 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=106437a53c371a202d7fbd669472ba7096ae2f71;p=sonarqube.git SONAR-9565 Move the Quality Gates link to organization level --- 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 6535201176f..7d7437238bf 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 @@ -20,6 +20,7 @@ import React from 'react'; import { Link } from 'react-router'; import { translate } from '../../../../helpers/l10n'; +import { getQualityGatesUrl } from '../../../../helpers/urls'; import { isMySet } from '../../../../apps/issues/utils'; export default class GlobalNavMenu extends React.PureComponent { @@ -98,7 +99,7 @@ export default class GlobalNavMenu extends React.PureComponent { renderQualityGatesLink() { return (
  • - + {translate('quality_gates.page')}
  • @@ -158,7 +159,7 @@ export default class GlobalNavMenu extends React.PureComponent { {this.renderIssuesLink()} {!organizationsEnabled && this.renderRulesLink()} {!organizationsEnabled && this.renderProfilesLink()} - {this.renderQualityGatesLink()} + {!organizationsEnabled && this.renderQualityGatesLink()} {this.renderAdministrationLink()} {this.renderMore()} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap index 3315afa83e2..f3541ea9d6d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap @@ -55,7 +55,11 @@ exports[`should show administration menu if the user has the rights 1`] = ` activeClassName="active" onlyActiveOnIndex={false} style={Object {}} - to="/quality_gates" + to={ + Object { + "pathname": "/quality_gates", + } + } > quality_gates.page @@ -129,7 +133,11 @@ exports[`should work with extensions 1`] = ` activeClassName="active" onlyActiveOnIndex={false} style={Object {}} - to="/quality_gates" + to={ + Object { + "pathname": "/quality_gates", + } + } > quality_gates.page 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 0d8939752c6..be4670195a1 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 @@ -26,6 +26,7 @@ import ContextNavBar from '../../../components/nav/ContextNavBar'; import NavBarTabs from '../../../components/nav/NavBarTabs'; import OrganizationIcon from '../../../components/icons-components/OrganizationIcon'; import { isMySet } from '../../issues/utils'; +import { getQualityGatesUrl } from '../../../helpers/urls'; import type { Organization } from '../../../store/organizations/duck'; const ADMIN_PATHS = [ @@ -228,6 +229,11 @@ export default class OrganizationNavigation extends React.PureComponent { {translate('coding_rules.page')} +
  • + + {translate('quality_gates.page')} + +
  • {this.renderExtensions(moreActive)} {organization.canAdmin && this.renderAdministration(adminActive)} 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 389fae8e92f..e3acf365788 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 @@ -87,6 +87,20 @@ exports[`admin 1`] = ` coding_rules.page +
  • + + quality_gates.page + +
  • @@ -257,6 +271,20 @@ exports[`regular user 1`] = ` coding_rules.page
  • +
  • + + quality_gates.page + +
  • `; @@ -348,6 +376,20 @@ exports[`undeletable org 1`] = ` coding_rules.page +
  • + + quality_gates.page + +
  • 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 90c22b3270b..ecdd76eb7e1 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.js +++ b/server/sonar-web/src/main/js/apps/organizations/routes.js @@ -31,6 +31,7 @@ import OrganizationPermissions from './components/OrganizationPermissions'; import OrganizationPermissionTemplates from './components/OrganizationPermissionTemplates'; import OrganizationProjectsManagement from './components/OrganizationProjectsManagement'; import OrganizationDelete from './components/OrganizationDelete'; +import qualityGatesRoutes from '../quality-gates/routes'; import qualityProfilesRoutes from '../quality-profiles/routes'; import issuesRoutes from '../issues/routes'; @@ -79,6 +80,11 @@ const routes = [ path: 'quality_profiles', childRoutes: qualityProfilesRoutes }, + { + path: 'quality_gates', + component: OrganizationContainer, + childRoutes: qualityGatesRoutes + }, { path: 'extension/:pluginKey/:extensionKey', component: OrganizationPageExtension 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 1a57590524f..4d84a3f2e23 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 @@ -43,7 +43,7 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles; const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate; - const shouldShowOrganizationKey = component.organization != null && areThereCustomOrganizations; + const hasOrganization = component.organization != null && areThereCustomOrganizations; return (
    @@ -58,7 +58,11 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route {isProject && } - {shouldShowQualityGate && } + {shouldShowQualityGate && + } {shouldShowQualityProfiles && - {shouldShowOrganizationKey && } + {hasOrganization && }
    ); }; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js index 3bdda5734d1..f9c3a1db5f6 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js @@ -22,7 +22,7 @@ import { Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; -const MetaQualityGate = ({ gate }) => { +const MetaQualityGate = ({ gate, organization }) => { return (

    @@ -35,7 +35,7 @@ const MetaQualityGate = ({ gate }) => { {'(' + translate('default') + ')'} } - + {gate.name}

  • diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js index 8fd787e3030..8f52c7bbe69 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js @@ -17,7 +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. */ -import React, { Component } from 'react'; +import React from 'react'; import Helmet from 'react-helmet'; import { fetchQualityGate, @@ -29,8 +29,9 @@ import DetailsContent from './DetailsContent'; import RenameView from '../views/rename-view'; import CopyView from '../views/copy-view'; import DeleteView from '../views/delete-view'; +import { getQualityGatesUrl, getQualityGateUrl } from '../../../helpers/urls'; -export default class Details extends Component { +export default class Details extends React.PureComponent { componentDidMount() { this.fetchDetails(); } @@ -62,14 +63,14 @@ export default class Details extends Component { } handleCopyClick() { - const { qualityGate, onCopy } = this.props; + const { qualityGate, onCopy, organization } = this.props; const { router } = this.context; new CopyView({ qualityGate, onCopy: newQualityGate => { onCopy(newQualityGate); - router.push(`/quality_gates/show/${newQualityGate.id}`); + router.push(getQualityGateUrl(newQualityGate.id, organization && organization.key)); } }).render(); } @@ -85,14 +86,13 @@ export default class Details extends Component { } handleDeleteClick() { - const { qualityGate, onDelete } = this.props; + const { qualityGate, onDelete, organization } = this.props; const { router } = this.context; - new DeleteView({ qualityGate, onDelete: qualityGate => { onDelete(qualityGate); - router.replace('/quality_gates'); + router.replace(getQualityGatesUrl(organization && organization.key)); } }).render(); } @@ -115,6 +115,7 @@ export default class Details extends Component { onCopy={this.handleCopyClick.bind(this)} onSetAsDefault={this.handleSetAsDefaultClick.bind(this)} onDelete={this.handleDeleteClick.bind(this)} + organization={this.props.organization} /> { e.preventDefault(); - onRename(); - } + this.props.onRename(); + }; - function handleCopyClick(e) { + handleCopyClick = e => { e.preventDefault(); - onCopy(); - } + this.props.onCopy(); + }; - function handleSetAsDefaultClick(e) { + handleSetAsDefaultClick = e => { e.preventDefault(); - onSetAsDefault(); - } + this.props.onSetAsDefault(); + }; - function handleDeleteClick(e) { + handleDeleteClick = e => { e.preventDefault(); - onDelete(); - } + this.props.onDelete(); + }; + + render() { + const { qualityGate, edit } = this.props; - return ( -
    -
    -
    -

    - {qualityGate.name} -

    - {edit && -
    -
    - - - - -
    -
    } + return ( +
    +
    +
    +

    + {qualityGate.name} +

    + {edit && +
    +
    + + + + +
    +
    } +
    -
    - ); + ); + } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.js b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js index 9729392a1b8..b6a4d3a8c23 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js @@ -20,14 +20,15 @@ import React from 'react'; import { Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; +import { getQualityGateUrl } from '../../../helpers/urls'; -export default function List({ qualityGates }) { +export default function List({ organization, qualityGates }) { return (
    {qualityGates.map(qualityGate => diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js index a5c96cd7967..33a9c447d13 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js @@ -17,10 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React, { Component } from 'react'; +import React from 'react'; import ProjectsView from '../views/gate-projects-view'; -export default class Projects extends Component { +export default class Projects extends React.PureComponent { componentDidMount() { this.renderView(); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js index ff9d95d7bd7..3ac0da688cc 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js @@ -26,9 +26,14 @@ import { fetchQualityGates as fetchQualityGatesAPI } from '../../../api/quality-gates'; import { translate } from '../../../helpers/l10n'; +import { getQualityGateUrl } from '../../../helpers/urls'; import '../styles.css'; export default class QualityGatesApp extends Component { + static contextTypes = { + router: React.PropTypes.object.isRequired + }; + state = {}; componentDidMount() { @@ -45,37 +50,35 @@ export default class QualityGatesApp extends Component { } handleAdd(qualityGate) { - const { addQualityGate } = this.props; + const { addQualityGate, organization } = this.props; const { router } = this.context; addQualityGate(qualityGate); - router.push(`/quality_gates/show/${qualityGate.id}`); + router.push(getQualityGateUrl(qualityGate.id, organization && organization.key)); } render() { - const { children, qualityGates, edit } = this.props; + const { children, qualityGates, edit, organization } = this.props; const defaultTitle = translate('quality_gates.page'); + const top = organization ? 95 : 30; return (
    -
    +
    - {qualityGates && } + {qualityGates && }
    - {!!qualityGates && children} + {qualityGates != null && + React.Children.map(children, child => React.cloneElement(child, { organization }))}
    ); } } - -QualityGatesApp.contextTypes = { - router: React.PropTypes.object.isRequired -}; diff --git a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.js b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.js index c2047a572a4..0f5fb66e39a 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.js +++ b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.js @@ -17,7 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getComponentUrl, getComponentIssuesUrl, getComponentDrilldownUrl } from '../urls'; +import { + getComponentUrl, + getComponentIssuesUrl, + getComponentDrilldownUrl, + getQualityGatesUrl, + getQualityGateUrl +} from '../urls'; const SIMPLE_COMPONENT_KEY = 'sonarqube'; const COMPLEX_COMPONENT_KEY = 'org.sonarsource.sonarqube:sonarqube'; @@ -77,10 +83,30 @@ describe('#getComponentDrilldownUrl', () => { }); }); - it('should encode component key', () => { + it('should not encode component key', () => { expect(getComponentDrilldownUrl(COMPLEX_COMPONENT_KEY, METRIC)).toEqual({ pathname: '/component_measures/metric/' + METRIC, query: { id: COMPLEX_COMPONENT_KEY } }); }); }); + +describe('#getQualityGate(s)Url', () => { + it('should take organization key into account', () => { + expect(getQualityGatesUrl()).toEqual({ pathname: '/quality_gates' }); + expect(getQualityGatesUrl('foo')).toEqual({ pathname: '/organizations/foo/quality_gates' }); + expect(getQualityGateUrl('bar')).toEqual({ pathname: '/quality_gates/show/bar' }); + expect(getQualityGateUrl('bar', 'foo')).toEqual({ + pathname: '/organizations/foo/quality_gates/show/bar' + }); + }); + + it('should encode keys', () => { + expect(getQualityGatesUrl(COMPLEX_COMPONENT_KEY)).toEqual({ + pathname: '/organizations/' + COMPLEX_COMPONENT_KEY_ENCODED + '/quality_gates' + }); + expect(getQualityGateUrl(COMPLEX_COMPONENT_KEY)).toEqual({ + pathname: '/quality_gates/show/' + COMPLEX_COMPONENT_KEY_ENCODED + }); + }); +}); diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js index 07103e12255..27c2ec6ee6c 100644 --- a/server/sonar-web/src/main/js/helpers/urls.js +++ b/server/sonar-web/src/main/js/helpers/urls.js @@ -100,16 +100,14 @@ export function getQualityProfileUrl(name, language, organization) { return getProfilePath(name, language, organization); } -/** - * Generate URL for a quality gate - * @param {string} key - * @returns {Object} - */ -export function getQualityGateUrl(key) { - return { - pathname: '/quality_gates/show/' + encodeURIComponent(key) - }; -} +export const getQualityGateUrl = (key: string, organization?: string) => ({ + pathname: getQualityGatesUrl(organization).pathname + '/show/' + encodeURIComponent(key) +}); + +export const getQualityGatesUrl = (organization?: string) => ({ + pathname: + (organization ? '/organizations/' + encodeURIComponent(organization) : '') + '/quality_gates' +}); /** * Generate URL for the rules page diff --git a/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java index 6bdf9be6156..b71307e1587 100644 --- a/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java +++ b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java @@ -31,7 +31,6 @@ import javax.annotation.Nullable; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.html5.WebStorage; -import org.sonarqube.pageobjects.issues.Issue; import org.sonarqube.tests.Tester; import org.sonarqube.pageobjects.issues.IssuesPage; import org.sonarqube.pageobjects.licenses.LicensesPage; @@ -109,6 +108,16 @@ public class Navigation { return open(url, ProjectLinksPage.class); } + public QualityGatePage openQualityGates() { + String url = "/quality_gates"; + return open(url, QualityGatePage.class); + } + + public QualityGatePage openQualityGates(String organization) { + String url = "/organizations/" + organization + "/quality_gates"; + return open(url, QualityGatePage.class); + } + public ProjectQualityGatePage openProjectQualityGate(String projectKey) { // TODO encode projectKey String url = "/project/quality_gate?id=" + projectKey; diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java index 60969c43fa5..673e037c193 100644 --- a/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java @@ -22,6 +22,7 @@ package org.sonarqube.pageobjects; import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.SelenideElement; import java.util.Arrays; +import org.openqa.selenium.By; import static com.codeborne.selenide.Condition.exist; import static com.codeborne.selenide.Condition.hasText; @@ -29,6 +30,7 @@ import static com.codeborne.selenide.Condition.text; import static com.codeborne.selenide.Condition.visible; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.$$; +import static org.assertj.core.api.Assertions.assertThat; public class ProjectDashboardPage { @@ -97,4 +99,11 @@ public class ProjectDashboardPage { tagsInput.sendKeys(charSequences); return this; } + + public ProjectDashboardPage hasQualityGateLink(String name, String link) { + SelenideElement elem = $(".overview-meta-header").should(exist) + .parent().find(By.linkText(name)).should(exist); + assertThat(elem.attr("href")).endsWith(link); + return this; + } } diff --git a/tests/src/test/java/org/sonarqube/pageobjects/QualityGatePage.java b/tests/src/test/java/org/sonarqube/pageobjects/QualityGatePage.java new file mode 100644 index 00000000000..3d7162f898d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/QualityGatePage.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class QualityGatePage { + public QualityGatePage() { + $(".quality-gates-results").shouldBe(Condition.visible); + } + + public QualityGatePage countQualityGates(Integer count) { + $$(".quality-gates-results .list-group-item").shouldHaveSize(count); + return this; + } + + public QualityGatePage canCreateQG() { + $("#quality-gate-add").should(Condition.exist).shouldBe(Condition.visible); + return this; + } + + public QualityGatePage canNotCreateQG() { + $("#quality-gate-add").shouldNot(Condition.exist); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index 69ea5870d25..206b264c24c 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -40,6 +40,7 @@ import org.sonarqube.tests.projectAdministration.ProjectKeyUpdateTest; import org.sonarqube.tests.projectAdministration.ProjectProvisioningTest; import org.sonarqube.tests.projectSearch.LeakProjectsPageTest; import org.sonarqube.tests.projectSearch.SearchProjectsTest; +import org.sonarqube.tests.qualityGate.OrganizationQualityGateUiTest; import org.sonarqube.tests.qualityProfile.BuiltInQualityProfilesTest; import org.sonarqube.tests.qualityProfile.CustomQualityProfilesTest; import org.sonarqube.tests.qualityProfile.OrganizationQualityProfilesUiTest; @@ -61,6 +62,7 @@ import static util.ItUtils.xooPlugin; OrganizationIssuesPageTest.class, OrganizationMembershipTest.class, OrganizationMembershipUiTest.class, + OrganizationQualityGateUiTest.class, OrganizationQualityProfilesUiTest.class, OrganizationTest.class, RootUserOnOrganizationTest.class, diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java new file mode 100644 index 00000000000..01ab1189d87 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityGate; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import com.sonar.orchestrator.Orchestrator; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.openqa.selenium.By; +import org.sonarqube.pageobjects.ProjectDashboardPage; +import org.sonarqube.pageobjects.QualityGatePage; +import org.sonarqube.tests.Category6Suite; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers; +import util.issue.IssueRule; + +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.restoreProfile; +import static util.ItUtils.runProjectAnalysis; +import static org.assertj.core.api.Assertions.assertThat; + +public class OrganizationQualityGateUiTest { + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Rule + public IssueRule issueRule = IssueRule.from(orchestrator); + + private Organizations.Organization organization; + private WsUsers.CreateWsResponse.User user; + + @Before + public void setUp() throws Exception { + organization = tester.organizations().generate(); + user = tester.users().generate(); + tester.organizations().addMember(organization, user); + restoreProfile(orchestrator, getClass().getResource("/issue/with-many-rules.xml"), organization.getKey()); + } + + @Test + public void should_have_a_link_to_quality_gates() { + tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openQualityGates(organization.getKey()); + + SelenideElement element = $(".navbar-context .navbar-nav") + .find(By.linkText("Quality Gates")) + .should(Condition.exist); + assertThat(element.attr("href")).endsWith("/organizations/" + organization.getKey() + "/quality_gates"); + } + + @Test + public void should_display_available_quality_gates() { + QualityGatePage page = tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openQualityGates(organization.getKey()); + page.countQualityGates(1); + } + + @Test + public void should_not_allow_random_user_to_create() { + tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openQualityGates(organization.getKey()) + .canNotCreateQG(); + tester.openBrowser() + .logIn().submitCredentials("admin") + .openQualityGates(organization.getKey()) + .canCreateQG(); + } + + @Test + public void quality_gate_link_on_project_dashboard_should_have_organization_context() { + String project = tester.projects().generate(organization).getKey(); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectKey", project, + "sonar.organization", organization.getKey(), + "sonar.login", "admin", + "sonar.password", "admin", + "sonar.scm.disabled", "false", + "sonar.scm.provider", "xoo"); + + String link = "/organizations/" + organization.getKey() + "/quality_gates/show/1"; + ProjectDashboardPage page = tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openProjectDashboard(project); + page.hasQualityGateLink("SonarQube way", link); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java index 4404dc7ca80..acb207f5271 100644 --- a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java @@ -19,8 +19,12 @@ */ package org.sonarqube.tests.qualityGate; +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarScanner; +import org.junit.Rule; +import org.openqa.selenium.By; import org.sonarqube.tests.Category1Suite; import java.util.Date; import javax.annotation.Nullable; @@ -37,8 +41,11 @@ import org.sonar.wsclient.qualitygate.QualityGateCondition; import org.sonar.wsclient.qualitygate.UpdateCondition; import org.sonarqube.pageobjects.Navigation; import org.sonarqube.pageobjects.ProjectActivityPage; +import org.sonarqube.tests.Tester; +import static com.codeborne.selenide.Selenide.$; import static org.apache.commons.lang.time.DateUtils.addDays; +import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.projectDir; import static util.ItUtils.resetPeriod; import static util.ItUtils.setServerProperty; @@ -49,6 +56,9 @@ public class QualityGateUiTest { @ClassRule public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + @Rule + public Tester tester = new Tester(orchestrator); + private static long DEFAULT_QUALITY_GATE; @BeforeClass @@ -102,6 +112,32 @@ public class QualityGateUiTest { runSelenese(orchestrator, "/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html"); } + @Test + public void should_have_a_global_link_to_quality_gates() { + String login = tester.users().generate().getLogin(); + tester.openBrowser() + .logIn().submitCredentials(login) + .openQualityGates(); + + SelenideElement element = $(".navbar-global .navbar-nav") + .find(By.linkText("Quality Gates")) + .should(Condition.exist); + assertThat(element.attr("href")).endsWith("/quality_gates"); + } + + @Test + public void should_not_allow_random_user_to_create() { + String login = tester.users().generate().getLogin(); + tester.openBrowser() + .logIn().submitCredentials(login) + .openQualityGates() + .canNotCreateQG(); + tester.openBrowser() + .logIn().submitCredentials("admin") + .openQualityGates() + .canCreateQG(); + } + private void scanSampleWithDate(String date) { scanSample(date, null); }