From: Stas Vilchik Date: Mon, 11 Dec 2017 09:39:12 +0000 (+0100) Subject: Merge branch 'branch-6.7' X-Git-Tag: 7.0-RC1~170 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=36685ff3e20875421162206c34aabb31d5b21fdb;p=sonarqube.git Merge branch 'branch-6.7' --- 36685ff3e20875421162206c34aabb31d5b21fdb diff --cc server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx index 00000000000,f05ba7eef69..f5be8f05f05 mode 000000,100644..100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx @@@ -1,0 -1,153 +1,132 @@@ + /* + * 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 ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown'; + 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(); ++ handleApplyTemplateClick = () => { + this.props.onApplyTemplate(this.props.project); + }; + - handleRestoreAccessClick = (event: React.SyntheticEvent) => { - event.preventDefault(); - event.currentTarget.blur(); ++ handleRestoreAccessClick = () => { + this.setState({ restoreAccessModal: true }); + }; + + handleRestoreAccessClose = () => this.setState({ restoreAccessModal: false }); + + handleRestoreAccessDone = () => { + this.setState({ hasAccess: true, restoreAccessModal: false }); + }; + + render() { - const { hasAccess, loading } = this.state; ++ const { hasAccess } = this.state; + + return ( -
- - {loading ? ( -
- -
- ) : ( - ++ ++ {hasAccess === true && ( ++ ++ {translate('edit_permissions')} ++ + )} + ++ {hasAccess === false && ( ++ ++ {translate('global_permissions.restore_access')} ++ ++ )} ++ ++ ++ {translate('projects_role.apply_template')} ++ ++ + {this.state.restoreAccessModal && ( + + )} -
++ + ); + } + } diff --cc server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx index 00000000000,104c71864be..6b1958f6fc5 mode 000000,100644..100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx @@@ -1,0 -1,113 +1,110 @@@ + /* + * SonarQube + * Copyright (C) 2009-2016 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 Modal from 'react-modal'; ++import { FormattedMessage } from 'react-intl'; + import { Project } from './utils'; + import { grantPermissionToUser } from '../../api/permissions'; ++import Modal from '../../components/controls/Modal'; + import { translate } from '../../helpers/l10n'; -import { FormattedMessage } from 'react-intl'; + + interface Props { + currentUser: { login: string }; + onClose: () => void; + onRestoreAccess: () => void; + project: Project; + } + + interface State { + loading: boolean; + } + -export default class BulkApplyTemplateModal extends React.PureComponent { ++export default class RestoreAccessModal 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 ( - -
-

{header}

-
- ++ +
++
++

{header}

++
++ +
+ {translate('projects_role.user')}, + administer: {translate('projects_role.admin')} + }} + /> +
+ + + +
+ ); + } + } diff --cc server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx index 00000000000,b6678d3e45e..ac55dd97de9 mode 000000,100644..100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx @@@ -1,0 -1,75 +1,75 @@@ + /* + * 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 { shallow } from 'enzyme'; + import ProjectRowActions, { Props } from '../ProjectRowActions'; + import { Visibility } from '../../../app/types'; + import { click } from '../../../helpers/testUtils'; + + jest.mock('../../../api/components', () => ({ + 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')); ++ wrapper.prop('onToggleClick')(); + 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 --cc server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap index 00000000000,b376ea3f6a6..a3bae67199d mode 000000,100644..100644 --- 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 @@@ -1,0 -1,131 +1,75 @@@ + // Jest Snapshot v1, https://goo.gl/fbAQLP + + exports[`restores access 1`] = ` -
- - -
++ projects_role.apply_template ++ ++ + `; + + exports[`restores access 2`] = ` - ++ projects_role.apply_template ++ ++ + `; + + exports[`restores access 3`] = ` - ++ + `; diff --cc server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap index 6705b982c5a,7bae7a95d3e..7a3728a28ad --- 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 @@@ -25,8 -25,12 +25,13 @@@ exports[`renders list of projects 1`] void; + small?: boolean; + toggleClassName?: string; +} + +export default function ActionsDropdown({ menuPosition = 'right', ...props }: Props) { + return ( +
+ +
    + {props.children} +
+
+ ); +} + +interface ItemProps { + className?: string; + children: React.ReactNode; + destructive?: boolean; + /** used to pass a name of downloaded file */ + download?: string; + id?: string; + onClick?: () => void; + to?: LocationDescriptor; +} + +export class ActionsDropdownItem extends React.PureComponent { + handleClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + if (this.props.onClick) { + this.props.onClick(); + } + }; + + render() { + const className = classNames(this.props.className, { 'text-danger': this.props.destructive }); + + if (this.props.download && typeof this.props.to === 'string') { + return ( +
  • + + {this.props.children} + +
  • + ); + } + + if (this.props.to) { + return ( +
  • + + {this.props.children} + +
  • + ); + } + + return ( +
  • + + {this.props.children} + +
  • + ); + } +} + +export function ActionsDropdownDivider() { + return
  • ; +}