aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-02-16 09:12:23 +0100
committerGitHub <noreply@github.com>2018-02-16 09:12:23 +0100
commit6dabceb176660fbed4195214d39c8f348fbe2c32 (patch)
tree21f322f558b69319ab7c5b2d679dbddb8d32c3c2 /server/sonar-web
parent11bd2fcdf399d62b277e9a69b7a6bc4f3ad696a8 (diff)
downloadsonarqube-6dabceb176660fbed4195214d39c8f348fbe2c32.tar.gz
sonarqube-6dabceb176660fbed4195214d39c8f348fbe2c32.zip
rewrite parts of permission templates app in react (#3070)
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/permissions.ts25
-rw-r--r--server/sonar-web/src/main/js/app/types.ts17
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx (renamed from server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js)135
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx154
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Header.js75
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx107
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-delete.hbs13
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs34
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js46
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js45
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js44
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js43
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx155
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js)76
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs41
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js78
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap4
25 files changed, 622 insertions, 553 deletions
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<void> {
return post('/api/permissions/update_template', data);
}
-export function deletePermissionTemplate(data: RequestData): Promise<void> {
- 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<void> {
- 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<void> {
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
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<Props, State> {
+ 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 (
<ActionsDropdownItem
- key={qualifier}
className="js-set-default"
data-qualifier={qualifier}
+ key={qualifier}
onClick={this.setDefault(qualifier)}>
{child}
</ActionsDropdownItem>
);
}
- 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 {
<ActionsDropdownItem className="js-update" onClick={this.handleUpdateClick}>
{translate('update_details')}
</ActionsDropdownItem>
+ {this.state.updateModal && (
+ <Form
+ confirmButtonText={translate('update_verb')}
+ header={translate('permission_template.edit_template')}
+ onClose={this.handleCloseUpdateModal}
+ onSubmit={this.handleSubmitUpdateModal}
+ permissionTemplate={t}
+ />
+ )}
{t.defaultFor.length === 0 && (
- <ActionsDropdownItem className="js-delete" onClick={this.handleDeleteClick}>
- {translate('delete')}
- </ActionsDropdownItem>
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ confirmData={t.id}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'permission_template.do_you_want_to_delete_template_xxx',
+ t.name
+ )}
+ modalHeader={translate('permission_template.delete_confirm_title')}
+ onConfirm={this.handleDelete}>
+ {({ onClick }) => (
+ <ActionsDropdownItem className="js-delete" destructive={true} onClick={onClick}>
+ {translate('delete')}
+ </ActionsDropdownItem>
+ )}
+ </ConfirmButton>
)}
</ActionsDropdown>
);
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<void>;
+}
+
+interface State {
+ description: string;
+ name: string;
+ projectKeyPattern: string;
+}
+
+export default class Form extends React.PureComponent<Props, State> {
+ 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<HTMLInputElement>) => {
+ this.setState({ name: event.currentTarget.value });
+ };
+
+ handleDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this.setState({ description: event.currentTarget.value });
+ };
+
+ handleProjectKeyPatternChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this.setState({ projectKeyPattern: event.currentTarget.value });
+ };
+
+ render() {
+ return (
+ <SimpleModal
+ header={this.props.header}
+ onClose={this.props.onClose}
+ onSubmit={this.handleSubmit}>
+ {({ onCloseClick, onFormSubmit, submitting }) => (
+ <form id="permission-template-form" onSubmit={onFormSubmit}>
+ <header className="modal-head">
+ <h2>{this.props.header}</h2>
+ </header>
+
+ <div className="modal-body">
+ <div className="modal-field">
+ <label htmlFor="permission-template-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="permission-template-name"
+ maxLength={256}
+ name="name"
+ onChange={this.handleNameChange}
+ required={true}
+ type="text"
+ value={this.state.name}
+ />
+ <div className="modal-field-description">{translate('should_be_unique')}</div>
+ </div>
+
+ <div className="modal-field">
+ <label htmlFor="permission-template-description">{translate('description')}</label>
+ <textarea
+ id="permission-template-description"
+ name="description"
+ onChange={this.handleDescriptionChange}
+ value={this.state.description}
+ />
+ </div>
+
+ <div className="modal-field">
+ <label htmlFor="permission-template-project-key-pattern">
+ {translate('permission_template.key_pattern')}
+ </label>
+ <input
+ id="permission-template-project-key-pattern"
+ maxLength={500}
+ name="projectKeyPattern"
+ onChange={this.handleProjectKeyPatternChange}
+ type="text"
+ value={this.state.projectKeyPattern}
+ />
+ <div className="modal-field-description">
+ {translate('permission_template.key_pattern.description')}
+ </div>
+ </div>
+ </div>
+
+ <footer className="modal-foot">
+ <DeferredSpinner className="spacer-right" loading={submitting} />
+ <button disabled={submitting} id="permission-template-submit" type="submit">
+ {this.props.confirmButtonText}
+ </button>
+ <button
+ className="button-link"
+ disabled={submitting}
+ id="permission-template-cancel"
+ onClick={onCloseClick}
+ type="reset">
+ {translate('cancel')}
+ </button>
+ </footer>
+ </form>
+ )}
+ </SimpleModal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.js
deleted file mode 100644
index 2635bbbe8a8..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.js
+++ /dev/null
@@ -1,75 +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 React from 'react';
-import PropTypes from 'prop-types';
-import CreateView from '../views/CreateView';
-import { translate } from '../../../helpers/l10n';
-import { CallbackType } from '../propTypes';
-
-export default class Header extends React.PureComponent {
- static propTypes = {
- organization: PropTypes.object,
- ready: PropTypes.bool.isRequired,
- refresh: CallbackType
- };
-
- static contextTypes = {
- router: PropTypes.object
- };
-
- componentWillMount() {
- this.handleCreateClick = this.handleCreateClick.bind(this);
- }
-
- handleCreateClick(e) {
- e.preventDefault();
- const { organization } = this.props;
-
- new CreateView({ organization })
- .on('done', r => {
- this.props.refresh().then(() => {
- const pathname = organization
- ? `/organizations/${organization.key}/permission_templates`
- : '/permission_templates';
- this.context.router.push({
- pathname,
- query: { id: r.permissionTemplate.id }
- });
- });
- })
- .render();
- }
-
- render() {
- return (
- <header id="project-permissions-header" className="page-header">
- <h1 className="page-title">{translate('permission_templates.page')}</h1>
-
- {!this.props.ready && <i className="spinner" />}
-
- <div className="page-actions">
- <button onClick={this.handleCreateClick}>{translate('create')}</button>
- </div>
-
- <p className="page-description">{translate('permission_templates.page.description')}</p>
- </header>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
new file mode 100644
index 00000000000..1db801795fe
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 * as PropTypes from 'prop-types';
+import Form from './Form';
+import { createPermissionTemplate } from '../../../api/permissions';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ organization?: { key: string };
+ ready?: boolean;
+ refresh: () => Promise<void>;
+}
+
+interface State {
+ createModal: boolean;
+}
+
+export default class Header extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ state: State = { createModal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCreateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ createModal: true });
+ };
+
+ handleCreateModalClose = () => {
+ if (this.mounted) {
+ this.setState({ createModal: false });
+ }
+ };
+
+ handleCreateModalSubmit = (data: {
+ description: string;
+ name: string;
+ projectKeyPattern: string;
+ }) => {
+ const organization = this.props.organization && this.props.organization.key;
+ return createPermissionTemplate({ ...data, organization }).then(response => {
+ this.props.refresh().then(() => {
+ const pathname = organization
+ ? `/organizations/${organization}/permission_templates`
+ : '/permission_templates';
+ this.context.router.push({ pathname, query: { id: response.permissionTemplate.id } });
+ });
+ });
+ };
+
+ render() {
+ return (
+ <header className="page-header" id="project-permissions-header">
+ <h1 className="page-title">{translate('permission_templates.page')}</h1>
+
+ {!this.props.ready && <i className="spinner" />}
+
+ <div className="page-actions">
+ <button onClick={this.handleCreateClick} type="button">
+ {translate('create')}
+ </button>
+
+ {this.state.createModal && (
+ <Form
+ confirmButtonText={translate('create')}
+ header={translate('permission_template.new_template')}
+ onClose={this.handleCreateModalClose}
+ onSubmit={this.handleCreateModalSubmit}
+ />
+ )}
+ </div>
+
+ <p className="page-description">{translate('permission_templates.page.description')}</p>
+ </header>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-delete.hbs b/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-delete.hbs
deleted file mode 100644
index 6cdf78a14a0..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-delete.hbs
+++ /dev/null
@@ -1,13 +0,0 @@
-<form id="delete-permission-template-form">
- <div class="modal-head">
- <h2>{{t 'permission_template.delete_confirm_title'}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- {{tp 'permission_template.do_you_want_to_delete_template_xxx' name}}
- </div>
- <div class="modal-foot">
- <button id="delete-permission-template-submit" class="button-red">{{t 'delete'}}</button>
- <a href="#" class="js-modal-close" id="delete-permission-template-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs b/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs
deleted file mode 100644
index b88decef2e4..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs
+++ /dev/null
@@ -1,34 +0,0 @@
-<form id="permission-template-form" autocomplete="off">
- <div class="modal-head">
- <h2>{{#if id}}{{t 'permission_template.edit_template'}}{{else}}{{t 'permission_template.new_template'}}{{/if}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- <div class="modal-field">
- <label for="permission-template-name">{{t 'name'}}<em class="mandatory">*</em></label>
- <input id="permission-template-name" name="name" type="text" maxlength="256" required value="{{name}}">
- <div class="modal-field-description">
- {{t 'should_be_unique'}}
- </div>
- </div>
-
- <div class="modal-field">
- <label for="permission-template-description">{{t 'description'}}</label>
- <textarea id="permission-template-description" name="description" maxlength="4000" rows="5">{{description}}</textarea>
- </div>
-
- <div class="modal-field">
- <label for="permission-template-project-key-pattern">{{t 'permission_template.key_pattern'}}</label>
- <input id="permission-template-project-key-pattern" name="keyPattern" type="text" maxlength="500"
- value="{{projectKeyPattern}}">
- <div class="modal-field-description">
- {{t 'permission_template.key_pattern.description'}}
- </div>
- </div>
- </div>
- <div class="modal-foot">
- <button id="permission-template-submit">{{#if id}}{{t 'update_verb'}}{{else}}{{t 'create'}}{{/if}}</button>
- <a href="#" class="js-modal-close" id="permission-template-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
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<Props, State> {
+ 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 (
+ <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}>
+ {({ onCloseClick, onFormSubmit, submitting }) => (
+ <form id="project-permissions-apply-template-form" onSubmit={onFormSubmit}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <div className="modal-body">
+ {this.state.done ? (
+ <div className="alert alert-success">
+ {translate('projects_role.apply_template.success')}
+ </div>
+ ) : (
+ <>
+ {this.state.loading ? (
+ <i className="spinner" />
+ ) : (
+ <div className="modal-field">
+ <label htmlFor="project-permissions-template">
+ {translate('template')}
+ <em className="mandatory">*</em>
+ </label>
+ {this.state.permissionTemplates && (
+ <Select
+ clearable={false}
+ onChange={this.handlePermissionTemplateChange}
+ options={this.state.permissionTemplates.map(permissionTemplate => ({
+ label: permissionTemplate.name,
+ value: permissionTemplate.id
+ }))}
+ value={this.state.permissionTemplate}
+ />
+ )}
+ </div>
+ )}
+ </>
+ )}
+ </div>
+
+ <footer className="modal-foot">
+ <DeferredSpinner className="spacer-right" loading={submitting} />
+ {!this.state.done && (
+ <button disabled={submitting || !this.state.permissionTemplate} type="submit">
+ {translate('apply')}
+ </button>
+ )}
+ <button className="button-link" onClick={onCloseClick} type="reset">
+ {translate(this.state.done ? 'close' : 'cancel')}
+ </button>
+ </footer>
+ </form>
+ )}
+ </SimpleModal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
index 02eed6c9310..bab1ba6490d 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
@@ -17,43 +17,48 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-import React from 'react';
+import * as React from 'react';
+import ApplyTemplate from './ApplyTemplate';
+import { Component } from '../../../../app/types';
import { translate } from '../../../../helpers/l10n';
-import ApplyTemplateView from '../views/ApplyTemplateView';
-/*::
-type Props = {|
- component: {
- configuration?: {
- canApplyPermissionTemplate: boolean,
- canUpdateProjectVisibilityToPrivate: boolean
- },
- key: string,
- qualifier: string,
- visibility: string
- },
- loadHolders: () => void,
- loading: boolean
-|};
-*/
+interface Props {
+ component: Component;
+ loadHolders: () => void;
+ loading: boolean;
+}
+
+interface State {
+ applyTemplateModal: boolean;
+}
+
+export default class PageHeader extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { applyTemplateModal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
-export default class PageHeader extends React.PureComponent {
- /*:: props: Props; */
+ componentWillUnmount() {
+ this.mounted = false;
+ }
- handleApplyTemplate = (e /*: Event & { target: HTMLButtonElement } */) => {
- e.preventDefault();
- e.target.blur();
- const { component, loadHolders } = this.props;
- const organization = component.organization ? { key: component.organization } : null;
- new ApplyTemplateView({ project: component, organization })
- .on('done', () => loadHolders())
- .render();
+ handleApplyTemplate = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ applyTemplateModal: true });
+ };
+
+ handleApplyTemplateClose = () => {
+ if (this.mounted) {
+ this.setState({ applyTemplateModal: false });
+ }
};
render() {
const { component } = this.props;
- const configuration = component.configuration;
+ const { configuration } = component;
const canApplyPermissionTemplate =
configuration != null && configuration.canApplyPermissionTemplate;
@@ -62,7 +67,7 @@ export default class PageHeader extends React.PureComponent {
: translate('roles.page.description2');
const visibilityDescription =
- component.qualifier === 'TRK'
+ component.qualifier === 'TRK' && component.visibility
? translate('visibility', component.visibility, 'description')
: null;
@@ -74,9 +79,18 @@ export default class PageHeader extends React.PureComponent {
{canApplyPermissionTemplate && (
<div className="page-actions">
- <button className="js-apply-template" onClick={this.handleApplyTemplate}>
+ <button className="js-apply-template" onClick={this.handleApplyTemplate} type="button">
{translate('projects_role.apply_template')}
</button>
+
+ {this.state.applyTemplateModal && (
+ <ApplyTemplate
+ onApply={this.props.loadHolders}
+ onClose={this.handleApplyTemplateClose}
+ organization={component.organization}
+ project={component}
+ />
+ )}
</div>
)}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs b/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs
deleted file mode 100644
index 37e8ff307e5..00000000000
--- a/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs
+++ /dev/null
@@ -1,41 +0,0 @@
-<form id="project-permissions-apply-template-form" autocomplete="off">
- <div class="modal-head">
- <h2>{{tp 'projects_role.apply_template_to_xxx' project.name}}</h2>
- </div>
-
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- {{#if done}}
- <div class="alert alert-success">
- {{t 'projects_role.apply_template.success'}}
- </div>
- {{/if}}
-
- {{#unless done}}
- {{#notNull permissionTemplates}}
- <div class="modal-field">
- <label for="project-permissions-template">
- {{t 'template'}}<em class="mandatory">*</em>
- </label>
- <select id="project-permissions-template">
- {{#each permissionTemplates}}
- <option value="{{id}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- {{else}}
- <i class="spinner"></i>
- {{/notNull}}
- {{/unless}}
- </div>
-
- <div class="modal-foot">
- {{#unless done}}
- {{#notNull permissionTemplates}}
- <button id="project-permissions-apply-template">{{t 'apply'}}</button>
- {{/notNull}}
- {{/unless}}
- <a href="#" class="js-modal-close">{{t 'close'}}</a>
- </div>
-</form>
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<Props> {
<td className="nowrap">
<Link
- to={{ pathname: '/dashboard', query: { id: project.key } }}
- className="link-with-icon">
+ className="link-with-icon"
+ to={{ pathname: '/dashboard', query: { id: project.key } }}>
<QualifierIcon qualifier={project.qualifier} /> <span>{project.name}</span>
</Link>
</td>
@@ -78,7 +78,7 @@ export default class ProjectRow extends React.PureComponent<Props> {
<td className="thin nowrap">
<ProjectRowActions
currentUser={this.props.currentUser}
- onApplyTemplate={this.props.onApplyTemplate}
+ organization={this.props.organization}
project={project}
/>
</td>
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<Props, State> {
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<Props, State>
};
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<Props, State>
project={this.props.project}
/>
)}
+
+ {this.state.applyTemplateModal && (
+ <ApplyTemplate
+ onClose={this.handleApplyTemplateClose}
+ organization={this.props.organization}
+ project={this.props.project}
+ />
+ )}
</ActionsDropdown>
);
}
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<Props> {
}
};
- handleApplyTemplate = (project: Project) => {
- new ApplyTemplateView({ project, organization: this.props.organization }).render();
- };
-
render() {
return (
<div className="boxed-group boxed-group-inner">
@@ -69,8 +64,8 @@ export default class Projects extends React.PureComponent<Props> {
<ProjectRow
currentUser={this.props.currentUser}
key={project.key}
- onApplyTemplate={this.handleApplyTemplate}
onProjectCheck={this.onProjectCheck}
+ organization={this.props.organization && this.props.organization.key}
project={project}
selected={this.props.selection.includes(project.key)}
/>
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<Props> = {}) {
const wrapper = shallow(
<ProjectRowActions
currentUser={{ login: 'admin' }}
- onApplyTemplate={jest.fn()}
+ organization="org"
project={project}
{...props}
/>
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<Function>('onApplyTemplate')(projects[0]);
- expect(ApplyTemplateView).toBeCalledWith({ organization, project: projects[0] });
-});
-
function shallowRender(props?: any) {
return shallow(
<Projects
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
index 7c62905a04c..6d05b0406f7 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
@@ -70,7 +70,6 @@ exports[`renders 1`] = `
"login": "foo",
}
}
- onApplyTemplate={[MockFunction]}
project={
Object {
"key": "project",
@@ -152,7 +151,6 @@ exports[`renders 2`] = `
"login": "foo",
}
}
- onApplyTemplate={[MockFunction]}
project={
Object {
"key": "project",
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
index d2737d58194..b824855ce96 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,5 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`applies permission template 1`] = `
+<ApplyTemplate
+ onClose={[Function]}
+ organization="org"
+ project={
+ Object {
+ "id": "",
+ "key": "project",
+ "name": "Project",
+ "organization": "org",
+ "qualifier": "TRK",
+ "visibility": "private",
+ }
+ }
+/>
+`;
+
exports[`restores access 1`] = `
<ActionsDropdown
onToggleClick={[Function]}
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 1503fed2c4e..678979cf56a 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
@@ -34,8 +34,8 @@ exports[`renders list of projects 1`] = `
}
}
key="a"
- onApplyTemplate={[Function]}
onProjectCheck={[Function]}
+ organization="org"
project={
Object {
"key": "a",
@@ -53,8 +53,8 @@ exports[`renders list of projects 1`] = `
}
}
key="b"
- onApplyTemplate={[Function]}
onProjectCheck={[Function]}
+ organization="org"
project={
Object {
"key": "b",