aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.js9
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.js (renamed from server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js)7
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js12
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js17
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js7
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap51
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.js10
-rw-r--r--tests/src/test/java/org/sonarqube/pageobjects/Navigation.java5
-rw-r--r--tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java15
-rw-r--r--tests/src/test/java/org/sonarqube/tests/Category6Suite.java2
-rw-r--r--tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssuesPageTest.java96
11 files changed, 218 insertions, 13 deletions
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 (
<div className="layout-page-side-outer">
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
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 (
<div>
<Helmet defaultTitle={organization.name} titleTemplate={'%s - ' + organization.name} />
- <OrganizationNavigation organization={organization} location={this.props.location} />
+ <OrganizationNavigation
+ currentUser={this.props.currentUser}
+ organization={organization}
+ location={this.props.location}
+ />
{this.props.children}
</div>
);
@@ -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` ||
@@ -197,6 +199,19 @@ export default class OrganizationNavigation extends React.PureComponent {
</Link>
</li>
<li>
+ <Link
+ to={{
+ pathname: `/organizations/${organization.key}/issues`,
+ query:
+ currentUser.isLoggedIn && isMySet()
+ ? { resolved: 'false', myIssues: 'true' }
+ : { resolved: 'false' }
+ }}
+ activeClassName="active">
+ {translate('issues.page')}
+ </Link>
+ </li>
+ <li>
<Link to={`/organizations/${organization.key}/members`} activeClassName="active">
{translate('organization.members.page')}
</Link>
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(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: true }}
location={{ pathname: '/organizations/foo' }}
organization={organization}
/>
@@ -38,6 +43,7 @@ it('admin', () => {
expect(
shallow(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: true }}
location={{ pathname: '/organizations/foo' }}
organization={organization}
/>
@@ -50,6 +56,7 @@ it('undeletable org', () => {
expect(
shallow(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: true }}
location={{ pathname: '/organizations/foo' }}
organization={organization}
/>
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
@@ -45,6 +45,23 @@ exports[`admin 1`] = `
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
to="/organizations/foo/members"
>
organization.members.page
@@ -198,6 +215,23 @@ exports[`regular user 1`] = `
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
to="/organizations/foo/members"
>
organization.members.page
@@ -272,6 +306,23 @@ exports[`undeletable org 1`] = `
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
to="/organizations/foo/members"
>
organization.members.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 f5880608456..90c22b3270b 100644
--- a/server/sonar-web/src/main/js/apps/organizations/routes.js
+++ b/server/sonar-web/src/main/js/apps/organizations/routes.js
@@ -19,8 +19,8 @@
*/
import OrganizationPage from './components/OrganizationPage';
import OrganizationPageExtension from '../../app/components/extensions/OrganizationPageExtension';
+import OrganizationContainer from './components/OrganizationContainer';
import OrganizationProjects from './components/OrganizationProjects';
-import OrganizationProjectsContainer from './components/OrganizationProjectsContainer';
import OrganizationFavoriteProjects from './components/OrganizationFavoriteProjects';
import OrganizationRules from './components/OrganizationRules';
import OrganizationAdmin from './components/OrganizationAdmin';
@@ -32,6 +32,7 @@ import OrganizationPermissionTemplates from './components/OrganizationPermission
import OrganizationProjectsManagement from './components/OrganizationProjectsManagement';
import OrganizationDelete from './components/OrganizationDelete';
import qualityProfilesRoutes from '../quality-profiles/routes';
+import issuesRoutes from '../issues/routes';
const routes = [
{
@@ -48,7 +49,7 @@ const routes = [
},
{
path: 'projects',
- component: OrganizationProjectsContainer,
+ component: OrganizationContainer,
childRoutes: [
{
indexRoute: {
@@ -62,6 +63,11 @@ const routes = [
]
},
{
+ path: 'issues',
+ component: OrganizationContainer,
+ childRoutes: issuesRoutes
+ },
+ {
path: 'members',
component: OrganizationMembersContainer
},
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java
index 342d2635462..6bdf9be6156 100644
--- a/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java
+++ b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java
@@ -31,6 +31,7 @@ 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;
@@ -88,6 +89,10 @@ public class Navigation {
return open("/issues", IssuesPage.class);
}
+ public IssuesPage openIssues(String organization) {
+ return open("/organizations/" + organization + "/issues", IssuesPage.class);
+ }
+
public IssuesPage openComponentIssues(String component) {
return open("/component_issues?id=" + component, IssuesPage.class);
}
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java b/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java
index d09c894f9d9..572b69b8454 100644
--- a/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java
+++ b/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java
@@ -25,6 +25,7 @@ import java.util.stream.Collectors;
import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan;
import static com.codeborne.selenide.Condition.exist;
+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.$$;
@@ -39,6 +40,10 @@ public class IssuesPage {
return $$(".issues .issue");
}
+ private ElementsCollection getIssuesPathComponents() {
+ return $$(".issues-workspace-list-component");
+ }
+
public List<Issue> 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");
+ }
+
+}