From 9076ba8bee6b8d28ea69ddb811e8951f21420bd4 Mon Sep 17 00:00:00 2001
From: Jeremy Davis
Date: Wed, 13 Oct 2021 15:02:59 +0200
Subject: [PATCH] SONAR-15440 Add QG permissions to a user
---
.../src/main/js/api/quality-gates.ts | 5 +
.../components/QualityGatePermissions.tsx | 53 ++-
.../QualityGatePermissionsAddModal.tsx | 115 +++++++
...QualityGatePermissionsAddModalRenderer.tsx | 97 ++++++
.../QualityGatePermissionsRenderer.tsx | 43 ++-
.../__tests__/QualityGatePermissions-test.tsx | 75 ++++-
.../QualityGatePermissionsAddModal-test.tsx | 68 ++++
...tyGatePermissionsAddModalRenderer-test.tsx | 53 +++
.../QualityGatePermissionsRenderer-test.tsx | 14 +-
.../QualityGatePermissions-test.tsx.snap | 11 +
...alityGatePermissionsAddModal-test.tsx.snap | 27 ++
...ePermissionsAddModalRenderer-test.tsx.snap | 316 ++++++++++++++++++
...alityGatePermissionsRenderer-test.tsx.snap | 121 +++++--
.../src/main/js/helpers/mocks/users.ts | 26 ++
.../src/main/js/types/quality-gates.ts | 5 +
.../resources/org/sonar/l10n/core.properties | 3 +-
16 files changed, 990 insertions(+), 42 deletions(-)
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModal-test.tsx
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModal-test.tsx.snap
create mode 100644 server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
create mode 100644 server/sonar-web/src/main/js/helpers/mocks/users.ts
diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts
index ae8a2f61c8b..60cf582e567 100644
--- a/server/sonar-web/src/main/js/api/quality-gates.ts
+++ b/server/sonar-web/src/main/js/api/quality-gates.ts
@@ -21,6 +21,7 @@ import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON, post, postJSON } from '../helpers/request';
import { BranchParameters } from '../types/branch-like';
import {
+ AddDeleteUserPermissionsParameters,
QualityGateApplicationStatus,
QualityGateProjectStatus,
SearchPermissionsParameters
@@ -129,6 +130,10 @@ export function getQualityGateProjectStatus(
.catch(throwGlobalError);
}
+export function addUser(data: AddDeleteUserPermissionsParameters) {
+ return post('/api/qualitygates/add_user', data).catch(throwGlobalError);
+}
+
export function searchUsers(data: SearchPermissionsParameters): Promise<{ users: T.UserBase[] }> {
return getJSON('/api/qualitygates/search_users', data).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx
index c9541a6a364..b4bad1f358a 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx
@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { sortBy } from 'lodash';
import * as React from 'react';
-import { searchUsers } from '../../../api/quality-gates';
+import { addUser, searchUsers } from '../../../api/quality-gates';
import QualityGatePermissionsRenderer from './QualityGatePermissionsRenderer';
interface Props {
@@ -26,14 +27,18 @@ interface Props {
}
interface State {
+ addingUser: boolean;
loading: boolean;
+ showAddModal: boolean;
users: T.UserBase[];
}
export default class QualityGatePermissions extends React.Component {
mounted = false;
state: State = {
+ addingUser: false,
loading: true,
+ showAddModal: false,
users: []
};
@@ -69,8 +74,50 @@ export default class QualityGatePermissions extends React.Component {
+ this.setState({ showAddModal: false });
+ };
+
+ handleClickAddPermission = () => {
+ this.setState({ showAddModal: true });
+ };
+
+ handleSubmitAddPermission = async (user: T.UserBase) => {
+ const { qualityGate } = this.props;
+ this.setState({ addingUser: true });
+
+ let error = false;
+ try {
+ await addUser({ qualityGate: qualityGate.id, userLogin: user.login });
+ } catch (_) {
+ error = true;
+ }
+
+ if (this.mounted) {
+ this.setState(({ users }) => {
+ return {
+ addingUser: false,
+ showAddModal: error,
+ users: sortBy(users.concat(user), ['name'])
+ };
+ });
+ }
+ };
+
render() {
- const { loading, users } = this.state;
- return ;
+ const { qualityGate } = this.props;
+ const { addingUser, loading, showAddModal, users } = this.state;
+ return (
+
+ );
}
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx
new file mode 100644
index 00000000000..ce7c92f7b63
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx
@@ -0,0 +1,115 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { debounce } from 'lodash';
+import * as React from 'react';
+import { searchUsers } from '../../../api/quality-gates';
+import QualityGatePermissionsAddModalRenderer from './QualityGatePermissionsAddModalRenderer';
+
+interface Props {
+ onClose: () => void;
+ onSubmit: (selectedUser: T.UserBase) => void;
+ qualityGate: T.QualityGate;
+ submitting: boolean;
+}
+
+interface State {
+ loading: boolean;
+ query?: string;
+ searchResults: T.UserBase[];
+ selection?: T.UserBase;
+}
+
+const DEBOUNCE_DELAY = 250;
+
+export default class QualityGatePermissionsAddModal extends React.Component {
+ mounted = false;
+ state: State = {
+ loading: false,
+ searchResults: []
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.handleSearch = debounce(this.handleSearch, DEBOUNCE_DELAY);
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleSearch = (query: string) => {
+ const { qualityGate } = this.props;
+ this.setState({ loading: true });
+ searchUsers({ qualityGate: qualityGate.id, q: query, selected: 'deselected' }).then(
+ result => {
+ if (this.mounted) {
+ this.setState({ loading: false, searchResults: result.users });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleInputChange = (query: string) => {
+ this.setState({ query });
+ if (query.length > 1) {
+ this.handleSearch(query);
+ }
+ };
+
+ handleSelection = (selection: T.UserBase) => {
+ this.setState({ selection });
+ };
+
+ handleSubmit = (event: React.SyntheticEvent) => {
+ event.preventDefault();
+ const { selection } = this.state;
+ if (selection) {
+ this.props.onSubmit(selection);
+ }
+ };
+
+ render() {
+ const { submitting } = this.props;
+ const { loading, query = '', searchResults, selection } = this.state;
+
+ return (
+
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
new file mode 100644
index 00000000000..dd4f7355f5d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
+import Modal from '../../../components/controls/Modal';
+import Select from '../../../components/controls/Select';
+import Avatar from '../../../components/ui/Avatar';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+export interface QualityGatePermissionsAddModalRendererProps {
+ onClose: () => void;
+ onInputChange: (query: string) => void;
+ onSubmit: (event: React.SyntheticEvent) => void;
+ onSelection: (selection: T.UserBase) => void;
+ submitting: boolean;
+ loading: boolean;
+ query: string;
+ searchResults: T.UserBase[];
+ selection?: T.UserBase;
+}
+
+type Option = T.UserBase & { value: string };
+
+export default function QualityGatePermissionsAddModalRenderer(
+ props: QualityGatePermissionsAddModalRendererProps
+) {
+ const { loading, query = '', searchResults, selection, submitting } = props;
+
+ const header = translate('quality_gates.permissions.grant');
+
+ const noResultsText =
+ query.length === 1 ? translateWithParameters('select2.tooShort', 2) : translate('no_results');
+
+ return (
+
+
+
+
+ );
+}
+
+function optionRenderer(option: Option) {
+ return (
+ <>
+
+ {option.name}
+ {option.login}
+ >
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx
index 0e60ff10db8..ff2be334b4c 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx
@@ -18,17 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Button } from '../../../components/controls/buttons';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import PermissionItem from './PermissionItem';
+import QualityGatePermissionsAddModal from './QualityGatePermissionsAddModal';
export interface QualityGatePermissionsRendererProps {
+ addingUser: boolean;
loading: boolean;
+ onClickAddPermission: () => void;
+ onCloseAddPermission: () => void;
+ onSubmitAddPermission: (user: T.UserBase) => void;
+ qualityGate: T.QualityGate;
+ showAddModal: boolean;
users: T.UserBase[];
}
export default function QualityGatePermissionsRenderer(props: QualityGatePermissionsRendererProps) {
- const { loading, users } = props;
+ const { addingUser, loading, qualityGate, showAddModal, users } = props;
return (
@@ -36,15 +44,30 @@ export default function QualityGatePermissionsRenderer(props: QualityGatePermiss
{translate('quality_gates.permissions')}
{translate('quality_gates.permissions.help')}
-
-
- {users.map(user => (
-
-
-
- ))}
-
-
+
+
+
+ {users.map(user => (
+
+
+
+ ))}
+
+
+
+
+
+ {translate('quality_gates.permissions.grant')}
+
+
+ {showAddModal && (
+
+ )}
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx
index b89c4a368be..9e750d1f860 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx
@@ -19,27 +19,96 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { searchUsers } from '../../../../api/quality-gates';
+import { addUser, searchUsers } from '../../../../api/quality-gates';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockUserBase } from '../../../../helpers/mocks/users';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import QualityGatePermissions from '../QualityGatePermissions';
jest.mock('../../../../api/quality-gates', () => ({
+ addUser: jest.fn().mockResolvedValue(undefined),
searchUsers: jest.fn().mockResolvedValue({ users: [] })
}));
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
it('should fetch users', async () => {
const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(searchUsers).toBeCalledWith({ qualityGate: '1', selected: 'selected' });
+});
+it('should fetch users on update', async () => {
+ const wrapper = shallowRender();
await waitAndUpdate(wrapper);
- expect(searchUsers).toBeCalledWith({ qualityGate: '1', selected: 'selected' });
+ (searchUsers as jest.Mock).mockClear();
+
+ wrapper.setProps({ qualityGate: mockQualityGate({ id: '2' }) });
+ expect(searchUsers).toBeCalledWith({ qualityGate: '2', selected: 'selected' });
+});
+
+it('should handleCloseAddPermission', () => {
+ const wrapper = shallowRender();
+ wrapper.setState({ showAddModal: true });
+ wrapper.instance().handleCloseAddPermission();
+ expect(wrapper.state().showAddModal).toBe(false);
+});
+
+it('should handleClickAddPermission', () => {
+ const wrapper = shallowRender();
+ wrapper.setState({ showAddModal: false });
+ wrapper.instance().handleClickAddPermission();
+ expect(wrapper.state().showAddModal).toBe(true);
+});
+
+it('should handleSubmitAddPermission', async () => {
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().users).toHaveLength(0);
+
+ wrapper.instance().handleSubmitAddPermission(mockUserBase({ login: 'user1', name: 'User One' }));
+ expect(wrapper.state().addingUser).toBe(true);
+
+ expect(addUser).toBeCalledWith({ qualityGate: '1', userLogin: 'user1' });
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().addingUser).toBe(false);
+ expect(wrapper.state().showAddModal).toBe(false);
+ expect(wrapper.state().users).toHaveLength(1);
+});
+
+it('should handleSubmitAddPermission if it returns an error', async () => {
+ (addUser as jest.Mock).mockRejectedValueOnce(undefined);
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().users).toHaveLength(0);
+
+ wrapper.instance().handleSubmitAddPermission(mockUserBase({ login: 'user1', name: 'User One' }));
+ expect(wrapper.state().addingUser).toBe(true);
+
+ expect(addUser).toBeCalledWith({ qualityGate: '1', userLogin: 'user1' });
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().addingUser).toBe(false);
+ expect(wrapper.state().showAddModal).toBe(true);
+ expect(wrapper.state().users).toHaveLength(1);
});
function shallowRender(overrides: Partial = {}) {
- return shallow( );
+ return shallow(
+
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModal-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModal-test.tsx
new file mode 100644
index 00000000000..59ba7c8e4a9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModal-test.tsx
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { searchUsers } from '../../../../api/quality-gates';
+import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockUserBase } from '../../../../helpers/mocks/users';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import QualityGatePermissionsAddModal from '../QualityGatePermissionsAddModal';
+
+jest.mock('../../../../api/quality-gates', () => ({
+ searchUsers: jest.fn().mockResolvedValue({ users: [] })
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting');
+});
+
+it('should fetch users', async () => {
+ (searchUsers as jest.Mock).mockResolvedValueOnce({ users: [mockUserBase()] });
+
+ const wrapper = shallowRender();
+
+ const query = 'query';
+
+ wrapper.instance().handleSearch(query);
+
+ expect(wrapper.state().loading).toBe(true);
+ expect(searchUsers).toBeCalledWith({ qualityGate: '1', q: query, selected: 'deselected' });
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().loading).toBe(false);
+ expect(wrapper.state().searchResults).toHaveLength(1);
+});
+
+function shallowRender(overrides: Partial = {}) {
+ return shallow(
+
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
new file mode 100644
index 00000000000..5801c783900
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockUserBase } from '../../../../helpers/mocks/users';
+import QualityGatePermissionsAddModalRenderer, {
+ QualityGatePermissionsAddModalRendererProps
+} from '../QualityGatePermissionsAddModalRenderer';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ query: 'a' })).toMatchSnapshot('short query');
+ expect(shallowRender({ selection: mockUserBase() })).toMatchSnapshot('selection');
+ expect(shallowRender({ selection: mockUserBase(), submitting: true })).toMatchSnapshot(
+ 'submitting'
+ );
+ expect(shallowRender({ query: 'ab', searchResults: [mockUserBase()] })).toMatchSnapshot(
+ 'query and results'
+ );
+});
+
+function shallowRender(overrides: Partial = {}) {
+ return shallow(
+
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx
index 6538f6641cb..f18b3c7a067 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
import { mockUser } from '../../../../helpers/testMocks';
import QualityGatePermissionsRenderer, {
QualityGatePermissionsRendererProps
@@ -27,10 +28,21 @@ import QualityGatePermissionsRenderer, {
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('with users');
expect(shallowRender({ users: [] })).toMatchSnapshot('with no users');
+ expect(shallowRender({ showAddModal: true })).toMatchSnapshot('show modal');
});
function shallowRender(overrides: Partial = {}) {
return shallow(
-
+
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap
index ce01fd4ae76..3d21e32513b 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap
@@ -2,7 +2,18 @@
exports[`should render correctly 1`] = `
`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModal-test.tsx.snap
new file mode 100644
index 00000000000..6711d9c224d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModal-test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+
+`;
+
+exports[`should render correctly: submitting 1`] = `
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
new file mode 100644
index 00000000000..34084b8235a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
@@ -0,0 +1,316 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+
+
+
+ quality_gates.permissions.grant
+
+
+
+
+`;
+
+exports[`should render correctly: query and results 1`] = `
+
+
+
+ quality_gates.permissions.grant
+
+
+
+
+`;
+
+exports[`should render correctly: selection 1`] = `
+
+
+
+ quality_gates.permissions.grant
+
+
+
+
+`;
+
+exports[`should render correctly: short query 1`] = `
+
+
+
+ quality_gates.permissions.grant
+
+
+
+
+`;
+
+exports[`should render correctly: submitting 1`] = `
+
+
+
+ quality_gates.permissions.grant
+
+
+
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap
index 1e2f83f6744..31179eaff5a 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap
@@ -1,5 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should render correctly: show modal 1`] = `
+
+
+
+ quality_gates.permissions
+
+
+
+ quality_gates.permissions.help
+
+
+
+ quality_gates.permissions.grant
+
+
+
+`;
+
exports[`should render correctly: with no users 1`] = `
quality_gates.permissions.help
-
+
+
+
+
+
-
-
+ quality_gates.permissions.grant
+
`;
@@ -36,26 +101,34 @@ exports[`should render correctly: with users 1`] = `
>
quality_gates.permissions.help
-
-
+
+
+
+ quality_gates.permissions.grant
+
`;
diff --git a/server/sonar-web/src/main/js/helpers/mocks/users.ts b/server/sonar-web/src/main/js/helpers/mocks/users.ts
new file mode 100644
index 00000000000..b1bd18b7566
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/mocks/users.ts
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+export function mockUserBase(overrides: Partial = {}): T.UserBase {
+ return {
+ login: 'userlogin',
+ ...overrides
+ };
+}
diff --git a/server/sonar-web/src/main/js/types/quality-gates.ts b/server/sonar-web/src/main/js/types/quality-gates.ts
index ae17deb3c0a..425b401d5de 100644
--- a/server/sonar-web/src/main/js/types/quality-gates.ts
+++ b/server/sonar-web/src/main/js/types/quality-gates.ts
@@ -86,3 +86,8 @@ export interface SearchPermissionsParameters {
q?: string;
selected?: 'all' | 'selected' | 'deselected';
}
+
+export interface AddDeleteUserPermissionsParameters {
+ qualityGate: string;
+ userLogin: string;
+}
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 2a56f06c555..2eccc6a6bc3 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1736,7 +1736,8 @@ quality_gates.status=Quality Gate status
quality_gates.help=A Quality Gate is a set of measure-based, Boolean conditions. It helps you know immediately whether your projects are production-ready. Ideally, all projects will use the same quality gate. Each project's Quality Gate status is displayed prominently on its homepage.
quality_gates.permissions=Permissions
quality_gates.permissions.help=Users with the global "Manage Quality Gates" permission can manage this Quality Gate.
-
+quality_gates.permissions.grant=Grant permissions to a user
+quality_gates.permissions.search=Search users by login or name:
#------------------------------------------------------------------------------
#
--
2.39.5