diff options
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' } |