From c86db4ee3737107b738e2bd83d5cfbad5d3575f2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 19 Jul 2017 15:10:29 +0200 Subject: [PATCH] SONAR-9566 Add Issues link at organization level navbar --- .../src/main/js/apps/issues/components/App.js | 9 +- ...sContainer.js => OrganizationContainer.js} | 7 +- .../components/OrganizationPage.js | 12 ++- .../navigation/OrganizationNavigation.js | 17 +++- .../__tests__/OrganizationNavigation-test.js | 7 ++ .../OrganizationNavigation-test.js.snap | 51 ++++++++++ .../src/main/js/apps/organizations/routes.js | 10 +- .../org/sonarqube/pageobjects/Navigation.java | 5 + .../pageobjects/issues/IssuesPage.java | 15 +++ .../org/sonarqube/tests/Category6Suite.java | 2 + .../issue/OrganizationIssuesPageTest.java | 96 +++++++++++++++++++ 11 files changed, 218 insertions(+), 13 deletions(-) rename server/sonar-web/src/main/js/apps/organizations/components/{OrganizationProjectsContainer.js => OrganizationContainer.js} (83%) create mode 100644 tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssuesPageTest.java diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index 60dd9d43f9f..48b6dbfcb0c 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -67,6 +67,7 @@ export type Props = { fetchIssues: (query: RawQuery) => Promise<*>, location: { pathname: string, query: RawQuery }, onRequestFail: Error => void, + organization?: { key: string }, router: { push: ({ pathname: string, query?: RawQuery }) => void, replace: ({ pathname: string, query?: RawQuery }) => void @@ -342,7 +343,7 @@ export default class App extends React.PureComponent { }; fetchIssues = (additional?: {}, requestFacets?: boolean = false): Promise<*> => { - const { component } = this.props; + const { component, organization } = this.props; const { myIssues, openFacets, query } = this.state; const facets = requestFacets @@ -358,6 +359,10 @@ export default class App extends React.PureComponent { ...additional }; + if (organization) { + parameters.organization = organization.key; + } + // only sorting by CREATION_DATE is allowed, so let's sort DESC if (query.sort) { Object.assign(parameters, { asc: 'false' }); @@ -730,7 +735,7 @@ export default class App extends React.PureComponent { } renderSide(openIssue: ?Issue) { - const top = this.props.component ? 95 : 30; + const top = this.props.component || this.props.organization ? 95 : 30; return (
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.js similarity index 83% rename from server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js rename to server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.js index a08e864d1b9..a48cf7f5cd6 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.js @@ -21,9 +21,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; -import { updateOrganization } from '../actions'; -class OrganizationProjectsContainer extends React.PureComponent { +class OrganizationContainer extends React.PureComponent { render() { return React.cloneElement(this.props.children, { currentUser: this.props.currentUser, @@ -37,6 +36,4 @@ const mapStateToProps = (state, ownProps) => ({ currentUser: getCurrentUser(state) }); -const mapDispatchToProps = { updateOrganization }; - -export default connect(mapStateToProps, mapDispatchToProps)(OrganizationProjectsContainer); +export default connect(mapStateToProps)(OrganizationContainer); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js index d08cfabf5d4..9111c0c70e2 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js @@ -22,10 +22,10 @@ import React from 'react'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; import OrganizationNavigation from '../navigation/OrganizationNavigation'; +import NotFound from '../../../app/components/NotFound'; import { fetchOrganization } from '../actions'; -import { getOrganizationByKey } from '../../../store/rootReducer'; +import { getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; import type { Organization } from '../../../store/organizations/duck'; -import NotFound from '../../../app/components/NotFound'; type OwnProps = { params: { organizationKey: string } @@ -33,6 +33,7 @@ type OwnProps = { type Props = { children?: React.Element<*>, + currentUser: { isLoggedIn: boolean, showOnboardingTutorial: true }, location: Object, organization: null | Organization, params: { organizationKey: string }, @@ -88,7 +89,11 @@ class OrganizationPage extends React.PureComponent { return (
- + {this.props.children}
); @@ -96,6 +101,7 @@ class OrganizationPage extends React.PureComponent { } const mapStateToProps = (state, ownProps: OwnProps) => ({ + currentUser: getCurrentUser(state), organization: getOrganizationByKey(state, ownProps.params.organizationKey) }); 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 e872e6e2ac1..ab54c533014 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 @@ -25,6 +25,7 @@ import { translate } from '../../../helpers/l10n'; 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 type { Organization } from '../../../store/organizations/duck'; const ADMIN_PATHS = [ @@ -38,6 +39,7 @@ const ADMIN_PATHS = [ export default class OrganizationNavigation extends React.PureComponent { props: { + currentUser: { isLoggedIn: boolean, showOnboardingTutorial: true }, location: { pathname: string }, organization: Organization }; @@ -135,7 +137,7 @@ export default class OrganizationNavigation extends React.PureComponent { } render() { - const { organization, location } = this.props; + const { currentUser, organization, location } = this.props; const isHomeActive = location.pathname === `organizations/${organization.key}/projects` || @@ -196,6 +198,19 @@ export default class OrganizationNavigation extends React.PureComponent { {translate('projects.page')} +
  • + + {translate('issues.page')} + +
  • {translate('organization.members.page')} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js index 09254c5aaf3..f4a2b09a38b 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js @@ -21,11 +21,16 @@ import React from 'react'; import { shallow } from 'enzyme'; import OrganizationNavigation from '../OrganizationNavigation'; +jest.mock('../../../issues/utils', () => ({ + isMySet: () => false +})); + it('regular user', () => { const organization = { key: 'foo', name: 'Foo', canAdmin: false, canDelete: false }; expect( shallow( @@ -38,6 +43,7 @@ it('admin', () => { expect( shallow( @@ -50,6 +56,7 @@ it('undeletable org', () => { expect( shallow( 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 05104c2e4d8..389fae8e92f 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 @@ -40,6 +40,23 @@ exports[`admin 1`] = ` projects.page
  • +
  • + + issues.page + +
  • +
  • + + issues.page + +
  • +
  • + + issues.page + +
  • getIssues() { return getIssuesElements() .stream() @@ -46,11 +51,21 @@ public class IssuesPage { .collect(Collectors.toList()); } + public IssuesPage issuesCount(Integer count) { + this.getIssuesElements().shouldHaveSize(count); + return this; + } + public Issue getFirstIssue() { getIssuesElements().shouldHave(sizeGreaterThan(0)); return new Issue(getIssuesElements().first()); } + public IssuesPage componentsShouldContain(String path) { + this.getIssuesPathComponents().forEach(element -> element.shouldHave(text(path))); + return this; + } + public IssuesPage bulkChangeOpen() { $("#issues-bulk-change").shouldBe(visible).click(); $("#bulk-change-form").shouldBe(visible); diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index 40e1f20930f..69ea5870d25 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -28,6 +28,7 @@ import org.junit.runners.Suite; import org.sonarqube.tests.authorisation.PermissionTemplateTest; import org.sonarqube.tests.issue.IssueTagsTest; import org.sonarqube.tests.issue.OrganizationIssueAssignTest; +import org.sonarqube.tests.issue.OrganizationIssuesPageTest; import org.sonarqube.tests.organization.BillingTest; import org.sonarqube.tests.organization.OrganizationMembershipTest; import org.sonarqube.tests.organization.OrganizationMembershipUiTest; @@ -57,6 +58,7 @@ import static util.ItUtils.xooPlugin; @Suite.SuiteClasses({ OrganizationIdentityProviderTest.class, OrganizationIssueAssignTest.class, + OrganizationIssuesPageTest.class, OrganizationMembershipTest.class, OrganizationMembershipUiTest.class, OrganizationQualityProfilesUiTest.class, diff --git a/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssuesPageTest.java b/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssuesPageTest.java new file mode 100644 index 00000000000..f6f5a1dcaeb --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssuesPageTest.java @@ -0,0 +1,96 @@ +/* + * 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.issue; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; +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 util.ItUtils.restoreProfile; +import static util.ItUtils.runProjectAnalysis; + +public class OrganizationIssuesPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Rule + public IssueRule issueRule = IssueRule.from(orchestrator); + + private Organizations.Organization org1; + private Organizations.Organization org2; + private WsUsers.CreateWsResponse.User user1; + private WsUsers.CreateWsResponse.User user2; + + @Before + public void setUp() throws Exception { + org1 = tester.organizations().generate(); + org2 = tester.organizations().generate(); + user1 = tester.users().generate(); + user2 = tester.users().generate(); + tester.organizations().addMember(org1, user1); + tester.organizations().addMember(org2, user2); + restoreProfile(orchestrator, getClass().getResource("/issue/with-many-rules.xml"), org1.getKey()); + restoreProfile(orchestrator, getClass().getResource("/issue/with-many-rules.xml"), org2.getKey()); + } + + @Test + public void display_organization_rules_only() { + String project1 = provisionProject(org1); + analyseProject(project1, org1.getKey()); + String project2 = provisionProject(org2); + analyseProject(project2, org2.getKey()); + Navigation nav = tester.openBrowser().logIn().submitCredentials(user1.getLogin()); + + nav.openIssues(org1.getKey()) + .issuesCount(2) + .componentsShouldContain(org1.getName()); + + nav.openIssues() + .issuesCount(4); + } + + private String provisionProject(Organizations.Organization organization) { + return tester.projects().generate(organization).getKey(); + } + + private void analyseProject(String projectKey, String organization) { + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectKey", projectKey, + "sonar.organization", organization, + "sonar.login", "admin", + "sonar.password", "admin", + "sonar.scm.disabled", "false", + "sonar.scm.provider", "xoo"); + } + +} -- 2.39.5