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
};
fetchIssues = (additional?: {}, requestFacets?: boolean = false): Promise<*> => {
- const { component } = this.props;
+ const { component, organization } = this.props;
const { myIssues, openFacets, query } = this.state;
const facets = requestFacets
...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' });
}
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">
--- /dev/null
+/*
+ * 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.
+ */
+//@flow
+import React from 'react';
+import { connect } from 'react-redux';
+import { getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
+
+class OrganizationContainer extends React.PureComponent {
+ render() {
+ return React.cloneElement(this.props.children, {
+ currentUser: this.props.currentUser,
+ organization: this.props.organization
+ });
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ organization: getOrganizationByKey(state, ownProps.params.organizationKey),
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(OrganizationContainer);
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 }
type Props = {
children?: React.Element<*>,
+ currentUser: { isLoggedIn: boolean, showOnboardingTutorial: true },
location: Object,
organization: null | Organization,
params: { organizationKey: string },
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>
);
}
const mapStateToProps = (state, ownProps: OwnProps) => ({
+ currentUser: getCurrentUser(state),
organization: getOrganizationByKey(state, ownProps.params.organizationKey)
});
+++ /dev/null
-/*
- * 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.
- */
-//@flow
-import React from 'react';
-import { connect } from 'react-redux';
-import { getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
-import { updateOrganization } from '../actions';
-
-class OrganizationProjectsContainer extends React.PureComponent {
- render() {
- return React.cloneElement(this.props.children, {
- currentUser: this.props.currentUser,
- organization: this.props.organization
- });
- }
-}
-
-const mapStateToProps = (state, ownProps) => ({
- organization: getOrganizationByKey(state, ownProps.params.organizationKey),
- currentUser: getCurrentUser(state)
-});
-
-const mapDispatchToProps = { updateOrganization };
-
-export default connect(mapStateToProps, mapDispatchToProps)(OrganizationProjectsContainer);
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 = [
export default class OrganizationNavigation extends React.PureComponent {
props: {
+ currentUser: { isLoggedIn: boolean, showOnboardingTutorial: true },
location: { pathname: string },
organization: Organization
};
}
render() {
- const { organization, location } = this.props;
+ const { currentUser, organization, location } = this.props;
const isHomeActive =
location.pathname === `organizations/${organization.key}/projects` ||
{translate('projects.page')}
</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')}
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}
/>
expect(
shallow(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: true }}
location={{ pathname: '/organizations/foo' }}
organization={organization}
/>
expect(
shallow(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: true }}
location={{ pathname: '/organizations/foo' }}
organization={organization}
/>
projects.page
</Link>
</li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
projects.page
</Link>
</li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
projects.page
</Link>
</li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
*/
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';
import OrganizationProjectsManagement from './components/OrganizationProjectsManagement';
import OrganizationDelete from './components/OrganizationDelete';
import qualityProfilesRoutes from '../quality-profiles/routes';
+import issuesRoutes from '../issues/routes';
const routes = [
{
},
{
path: 'projects',
- component: OrganizationProjectsContainer,
+ component: OrganizationContainer,
childRoutes: [
{
indexRoute: {
}
]
},
+ {
+ path: 'issues',
+ component: OrganizationContainer,
+ childRoutes: issuesRoutes
+ },
{
path: 'members',
component: OrganizationMembersContainer
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;
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);
}
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.$$;
return $$(".issues .issue");
}
+ private ElementsCollection getIssuesPathComponents() {
+ return $$(".issues-workspace-list-component");
+ }
+
public List<Issue> getIssues() {
return getIssuesElements()
.stream()
.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);
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;
@Suite.SuiteClasses({
OrganizationIdentityProviderTest.class,
OrganizationIssueAssignTest.class,
+ OrganizationIssuesPageTest.class,
OrganizationMembershipTest.class,
OrganizationMembershipUiTest.class,
OrganizationQualityProfilesUiTest.class,
--- /dev/null
+/*
+ * 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");
+ }
+
+}