diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2021-10-13 18:02:42 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-10-22 20:03:27 +0000 |
commit | dd6441108cb87bb35bd6247c160736a9b2835c5a (patch) | |
tree | cc0b1add45422f541690b4780b27bf3f9f2f55c3 /server | |
parent | 9076ba8bee6b8d28ea69ddb811e8951f21420bd4 (diff) | |
download | sonarqube-dd6441108cb87bb35bd6247c160736a9b2835c5a.tar.gz sonarqube-dd6441108cb87bb35bd6247c160736a9b2835c5a.zip |
SONAR-15440 Remove QG permissions for a user
Diffstat (limited to 'server')
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); +} |