From dda0900b46b558e6cfe0479552852fc5c9456a22 Mon Sep 17 00:00:00 2001
From: Stas Vilchik
Date: Tue, 5 Dec 2017 17:02:00 +0100
Subject: SONAR-10159 display sub-portfolios before projects
---
.../src/main/js/apps/portfolio/components/App.tsx | 17 ++++++++---------
.../js/apps/portfolio/components/__tests__/App-test.tsx | 2 +-
2 files changed, 9 insertions(+), 10 deletions(-)
(limited to 'server')
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
index ec7f35fc0b2..3ca9185e50c 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
@@ -26,12 +26,12 @@ import ReliabilityBox from './ReliabilityBox';
import SecurityBox from './SecurityBox';
import MaintainabilityBox from './MaintainabilityBox';
import Activity from './Activity';
+import { SubComponent } from '../types';
+import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils';
import { getMeasures } from '../../../api/measures';
import { getChildren } from '../../../api/components';
-import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils';
-import { SubComponent } from '../types';
-import '../styles.css';
import { translate } from '../../../helpers/l10n';
+import '../styles.css';
interface Props {
component: { key: string; name: string };
@@ -75,7 +75,7 @@ export default class App extends React.PureComponent {
this.setState({ loading: true });
Promise.all([
getMeasures(this.props.component.key, PORTFOLIO_METRICS),
- getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20 })
+ getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20, s: 'qualifier' })
]).then(
([measures, subComponents]) => {
if (this.mounted) {
@@ -98,10 +98,9 @@ export default class App extends React.PureComponent {
);
}
- isEmpty = () => this.state.measures == undefined || this.state.measures['ncloc'] == undefined;
+ isEmpty = () => !this.state.measures || !this.state.measures['ncloc'];
- isNotComputed = () =>
- this.state.measures && this.state.measures['reliability_rating'] == undefined;
+ isNotComputed = () => this.state.measures && !this.state.measures['reliability_rating'];
renderSpinner() {
return (
@@ -151,8 +150,8 @@ export default class App extends React.PureComponent {
- {subComponents != undefined &&
- totalSubComponents != undefined && (
+ {subComponents !== undefined &&
+ totalSubComponents !== undefined && (
{
'sqale_rating',
'alert_status'
],
- { ps: 20 }
+ { ps: 20, s: 'qualifier' }
);
});
--
cgit v1.2.3
From da80cca3a9fc255e0ebaa1f7ea2ba098725ee7c2 Mon Sep 17 00:00:00 2001
From: Stas Vilchik
Date: Tue, 5 Dec 2017 17:08:22 +0100
Subject: SONAR-10076 Impossible to create a QP using IE11
---
.../src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx | 2 ++
1 file changed, 2 insertions(+)
(limited to 'server')
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
index ed4dbb325ac..bdc3ac85a88 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
@@ -176,6 +176,8 @@ export default class CreateProfileForm extends React.PureComponent
))}
+ {/* drop me when we stop supporting ie11 */}
+
)}
--
cgit v1.2.3
From ae63a6af4780af4527dd453af7ed8923ed6bd07f Mon Sep 17 00:00:00 2001
From: Stas Vilchik
Date: Thu, 7 Dec 2017 09:57:36 +0100
Subject: SONAR-10067 add "Restore Access" action on projects management page
---
.../src/main/js/apps/projectsManagement/App.tsx | 2 +
.../js/apps/projectsManagement/AppContainer.tsx | 5 +-
.../main/js/apps/projectsManagement/ProjectRow.tsx | 34 ++---
.../apps/projectsManagement/ProjectRowActions.tsx | 153 +++++++++++++++++++++
.../main/js/apps/projectsManagement/Projects.tsx | 13 +-
.../apps/projectsManagement/RestoreAccessModal.tsx | 113 +++++++++++++++
.../apps/projectsManagement/__tests__/App-test.tsx | 1 +
.../__tests__/ProjectRow-test.tsx | 11 +-
.../__tests__/ProjectRowActions-test.tsx | 75 ++++++++++
.../projectsManagement/__tests__/Projects-test.tsx | 3 +-
.../__snapshots__/ProjectRow-test.tsx.snap | 119 +++++-----------
.../__snapshots__/ProjectRowActions-test.tsx.snap | 131 ++++++++++++++++++
.../__tests__/__snapshots__/Projects-test.tsx.snap | 20 ++-
.../main/resources/org/sonar/l10n/core.properties | 3 +
14 files changed, 550 insertions(+), 133 deletions(-)
create mode 100644 server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
create mode 100644 server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx
create mode 100644 server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
create mode 100644 server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
(limited to 'server')
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
index f92519a6a9e..c714647d45a 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
@@ -31,6 +31,7 @@ import { Organization } from '../../app/types';
import { translate } from '../../helpers/l10n';
export interface Props {
+ currentUser: { login: string };
hasProvisionPermission?: boolean;
onVisibilityChange: (visibility: string) => void;
organization: Organization;
@@ -191,6 +192,7 @@ export default class App extends React.PureComponent {
/>
void;
onVisibilityChange: (organization: Organization, visibility: string) => void;
onRequestFail: (error: any) => void;
@@ -64,6 +65,7 @@ class AppContainer extends React.PureComponent {
return (
{
const mapStateToProps = (state: any, ownProps: Props) => ({
appState: getAppState(state),
+ currentUser: getCurrentUser(state),
organization:
ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
index ce7df2a1c43..9b243f7ec6b 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
@@ -19,17 +19,17 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
+import ProjectRowActions from './ProjectRowActions';
import { Project } from './utils';
import { Visibility } from '../../app/types';
import PrivateBadge from '../../components/common/PrivateBadge';
import Checkbox from '../../components/controls/Checkbox';
import QualifierIcon from '../../components/shared/QualifierIcon';
-import { translate } from '../../helpers/l10n';
-import { getComponentPermissionsUrl } from '../../helpers/urls';
import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter';
interface Props {
- onApplyTemplateClick: (project: Project) => void;
+ currentUser: { login: string };
+ onApplyTemplate: (project: Project) => void;
onProjectCheck: (project: Project, checked: boolean) => void;
project: Project;
selected: boolean;
@@ -40,12 +40,6 @@ export default class ProjectRow extends React.PureComponent {
this.props.onProjectCheck(this.props.project, checked);
};
- handleApplyTemplateClick = (event: React.SyntheticEvent) => {
- event.preventDefault();
- event.currentTarget.blur();
- this.props.onApplyTemplateClick(this.props.project);
- };
-
render() {
const { project, selected } = this.props;
@@ -82,23 +76,11 @@ export default class ProjectRow extends React.PureComponent {
-
-
- {translate('actions')}
-
-
-
+
);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
new file mode 100644
index 00000000000..f05ba7eef69
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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 { Link } from 'react-router';
+import RestoreAccessModal from './RestoreAccessModal';
+import { Project } from './utils';
+import { getComponentShow } from '../../api/components';
+import { getComponentNavigation } from '../../api/nav';
+import { translate } from '../../helpers/l10n';
+import { getComponentPermissionsUrl } from '../../helpers/urls';
+
+export interface Props {
+ currentUser: { login: string };
+ onApplyTemplate: (project: Project) => void;
+ project: Project;
+}
+
+interface State {
+ hasAccess?: boolean;
+ loading: boolean;
+ restoreAccessModal: boolean;
+}
+
+export default class ProjectRowActions extends React.PureComponent {
+ mounted: boolean;
+ state: State = { loading: false, restoreAccessModal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchPermissions = () => {
+ this.setState({ loading: false });
+ // call `getComponentNavigation` to check if user has the "Administer" permission
+ // call `getComponentShow` to check if user has the "Browse" permission
+ Promise.all([
+ getComponentNavigation(this.props.project.key),
+ getComponentShow(this.props.project.key)
+ ]).then(
+ ([navResponse]) => {
+ if (this.mounted) {
+ const hasAccess = Boolean(
+ navResponse.configuration && navResponse.configuration.showPermissions
+ );
+ this.setState({ hasAccess, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ hasAccess: false, loading: false });
+ }
+ }
+ );
+ };
+
+ handleDropdownClick = () => {
+ if (this.state.hasAccess === undefined && !this.state.loading) {
+ this.fetchPermissions();
+ }
+ };
+
+ handleApplyTemplateClick = (event: React.SyntheticEvent) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onApplyTemplate(this.props.project);
+ };
+
+ handleRestoreAccessClick = (event: React.SyntheticEvent) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ restoreAccessModal: true });
+ };
+
+ handleRestoreAccessClose = () => this.setState({ restoreAccessModal: false });
+
+ handleRestoreAccessDone = () => {
+ this.setState({ hasAccess: true, restoreAccessModal: false });
+ };
+
+ render() {
+ const { hasAccess, loading } = this.state;
+
+ return (
+
+
+ {translate('actions')}
+
+ {loading ? (
+
+
+
+ ) : (
+
+ )}
+
+ {this.state.restoreAccessModal && (
+
+ )}
+
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
index 2a2b3c1cb2c..d3399685066 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
@@ -23,8 +23,10 @@ import ProjectRow from './ProjectRow';
import { Project } from './utils';
import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
import { Organization } from '../../app/types';
+import { translate } from '../../helpers/l10n';
interface Props {
+ currentUser: { login: string };
onProjectDeselected: (project: string) => void;
onProjectSelected: (project: string) => void;
organization: Organization;
@@ -42,7 +44,7 @@ export default class Projects extends React.PureComponent {
}
};
- onApplyTemplateClick = (project: Project) => {
+ handleApplyTemplate = (project: Project) => {
new ApplyTemplateView({ project, organization: this.props.organization }).render();
};
@@ -54,18 +56,19 @@ export default class Projects extends React.PureComponent {
- Name
+ {translate('name')}
- Key
- Last Analysis
+ {translate('key')}
+ {translate('last_analysis')}
{this.props.projects.map(project => (
void;
+ onRestoreAccess: () => void;
+ project: Project;
+}
+
+interface State {
+ loading: boolean;
+}
+
+export default class BulkApplyTemplateModal extends React.PureComponent {
+ mounted: boolean;
+ state: State = { loading: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent) => {
+ event.preventDefault();
+ this.setState({ loading: true });
+ Promise.all([this.grantPermission('user'), this.grantPermission('admin')]).then(
+ this.props.onRestoreAccess,
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ grantPermission = (permission: string) =>
+ grantPermissionToUser(
+ this.props.project.key,
+ this.props.currentUser.login,
+ permission,
+ this.props.project.organization
+ );
+
+ render() {
+ const header = translate('global_permissions.restore_access');
+
+ return (
+
+
+
+
+
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
index 0fb6b1d21d0..36dde628a47 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
@@ -138,6 +138,7 @@ it('changes default project visibility', () => {
function mountRender(props?: { [P in keyof Props]?: Props[P] }) {
return mount(
{
expect(onProjectCheck).toBeCalledWith(project, false);
});
-it('applies permission template', () => {
- const onApplyTemplateClick = jest.fn();
- const wrapper = shallowRender({ onApplyTemplateClick });
- click(wrapper.find('.js-apply-template'));
- expect(onApplyTemplateClick).toBeCalledWith(project);
-});
-
function shallowRender(props?: any) {
return shallow(
({
+ getComponentShow: jest.fn(() => Promise.reject(undefined))
+}));
+
+jest.mock('../../../api/nav', () => ({
+ getComponentNavigation: jest.fn(() => Promise.resolve())
+}));
+
+const project = {
+ id: '',
+ key: 'project',
+ name: 'Project',
+ organization: 'org',
+ qualifier: 'TRK',
+ visibility: Visibility.Private
+};
+
+it('restores access', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.dropdown-toggle'));
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.js-restore-access'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('applies permission template', () => {
+ const onApplyTemplate = jest.fn();
+ const wrapper = shallowRender({ onApplyTemplate });
+ click(wrapper.find('.js-apply-template'));
+ expect(onApplyTemplate).toBeCalledWith(project);
+});
+
+function shallowRender(props: Partial = {}) {
+ const wrapper = shallow(
+
+ );
+ (wrapper.instance() as ProjectRowActions).mounted = true;
+ return wrapper;
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
index 792e29ccc48..23f93bc8d26 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
@@ -59,13 +59,14 @@ it('opens modal to apply permission template', () => {
wrapper
.find('ProjectRow')
.first()
- .prop('onApplyTemplateClick')(projects[0]);
+ .prop('onApplyTemplate')(projects[0]);
expect(ApplyTemplateView).toBeCalledWith({ organization, project: projects[0] });
});
function shallowRender(props?: any) {
return shallow(
-
+
`;
@@ -173,49 +146,23 @@ exports[`renders 2`] = `
-
+
`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
new file mode 100644
index 00000000000..b376ea3f6a6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
@@ -0,0 +1,131 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`restores access 1`] = `
+
+`;
+
+exports[`restores access 2`] = `
+
+`;
+
+exports[`restores access 3`] = `
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
index 2c60880eb28..7bae7a95d3e 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
@@ -9,23 +9,28 @@ exports[`renders list of projects 1`] = `
- Name
+ name
- Key
+ key
- Last Analysis
+ last_analysis