From 6dabceb176660fbed4195214d39c8f348fbe2c32 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Fri, 16 Feb 2018 09:12:23 +0100 Subject: [PATCH] rewrite parts of permission templates app in react (#3070) --- .../sonar-web/src/main/js/api/permissions.ts | 25 +-- server/sonar-web/src/main/js/app/types.ts | 17 ++ .../{ActionsCell.js => ActionsCell.tsx} | 135 ++++++++++----- .../permission-templates/components/Form.tsx | 154 +++++++++++++++++ .../permission-templates/components/Header.js | 75 --------- .../components/Header.tsx | 107 ++++++++++++ .../templates/permission-templates-delete.hbs | 13 -- .../templates/permission-templates-form.hbs | 34 ---- .../permission-templates/views/CreateView.js | 46 ------ .../permission-templates/views/DeleteView.js | 45 ----- .../permission-templates/views/FormView.js | 44 ----- .../permission-templates/views/UpdateView.js | 43 ----- .../project/components/ApplyTemplate.tsx | 155 ++++++++++++++++++ .../{PageHeader.js => PageHeader.tsx} | 76 +++++---- .../templates/ApplyTemplateTemplate.hbs | 41 ----- .../project/views/ApplyTemplateView.js | 78 --------- .../BulkApplyTemplateModal.tsx | 7 +- .../js/apps/projectsManagement/ProjectRow.tsx | 8 +- .../projectsManagement/ProjectRowActions.tsx | 22 ++- .../js/apps/projectsManagement/Projects.tsx | 7 +- .../__tests__/ProjectRowActions-test.tsx | 7 +- .../__tests__/Projects-test.tsx | 13 -- .../__snapshots__/ProjectRow-test.tsx.snap | 2 - .../ProjectRowActions-test.tsx.snap | 17 ++ .../__snapshots__/Projects-test.tsx.snap | 4 +- 25 files changed, 622 insertions(+), 553 deletions(-) rename server/sonar-web/src/main/js/apps/permission-templates/components/{ActionsCell.js => ActionsCell.tsx} (53%) create mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/Header.js create mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-delete.hbs delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js create mode 100644 server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx rename server/sonar-web/src/main/js/apps/permissions/project/components/{PageHeader.js => PageHeader.tsx} (60%) delete mode 100644 server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs delete mode 100644 server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts index b87dbc9956d..465d4fedea3 100644 --- a/server/sonar-web/src/main/js/api/permissions.ts +++ b/server/sonar-web/src/main/js/api/permissions.ts @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { BaseSearchProjectsParameters } from './components'; +import { PermissionTemplate } from '../app/types'; +import throwGlobalError from '../app/utils/throwGlobalError'; import { getJSON, post, postJSON, RequestData } from '../helpers/request'; const PAGE_SIZE = 100; @@ -86,21 +88,6 @@ export function revokePermissionFromGroup( return post('/api/permissions/remove_group', data); } -export interface PermissionTemplate { - id: string; - name: string; - description?: string; - projectKeyPattern?: string; - createdAt: string; - updatedAt?: string; - permissions: Array<{ - key: string; - usersCount: number; - groupsCount: number; - withProjectCreator?: boolean; - }>; -} - interface GetPermissionTemplatesResponse { permissionTemplates: PermissionTemplate[]; defaultTemplates: Array<{ templateId: string; qualifier: string }>; @@ -122,8 +109,8 @@ export function updatePermissionTemplate(data: RequestData): Promise { return post('/api/permissions/update_template', data); } -export function deletePermissionTemplate(data: RequestData): Promise { - return post('/api/permissions/delete_template', data); +export function deletePermissionTemplate(data: RequestData) { + return post('/api/permissions/delete_template', data).catch(throwGlobalError); } /** @@ -133,8 +120,8 @@ export function setDefaultPermissionTemplate(templateId: string, qualifier: stri return post('/api/permissions/set_default_template', { templateId, qualifier }); } -export function applyTemplateToProject(data: RequestData): Promise { - return post('/api/permissions/apply_template', data); +export function applyTemplateToProject(data: RequestData) { + return post('/api/permissions/apply_template', data).catch(throwGlobalError); } export function bulkApplyTemplate(data: BaseSearchProjectsParameters): Promise { diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 504e86547be..4ec38c6b961 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -99,6 +99,7 @@ export interface Component extends LightComponent { } interface ComponentConfiguration { + canApplyPermissionTemplate?: boolean; extensions?: Extension[]; showBackgroundTasks?: boolean; showLinks?: boolean; @@ -314,3 +315,19 @@ export interface CustomMeasure { value: string; updatedAt?: string; } + +export interface PermissionTemplate { + defaultFor: string[]; + id: string; + name: string; + description?: string; + projectKeyPattern?: string; + createdAt: string; + updatedAt?: string; + permissions: Array<{ + key: string; + usersCount: number; + groupsCount: number; + withProjectCreator?: boolean; + }>; +} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx similarity index 53% rename from server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js rename to server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx index e01fe8c35cd..121c008ce9a 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx @@ -17,58 +17,81 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; import { difference } from 'lodash'; -import Backbone from 'backbone'; -import { PermissionTemplateType, CallbackType } from '../propTypes'; +import Form from './Form'; +import { + setDefaultPermissionTemplate, + deletePermissionTemplate, + updatePermissionTemplate +} from '../../../api/permissions'; +import { PermissionTemplate } from '../../../app/types'; import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown'; +import ConfirmButton from '../../../components/controls/ConfirmButton'; import QualifierIcon from '../../../components/shared/QualifierIcon'; -import UpdateView from '../views/UpdateView'; -import DeleteView from '../views/DeleteView'; -import { translate } from '../../../helpers/l10n'; -import { setDefaultPermissionTemplate } from '../../../api/permissions'; - -export default class ActionsCell extends React.PureComponent { - static propTypes = { - organization: PropTypes.object, - permissionTemplate: PermissionTemplateType.isRequired, - topQualifiers: PropTypes.array.isRequired, - refresh: CallbackType, - fromDetails: PropTypes.bool - }; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + fromDetails?: boolean; + organization?: { isDefault?: boolean; key: string }; + permissionTemplate: PermissionTemplate; + refresh: () => void; + topQualifiers: string[]; +} - static defaultProps = { - fromDetails: false - }; +interface State { + updateModal: boolean; +} + +export default class ActionsCell extends React.PureComponent { + mounted = false; static contextTypes = { router: PropTypes.object }; + state: State = { updateModal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + handleUpdateClick = () => { - new UpdateView({ - model: new Backbone.Model(this.props.permissionTemplate), - refresh: this.props.refresh - }).render(); + this.setState({ updateModal: true }); }; - handleDeleteClick = () => { - new DeleteView({ - model: new Backbone.Model(this.props.permissionTemplate) - }) - .on('done', () => { - const pathname = this.props.organization - ? `/organizations/${this.props.organization.key}/permission_templates` - : '/permission_templates'; - this.context.router.replace(pathname); - this.props.refresh(); - }) - .render(); + handleCloseUpdateModal = () => { + if (this.mounted) { + this.setState({ updateModal: false }); + } + }; + + handleSubmitUpdateModal = (data: { + description: string; + name: string; + projectKeyPattern: string; + }) => { + return updatePermissionTemplate({ id: this.props.permissionTemplate.id, ...data }).then( + this.props.refresh + ); + }; + + handleDelete = (templateId: string) => { + return deletePermissionTemplate({ templateId }).then(() => { + const pathname = this.props.organization + ? `/organizations/${this.props.organization.key}/permission_templates` + : '/permission_templates'; + this.context.router.replace(pathname); + this.props.refresh(); + }); }; - setDefault = qualifier => () => { + setDefault = (qualifier: string) => () => { setDefaultPermissionTemplate(this.props.permissionTemplate.id, qualifier).then( this.props.refresh, () => {} @@ -95,19 +118,19 @@ export default class ActionsCell extends React.PureComponent { : this.renderIfMultipleTopQualifiers(availableQualifiers); } - renderSetDefaultLink(qualifier, child) { + renderSetDefaultLink(qualifier: string, child: React.ReactNode) { return ( {child} ); } - renderIfSingleTopQualifier(availableQualifiers) { + renderIfSingleTopQualifier(availableQualifiers: string[]) { return availableQualifiers.map(qualifier => this.renderSetDefaultLink( qualifier, @@ -116,7 +139,7 @@ export default class ActionsCell extends React.PureComponent { ); } - renderIfMultipleTopQualifiers(availableQualifiers) { + renderIfMultipleTopQualifiers(availableQualifiers: string[]) { return availableQualifiers.map(qualifier => this.renderSetDefaultLink( qualifier, @@ -148,11 +171,33 @@ export default class ActionsCell extends React.PureComponent { {translate('update_details')} + {this.state.updateModal && ( +
+ )} {t.defaultFor.length === 0 && ( - - {translate('delete')} - + + {({ onClick }) => ( + + {translate('delete')} + + )} + )} ); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx new file mode 100644 index 00000000000..3bf36d12b1d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx @@ -0,0 +1,154 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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/common/DeferredSpinner'; +import SimpleModal from '../../../components/controls/SimpleModal'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + confirmButtonText: string; + header: string; + permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string }; + onClose: () => void; + onSubmit: ( + data: { description: string; name: string; projectKeyPattern: string } + ) => Promise; +} + +interface State { + description: string; + name: string; + projectKeyPattern: string; +} + +export default class Form extends React.PureComponent { + mounted = false; + + constructor(props: Props) { + super(props); + this.state = { + description: (props.permissionTemplate && props.permissionTemplate.description) || '', + name: (props.permissionTemplate && props.permissionTemplate.name) || '', + projectKeyPattern: + (props.permissionTemplate && props.permissionTemplate.projectKeyPattern) || '' + }; + } + + handleSubmit = () => { + return this.props + .onSubmit({ + description: this.state.description, + name: this.state.name, + projectKeyPattern: this.state.projectKeyPattern + }) + .then(this.props.onClose); + }; + + handleNameChange = (event: React.ChangeEvent) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleDescriptionChange = (event: React.ChangeEvent) => { + this.setState({ description: event.currentTarget.value }); + }; + + handleProjectKeyPatternChange = (event: React.ChangeEvent) => { + this.setState({ projectKeyPattern: event.currentTarget.value }); + }; + + render() { + return ( + + {({ onCloseClick, onFormSubmit, submitting }) => ( + +
+

