diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2018-12-18 11:36:11 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-12-24 20:20:54 +0100 |
commit | 772a9e2f8ee92d98fc6f0114bcea700d8c3e8670 (patch) | |
tree | de793c7c8eccffbd6682e9161d5745a24b5ce19c /server/sonar-web/src/main/js | |
parent | 0225aef248c18e61246b1f50e4ec189e8192511a (diff) | |
download | sonarqube-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')
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( |