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 ( + +
+

{header}

+
+
+
+
+ + +
+
+
+ + add_verb + + + cancel + +
+
+
+`; + +exports[`should render correctly: query and results 1`] = ` + +
+

+ quality_gates.permissions.grant +

+
+
+
+
+ + +
+
+
+ + add_verb + + + cancel + +
+
+
+`; + +exports[`should render correctly: short query 1`] = ` + +
+

+ quality_gates.permissions.grant +

+
+
+
+
+ + +
+
+
+ + + add_verb + + + cancel + +
+ +
+`; 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 +

+
+ +
    +
  • + +
  • +
+
+
+ + +
+`; + exports[`should render correctly: with no users 1`] = `
quality_gates.permissions.help

- + +
    + +
+ `; @@ -36,26 +101,34 @@ exports[`should render correctly: with users 1`] = ` > quality_gates.permissions.help

- -
    -
  • - + +
      +
    • + -
    • -
    -
    + /> +
  • +
+
+ + `; 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