aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2021-10-08 11:35:12 +0200
committersonartech <sonartech@sonarsource.com>2021-10-22 20:03:27 +0000
commitf1dee48a75bd18996797b269aa08ea3758df083b (patch)
treeba84125db18ad23e95ef37fa1850025cbb51dd80 /server/sonar-web/src/main/js
parenta1771496e8535a4fe0197690ebbf048a31970472 (diff)
downloadsonarqube-f1dee48a75bd18996797b269aa08ea3758df083b.tar.gz
sonarqube-f1dee48a75bd18996797b269aa08ea3758df083b.zip
SONAR-15440 Show users with permissions on QG
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/quality-gates.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx76
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx45
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap256
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap25
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/types/permissions.ts3
-rw-r--r--server/sonar-web/src/main/js/types/quality-gates.ts6
16 files changed, 620 insertions, 99 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 9c33f6b60e0..ae8a2f61c8b 100644
--- a/server/sonar-web/src/main/js/api/quality-gates.ts
+++ b/server/sonar-web/src/main/js/api/quality-gates.ts
@@ -20,7 +20,11 @@
import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON, post, postJSON } from '../helpers/request';
import { BranchParameters } from '../types/branch-like';
-import { QualityGateApplicationStatus, QualityGateProjectStatus } from '../types/quality-gates';
+import {
+ QualityGateApplicationStatus,
+ QualityGateProjectStatus,
+ SearchPermissionsParameters
+} from '../types/quality-gates';
export function fetchQualityGates(): Promise<{
actions: { create: boolean };
@@ -124,3 +128,7 @@ export function getQualityGateProjectStatus(
.then(r => r.projectStatus)
.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/DetailsContent.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
index c37b90a2ff0..ffdc7c0b09f 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
@@ -19,12 +19,17 @@
*/
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
+import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
+import { hasGlobalPermission } from '../../../helpers/users';
+import { Permissions } from '../../../types/permissions';
import Conditions from './Conditions';
import Projects from './Projects';
+import QualityGatePermissions from './QualityGatePermissions';
export interface DetailsContentProps {
+ currentUser: T.CurrentUser;
isDefault?: boolean;
metrics: T.Dict<T.Metric>;
onAddCondition: (condition: T.Condition) => void;
@@ -35,9 +40,11 @@ export interface DetailsContentProps {
}
export function DetailsContent(props: DetailsContentProps) {
- const { isDefault, metrics, qualityGate, updatedConditionId } = props;
+ const { currentUser, isDefault, metrics, qualityGate, updatedConditionId } = props;
const conditions = qualityGate.conditions || [];
- const actions = qualityGate.actions || ({} as any);
+ const actions = qualityGate.actions || {};
+
+ const displayPermissions = hasGlobalPermission(currentUser, Permissions.QualityGateAdmin);
return (
<div className="layout-page-main-inner">
@@ -48,7 +55,7 @@ export function DetailsContent(props: DetailsContentProps) {
)}
<Conditions
- canEdit={actions.manageConditions}
+ canEdit={Boolean(actions.manageConditions)}
conditions={conditions}
metrics={metrics}
onAddCondition={props.onAddCondition}
@@ -58,31 +65,38 @@ export function DetailsContent(props: DetailsContentProps) {
updatedConditionId={updatedConditionId}
/>
- <div className="quality-gate-section" id="quality-gate-projects">
- <header className="display-flex-center spacer-bottom">
- <h3>{translate('quality_gates.projects')}</h3>
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div className="big-padded-top big-padded-bottom">
- {translate('quality_gates.projects.help')}
- </div>
- }
- />
- </header>
- {isDefault ? (
- translate('quality_gates.projects_for_default')
- ) : (
- <Projects
- canEdit={actions.associateProjects}
- // pass unique key to re-mount the component when the quality gate changes
- key={qualityGate.id}
- qualityGate={qualityGate}
- />
+ <div className="display-flex-row huge-spacer-top">
+ <div className="quality-gate-section width-50 big-padded-right" id="quality-gate-projects">
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.projects')}</h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div className="big-padded-top big-padded-bottom">
+ {translate('quality_gates.projects.help')}
+ </div>
+ }
+ />
+ </header>
+ {isDefault ? (
+ translate('quality_gates.projects_for_default')
+ ) : (
+ <Projects
+ canEdit={actions.associateProjects}
+ // pass unique key to re-mount the component when the quality gate changes
+ key={qualityGate.id}
+ qualityGate={qualityGate}
+ />
+ )}
+ </div>
+ {displayPermissions && (
+ <div className="width-50 big-padded-left">
+ <QualityGatePermissions qualityGate={qualityGate} />
+ </div>
)}
</div>
</div>
);
}
-export default React.memo(DetailsContent);
+export default React.memo(withCurrentUser(DetailsContent));
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
new file mode 100644
index 00000000000..94cddb85fa0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/PermissionItem.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 Avatar from '../../../components/ui/Avatar';
+
+interface Props {
+ user: T.UserBase;
+}
+
+export default function PermissionItem(props: Props) {
+ const { user } = props;
+
+ return (
+ <div className="display-flex-row">
+ <Avatar className="spacer-right" hash={user.avatar} name={user.name} size={32} />
+
+ <div className="overflow-hidden">
+ <strong>{user.name}</strong>
+ <div className="note">{user.login}</div>
+ </div>
+ </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
new file mode 100644
index 00000000000..c9541a6a364
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 { searchUsers } from '../../../api/quality-gates';
+import QualityGatePermissionsRenderer from './QualityGatePermissionsRenderer';
+
+interface Props {
+ qualityGate: T.QualityGate;
+}
+
+interface State {
+ loading: boolean;
+ users: T.UserBase[];
+}
+
+export default class QualityGatePermissions extends React.Component<Props, State> {
+ mounted = false;
+ state: State = {
+ loading: true,
+ users: []
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+
+ this.fetchPermissions();
+ }
+
+ componentDidUpdate(newProps: Props) {
+ if (this.props.qualityGate.id !== newProps.qualityGate.id) {
+ this.fetchPermissions();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchPermissions = async () => {
+ const { qualityGate } = this.props;
+ this.setState({ loading: true });
+
+ const { users } = await searchUsers({
+ qualityGate: qualityGate.id,
+ selected: 'selected'
+ }).catch(() => ({
+ users: []
+ }));
+
+ if (this.mounted) {
+ this.setState({ loading: false, users });
+ }
+ };
+
+ render() {
+ const { loading, users } = this.state;
+ return <QualityGatePermissionsRenderer loading={loading} 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
new file mode 100644
index 00000000000..0e60ff10db8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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 DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { translate } from '../../../helpers/l10n';
+import PermissionItem from './PermissionItem';
+
+export interface QualityGatePermissionsRendererProps {
+ loading: boolean;
+ users: T.UserBase[];
+}
+
+export default function QualityGatePermissionsRenderer(props: QualityGatePermissionsRendererProps) {
+ const { loading, users } = props;
+
+ return (
+ <div>
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.permissions')}</h3>
+ </header>
+ <p className="spacer-bottom">{translate('quality_gates.permissions.help')}</p>
+ <DeferredSpinner loading={loading}>
+ <ul>
+ {users.map(user => (
+ <li key={user.login} className="spacer-top">
+ <PermissionItem user={user} />
+ </li>
+ ))}
+ </ul>
+ </DeferredSpinner>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx
index 8f8c3c7af2f..1c88e599b0a 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx
@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
-import { mockCondition } from '../../../../helpers/testMocks';
+import { mockCondition, mockLoggedInUser } from '../../../../helpers/testMocks';
import { DetailsContent, DetailsContentProps } from '../DetailsContent';
it('should render correctly', () => {
@@ -29,11 +29,15 @@ it('should render correctly', () => {
expect(
shallowRender({ isDefault: true, qualityGate: mockQualityGate({ conditions: [] }) })
).toMatchSnapshot('is default, no conditions');
+ expect(
+ shallowRender({ currentUser: mockLoggedInUser({ permissions: { global: ['gateadmin'] } }) })
+ ).toMatchSnapshot('Admin');
});
function shallowRender(props: Partial<DetailsContentProps> = {}) {
return shallow(
<DetailsContent
+ currentUser={mockLoggedInUser()}
metrics={{}}
onAddCondition={jest.fn()}
onRemoveCondition={jest.fn()}
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
new file mode 100644
index 00000000000..cf6fc7c6455
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/PermissionItem-test.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 { mockUser } from '../../../../helpers/testMocks';
+import PermissionItem from '../PermissionItem';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender() {
+ return shallow(<PermissionItem 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
new file mode 100644
index 00000000000..b89c4a368be
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissions-test.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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 { waitAndUpdate } from '../../../../helpers/testUtils';
+import QualityGatePermissions from '../QualityGatePermissions';
+
+jest.mock('../../../../api/quality-gates', () => ({
+ searchUsers: jest.fn().mockResolvedValue({ users: [] })
+}));
+
+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' });
+});
+
+function shallowRender(overrides: Partial<QualityGatePermissions['props']> = {}) {
+ return shallow(<QualityGatePermissions qualityGate={mockQualityGate()} {...overrides} />);
+}
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
new file mode 100644
index 00000000000..6538f6641cb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsRenderer-test.tsx
@@ -0,0 +1,36 @@
+/*
+ * 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 { mockUser } from '../../../../helpers/testMocks';
+import QualityGatePermissionsRenderer, {
+ QualityGatePermissionsRendererProps
+} from '../QualityGatePermissionsRenderer';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('with users');
+ expect(shallowRender({ users: [] })).toMatchSnapshot('with no users');
+});
+
+function shallowRender(overrides: Partial<QualityGatePermissionsRendererProps> = {}) {
+ return shallow(
+ <QualityGatePermissionsRenderer loading={false} users={[mockUser()]} {...overrides} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap
index ba5a76662eb..68f3b08cce1 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap
@@ -24,7 +24,7 @@ exports[`should render correctly: loaded 1`] = `
refreshItem={[Function]}
refreshList={[MockFunction]}
/>
- <Memo(DetailsContent)
+ <Memo(Connect(withCurrentUser(DetailsContent)))
isDefault={false}
metrics={Object {}}
onAddCondition={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap
index a8261ab3b2e..1be520d5214 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap
@@ -1,10 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly: is default 1`] = `
+exports[`should render correctly: Admin 1`] = `
<div
className="layout-page-main-inner"
>
<Connect(withAppState(Conditions))
+ canEdit={false}
conditions={
Array [
Object {
@@ -35,27 +36,132 @@ exports[`should render correctly: is default 1`] = `
}
/>
<div
- className="quality-gate-section"
- id="quality-gate-projects"
+ className="display-flex-row huge-spacer-top"
>
- <header
- className="display-flex-center spacer-bottom"
+ <div
+ className="quality-gate-section width-50 big-padded-right"
+ id="quality-gate-projects"
>
- <h3>
- quality_gates.projects
- </h3>
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div
- className="big-padded-top big-padded-bottom"
- >
- quality_gates.projects.help
- </div>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.projects
+ </h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div
+ className="big-padded-top big-padded-bottom"
+ >
+ quality_gates.projects.help
+ </div>
+ }
+ />
+ </header>
+ <Projects
+ key="1"
+ qualityGate={
+ Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
+ "id": "1",
+ "name": "qualitygate",
+ }
}
/>
- </header>
- quality_gates.projects_for_default
+ </div>
+ <div
+ className="width-50 big-padded-left"
+ >
+ <QualityGatePermissions
+ qualityGate={
+ Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
+ "id": "1",
+ "name": "qualitygate",
+ }
+ }
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: is default 1`] = `
+<div
+ className="layout-page-main-inner"
+>
+ <Connect(withAppState(Conditions))
+ canEdit={false}
+ conditions={
+ Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ]
+ }
+ metrics={Object {}}
+ onAddCondition={[MockFunction]}
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
+ "id": "1",
+ "name": "qualitygate",
+ }
+ }
+ />
+ <div
+ className="display-flex-row huge-spacer-top"
+ >
+ <div
+ className="quality-gate-section width-50 big-padded-right"
+ id="quality-gate-projects"
+ >
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.projects
+ </h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div
+ className="big-padded-top big-padded-bottom"
+ >
+ quality_gates.projects.help
+ </div>
+ }
+ />
+ </header>
+ quality_gates.projects_for_default
+ </div>
</div>
</div>
`;
@@ -71,6 +177,7 @@ exports[`should render correctly: is default, no conditions 1`] = `
quality_gates.is_default_no_conditions
</Alert>
<Connect(withAppState(Conditions))
+ canEdit={false}
conditions={Array []}
metrics={Object {}}
onAddCondition={[MockFunction]}
@@ -85,27 +192,31 @@ exports[`should render correctly: is default, no conditions 1`] = `
}
/>
<div
- className="quality-gate-section"
- id="quality-gate-projects"
+ className="display-flex-row huge-spacer-top"
>
- <header
- className="display-flex-center spacer-bottom"
+ <div
+ className="quality-gate-section width-50 big-padded-right"
+ id="quality-gate-projects"
>
- <h3>
- quality_gates.projects
- </h3>
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div
- className="big-padded-top big-padded-bottom"
- >
- quality_gates.projects.help
- </div>
- }
- />
- </header>
- quality_gates.projects_for_default
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.projects
+ </h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div
+ className="big-padded-top big-padded-bottom"
+ >
+ quality_gates.projects.help
+ </div>
+ }
+ />
+ </header>
+ quality_gates.projects_for_default
+ </div>
</div>
</div>
`;
@@ -115,6 +226,7 @@ exports[`should render correctly: is not default 1`] = `
className="layout-page-main-inner"
>
<Connect(withAppState(Conditions))
+ canEdit={false}
conditions={
Array [
Object {
@@ -145,43 +257,47 @@ exports[`should render correctly: is not default 1`] = `
}
/>
<div
- className="quality-gate-section"
- id="quality-gate-projects"
+ className="display-flex-row huge-spacer-top"
>
- <header
- className="display-flex-center spacer-bottom"
+ <div
+ className="quality-gate-section width-50 big-padded-right"
+ id="quality-gate-projects"
>
- <h3>
- quality_gates.projects
- </h3>
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div
- className="big-padded-top big-padded-bottom"
- >
- quality_gates.projects.help
- </div>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.projects
+ </h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div
+ className="big-padded-top big-padded-bottom"
+ >
+ quality_gates.projects.help
+ </div>
+ }
+ />
+ </header>
+ <Projects
+ key="1"
+ qualityGate={
+ Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
+ "id": "1",
+ "name": "qualitygate",
+ }
}
/>
- </header>
- <Projects
- key="1"
- qualityGate={
- Object {
- "conditions": Array [
- Object {
- "error": "10",
- "id": 1,
- "metric": "coverage",
- "op": "LT",
- },
- ],
- "id": "1",
- "name": "qualitygate",
- }
- }
- />
+ </div>
</div>
</div>
`;
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
new file mode 100644
index 00000000000..09a811d368b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="display-flex-row"
+>
+ <Connect(Avatar)
+ className="spacer-right"
+ name="John Doe"
+ size={32}
+ />
+ <div
+ className="overflow-hidden"
+ >
+ <strong>
+ John Doe
+ </strong>
+ <div
+ className="note"
+ >
+ john.doe
+ </div>
+ </div>
+</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
new file mode 100644
index 00000000000..ce01fd4ae76
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissions-test.tsx.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<QualityGatePermissionsRenderer
+ loading={true}
+ users={Array []}
+/>
+`;
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
new file mode 100644
index 00000000000..1e2f83f6744
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsRenderer-test.tsx.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: with no users 1`] = `
+<div>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.permissions
+ </h3>
+ </header>
+ <p
+ className="spacer-bottom"
+ >
+ quality_gates.permissions.help
+ </p>
+ <DeferredSpinner
+ loading={false}
+ >
+ <ul />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: with users 1`] = `
+<div>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.permissions
+ </h3>
+ </header>
+ <p
+ className="spacer-bottom"
+ >
+ quality_gates.permissions.help
+ </p>
+ <DeferredSpinner
+ loading={false}
+ >
+ <ul>
+ <li
+ className="spacer-top"
+ key="john.doe"
+ >
+ <PermissionItem
+ user={
+ Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ }
+ }
+ />
+ </li>
+ </ul>
+ </DeferredSpinner>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/types/permissions.ts b/server/sonar-web/src/main/js/types/permissions.ts
index 2da4b7d21c3..197ed525c21 100644
--- a/server/sonar-web/src/main/js/types/permissions.ts
+++ b/server/sonar-web/src/main/js/types/permissions.ts
@@ -21,5 +21,6 @@
export enum Permissions {
Admin = 'admin',
ProjectCreation = 'provisioning',
- ApplicationCreation = 'applicationcreator'
+ ApplicationCreation = 'applicationcreator',
+ QualityGateAdmin = 'gateadmin'
}
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 34ec4e75f24..ae17deb3c0a 100644
--- a/server/sonar-web/src/main/js/types/quality-gates.ts
+++ b/server/sonar-web/src/main/js/types/quality-gates.ts
@@ -80,3 +80,9 @@ export interface QualityGateStatusCondition {
export interface QualityGateStatusConditionEnhanced extends QualityGateStatusCondition {
measure: T.MeasureEnhanced;
}
+
+export interface SearchPermissionsParameters {
+ qualityGate: string;
+ q?: string;
+ selected?: 'all' | 'selected' | 'deselected';
+}