aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/api/organizations.ts15
-rw-r--r--server/sonar-web/src/main/js/api/permissions.ts3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/App.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx100
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx48
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap79
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap54
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/store/rootActions.ts24
-rw-r--r--server/sonar-web/src/main/js/types/settings.ts3
17 files changed, 187 insertions, 327 deletions
diff --git a/server/sonar-web/src/main/js/api/organizations.ts b/server/sonar-web/src/main/js/api/organizations.ts
index e5c2a1c0773..dd38f41fa6c 100644
--- a/server/sonar-web/src/main/js/api/organizations.ts
+++ b/server/sonar-web/src/main/js/api/organizations.ts
@@ -36,18 +36,3 @@ export function getOrganization(key: string): Promise<T.Organization | undefined
throwGlobalError
);
}
-
-interface GetOrganizationNavigation {
- adminPages: T.Extension[];
- alm?: { key: string; membersSync: boolean; personal: boolean; url: string };
- canUpdateProjectsVisibilityToPrivate: boolean;
- isDefault: boolean;
- pages: T.Extension[];
-}
-
-export function getOrganizationNavigation(key: string): Promise<GetOrganizationNavigation> {
- return getJSON('/api/navigation/organization', { organization: key }).then(
- r => r.organization,
- throwGlobalError
- );
-}
diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts
index d87932fe7cd..a5b120e0362 100644
--- a/server/sonar-web/src/main/js/api/permissions.ts
+++ b/server/sonar-web/src/main/js/api/permissions.ts
@@ -216,10 +216,9 @@ export function changeProjectVisibility(
}
export function changeProjectDefaultVisibility(
- organization: string,
projectVisibility: T.Visibility
): Promise<void | Response> {
- return post('/api/projects/update_default_visibility', { organization, projectVisibility }).catch(
+ return post('/api/projects/update_default_visibility', { projectVisibility }).catch(
throwGlobalError
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
index 0263a094a3b..a234b778029 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
@@ -20,27 +20,31 @@
import { debounce, uniq, without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { connect } from 'react-redux';
import ListFooter from 'sonar-ui-common/components/controls/ListFooter';
import { toShortNotSoISOString } from 'sonar-ui-common/helpers/dates';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getComponents, Project } from '../../api/components';
+import { changeProjectDefaultVisibility } from '../../api/permissions';
+import { getValues } from '../../api/settings';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import { hasGlobalPermission } from '../../helpers/users';
+import { getAppState, getCurrentUser, Store } from '../../store/rootReducer';
+import { SettingsKey } from '../../types/settings';
import CreateProjectForm from './CreateProjectForm';
import Header from './Header';
import Projects from './Projects';
import Search from './Search';
export interface Props {
- currentUser: Pick<T.LoggedInUser, 'login'>;
- hasProvisionPermission?: boolean;
- onVisibilityChange: (visibility: T.Visibility) => void;
- organization: T.Organization;
- topLevelQualifiers: string[];
+ currentUser: T.LoggedInUser;
+ appState: Pick<T.AppState, 'qualifiers'>;
}
interface State {
analyzedBefore?: Date;
createProjectForm: boolean;
+ defaultProjectVisibility?: T.Visibility;
page: number;
projects: Project[];
provisioned: boolean;
@@ -54,7 +58,7 @@ interface State {
const PAGE_SIZE = 50;
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
@@ -76,12 +80,29 @@ export default class App extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
this.requestProjects();
+ this.fetchDefaultProjectVisibility();
}
componentWillUnmount() {
this.mounted = false;
}
+ fetchDefaultProjectVisibility = async () => {
+ const results = await getValues({ keys: SettingsKey.DefaultProjectVisibility });
+
+ if (this.mounted && results.length > 0 && results[0].value) {
+ this.setState({ defaultProjectVisibility: results[0].value as T.Visibility });
+ }
+ };
+
+ handleDefaultProjectVisibilityChange = async (visibility: T.Visibility) => {
+ await changeProjectDefaultVisibility(visibility);
+
+ if (this.mounted) {
+ this.setState({ defaultProjectVisibility: visibility });
+ }
+ };
+
requestProjects = () => {
const { analyzedBefore } = this.state;
const parameters = {
@@ -93,18 +114,15 @@ export default class App extends React.PureComponent<Props, State> {
qualifiers: this.state.qualifiers,
visibility: this.state.visibility
};
- getComponents(parameters).then(
- r => {
- if (this.mounted) {
- let projects: Project[] = r.components;
- if (this.state.page > 1) {
- projects = [...this.state.projects, ...projects];
- }
- this.setState({ ready: true, projects, selection: [], total: r.paging.total });
+ return getComponents(parameters).then(r => {
+ if (this.mounted) {
+ let projects: Project[] = r.components;
+ if (this.state.page > 1) {
+ projects = [...this.state.projects, ...projects];
}
- },
- () => {}
- );
+ this.setState({ ready: true, projects, selection: [], total: r.paging.total });
+ }
+ });
};
loadMore = () => {
@@ -178,16 +196,18 @@ export default class App extends React.PureComponent<Props, State> {
};
render() {
+ const { appState, currentUser } = this.props;
+ const { defaultProjectVisibility } = this.state;
return (
<div className="page page-limited" id="projects-management-page">
<Suggestions suggestions="projects_management" />
<Helmet defer={false} title={translate('projects_management')} />
<Header
- hasProvisionPermission={this.props.hasProvisionPermission}
+ defaultProjectVisibility={defaultProjectVisibility}
+ hasProvisionPermission={hasGlobalPermission(currentUser, 'provisioning')}
+ onChangeDefaultProjectVisibility={this.handleDefaultProjectVisibilityChange}
onProjectCreate={this.openCreateProjectForm}
- onVisibilityChange={this.props.onVisibilityChange}
- organization={this.props.organization}
/>
<Search
@@ -206,7 +226,7 @@ export default class App extends React.PureComponent<Props, State> {
query={this.state.query}
ready={this.state.ready}
selection={this.state.selection}
- topLevelQualifiers={this.props.topLevelQualifiers}
+ topLevelQualifiers={appState.qualifiers}
total={this.state.total}
visibility={this.state.visibility}
/>
@@ -229,12 +249,19 @@ export default class App extends React.PureComponent<Props, State> {
{this.state.createProjectForm && (
<CreateProjectForm
+ defaultProjectVisibility={defaultProjectVisibility}
onClose={this.closeCreateProjectForm}
onProjectCreated={this.requestProjects}
- organization={this.props.organization}
/>
)}
</div>
);
}
}
+
+const mapStateToProps = (state: Store) => ({
+ appState: getAppState(state),
+ currentUser: getCurrentUser(state) as T.LoggedInUser
+});
+
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
deleted file mode 100644
index 593b7b70407..00000000000
--- a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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 { connect } from 'react-redux';
-import { changeProjectDefaultVisibility } from '../../api/permissions';
-import { receiveOrganizations } from '../../store/organizations';
-import { fetchOrganization } from '../../store/rootActions';
-import { getAppState, getCurrentUser, getOrganizationByKey, Store } from '../../store/rootReducer';
-import App from './App';
-
-interface StateProps {
- appState: { defaultOrganization: string; qualifiers: string[] };
- currentUser: T.LoggedInUser;
- organization?: T.Organization;
-}
-
-interface DispatchProps {
- fetchOrganization: (organization: string) => void;
- onVisibilityChange: (organization: T.Organization, visibility: T.Visibility) => void;
-}
-
-interface OwnProps {
- onRequestFail: (error: any) => void;
- organization: T.Organization;
-}
-
-class AppContainer extends React.PureComponent<OwnProps & StateProps & DispatchProps> {
- componentDidMount() {
- this.props.fetchOrganization(this.props.appState.defaultOrganization);
- }
-
- handleVisibilityChange = (visibility: T.Visibility) => {
- if (this.props.organization) {
- this.props.onVisibilityChange(this.props.organization, visibility);
- }
- };
-
- render() {
- const { organization } = this.props;
-
- if (!organization) {
- return null;
- }
-
- const topLevelQualifiers = organization.isDefault ? this.props.appState.qualifiers : ['TRK'];
- const { actions = {} } = organization;
-
- return (
- <App
- currentUser={this.props.currentUser}
- hasProvisionPermission={actions.provision}
- onVisibilityChange={this.handleVisibilityChange}
- organization={organization}
- topLevelQualifiers={topLevelQualifiers}
- />
- );
- }
-}
-
-const mapStateToProps = (state: Store, ownProps: OwnProps) => ({
- appState: getAppState(state),
- currentUser: getCurrentUser(state) as T.LoggedInUser,
- organization:
- ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
-});
-
-const onVisibilityChange = (organization: T.Organization, visibility: T.Visibility) => (
- dispatch: Function
-) => {
- const currentVisibility = organization.projectVisibility;
- dispatch(receiveOrganizations([{ ...organization, projectVisibility: visibility }]));
- changeProjectDefaultVisibility(organization.key, visibility).catch(() => {
- dispatch(receiveOrganizations([{ ...organization, projectVisibility: currentVisibility }]));
- });
-};
-
-const mapDispatchToProps = (dispatch: Function) => ({
- fetchOrganization: (key: string) => dispatch(fetchOrganization(key)),
- onVisibilityChange: (organization: T.Organization, visibility: T.Visibility) =>
- dispatch(onVisibilityChange(organization, visibility))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx
index 8a037674227..b9545f8f0a7 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx
@@ -25,9 +25,9 @@ import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
export interface Props {
+ defaultVisibility: T.Visibility;
onClose: () => void;
onConfirm: (visiblity: T.Visibility) => void;
- organization: Pick<T.Organization, 'canUpdateProjectsVisibilityToPrivate' | 'projectVisibility'>;
}
interface State {
@@ -37,7 +37,7 @@ interface State {
export default class ChangeDefaultVisibilityForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
- this.state = { visibility: props.organization.projectVisibility as T.Visibility };
+ this.state = { visibility: props.defaultVisibility };
}
handleConfirmClick = () => {
@@ -50,7 +50,6 @@ export default class ChangeDefaultVisibilityForm extends React.PureComponent<Pro
};
render() {
- const { organization } = this.props;
return (
<Modal contentLabel="modal form" onRequestClose={this.props.onClose}>
<header className="modal-head">
@@ -63,10 +62,7 @@ export default class ChangeDefaultVisibilityForm extends React.PureComponent<Pro
<Radio
value={visibility}
checked={this.state.visibility === visibility}
- onCheck={this.handleVisibilityChange}
- disabled={
- visibility === 'private' && !organization.canUpdateProjectsVisibilityToPrivate
- }>
+ onCheck={this.handleVisibilityChange}>
<div>
{translate('visibility', visibility)}
<p className="text-muted spacer-top">
@@ -77,11 +73,9 @@ export default class ChangeDefaultVisibilityForm extends React.PureComponent<Pro
</div>
))}
- {organization.canUpdateProjectsVisibilityToPrivate && (
- <Alert variant="warning">
- {translate('organization.change_visibility_form.warning')}
- </Alert>
- )}
+ <Alert variant="warning">
+ {translate('organization.change_visibility_form.warning')}
+ </Alert>
</div>
<footer className="modal-foot">
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
index 914e1d96a66..e5bf0c2e732 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
@@ -29,9 +29,9 @@ import VisibilitySelector from '../../components/common/VisibilitySelector';
import { getProjectUrl } from '../../helpers/urls';
interface Props {
+ defaultProjectVisibility?: T.Visibility;
onClose: () => void;
onProjectCreated: () => void;
- organization: Pick<T.Organization, 'canUpdateProjectsVisibilityToPrivate' | 'projectVisibility'>;
}
interface State {
@@ -54,7 +54,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
key: '',
loading: false,
name: '',
- visibility: props.organization.projectVisibility
+ visibility: props.defaultProjectVisibility
};
}
@@ -110,9 +110,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
};
render() {
- const {
- organization: { canUpdateProjectsVisibilityToPrivate }
- } = this.props;
+ const { defaultProjectVisibility } = this.props;
const { createdProject } = this.state;
return (
@@ -189,7 +187,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
<div className="modal-field">
<label>{translate('visibility')}</label>
<VisibilitySelector
- canTurnToPrivate={canUpdateProjectsVisibilityToPrivate}
+ canTurnToPrivate={defaultProjectVisibility !== undefined}
className="little-spacer-top"
onChange={this.handleVisibilityChange}
visibility={this.state.visibility}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
index c46a75c0aa9..b43023411fb 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
@@ -23,10 +23,10 @@ import { translate } from 'sonar-ui-common/helpers/l10n';
import ChangeDefaultVisibilityForm from './ChangeDefaultVisibilityForm';
export interface Props {
+ defaultProjectVisibility?: T.Visibility;
hasProvisionPermission?: boolean;
onProjectCreate: () => void;
- onVisibilityChange: (visibility: T.Visibility) => void;
- organization: Pick<T.Organization, 'canUpdateProjectsVisibilityToPrivate' | 'projectVisibility'>;
+ onChangeDefaultProjectVisibility: (visibility: T.Visibility) => void;
}
interface State {
@@ -45,26 +45,28 @@ export default class Header extends React.PureComponent<Props, State> {
};
render() {
- const { organization } = this.props;
+ const { defaultProjectVisibility, hasProvisionPermission } = this.props;
+ const { visibilityForm } = this.state;
return (
<header className="page-header">
<h1 className="page-title">{translate('projects_management')}</h1>
<div className="page-actions">
- {organization.projectVisibility && (
- <span className="big-spacer-right">
- <span className="text-middle">
- {translate('organization.default_visibility_of_new_projects')}{' '}
- <strong>{translate('visibility', organization.projectVisibility)}</strong>
- </span>
- <EditButton
- className="js-change-visibility spacer-left button-small"
- onClick={this.handleChangeVisibilityClick}
- />
+ <span className="big-spacer-right">
+ <span className="text-middle">
+ {translate('organization.default_visibility_of_new_projects')}{' '}
+ <strong>
+ {defaultProjectVisibility ? translate('visibility', defaultProjectVisibility) : '—'}
+ </strong>
</span>
- )}
- {this.props.hasProvisionPermission && (
+ <EditButton
+ className="js-change-visibility spacer-left button-small"
+ onClick={this.handleChangeVisibilityClick}
+ />
+ </span>
+
+ {hasProvisionPermission && (
<Button id="create-project" onClick={this.props.onProjectCreate}>
{translate('qualifiers.create.TRK')}
</Button>
@@ -73,11 +75,11 @@ export default class Header extends React.PureComponent<Props, State> {
<p className="page-description">{translate('projects_management.page.description')}</p>
- {this.state.visibilityForm && (
+ {visibilityForm && (
<ChangeDefaultVisibilityForm
+ defaultVisibility={defaultProjectVisibility || 'public'}
onClose={this.closeVisiblityForm}
- onConfirm={this.props.onVisibilityChange}
- organization={organization}
+ onConfirm={this.props.onChangeDefaultProjectVisibility}
/>
)}
</header>
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
index 2bf5d2725ae..15c7c370d84 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
@@ -19,8 +19,12 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getComponents } from '../../../api/components';
-import App, { Props } from '../App';
+import { changeProjectDefaultVisibility } from '../../../api/permissions';
+import { getValues } from '../../../api/settings';
+import { mockAppState, mockLoggedInUser } from '../../../helpers/testMocks';
+import { App, Props } from '../App';
import Search from '../Search';
jest.mock('lodash', () => {
@@ -33,7 +37,13 @@ jest.mock('../../../api/components', () => ({
getComponents: jest.fn().mockResolvedValue({ paging: { total: 0 }, components: [] })
}));
-const organization: T.Organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+jest.mock('../../../api/permissions', () => ({
+ changeProjectDefaultVisibility: jest.fn().mockResolvedValue({})
+}));
+
+jest.mock('../../../api/settings', () => ({
+ getValues: jest.fn().mockResolvedValue([{ value: 'public' }])
+}));
const defaultSearchParameters = {
p: undefined,
@@ -45,9 +55,12 @@ beforeEach(() => {
jest.clearAllMocks();
});
-it('fetches all projects on mount', () => {
- shallowRender();
+it('fetches all projects on mount', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'TRK' });
+ expect(getValues).toBeCalled();
+ expect(wrapper.state().defaultProjectVisibility).toBe('public');
});
it('selects provisioned', () => {
@@ -86,6 +99,19 @@ it('should handle date filtering', () => {
});
});
+it('should handle default project visibility change', async () => {
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().defaultProjectVisibility).toBe('public');
+ wrapper.instance().handleDefaultProjectVisibilityChange('private');
+
+ expect(changeProjectDefaultVisibility).toBeCalledWith('private');
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().defaultProjectVisibility).toBe('private');
+});
+
it('loads more', () => {
const wrapper = shallowRender();
wrapper.find('ListFooter').prop<Function>('loadMore')();
@@ -136,21 +162,11 @@ it('creates project', () => {
expect(wrapper.find('CreateProjectForm').exists()).toBe(false);
});
-it('changes default project visibility', () => {
- const onVisibilityChange = jest.fn();
- const wrapper = shallowRender({ onVisibilityChange });
- wrapper.find('Header').prop<Function>('onVisibilityChange')('private');
- expect(onVisibilityChange).toBeCalledWith('private');
-});
-
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
return shallow<App>(
<App
- currentUser={{ login: 'foo' }}
- hasProvisionPermission={true}
- onVisibilityChange={jest.fn()}
- organization={organization}
- topLevelQualifiers={['TRK', 'VW', 'APP']}
+ appState={mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] })}
+ currentUser={mockLoggedInUser({ login: 'foo', permissions: { global: ['provisioning'] } })}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx
index 34fbd344a8e..caafbd74c3f 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx
@@ -21,22 +21,8 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import Radio from 'sonar-ui-common/components/controls/Radio';
import { click } from 'sonar-ui-common/helpers/testUtils';
-import { mockOrganization } from '../../../helpers/testMocks';
import ChangeDefaultVisibilityForm from '../ChangeDefaultVisibilityForm';
-const organization: T.Organization = mockOrganization({
- canUpdateProjectsVisibilityToPrivate: true,
- projectVisibility: 'public'
-});
-
-it('renders disabled', () => {
- expect(
- shallowRender({
- organization: { ...organization, canUpdateProjectsVisibilityToPrivate: false }
- })
- ).toMatchSnapshot();
-});
-
it('closes', () => {
const onClose = jest.fn();
const wrapper = shallowRender({ onClose });
@@ -63,9 +49,9 @@ it('changes visibility', () => {
function shallowRender(props: Partial<ChangeDefaultVisibilityForm['props']> = {}) {
return shallow(
<ChangeDefaultVisibilityForm
+ defaultVisibility="public"
onClose={jest.fn()}
onConfirm={jest.fn()}
- organization={organization}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
index c135c2be1b1..3f752cea255 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
@@ -31,19 +31,12 @@ import CreateProjectForm from '../CreateProjectForm';
const createProject = require('../../../api/components').createProject as jest.Mock<any>;
-const organization: T.Organization = {
- actions: { admin: true },
- key: 'org',
- name: 'org',
- projectVisibility: 'public'
-};
-
it('creates project', async () => {
const wrapper = shallow(
<CreateProjectForm
+ defaultProjectVisibility="public"
onClose={jest.fn()}
onProjectCreated={jest.fn()}
- organization={organization}
/>
);
(wrapper.instance() as CreateProjectForm).mounted = true;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
index 3ca70e81917..c114a0d1362 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
@@ -24,14 +24,11 @@ import Header, { Props } from '../Header';
jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn().mockReturnValue(false) }));
-const organization: T.Organization = {
- key: 'org',
- name: 'org',
- projectVisibility: 'public'
-};
-
it('renders', () => {
- expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ defaultProjectVisibility: undefined })).toMatchSnapshot(
+ 'undefined visibility'
+ );
});
it('creates project', () => {
@@ -42,15 +39,15 @@ it('creates project', () => {
});
it('changes default visibility', () => {
- const onVisibilityChange = jest.fn();
- const wrapper = shallowRender({ onVisibilityChange });
+ const onChangeDefaultProjectVisibility = jest.fn();
+ const wrapper = shallowRender({ onChangeDefaultProjectVisibility });
click(wrapper.find('.js-change-visibility'));
const modalWrapper = wrapper.find('ChangeDefaultVisibilityForm');
expect(modalWrapper).toMatchSnapshot();
modalWrapper.prop<Function>('onConfirm')('private');
- expect(onVisibilityChange).toBeCalledWith('private');
+ expect(onChangeDefaultProjectVisibility).toBeCalledWith('private');
modalWrapper.prop<Function>('onClose')();
wrapper.update();
@@ -60,10 +57,10 @@ it('changes default visibility', () => {
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
return shallow(
<Header
+ defaultProjectVisibility="public"
hasProvisionPermission={true}
+ onChangeDefaultProjectVisibility={jest.fn()}
onProjectCreate={jest.fn()}
- onVisibilityChange={jest.fn()}
- organization={organization}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap
index 756189b1ca5..d735f686fb6 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap
@@ -21,7 +21,6 @@ exports[`changes visibility 1`] = `
>
<Radio
checked={true}
- disabled={false}
onCheck={[Function]}
value="public"
>
@@ -41,7 +40,6 @@ exports[`changes visibility 1`] = `
>
<Radio
checked={false}
- disabled={false}
onCheck={[Function]}
value="private"
>
@@ -101,7 +99,6 @@ exports[`changes visibility 2`] = `
>
<Radio
checked={false}
- disabled={false}
onCheck={[Function]}
value="public"
>
@@ -121,7 +118,6 @@ exports[`changes visibility 2`] = `
>
<Radio
checked={true}
- disabled={false}
onCheck={[Function]}
value="private"
>
@@ -159,78 +155,3 @@ exports[`changes visibility 2`] = `
</footer>
</Modal>
`;
-
-exports[`renders disabled 1`] = `
-<Modal
- contentLabel="modal form"
- onRequestClose={[MockFunction]}
->
- <header
- className="modal-head"
- >
- <h2>
- organization.change_visibility_form.header
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <div
- className="big-spacer-bottom"
- key="public"
- >
- <Radio
- checked={true}
- disabled={false}
- onCheck={[Function]}
- value="public"
- >
- <div>
- visibility.public
- <p
- className="text-muted spacer-top"
- >
- visibility.public.description.short
- </p>
- </div>
- </Radio>
- </div>
- <div
- className="big-spacer-bottom"
- key="private"
- >
- <Radio
- checked={false}
- disabled={true}
- onCheck={[Function]}
- value="private"
- >
- <div>
- visibility.private
- <p
- className="text-muted spacer-top"
- >
- visibility.private.description.short
- </p>
- </div>
- </Radio>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <Button
- className="js-confirm"
- onClick={[Function]}
- >
- organization.change_visibility_form.submit
- </Button>
- <ResetButtonLink
- className="js-modal-close"
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
index bf16aa754b7..a5346ffd55e 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
@@ -73,6 +73,7 @@ exports[`creates project 1`] = `
visibility
</label>
<VisibilitySelector
+ canTurnToPrivate={true}
className="little-spacer-top"
onChange={[Function]}
visibility="public"
@@ -172,6 +173,7 @@ exports[`creates project 2`] = `
visibility
</label>
<VisibilitySelector
+ canTurnToPrivate={true}
className="little-spacer-top"
onChange={[Function]}
visibility="private"
@@ -271,6 +273,7 @@ exports[`creates project 3`] = `
visibility
</label>
<VisibilitySelector
+ canTurnToPrivate={true}
className="little-spacer-top"
onChange={[Function]}
visibility="private"
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap
index fba62f83057..fc41e3dd39c 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap
@@ -2,19 +2,13 @@
exports[`changes default visibility 1`] = `
<ChangeDefaultVisibilityForm
+ defaultVisibility="public"
onClose={[Function]}
onConfirm={[MockFunction]}
- organization={
- Object {
- "key": "org",
- "name": "org",
- "projectVisibility": "public",
- }
- }
/>
`;
-exports[`renders 1`] = `
+exports[`renders: default 1`] = `
<header
className="page-header"
>
@@ -57,3 +51,47 @@ exports[`renders 1`] = `
</p>
</header>
`;
+
+exports[`renders: undefined visibility 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ projects_management
+ </h1>
+ <div
+ className="page-actions"
+ >
+ <span
+ className="big-spacer-right"
+ >
+ <span
+ className="text-middle"
+ >
+ organization.default_visibility_of_new_projects
+
+ <strong>
+ —
+ </strong>
+ </span>
+ <EditButton
+ className="js-change-visibility spacer-left button-small"
+ onClick={[Function]}
+ />
+ </span>
+ <Button
+ id="create-project"
+ onClick={[MockFunction]}
+ >
+ qualifiers.create.TRK
+ </Button>
+ </div>
+ <p
+ className="page-description"
+ >
+ projects_management.page.description
+ </p>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts b/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
index 729a854fd60..d82abd3b146 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
@@ -21,7 +21,7 @@ import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'
const routes = [
{
- indexRoute: { component: lazyLoadComponent(() => import('./AppContainer')) }
+ indexRoute: { component: lazyLoadComponent(() => import('./App')) }
}
];
diff --git a/server/sonar-web/src/main/js/store/rootActions.ts b/server/sonar-web/src/main/js/store/rootActions.ts
index c6471073c65..70d9b2a4911 100644
--- a/server/sonar-web/src/main/js/store/rootActions.ts
+++ b/server/sonar-web/src/main/js/store/rootActions.ts
@@ -22,7 +22,7 @@ import { Dispatch } from 'redux';
import * as auth from '../api/auth';
import { getLanguages } from '../api/languages';
import { getAllMetrics } from '../api/metrics';
-import { getOrganization, getOrganizationNavigation } from '../api/organizations';
+import { getOrganization } from '../api/organizations';
import { getQualityGateProjectStatus } from '../api/quality-gates';
import { getBranchLikeQuery } from '../helpers/branch-like';
import { extractStatusConditionsFromProjectStatus } from '../helpers/qualityGates';
@@ -38,7 +38,9 @@ export function fetchLanguages() {
return (dispatch: Dispatch) => {
getLanguages().then(
languages => dispatch(receiveLanguages(languages)),
- () => {}
+ () => {
+ /* do nothing */
+ }
);
};
}
@@ -47,20 +49,18 @@ export function fetchMetrics() {
return (dispatch: Dispatch) => {
getAllMetrics().then(
metrics => dispatch(receiveMetrics(metrics)),
- () => {}
+ () => {
+ /* do nothing */
+ }
);
};
}
-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 const fetchOrganization = (key: string) => async (dispatch: Dispatch) => {
+ const organization = await getOrganization(key);
+ if (organization) {
+ dispatch(receiveOrganizations([organization]));
+ }
};
export function fetchBranchStatus(branchLike: BranchLike, projectKey: string) {
diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts
index 8ad3ca4df61..8588f8b4a29 100644
--- a/server/sonar-web/src/main/js/types/settings.ts
+++ b/server/sonar-web/src/main/js/types/settings.ts
@@ -18,5 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export const enum SettingsKey {
- DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs'
+ DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
+ DefaultProjectVisibility = 'projects.default.visibility'
}