aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2021-10-13 18:02:42 +0200
committersonartech <sonartech@sonarsource.com>2021-10-22 20:03:27 +0000
commitdd6441108cb87bb35bd6247c160736a9b2835c5a (patch)
treecc0b1add45422f541690b4780b27bf3f9f2f55c3 /server
parent9076ba8bee6b8d28ea69ddb811e8951f21420bd4 (diff)
downloadsonarqube-dd6441108cb87bb35bd6247c160736a9b2835c5a.tar.gz
sonarqube-dd6441108cb87bb35bd6247c160736a9b2835c5a.zip
SONAR-15440 Remove QG permissions for a user
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/quality-gates.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx48
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx66
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModal-test.tsx42
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap5
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap20
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap88
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/styles.css4
14 files changed, 315 insertions, 35 deletions
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 60cf582e567..c4c41090810 100644
--- a/server/sonar-web/src/main/js/api/quality-gates.ts
+++ b/server/sonar-web/src/main/js/api/quality-gates.ts
@@ -134,6 +134,10 @@ export function addUser(data: AddDeleteUserPermissionsParameters) {
return post('/api/qualitygates/add_user', data).catch(throwGlobalError);
}
+export function removeUser(data: AddDeleteUserPermissionsParameters) {
+ return post('/api/qualitygates/remove_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/PermissionItem.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx
index 94cddb85fa0..44a99ffe2d1 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx
@@ -18,9 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { DeleteButton } from '../../../components/controls/buttons';
import Avatar from '../../../components/ui/Avatar';
interface Props {
+ onClickDelete: (user: T.UserBase) => void;
user: T.UserBase;
}
@@ -28,13 +30,15 @@ export default function PermissionItem(props: Props) {
const { user } = props;
return (
- <div className="display-flex-row">
+ <div className="display-flex-center permission-list-item padded">
<Avatar className="spacer-right" hash={user.avatar} name={user.name} size={32} />
- <div className="overflow-hidden">
+ <div className="overflow-hidden flex-1">
<strong>{user.name}</strong>
<div className="note">{user.login}</div>
</div>
+
+ <DeleteButton onClick={() => props.onClickDelete(user)} />
</div>
);
}
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 b4bad1f358a..0e0c415eeb3 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
@@ -19,7 +19,7 @@
*/
import { sortBy } from 'lodash';
import * as React from 'react';
-import { addUser, searchUsers } from '../../../api/quality-gates';
+import { addUser, removeUser, searchUsers } from '../../../api/quality-gates';
import QualityGatePermissionsRenderer from './QualityGatePermissionsRenderer';
interface Props {
@@ -27,16 +27,17 @@ interface Props {
}
interface State {
- addingUser: boolean;
+ submitting: boolean;
loading: boolean;
showAddModal: boolean;
+ userPermissionToDelete?: T.UserBase;
users: T.UserBase[];
}
export default class QualityGatePermissions extends React.Component<Props, State> {
mounted = false;
state: State = {
- addingUser: false,
+ submitting: false,
loading: true,
showAddModal: false,
users: []
@@ -84,38 +85,67 @@ export default class QualityGatePermissions extends React.Component<Props, State
handleSubmitAddPermission = async (user: T.UserBase) => {
const { qualityGate } = this.props;
- this.setState({ addingUser: true });
+ this.setState({ submitting: true });
let error = false;
try {
await addUser({ qualityGate: qualityGate.id, userLogin: user.login });
- } catch (_) {
+ } catch {
error = true;
}
if (this.mounted) {
this.setState(({ users }) => {
return {
- addingUser: false,
+ submitting: false,
showAddModal: error,
- users: sortBy(users.concat(user), ['name'])
+ users: sortBy(users.concat(user), u => u.name)
};
});
}
};
+ handleCloseDeletePermission = () => {
+ this.setState({ userPermissionToDelete: undefined });
+ };
+
+ handleClickDeletePermission = (userPermissionToDelete?: T.UserBase) => {
+ this.setState({ userPermissionToDelete });
+ };
+
+ handleConfirmDeletePermission = async (user: T.UserBase) => {
+ const { qualityGate } = this.props;
+
+ let error = false;
+ try {
+ await removeUser({ qualityGate: qualityGate.id, userLogin: user.login });
+ } catch {
+ error = true;
+ }
+
+ if (this.mounted && !error) {
+ this.setState(({ users }) => ({
+ users: users.filter(u => u.login !== user.login)
+ }));
+ }
+ };
+
render() {
const { qualityGate } = this.props;
- const { addingUser, loading, showAddModal, users } = this.state;
+ const { submitting, loading, showAddModal, userPermissionToDelete, users } = this.state;
return (
<QualityGatePermissionsRenderer
- addingUser={addingUser}
loading={loading}
onClickAddPermission={this.handleClickAddPermission}
onCloseAddPermission={this.handleCloseAddPermission}
onSubmitAddPermission={this.handleSubmitAddPermission}
+ onCloseDeletePermission={this.handleCloseDeletePermission}
+ onClickDeletePermission={this.handleClickDeletePermission}
+ onConfirmDeletePermission={this.handleConfirmDeletePermission}
qualityGate={qualityGate}
showAddModal={showAddModal}
+ submitting={submitting}
+ userPermissionToDelete={userPermissionToDelete}
users={users}
/>
);
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 ff2be334b4c..bf29803a3a8 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,28 +18,34 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import { Button } from '../../../components/controls/buttons';
+import ConfirmModal from '../../../components/controls/ConfirmModal';
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;
+ onCloseDeletePermission: () => void;
+ onConfirmDeletePermission: (user: T.UserBase) => void;
+ onClickDeletePermission: (user: T.UserBase) => void;
qualityGate: T.QualityGate;
showAddModal: boolean;
+ submitting: boolean;
+ userPermissionToDelete?: T.UserBase;
users: T.UserBase[];
}
export default function QualityGatePermissionsRenderer(props: QualityGatePermissionsRendererProps) {
- const { addingUser, loading, qualityGate, showAddModal, users } = props;
+ const { loading, qualityGate, showAddModal, submitting, userPermissionToDelete, users } = props;
return (
- <div>
+ <div className="quality-gate-permissions">
<header className="display-flex-center spacer-bottom">
<h3>{translate('quality_gates.permissions')}</h3>
</header>
@@ -48,8 +54,8 @@ export default function QualityGatePermissionsRenderer(props: QualityGatePermiss
<DeferredSpinner loading={loading}>
<ul>
{users.map(user => (
- <li key={user.login} className="spacer-top">
- <PermissionItem user={user} />
+ <li key={user.login}>
+ <PermissionItem onClickDelete={props.onClickDeletePermission} user={user} />
</li>
))}
</ul>
@@ -65,9 +71,27 @@ export default function QualityGatePermissionsRenderer(props: QualityGatePermiss
qualityGate={qualityGate}
onClose={props.onCloseAddPermission}
onSubmit={props.onSubmitAddPermission}
- submitting={addingUser}
+ submitting={submitting}
/>
)}
+
+ {userPermissionToDelete && (
+ <ConfirmModal
+ header={translate('users.remove')}
+ confirmButtonText={translate('remove')}
+ isDestructive={true}
+ confirmData={userPermissionToDelete}
+ onClose={props.onCloseDeletePermission}
+ onConfirm={props.onConfirmDeletePermission}>
+ <FormattedMessage
+ defaultMessage={translate('users.remove.confirmation')}
+ id="users.remove.confirmation"
+ values={{
+ user: <strong>{userPermissionToDelete.name}</strong>
+ }}
+ />
+ </ConfirmModal>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx
index cf6fc7c6455..4724e340757 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx
@@ -27,5 +27,5 @@ it('should render correctly', () => {
});
function shallowRender() {
- return shallow(<PermissionItem user={mockUser()} />);
+ return shallow(<PermissionItem onClickDelete={jest.fn()} user={mockUser()} />);
}
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 9e750d1f860..04eccce3a1c 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,7 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { addUser, searchUsers } from '../../../../api/quality-gates';
+import { addUser, removeUser, searchUsers } from '../../../../api/quality-gates';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
import { mockUserBase } from '../../../../helpers/mocks/users';
import { waitAndUpdate } from '../../../../helpers/testUtils';
@@ -27,6 +27,7 @@ import QualityGatePermissions from '../QualityGatePermissions';
jest.mock('../../../../api/quality-gates', () => ({
addUser: jest.fn().mockResolvedValue(undefined),
+ removeUser: jest.fn().mockResolvedValue(undefined),
searchUsers: jest.fn().mockResolvedValue({ users: [] })
}));
@@ -76,13 +77,13 @@ it('should handleSubmitAddPermission', async () => {
expect(wrapper.state().users).toHaveLength(0);
wrapper.instance().handleSubmitAddPermission(mockUserBase({ login: 'user1', name: 'User One' }));
- expect(wrapper.state().addingUser).toBe(true);
+ expect(wrapper.state().submitting).toBe(true);
expect(addUser).toBeCalledWith({ qualityGate: '1', userLogin: 'user1' });
await waitAndUpdate(wrapper);
- expect(wrapper.state().addingUser).toBe(false);
+ expect(wrapper.state().submitting).toBe(false);
expect(wrapper.state().showAddModal).toBe(false);
expect(wrapper.state().users).toHaveLength(1);
});
@@ -96,17 +97,72 @@ it('should handleSubmitAddPermission if it returns an error', async () => {
expect(wrapper.state().users).toHaveLength(0);
wrapper.instance().handleSubmitAddPermission(mockUserBase({ login: 'user1', name: 'User One' }));
- expect(wrapper.state().addingUser).toBe(true);
+ expect(wrapper.state().submitting).toBe(true);
expect(addUser).toBeCalledWith({ qualityGate: '1', userLogin: 'user1' });
await waitAndUpdate(wrapper);
- expect(wrapper.state().addingUser).toBe(false);
+ expect(wrapper.state().submitting).toBe(false);
expect(wrapper.state().showAddModal).toBe(true);
expect(wrapper.state().users).toHaveLength(1);
});
+it('should handleCloseDeletePermission', () => {
+ const wrapper = shallowRender();
+ wrapper.setState({ userPermissionToDelete: mockUserBase() });
+ wrapper.instance().handleCloseDeletePermission();
+ expect(wrapper.state().userPermissionToDelete).toBeUndefined();
+});
+
+it('should handleClickDeletePermission', () => {
+ const user = mockUserBase();
+
+ const wrapper = shallowRender();
+ wrapper.setState({ userPermissionToDelete: undefined });
+ wrapper.instance().handleClickDeletePermission(user);
+ expect(wrapper.state().userPermissionToDelete).toBe(user);
+});
+
+it('should handleConfirmDeletePermission', async () => {
+ const deleteThisUser = mockUserBase();
+ (searchUsers as jest.Mock).mockResolvedValueOnce({ users: [deleteThisUser] });
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().users).toHaveLength(1);
+
+ wrapper.instance().handleConfirmDeletePermission(deleteThisUser);
+
+ expect(removeUser).toBeCalledWith({ qualityGate: '1', userLogin: deleteThisUser.login });
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().userPermissionToDelete).toBeUndefined();
+ expect(wrapper.state().users).toHaveLength(0);
+});
+
+it('should handleConfirmDeletePermission if it returns an error', async () => {
+ const deleteThisUser = mockUserBase();
+ (searchUsers as jest.Mock).mockResolvedValueOnce({ users: [deleteThisUser] });
+ (removeUser as jest.Mock).mockRejectedValueOnce(undefined);
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().users).toHaveLength(1);
+
+ wrapper.instance().handleConfirmDeletePermission(deleteThisUser);
+
+ expect(removeUser).toBeCalledWith({ qualityGate: '1', userLogin: deleteThisUser.login });
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().userPermissionToDelete).toBeUndefined();
+ expect(wrapper.state().users).toHaveLength(1);
+});
+
function shallowRender(overrides: Partial<QualityGatePermissions['props']> = {}) {
return shallow<QualityGatePermissions>(
<QualityGatePermissions qualityGate={mockQualityGate()} {...overrides} />
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
index 59ba7c8e4a9..766688a5fff 100644
--- 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
@@ -22,7 +22,7 @@ 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 { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
import QualityGatePermissionsAddModal from '../QualityGatePermissionsAddModal';
jest.mock('../../../../api/quality-gates', () => ({
@@ -32,6 +32,7 @@ jest.mock('../../../../api/quality-gates', () => ({
beforeEach(() => {
jest.clearAllMocks();
});
+
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting');
@@ -55,6 +56,45 @@ it('should fetch users', async () => {
expect(wrapper.state().searchResults).toHaveLength(1);
});
+it('should handle input change', () => {
+ const wrapper = shallowRender();
+
+ wrapper.instance().handleSearch = jest.fn();
+ const { handleSearch } = wrapper.instance();
+
+ wrapper.instance().handleInputChange('a');
+
+ expect(wrapper.state().query).toBe('a');
+ expect(handleSearch).not.toBeCalled();
+
+ const query = 'query';
+ wrapper.instance().handleInputChange(query);
+
+ expect(wrapper.state().query).toBe(query);
+ expect(handleSearch).toBeCalledWith(query);
+});
+
+it('should handleSelection', () => {
+ const wrapper = shallowRender();
+ const selection = mockUserBase();
+ wrapper.instance().handleSelection(selection);
+ expect(wrapper.state().selection).toBe(selection);
+});
+
+it('should handleSubmit', () => {
+ const onSubmit = jest.fn();
+ const wrapper = shallowRender({ onSubmit });
+
+ wrapper.instance().handleSubmit(mockEvent());
+
+ expect(onSubmit).not.toBeCalled();
+
+ const selection = mockUserBase();
+ wrapper.setState({ selection });
+ wrapper.instance().handleSubmit(mockEvent());
+ expect(onSubmit).toBeCalledWith(selection);
+});
+
function shallowRender(overrides: Partial<QualityGatePermissionsAddModal['props']> = {}) {
return shallow<QualityGatePermissionsAddModal>(
<QualityGatePermissionsAddModal
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
index 5801c783900..6fddecf8d0f 100644
--- 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
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import Select from '../../../../components/controls/Select';
import { mockUserBase } from '../../../../helpers/mocks/users';
import QualityGatePermissionsAddModalRenderer, {
QualityGatePermissionsAddModalRendererProps
@@ -36,6 +37,14 @@ it('should render correctly', () => {
);
});
+it('should render options correctly', () => {
+ const wrapper = shallowRender();
+
+ const { optionRenderer = () => null } = wrapper.find(Select).props();
+
+ expect(optionRenderer({ avatar: 'avatar', name: 'name', login: 'login' })).toMatchSnapshot();
+});
+
function shallowRender(overrides: Partial<QualityGatePermissionsAddModalRendererProps> = {}) {
return shallow(
<QualityGatePermissionsAddModalRenderer
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 f18b3c7a067..d190f4b89af 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
@@ -20,6 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockUserBase } from '../../../../helpers/mocks/users';
import { mockUser } from '../../../../helpers/testMocks';
import QualityGatePermissionsRenderer, {
QualityGatePermissionsRendererProps
@@ -28,19 +29,25 @@ 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');
+ expect(shallowRender({ showAddModal: true })).toMatchSnapshot('show add modal');
+ expect(shallowRender({ userPermissionToDelete: mockUserBase() })).toMatchSnapshot(
+ 'show remove modal'
+ );
});
function shallowRender(overrides: Partial<QualityGatePermissionsRendererProps> = {}) {
return shallow(
<QualityGatePermissionsRenderer
- addingUser={false}
loading={false}
onClickAddPermission={jest.fn()}
onCloseAddPermission={jest.fn()}
onSubmitAddPermission={jest.fn()}
+ onCloseDeletePermission={jest.fn()}
+ onClickDeletePermission={jest.fn()}
+ onConfirmDeletePermission={jest.fn()}
qualityGate={mockQualityGate()}
showAddModal={false}
+ submitting={false}
users={[mockUser()]}
{...overrides}
/>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap
index 09a811d368b..41a7f6881c1 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should render correctly 1`] = `
<div
- className="display-flex-row"
+ className="display-flex-center permission-list-item padded"
>
<Connect(Avatar)
className="spacer-right"
@@ -10,7 +10,7 @@ exports[`should render correctly 1`] = `
size={32}
/>
<div
- className="overflow-hidden"
+ className="overflow-hidden flex-1"
>
<strong>
John Doe
@@ -21,5 +21,8 @@ exports[`should render correctly 1`] = `
john.doe
</div>
</div>
+ <DeleteButton
+ onClick={[Function]}
+ />
</div>
`;
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 3d21e32513b..dc40f7963b4 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,10 +2,12 @@
exports[`should render correctly 1`] = `
<QualityGatePermissionsRenderer
- addingUser={false}
loading={true}
onClickAddPermission={[Function]}
+ onClickDeletePermission={[Function]}
onCloseAddPermission={[Function]}
+ onCloseDeletePermission={[Function]}
+ onConfirmDeletePermission={[Function]}
onSubmitAddPermission={[Function]}
qualityGate={
Object {
@@ -14,6 +16,7 @@ exports[`should render correctly 1`] = `
}
}
showAddModal={false}
+ submitting={false}
users={Array []}
/>
`;
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
index 34084b8235a..41c4bb96a8d 100644
--- 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
@@ -314,3 +314,23 @@ exports[`should render correctly: submitting 1`] = `
</form>
</Modal>
`;
+
+exports[`should render options correctly 1`] = `
+<React.Fragment>
+ <Connect(Avatar)
+ hash="avatar"
+ name="name"
+ size={16}
+ />
+ <strong
+ className="spacer-left"
+ >
+ name
+ </strong>
+ <span
+ className="note little-spacer-left"
+ >
+ login
+ </span>
+</React.Fragment>
+`;
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 31179eaff5a..22f6cdc7bc0 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,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly: show modal 1`] = `
-<div>
+exports[`should render correctly: show add modal 1`] = `
+<div
+ className="quality-gate-permissions"
+>
<header
className="display-flex-center spacer-bottom"
>
@@ -20,10 +22,10 @@ exports[`should render correctly: show modal 1`] = `
>
<ul>
<li
- className="spacer-top"
key="john.doe"
>
<PermissionItem
+ onClickDelete={[MockFunction]}
user={
Object {
"active": true,
@@ -57,8 +59,80 @@ exports[`should render correctly: show modal 1`] = `
</div>
`;
+exports[`should render correctly: show remove modal 1`] = `
+<div
+ className="quality-gate-permissions"
+>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.permissions
+ </h3>
+ </header>
+ <p
+ className="spacer-bottom"
+ >
+ quality_gates.permissions.help
+ </p>
+ <div>
+ <DeferredSpinner
+ loading={false}
+ >
+ <ul>
+ <li
+ key="john.doe"
+ >
+ <PermissionItem
+ onClickDelete={[MockFunction]}
+ user={
+ Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ }
+ }
+ />
+ </li>
+ </ul>
+ </DeferredSpinner>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[MockFunction]}
+ >
+ quality_gates.permissions.grant
+ </Button>
+ <ConfirmModal
+ confirmButtonText="remove"
+ confirmData={
+ Object {
+ "login": "userlogin",
+ }
+ }
+ header="users.remove"
+ isDestructive={true}
+ onClose={[MockFunction]}
+ onConfirm={[MockFunction]}
+ >
+ <FormattedMessage
+ defaultMessage="users.remove.confirmation"
+ id="users.remove.confirmation"
+ values={
+ Object {
+ "user": <strong />,
+ }
+ }
+ />
+ </ConfirmModal>
+</div>
+`;
+
exports[`should render correctly: with no users 1`] = `
-<div>
+<div
+ className="quality-gate-permissions"
+>
<header
className="display-flex-center spacer-bottom"
>
@@ -88,7 +162,9 @@ exports[`should render correctly: with no users 1`] = `
`;
exports[`should render correctly: with users 1`] = `
-<div>
+<div
+ className="quality-gate-permissions"
+>
<header
className="display-flex-center spacer-bottom"
>
@@ -107,10 +183,10 @@ exports[`should render correctly: with users 1`] = `
>
<ul>
<li
- className="spacer-top"
key="john.doe"
>
<PermissionItem
+ onClickDelete={[MockFunction]}
user={
Object {
"active": true,
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/styles.css b/server/sonar-web/src/main/js/apps/quality-gates/styles.css
index 3a2201b62af..f90273b2018 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/styles.css
+++ b/server/sonar-web/src/main/js/apps/quality-gates/styles.css
@@ -37,3 +37,7 @@
padding-left: var(--gridSize);
font-style: normal;
}
+
+.quality-gate-permissions .permission-list-item:hover {
+ background-color: var(--rowHoverHighlight);
+}