aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2018-12-18 11:36:11 +0100
committerSonarTech <sonartech@sonarsource.com>2018-12-24 20:20:54 +0100
commit772a9e2f8ee92d98fc6f0114bcea700d8c3e8670 (patch)
treede793c7c8eccffbd6682e9161d5745a24b5ce19c /server/sonar-web/src/main/js
parent0225aef248c18e61246b1f50e4ec189e8192511a (diff)
downloadsonarqube-772a9e2f8ee92d98fc6f0114bcea700d8c3e8670.tar.gz
sonarqube-772a9e2f8ee92d98fc6f0114bcea700d8c3e8670.zip
SONARCLOUD-235 Hide QG and QP links on Overview for non-members
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/actions.ts11
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaContainer-test.tsx68
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap293
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/store/rootActions.ts13
11 files changed, 395 insertions, 32 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index a031c478169..3cde92f9731 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -29,7 +29,7 @@ import { getTasksForComponent, getAnalysisStatus } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getMeasures } from '../../api/measures';
import { getComponentNavigation } from '../../api/nav';
-import { fetchOrganizations } from '../../store/rootActions';
+import { fetchOrganization } from '../../store/rootActions';
import { STATUSES } from '../../apps/background-tasks/constants';
import {
isPullRequest,
@@ -44,7 +44,7 @@ import { Store, getAppState } from '../../store/rootReducer';
interface Props {
appState: Pick<T.AppState, 'organizationsEnabled'>;
children: any;
- fetchOrganizations: (organizations: string[]) => void;
+ fetchOrganization: (organization: string) => void;
location: {
query: { branch?: string; id: string; pullRequest?: string };
};
@@ -116,7 +116,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
const component = this.addQualifier({ ...nav, ...data });
if (this.props.appState.organizationsEnabled) {
- this.props.fetchOrganizations([component.organization]);
+ this.props.fetchOrganization(component.organization);
}
return component;
})
@@ -379,7 +379,7 @@ const mapStateToProps = (state: Store) => ({
appState: getAppState(state)
});
-const mapDispatchToProps = { fetchOrganizations };
+const mapDispatchToProps = { fetchOrganization };
export default connect(
mapStateToProps,
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index 34d513e1b17..a98614a257a 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -78,7 +78,7 @@ it('changes component', () => {
const wrapper = shallow<ComponentContainer>(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
@@ -105,7 +105,7 @@ it("loads branches for module's project", async () => {
mount(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'moduleKey' } }}>
<Inner />
</ComponentContainer>
@@ -122,7 +122,7 @@ it("doesn't load branches portfolio", async () => {
const wrapper = mount(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
@@ -141,7 +141,7 @@ it('updates branches on change', () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
@@ -169,7 +169,7 @@ it('updates the branch measures', async () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'foo', branch: 'feature' } }}>
<Inner />
</ComponentContainer>
@@ -195,18 +195,18 @@ it('updates the branch measures', async () => {
it('loads organization', async () => {
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });
- const fetchOrganizations = jest.fn();
+ const fetchOrganization = jest.fn();
mount(
<ComponentContainer
appState={{ organizationsEnabled: true }}
- fetchOrganizations={fetchOrganizations}
+ fetchOrganization={fetchOrganization}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
await new Promise(setImmediate);
- expect(fetchOrganizations).toBeCalledWith(['org']);
+ expect(fetchOrganization).toBeCalledWith('org');
});
it('fetches status', async () => {
@@ -215,7 +215,7 @@ it('fetches status', async () => {
mount(
<ComponentContainer
appState={{ organizationsEnabled: true }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
@@ -229,7 +229,7 @@ it('filters correctly the pending tasks for a main branch', () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
@@ -297,7 +297,7 @@ it('reload component after task progress finished', async () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
- fetchOrganizations={jest.fn()}
+ fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
diff --git a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
index ac53b37e041..b9abb6d7188 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
@@ -22,7 +22,7 @@ import { connect } from 'react-redux';
import Extension from './Extension';
import NotFound from '../NotFound';
import { getOrganizationByKey, Store } from '../../../store/rootReducer';
-import { fetchOrganization } from '../../../apps/organizations/actions';
+import { fetchOrganization } from '../../../store/rootActions';
interface StateToProps {
organization?: T.Organization;
diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.ts b/server/sonar-web/src/main/js/apps/organizations/actions.ts
index 6d72a0b9318..846b0b2e632 100644
--- a/server/sonar-web/src/main/js/apps/organizations/actions.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/actions.ts
@@ -23,17 +23,6 @@ import * as actions from '../../store/organizations';
import { addGlobalSuccessMessage } from '../../store/globalMessages';
import { translate, translateWithParameters } from '../../helpers/l10n';
-export const fetchOrganization = (key: string) => (dispatch: Dispatch) => {
- return Promise.all([api.getOrganization(key), api.getOrganizationNavigation(key)]).then(
- ([organization, navigation]) => {
- if (organization) {
- const organizationWithPermissions = { ...organization, ...navigation };
- dispatch(actions.receiveOrganizations([organizationWithPermissions]));
- }
- }
- );
-};
-
export const createOrganization = (organization: T.OrganizationBase) => (
dispatch: Dispatch<any>
) => {
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx
index e74b3a8b651..47a503bf371 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx
@@ -22,7 +22,6 @@ import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { Location } from 'history';
import OrganizationNavigation from '../navigation/OrganizationNavigation';
-import { fetchOrganization } from '../actions';
import NotFound from '../../../app/components/NotFound';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import {
@@ -31,6 +30,7 @@ import {
getMyOrganizations,
Store
} from '../../../store/rootReducer';
+import { fetchOrganization } from '../../../store/rootActions';
interface OwnProps {
children?: React.ReactNode;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
index 611b3f5087f..edee5a58263 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
@@ -261,7 +261,9 @@ export class OverviewApp extends React.PureComponent<Props, State> {
}
}
-const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+const mapDispatchToProps: DispatchToProps = {
+ fetchMetrics
+};
const mapStateToProps = (state: Store): StateToProps => ({
metrics: getMetrics(state)
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaContainer-test.tsx b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaContainer-test.tsx
new file mode 100644
index 00000000000..9117279f2b1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaContainer-test.tsx
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { Meta } from '../MetaContainer';
+import {
+ mockAppState,
+ mockCurrentUser,
+ mockOrganization,
+ mockComponent
+} from '../../../../helpers/testUtils';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ expect(metaQualityGateRendered(wrapper)).toBe(true);
+});
+
+it('should hide QG and QP links if the organization has a paid plan, and the user is not a member', () => {
+ const wrapper = shallowRender({
+ organization: mockOrganization({ key: 'other_key', subscription: 'PAID' })
+ });
+ expect(wrapper).toMatchSnapshot();
+ expect(metaQualityGateRendered(wrapper)).toBe(false);
+});
+
+it('should show QG and QP links if the organization has a paid plan, and the user is a member', () => {
+ const wrapper = shallowRender({
+ organization: mockOrganization({ subscription: 'PAID' })
+ });
+ expect(wrapper).toMatchSnapshot();
+ expect(metaQualityGateRendered(wrapper)).toBe(true);
+});
+
+function metaQualityGateRendered(wrapper: any) {
+ return wrapper.find('#overview-meta-quality-gate').exists();
+}
+
+function shallowRender(props: Partial<Meta['props']> = {}) {
+ return shallow(
+ <Meta
+ appState={mockAppState({ organizationsEnabled: true })}
+ component={mockComponent()}
+ currentUser={mockCurrentUser()}
+ onComponentChange={jest.fn()}
+ organization={mockOrganization()}
+ userOrganizations={[mockOrganization()]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap
new file mode 100644
index 00000000000..fc95c357f57
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap
@@ -0,0 +1,293 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should hide QG and QP links if the organization has a paid plan, and the user is not a member 1`] = `
+<div
+ className="overview-meta"
+>
+ <div
+ className="overview-meta-card"
+ >
+ <h4
+ className="overview-meta-header"
+ >
+ overview.about_this_project.TRK
+ </h4>
+ <MetaTags
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </div>
+ <MetaLinks
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <div
+ className="overview-meta-card"
+ >
+ <MetaKey
+ componentKey="my-project"
+ qualifier="TRK"
+ />
+ <MetaOrganizationKey
+ organization="foo"
+ />
+ </div>
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="overview-meta"
+>
+ <div
+ className="overview-meta-card"
+ >
+ <h4
+ className="overview-meta-header"
+ >
+ overview.about_this_project.TRK
+ </h4>
+ <MetaTags
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </div>
+ <div
+ className="overview-meta-card"
+ id="overview-meta-quality-gate"
+ >
+ <MetaQualityGate
+ organization="foo"
+ qualityGate={
+ Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ }
+ }
+ />
+ <Connect(MetaQualityProfiles)
+ headerClassName="big-spacer-top"
+ organization="foo"
+ profiles={
+ Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ]
+ }
+ />
+ </div>
+ <MetaLinks
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <div
+ className="overview-meta-card"
+ >
+ <MetaKey
+ componentKey="my-project"
+ qualifier="TRK"
+ />
+ <MetaOrganizationKey
+ organization="foo"
+ />
+ </div>
+</div>
+`;
+
+exports[`should show QG and QP links if the organization has a paid plan, and the user is a member 1`] = `
+<div
+ className="overview-meta"
+>
+ <div
+ className="overview-meta-card"
+ >
+ <h4
+ className="overview-meta-header"
+ >
+ overview.about_this_project.TRK
+ </h4>
+ <MetaTags
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </div>
+ <div
+ className="overview-meta-card"
+ id="overview-meta-quality-gate"
+ >
+ <MetaQualityGate
+ organization="foo"
+ qualityGate={
+ Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ }
+ }
+ />
+ <Connect(MetaQualityProfiles)
+ headerClassName="big-spacer-top"
+ organization="foo"
+ profiles={
+ Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ]
+ }
+ />
+ </div>
+ <MetaLinks
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <div
+ className="overview-meta-card"
+ >
+ <MetaKey
+ componentKey="my-project"
+ qualifier="TRK"
+ />
+ <MetaOrganizationKey
+ organization="foo"
+ />
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts
index ac9900803c7..baf32b46814 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts
@@ -20,7 +20,7 @@
import { connect } from 'react-redux';
import App from './App';
import { getCurrentUser, getOrganizationByKey, Store } from '../../../../store/rootReducer';
-import { fetchOrganization } from '../../../organizations/actions';
+import { fetchOrganization } from '../../../../store/rootActions';
interface OwnProps {
component: T.Component;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
index 35419fc25b6..a3f04d6ac86 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
@@ -24,7 +24,7 @@ import forSingleOrganization from '../organizations/forSingleOrganization';
import { getAppState, getOrganizationByKey, getCurrentUser, Store } from '../../store/rootReducer';
import { receiveOrganizations } from '../../store/organizations';
import { changeProjectDefaultVisibility } from '../../api/permissions';
-import { fetchOrganization } from '../organizations/actions';
+import { fetchOrganization } from '../../store/rootActions';
interface StateProps {
appState: { defaultOrganization: string; qualifiers: string[] };
diff --git a/server/sonar-web/src/main/js/store/rootActions.ts b/server/sonar-web/src/main/js/store/rootActions.ts
index adb81802919..4247ac33462 100644
--- a/server/sonar-web/src/main/js/store/rootActions.ts
+++ b/server/sonar-web/src/main/js/store/rootActions.ts
@@ -25,7 +25,7 @@ import { receiveOrganizations } from './organizations';
import * as auth from '../api/auth';
import { getLanguages } from '../api/languages';
import { getAllMetrics } from '../api/metrics';
-import { getOrganizations } from '../api/organizations';
+import { getOrganizations, getOrganization, getOrganizationNavigation } from '../api/organizations';
export function fetchLanguages() {
return (dispatch: Dispatch) => {
@@ -48,6 +48,17 @@ export function fetchOrganizations(organizations: string[]) {
};
}
+export const fetchOrganization = (key: string) => (dispatch: Dispatch) => {
+ return Promise.all([getOrganization(key), getOrganizationNavigation(key)]).then(
+ ([organization, navigation]) => {
+ if (organization) {
+ const organizationWithPermissions = { ...organization, ...navigation };
+ dispatch(receiveOrganizations([organizationWithPermissions]));
+ }
+ }
+ );
+};
+
export function doLogin(login: string, password: string) {
return (dispatch: Dispatch<any>) =>
auth.login(login, password).then(