{this.props.header}

+
+ +
+
+ + +
{translate('should_be_unique')}
+
+ +
+ + -
- - -
- - diff --git a/server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js b/server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js deleted file mode 100644 index 0e788921b2f..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 FormView from './FormView'; -import { createPermissionTemplate } from '../../../api/permissions'; -import { parseError } from '../../../helpers/request'; - -export default FormView.extend({ - sendRequest() { - this.disableForm(); - const data = { - name: this.$('#permission-template-name').val(), - description: this.$('#permission-template-description').val(), - projectKeyPattern: this.$('#permission-template-project-key-pattern').val() - }; - if (this.options.organization) { - Object.assign(data, { organization: this.options.organization.key }); - } - createPermissionTemplate(data).then( - r => { - this.trigger('done', r); - this.destroy(); - }, - e => { - this.enableForm(); - parseError(e).then(message => this.showSingleError(message)); - } - ); - } -}); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js b/server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js deleted file mode 100644 index ea780ddf33a..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 ModalForm from '../../../components/common/modal-form'; -import { deletePermissionTemplate } from '../../../api/permissions'; -import Template from '../templates/permission-templates-delete.hbs'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.sendRequest(); - }, - - sendRequest() { - deletePermissionTemplate({ templateId: this.model.id }).then( - () => { - this.trigger('done'); - this.destroy(); - }, - e => { - this.enableForm(); - parseError(e).then(message => this.showSingleError(message)); - } - ); - } -}); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js b/server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js deleted file mode 100644 index 0c9e5d40c4d..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/permission-templates-form.hbs'; - -export default ModalForm.extend({ - template: Template, - - onRender() { - ModalForm.prototype.onRender.apply(this, arguments); - this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); - this.$('#create-custom-measure-metric').select2({ - width: '250px', - minimumResultsForSearch: 20 - }); - }, - - onDestroy() { - ModalForm.prototype.onDestroy.apply(this, arguments); - this.$('[data-toggle="tooltip"]').tooltip('destroy'); - }, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.sendRequest(); - } -}); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js b/server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js deleted file mode 100644 index 5284685848f..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 FormView from './FormView'; -import { updatePermissionTemplate } from '../../../api/permissions'; -import { parseError } from '../../../helpers/request'; - -export default FormView.extend({ - sendRequest() { - this.disableForm(); - updatePermissionTemplate({ - id: this.model.id, - name: this.$('#permission-template-name').val(), - description: this.$('#permission-template-description').val(), - projectKeyPattern: this.$('#permission-template-project-key-pattern').val() - }).then( - () => { - this.options.refresh(); - this.destroy(); - }, - e => { - this.enableForm(); - parseError(e).then(message => this.showSingleError(message)); - } - ); - } -}); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx new file mode 100644 index 00000000000..e8550cef79a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { getPermissionTemplates, applyTemplateToProject } from '../../../../api/permissions'; +import { PermissionTemplate } from '../../../../app/types'; +import DeferredSpinner from '../../../../components/common/DeferredSpinner'; +import SimpleModal from '../../../../components/controls/SimpleModal'; +import Select from '../../../../components/controls/Select'; +import { translateWithParameters, translate } from '../../../../helpers/l10n'; + +interface Props { + onApply?: () => void; + onClose: () => void; + organization: string | undefined; + project: { key: string; name: string }; +} + +interface State { + done: boolean; + loading: boolean; + permissionTemplate?: string; + permissionTemplates?: PermissionTemplate[]; +} + +export default class ApplyTemplate extends React.PureComponent { + mounted = false; + state: State = { done: false, loading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchPermissionTemplates(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchPermissionTemplates = () => { + getPermissionTemplates(this.props.organization).then( + ({ permissionTemplates }) => { + if (this.mounted) { + this.setState({ loading: false, permissionTemplates }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + handleSubmit = () => { + if (this.state.permissionTemplate) { + return applyTemplateToProject({ + organization: this.props.organization, + projectKey: this.props.project.key, + templateId: this.state.permissionTemplate + }).then(() => { + if (this.mounted) { + if (this.props.onApply) { + this.props.onApply(); + } + this.setState({ done: true }); + } + }); + } else { + return Promise.reject(undefined); + } + }; + + handlePermissionTemplateChange = ({ value }: { value: string }) => { + this.setState({ permissionTemplate: value }); + }; + + render() { + const header = translateWithParameters( + 'projects_role.apply_template_to_xxx', + this.props.project.name + ); + + return ( + + {({ onCloseClick, onFormSubmit, submitting }) => ( +
+
+

