diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2023-03-08 16:59:09 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-22 20:04:07 +0000 |
commit | d9803bb1f6e484c7e1c42384b0848952efcb54d5 (patch) | |
tree | ea9bdf92ec8e3b195128eb0b7b16c7e5f736c461 /server/sonar-web/src/main/js/apps/users | |
parent | 9d02b0639e617458d771c4a794db33e70cbb35a1 (diff) | |
download | sonarqube-d9803bb1f6e484c7e1c42384b0848952efcb54d5.tar.gz sonarqube-d9803bb1f6e484c7e1c42384b0848952efcb54d5.zip |
SONAR-18654 Disable user creation when user auto provisioning is enable
Diffstat (limited to 'server/sonar-web/src/main/js/apps/users')
4 files changed, 119 insertions, 39 deletions
diff --git a/server/sonar-web/src/main/js/apps/users/Header.tsx b/server/sonar-web/src/main/js/apps/users/Header.tsx index 4613b130420..a063aed87cd 100644 --- a/server/sonar-web/src/main/js/apps/users/Header.tsx +++ b/server/sonar-web/src/main/js/apps/users/Header.tsx @@ -18,7 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import DocLink from '../../components/common/DocLink'; import { Button } from '../../components/controls/buttons'; +import { Alert } from '../../components/ui/Alert'; import DeferredSpinner from '../../components/ui/DeferredSpinner'; import { translate } from '../../helpers/l10n'; import UserForm from './components/UserForm'; @@ -26,40 +29,49 @@ import UserForm from './components/UserForm'; interface Props { loading: boolean; onUpdateUsers: () => void; + manageProvider?: string; } -interface State { - openUserForm: boolean; -} - -export default class Header extends React.PureComponent<Props, State> { - state: State = { openUserForm: false }; - - handleOpenUserForm = () => { - this.setState({ openUserForm: true }); - }; - - handleCloseUserForm = () => { - this.setState({ openUserForm: false }); - }; +export default function Header(props: Props) { + const [openUserForm, setOpenUserForm] = React.useState(false); - render() { - return ( - <header className="page-header" id="users-header"> - <h1 className="page-title">{translate('users.page')}</h1> - <DeferredSpinner loading={this.props.loading} /> + const { manageProvider, loading } = props; + return ( + <div className="page-header null-spacer-bottom"> + <h2 className="page-title">{translate('users.page')}</h2> + <DeferredSpinner loading={loading} /> - <div className="page-actions"> - <Button id="users-create" onClick={this.handleOpenUserForm}> - {translate('users.create_user')} - </Button> - </div> + <div className="page-actions"> + <Button + id="users-create" + disabled={manageProvider !== undefined} + onClick={() => setOpenUserForm(true)} + > + {translate('users.create_user')} + </Button> + </div> + {manageProvider === undefined ? ( <p className="page-description">{translate('users.page.description')}</p> - {this.state.openUserForm && ( - <UserForm onClose={this.handleCloseUserForm} onUpdateUsers={this.props.onUpdateUsers} /> - )} - </header> - ); - } + ) : ( + <Alert className="page-description max-width-100" variant="info"> + <FormattedMessage + defaultMessage={translate('users.page.managed_description')} + id="users.page.managed_description" + values={{ + provider: manageProvider, + link: ( + <DocLink to="/instance-administration/authentication/overview/"> + {translate('documentation')} + </DocLink> + ), + }} + /> + </Alert> + )} + {openUserForm && ( + <UserForm onClose={() => setOpenUserForm(false)} onUpdateUsers={props.onUpdateUsers} /> + )} + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx index b0d7c36df4f..768b2c8f7e6 100644 --- a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx +++ b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx @@ -19,13 +19,14 @@ */ import * as React from 'react'; import { Helmet } from 'react-helmet-async'; +import { getSystemInfo } from '../../api/system'; import { getIdentityProviders, searchUsers } from '../../api/users'; import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; import ListFooter from '../../components/controls/ListFooter'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; import { translate } from '../../helpers/l10n'; -import { IdentityProvider, Paging } from '../../types/types'; +import { IdentityProvider, Paging, SysInfoCluster } from '../../types/types'; import { CurrentUser, User } from '../../types/users'; import Header from './Header'; import Search from './Search'; @@ -40,6 +41,7 @@ interface Props { interface State { identityProviders: IdentityProvider[]; + manageProvider?: string; loading: boolean; paging?: Paging; users: User[]; @@ -52,6 +54,7 @@ export class UsersApp extends React.PureComponent<Props, State> { componentDidMount() { this.mounted = true; this.fetchIdentityProviders(); + this.fetchManageInstance(); this.fetchUsers(); } @@ -71,6 +74,15 @@ export class UsersApp extends React.PureComponent<Props, State> { } }; + async fetchManageInstance() { + const info = (await getSystemInfo()) as SysInfoCluster; + if (this.mounted) { + this.setState({ + manageProvider: info.System['External Users and Groups Provisioning'], + }); + } + } + fetchIdentityProviders = () => getIdentityProviders().then(({ identityProviders }) => { if (this.mounted) { @@ -116,12 +128,12 @@ export class UsersApp extends React.PureComponent<Props, State> { render() { const query = parseQuery(this.props.location.query); - const { loading, paging, users } = this.state; + const { loading, paging, users, manageProvider } = this.state; return ( <main className="page page-limited" id="users-page"> <Suggestions suggestions="users" /> <Helmet defer={false} title={translate('users.page')} /> - <Header loading={loading} onUpdateUsers={this.fetchUsers} /> + <Header loading={loading} onUpdateUsers={this.fetchUsers} manageProvider={manageProvider} /> <Search query={query} updateQuery={this.updateQuery} /> <UsersList currentUser={this.props.currentUser} diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx new file mode 100644 index 00000000000..3917a11296e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { byRole, byText } from 'testing-library-selector'; +import UsersServiceMock from '../../../api/mocks/UsersServiceMock'; +import { renderApp } from '../../../helpers/testReactTestingUtils'; +import UsersApp from '../UsersApp'; + +jest.mock('../../../api/users'); +jest.mock('../../../api/system'); + +const handler = new UsersServiceMock(); + +const ui = { + createUserButton: byRole('button', { name: 'users.create_user' }), + infoManageMode: byText(/users\.page\.managed_description/), + description: byText('users.page.description'), +}; + +it('should render list of user in non manage mode', async () => { + handler.setIsManaged(false); + renderUsersApp(); + + expect(await ui.description.find()).toBeInTheDocument(); + expect(ui.createUserButton.get()).toBeEnabled(); +}); + +it('should render list of user in manage mode', async () => { + handler.setIsManaged(true); + renderUsersApp(); + + expect(await ui.infoManageMode.find()).toBeInTheDocument(); + expect(ui.createUserButton.get()).toBeDisabled(); +}); + +function renderUsersApp() { + renderApp('admin/users', <UsersApp />); +} diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap index e09d8ad8088..1e3f25b280f 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap @@ -1,15 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly 1`] = ` -<header - className="page-header" - id="users-header" +<div + className="page-header null-spacer-bottom" > - <h1 + <h2 className="page-title" > users.page - </h1> + </h2> <DeferredSpinner loading={true} /> @@ -17,6 +16,7 @@ exports[`should render correctly 1`] = ` className="page-actions" > <Button + disabled={false} id="users-create" onClick={[Function]} > @@ -28,5 +28,5 @@ exports[`should render correctly 1`] = ` > users.page.description </p> -</header> +</div> `; |