diff options
5 files changed, 79 insertions, 62 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/components/App.tsx b/server/sonar-web/src/main/js/apps/groups/components/App.tsx index e282044653b..31cd060f6c9 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/App.tsx @@ -20,13 +20,14 @@ import { omit } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; +import { getSystemInfo } from '../../../api/system'; import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups'; import ListFooter from '../../../components/controls/ListFooter'; import SearchBox from '../../../components/controls/SearchBox'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { translate } from '../../../helpers/l10n'; import { omitNil } from '../../../helpers/request'; -import { Group, Paging } from '../../../types/types'; +import { Group, Paging, SysInfoCluster } from '../../../types/types'; import DeleteForm from './DeleteForm'; import Form from './Form'; import Header from './Header'; @@ -39,6 +40,7 @@ interface State { loading: boolean; paging?: Paging; query: string; + manageProvider?: string; } export default class App extends React.PureComponent<{}, State> { @@ -48,6 +50,7 @@ export default class App extends React.PureComponent<{}, State> { componentDidMount() { this.mounted = true; this.fetchGroups(); + this.fetchManageInstance(); } componentWillUnmount() { @@ -62,6 +65,15 @@ export default class App extends React.PureComponent<{}, State> { }); }; + async fetchManageInstance() { + const info = (await getSystemInfo()) as SysInfoCluster; + if (this.mounted) { + this.setState({ + manageProvider: info.System['External Users and Groups Provisioning'], + }); + } + } + stopLoading = () => { if (this.mounted) { this.setState({ loading: false }); @@ -188,7 +200,8 @@ export default class App extends React.PureComponent<{}, State> { }; render() { - const { editedGroup, groupToBeDeleted, groups, loading, paging, query } = this.state; + const { editedGroup, groupToBeDeleted, groups, loading, paging, query, manageProvider } = + this.state; const showAnyone = 'anyone'.includes(query.toLowerCase()); @@ -197,7 +210,7 @@ export default class App extends React.PureComponent<{}, State> { <Suggestions suggestions="user_groups" /> <Helmet defer={false} title={translate('user_groups.page')} /> <main className="page page-limited" id="groups-page"> - <Header onCreate={this.handleCreate} /> + <Header onCreate={this.handleCreate} manageProvider={manageProvider} /> <SearchBox className="big-spacer-bottom" diff --git a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx index 678446455bd..81e71d6cd8d 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx @@ -18,67 +18,64 @@ * 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 { translate } from '../../../helpers/l10n'; import Form from './Form'; interface Props { onCreate: (data: { description: string; name: string }) => Promise<void>; + manageProvider?: string; } -interface State { - createModal: boolean; -} - -export default class Header extends React.PureComponent<Props, State> { - mounted = false; - state: State = { createModal: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleCreateClick = () => { - this.setState({ createModal: true }); - }; - - handleClose = () => { - if (this.mounted) { - this.setState({ createModal: false }); - } - }; - - handleSubmit = (data: { name: string; description: string }) => { - return this.props.onCreate(data); - }; +export default function Header(props: Props) { + const { manageProvider } = props; + const [createModal, setCreateModal] = React.useState(false); - render() { - return ( - <> - <header className="page-header" id="groups-header"> - <h1 className="page-title">{translate('user_groups.page')}</h1> + return ( + <> + <div className="page-header" id="groups-header"> + <h2 className="page-title">{translate('user_groups.page')}</h2> - <div className="page-actions"> - <Button id="groups-create" onClick={this.handleCreateClick}> - {translate('groups.create_group')} - </Button> - </div> + <div className="page-actions"> + <Button + id="groups-create" + disabled={manageProvider !== undefined} + onClick={() => setCreateModal(true)} + > + {translate('groups.create_group')} + </Button> + </div> + {manageProvider === undefined ? ( <p className="page-description">{translate('user_groups.page.description')}</p> - </header> - {this.state.createModal && ( - <Form - confirmButtonText={translate('create')} - header={translate('groups.create_group')} - onClose={this.handleClose} - onSubmit={this.handleSubmit} - /> + ) : ( + <Alert className="page-description max-width-100 width-100" variant="info"> + <FormattedMessage + defaultMessage={translate('user_groups.page.managed_description')} + id="user_groups.page.managed_description" + values={{ + provider: manageProvider, + link: ( + <DocLink to="/instance-administration/authentication/overview/"> + {translate('documentation')} + </DocLink> + ), + }} + /> + </Alert> )} - </> - ); - } + </div> + {createModal && ( + <Form + confirmButtonText={translate('create')} + header={translate('groups.create_group')} + onClose={() => setCreateModal(false)} + onSubmit={props.onCreate} + /> + )} + </> + ); } diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx index 3c64bcf3a03..8858be2fd5e 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx @@ -57,6 +57,10 @@ jest.mock('../../../../api/user_groups', () => ({ updateGroup: jest.fn().mockResolvedValue({}), })); +jest.mock('../../../../api/system', () => ({ + getSystemInfo: jest.fn().mockResolvedValue({ System: {} }), +})); + beforeEach(() => { jest.clearAllMocks(); }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap index 3a12724c7c9..ba792ca89fc 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap @@ -2,19 +2,20 @@ exports[`should create new group 1`] = ` <Fragment> - <header + <div className="page-header" id="groups-header" > - <h1 + <h2 className="page-title" > user_groups.page - </h1> + </h2> <div className="page-actions" > <Button + disabled={false} id="groups-create" onClick={[Function]} > @@ -26,25 +27,26 @@ exports[`should create new group 1`] = ` > user_groups.page.description </p> - </header> + </div> </Fragment> `; exports[`should create new group 2`] = ` <Fragment> - <header + <div className="page-header" id="groups-header" > - <h1 + <h2 className="page-title" > user_groups.page - </h1> + </h2> <div className="page-actions" > <Button + disabled={false} id="groups-create" onClick={[Function]} > @@ -56,12 +58,12 @@ exports[`should create new group 2`] = ` > user_groups.page.description </p> - </header> + </div> <Form confirmButtonText="create" header="groups.create_group" onClose={[Function]} - onSubmit={[Function]} + onSubmit={[MockFunction]} /> </Fragment> `; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index f0c58f35917..5ea15bc32c3 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4392,6 +4392,7 @@ users.change_admin_password.form.continue_to_app=Continue to SonarQube #------------------------------------------------------------------------------ user_groups.page=Groups user_groups.page.description=Create and administer groups of users. +user_groups.page.managed_description=Your instance is managed by {provider}. No modification is allowed. You can still delete local groups. All other operations should be done on your identity provider. See {link} for help managing groups. user_groups.anyone.description=Anybody who browses the application belongs to this group. If authentication is not enforced, assigned permissions also apply to non-authenticated users. groups.delete_group=Delete Group groups.delete_group.confirmation=Are you sure you want to delete "{0}"? |