{header}

+
+ +
+ {this.state.done ? ( +
+ {translate('projects_role.apply_template.success')} +
+ ) : ( + <> + {this.state.loading ? ( + + ) : ( +
+ + {this.state.permissionTemplates && ( + - {{#each permissionTemplates}} - - {{/each}} - -
- {{else}} - - {{/notNull}} - {{/unless}} -
- - - diff --git a/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js b/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js deleted file mode 100644 index 6bba0c09b5b..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 ModalForm from '../../../../components/common/modal-form'; -import { applyTemplateToProject, getPermissionTemplates } from '../../../../api/permissions'; -import Template from '../templates/ApplyTemplateTemplate.hbs'; - -export default ModalForm.extend({ - template: Template, - - initialize() { - this.loadPermissionTemplates(); - this.done = false; - }, - - loadPermissionTemplates() { - return getPermissionTemplates(this.options.organization.key).then(r => { - this.permissionTemplates = r.permissionTemplates; - this.render(); - }); - }, - - onRender() { - ModalForm.prototype.onRender.apply(this, arguments); - this.$('#project-permissions-template').select2({ - width: '250px', - minimumResultsForSearch: 20 - }); - }, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - const permissionTemplate = this.$('#project-permissions-template').val(); - this.disableForm(); - - const data = { - organization: this.options.organization.key, - projectKey: this.options.project.key, - templateId: permissionTemplate - }; - applyTemplateToProject(data) - .then(() => { - this.trigger('done'); - this.done = true; - this.render(); - }) - .catch(function(e) { - e.response.json().then(r => { - this.showErrors(r.errors, r.warnings); - this.enableForm(); - }); - }); - }, - - serializeData() { - return { - permissionTemplates: this.permissionTemplates, - project: this.options.project, - done: this.done - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx index 726b4f69709..69bb55161c7 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -18,11 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { - getPermissionTemplates, - bulkApplyTemplate, - PermissionTemplate -} from '../../api/permissions'; +import { getPermissionTemplates, bulkApplyTemplate } from '../../api/permissions'; +import { PermissionTemplate } from '../../app/types'; import { translate, translateWithParameters } from '../../helpers/l10n'; import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon'; import Modal from '../../components/controls/Modal'; 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 cea505f986d..72e658c8727 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx @@ -29,8 +29,8 @@ import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter'; interface Props { currentUser: { login: string }; - onApplyTemplate: (project: Project) => void; onProjectCheck: (project: Project, checked: boolean) => void; + organization: string | undefined; project: Project; selected: boolean; } @@ -51,8 +51,8 @@ export default class ProjectRow extends React.PureComponent { + className="link-with-icon" + to={{ pathname: '/dashboard', query: { id: project.key } }}> {project.name} @@ -78,7 +78,7 @@ export default class ProjectRow extends React.PureComponent { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx index 7f188592892..b43fd2d96fa 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import RestoreAccessModal from './RestoreAccessModal'; import { Project } from './utils'; +import ApplyTemplate from '../permissions/project/components/ApplyTemplate'; import { getComponentShow } from '../../api/components'; import { getComponentNavigation } from '../../api/nav'; import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown'; @@ -28,11 +29,12 @@ import { getComponentPermissionsUrl } from '../../helpers/urls'; export interface Props { currentUser: { login: string }; - onApplyTemplate: (project: Project) => void; + organization: string | undefined; project: Project; } interface State { + applyTemplateModal: boolean; hasAccess?: boolean; loading: boolean; restoreAccessModal: boolean; @@ -40,7 +42,7 @@ interface State { export default class ProjectRowActions extends React.PureComponent { mounted = false; - state: State = { loading: false, restoreAccessModal: false }; + state: State = { applyTemplateModal: false, loading: false, restoreAccessModal: false }; componentDidMount() { this.mounted = true; @@ -81,7 +83,13 @@ export default class ProjectRowActions extends React.PureComponent }; handleApplyTemplateClick = () => { - this.props.onApplyTemplate(this.props.project); + this.setState({ applyTemplateModal: true }); + }; + + handleApplyTemplateClose = () => { + if (this.mounted) { + this.setState({ applyTemplateModal: false }); + } }; handleRestoreAccessClick = () => { @@ -125,6 +133,14 @@ export default class ProjectRowActions extends React.PureComponent project={this.props.project} /> )} + + {this.state.applyTemplateModal && ( + + )} ); } 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 b0872d7532b..7e72fb9b057 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx @@ -21,7 +21,6 @@ import * as React from 'react'; import * as classNames from 'classnames'; 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'; @@ -44,10 +43,6 @@ export default class Projects extends React.PureComponent { } }; - handleApplyTemplate = (project: Project) => { - new ApplyTemplateView({ project, organization: this.props.organization }).render(); - }; - render() { return (
@@ -69,8 +64,8 @@ export default class Projects extends React.PureComponent { diff --git 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 index f9a5eee8406..4c9e74dc1b1 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 @@ -54,17 +54,16 @@ it('restores access', async () => { }); it('applies permission template', () => { - const onApplyTemplate = jest.fn(); - const wrapper = shallowRender({ onApplyTemplate }); + const wrapper = shallowRender(); click(wrapper.find('.js-apply-template')); - expect(onApplyTemplate).toBeCalledWith(project); + expect(wrapper.find('ApplyTemplate')).toMatchSnapshot(); }); function shallowRender(props: Partial = {}) { const wrapper = shallow( 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 76c6be42713..25b2a6ac78d 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 @@ -17,13 +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. */ -/* eslint-disable import/first */ -jest.mock('../../permissions/project/views/ApplyTemplateView'); - import * as React from 'react'; import { shallow } from 'enzyme'; import Projects from '../Projects'; -import ApplyTemplateView from '../../permissions/project/views/ApplyTemplateView'; import { Visibility } from '../../../app/types'; const organization = { key: 'org', name: 'org', projectVisibility: 'public' }; @@ -55,15 +51,6 @@ it('selects and deselects project', () => { expect(onProjectDeselected).toBeCalledWith('a'); }); -it('opens modal to apply permission template', () => { - const wrapper = shallowRender({ projects }); - wrapper - .find('ProjectRow') - .first() - .prop('onApplyTemplate')(projects[0]); - expect(ApplyTemplateView).toBeCalledWith({ organization, project: projects[0] }); -}); - function shallowRender(props?: any) { return shallow( +`; + exports[`restores access 1`] = `