Browse Source

SONAR-7918 Rewrite "Deletion" project page (#1116)

tags/6.1-RC1
Stas Vilchik 7 years ago
parent
commit
fcba3fa165
21 changed files with 257 additions and 154 deletions
  1. 11
    21
      it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html
  2. 0
    2
      server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java
  3. 0
    1
      server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/on_module.json
  4. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json
  5. 0
    1
      server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json
  6. 0
    1
      server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_all_properties.json
  7. 6
    0
      server/sonar-web/src/main/js/api/components.js
  8. 41
    0
      server/sonar-web/src/main/js/apps/project-admin/app.js
  9. 47
    0
      server/sonar-web/src/main/js/apps/project-admin/deletion/ConfirmationModal.js
  10. 13
    0
      server/sonar-web/src/main/js/apps/project-admin/deletion/ConfirmationModalTemplate.hbs
  11. 37
    0
      server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js
  12. 47
    0
      server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js
  13. 36
    0
      server/sonar-web/src/main/js/apps/project-admin/deletion/Header.js
  14. 5
    1
      server/sonar-web/src/main/js/main/nav/app.js
  15. 7
    6
      server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js
  16. 0
    61
      server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
  17. 0
    34
      server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_delete_form.html.erb
  18. 3
    18
      server/sonar-web/src/main/webapp/WEB-INF/app/views/project/deletion.html.erb
  19. 0
    2
      server/sonar-web/src/main/webapp/WEB-INF/app/views/project/pending_deletion.html.erb
  20. 1
    0
      server/sonar-web/webpack.config.js
  21. 2
    4
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 11
- 21
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html View File

@@ -55,43 +55,33 @@
</tr>
<tr>
<td>open</td>
<td>/dashboard/index/sample</td>
<td>/project/deletion?id=sample</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=#context-navigation .navbar-admin-link</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>link=Deletion</td>
<td>css=#delete-project</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>link=Deletion</td>
<td>click</td>
<td>css=#delete-project</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>delete_resource</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>delete-project-form</td>
<td>*Delete Project*</td>
<td>waitForElementPresent</td>
<td>css=#delete-project-confirm</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>delete-project-submit</td>
<td>css=#delete-project-confirm</td>
<td></td>
</tr>
<tr>
<td>pause</td>
<td>3000</td>
<td>NOTE: necessary as the deletion is asynchronous</td>
<td>waitForElementNotPresent</td>
<td>css=#delete-project-confirm</td>
<td></td>
</tr>
<tr>
<td>open</td>

+ 0
- 2
server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java View File

@@ -66,7 +66,6 @@ public class ComponentNavigationAction implements NavigationWsAction {
private static final String PROPERTY_HAS_ROLE_POLICY = "hasRolePolicy";
private static final String PROPERTY_MODIFIABLE_HISTORY = "modifiable_history";
private static final String PROPERTY_UPDATABLE_KEY = "updatable_key";
private static final String PROPERTY_DELETABLE = "deletable";

private final DbClient dbClient;
private final Views views;
@@ -221,7 +220,6 @@ public class ComponentNavigationAction implements NavigationWsAction {
json.prop("showPermissions", isAdmin && componentTypeHasProperty(component, PROPERTY_HAS_ROLE_POLICY));
json.prop("showHistory", isAdmin && componentTypeHasProperty(component, PROPERTY_MODIFIABLE_HISTORY));
json.prop("showUpdateKey", isAdmin && componentTypeHasProperty(component, PROPERTY_UPDATABLE_KEY));
json.prop("showDeletion", isAdmin && componentTypeHasProperty(component, PROPERTY_DELETABLE));
json.prop("showBackgroundTasks", ActivityAction.isAllowedOnComponentUuid(userSession, component.uuid()));
}


+ 0
- 1
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/on_module.json View File

@@ -15,7 +15,6 @@
"showPermissions": false,
"showHistory": false,
"showUpdateKey": false,
"showDeletion": false,
"extensions": []
},
"breadcrumbs": [

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json View File

@@ -14,8 +14,7 @@
"showLinks": false,
"showPermissions": false,
"showHistory": false,
"showUpdateKey": false,
"showDeletion": false
"showUpdateKey": false
},
"breadcrumbs": [
{

+ 0
- 1
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json View File

@@ -15,7 +15,6 @@
"showPermissions": false,
"showHistory": false,
"showUpdateKey": false,
"showDeletion": false,
"extensions": [
{
"name": "First Page",

+ 0
- 1
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_all_properties.json View File

@@ -15,7 +15,6 @@
"showPermissions": true,
"showHistory": true,
"showUpdateKey": true,
"showDeletion": true,
"extensions": []
},
"breadcrumbs": [

+ 6
- 0
server/sonar-web/src/main/js/api/components.js View File

@@ -39,6 +39,12 @@ export function deleteComponents (data) {
return post(url, data);
}

export function deleteProject (key) {
const url = '/api/projects/delete';
const data = { key };
return post(url, data);
}

export function createProject (data) {
const url = '/api/projects/create';
return postJSON(url, data);

+ 41
- 0
server/sonar-web/src/main/js/apps/project-admin/app.js View File

@@ -0,0 +1,41 @@
/*
* 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 React from 'react';
import { render } from 'react-dom';
import { Router, Route, useRouterHistory } from 'react-router';
import { createHistory } from 'history';
import Deletion from './deletion/Deletion';

window.sonarqube.appStarted.then(options => {
const el = document.querySelector(options.el);

const history = useRouterHistory(createHistory)({
basename: window.baseUrl + '/project'
});

const withComponent = ComposedComponent => props =>
<ComposedComponent {...props} component={options.component}/>;

render((
<Router history={history}>
<Route path="/deletion" component={withComponent(Deletion)}/>
</Router>
), el);
});

+ 47
- 0
server/sonar-web/src/main/js/apps/project-admin/deletion/ConfirmationModal.js View File

@@ -0,0 +1,47 @@
/*
* 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 ModalForm from '../../../components/common/modal-form';
import Template from './ConfirmationModalTemplate.hbs';
import { deleteProject } from '../../../api/components';

export default ModalForm.extend({
template: Template,

onFormSubmit () {
ModalForm.prototype.onFormSubmit.apply(this, arguments);
this.disableForm();

deleteProject(this.options.project.key)
.then(() => {
this.trigger('done');
})
.catch(function (e) {
e.response.json().then(r => {
this.showErrors(r.errors, r.warnings);
this.enableForm();
});
});
},

serializeData () {
return { project: this.options.project };
}
});


+ 13
- 0
server/sonar-web/src/main/js/apps/project-admin/deletion/ConfirmationModalTemplate.hbs View File

@@ -0,0 +1,13 @@
<form id="deactivate-user-form" autocomplete="off">
<div class="modal-head">
<h2>{{t 'qualifiers.delete.TRK'}}</h2>
</div>
<div class="modal-body">
<div class="js-modal-messages"></div>
{{tp 'project_deletion.delete_resource_confirmation' project.name}}
</div>
<div class="modal-foot">
<button id="delete-project-confirm" class="button-red">{{t 'delete'}}</button>
<a href="#" class="js-modal-close">{{t 'cancel'}}</a>
</div>
</form>

+ 37
- 0
server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js View File

@@ -0,0 +1,37 @@
/*
* 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 React from 'react';
import Header from './Header';
import Form from './Form';

export default class Deletion extends React.Component {
static propTypes = {
component: React.PropTypes.object.isRequired
};

render () {
return (
<div className="page page-limited">
<Header/>
<Form component={this.props.component}/>
</div>
);
}
}

+ 47
- 0
server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js View File

@@ -0,0 +1,47 @@
/*
* 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 React from 'react';
import ConfirmationModal from './ConfirmationModal';
import { translate } from '../../../helpers/l10n';

export default class Form extends React.Component {
static propTypes = {
component: React.PropTypes.object.isRequired
};

handleDelete (e) {
e.preventDefault();
new ConfirmationModal({ project: this.props.component })
.on('done', () => {
window.location = window.baseUrl + '/';
})
.render();
}

render () {
return (
<form onSubmit={this.handleDelete.bind(this)}>
<button id="delete-project" className="button-red">
{translate('delete')}
</button>
</form>
);
}
}

+ 36
- 0
server/sonar-web/src/main/js/apps/project-admin/deletion/Header.js View File

@@ -0,0 +1,36 @@
/*
* 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 React from 'react';
import { translate } from '../../../helpers/l10n';

export default class Header extends React.Component {
render () {
return (
<header className="page-header">
<h1 className="page-title">
{translate('deletion.page')}
</h1>
<div className="page-description">
{translate('project_deletion.page.description')}
</div>
</header>
);
}
}

+ 5
- 1
server/sonar-web/src/main/js/main/nav/app.js View File

@@ -67,8 +67,12 @@ export default class App {
static renderComponentNav (options) {
return getComponentNavigation(options.componentKey).then(component => {
const el = document.getElementById('context-navigation');
const nextComponent = {
...component,
qualifier: _.last(component.breadcrumbs).qualifier
};
if (el) {
ReactDOM.render(<ComponentNav component={component} conf={component.configuration || {}}/>, el);
ReactDOM.render(<ComponentNav component={nextComponent} conf={component.configuration || {}}/>, el);
}
return component;
});

+ 7
- 6
server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js View File

@@ -42,12 +42,11 @@ export default React.createClass({
mixins: [LinksMixin],

isDeveloper() {
const qualifier = _.last(this.props.component.breadcrumbs).qualifier;
return qualifier === 'DEV';
return this.props.component.qualifier === 'DEV';
},

isView() {
const qualifier = _.last(this.props.component.breadcrumbs).qualifier;
const { qualifier } = this.props.component;
return qualifier === 'VW' || qualifier === 'SVW';
},

@@ -100,7 +99,6 @@ export default React.createClass({
renderAdministration() {
const shouldShowAdministration =
this.props.conf.showBackgroundTasks ||
this.props.conf.showDeletion ||
this.props.conf.showHistory ||
this.props.conf.showLinks ||
this.props.conf.showManualMeasures ||
@@ -132,8 +130,8 @@ export default React.createClass({
{this.renderHistoryLink()}
{this.renderBackgroundTasksLink()}
{this.renderUpdateKeyLink()}
{this.renderDeletionLink()}
{this.renderExtensions()}
{this.renderDeletionLink()}
</ul>
</li>
);
@@ -212,9 +210,12 @@ export default React.createClass({
},

renderDeletionLink() {
if (!this.props.conf.showDeletion) {
const { qualifier } = this.props.component;

if (qualifier !== 'TRK' && qualifier !== 'VW') {
return null;
}

const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('deletion.page'), '/project/deletion');
},

+ 0
- 61
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb View File

@@ -20,7 +20,6 @@
class ProjectController < ApplicationController
verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions, :update_key, :perform_key_bulk_update],
:redirect_to => {:action => :index}
verify :method => :delete, :only => [:delete], :redirect_to => {:action => :index}

SECTION=Navigation::SECTION_RESOURCE

@@ -29,56 +28,8 @@ class ProjectController < ApplicationController
redirect_to :overwrite_params => {:controller => :dashboard, :action => 'index'}
end

def delete_form
@project = get_current_project(params[:id])
render :partial => 'delete_form'
end

def delete
@project = get_current_project(params[:id])

# Ask the resource deletion manager to start the migration
# => this is an asynchronous AJAX call
ResourceDeletionManager.instance.delete_resources([@project.id])

# and return some text that will actually never be displayed
render :text => ResourceDeletionManager.instance.message
end

def deletion
@project = get_current_project(params[:id])

if java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable')
deletion_manager = ResourceDeletionManager.instance
if deletion_manager.currently_deleting_resources? ||
(!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?)
# a deletion is happening or it has just finished with errors => display the message from the Resource Deletion Manager
render :template => 'project/pending_deletion'
else
@snapshot=@project.last_snapshot
end
else
redirect_to :action => 'index', :id => params[:id]
end
end

def pending_deletion
deletion_manager = ResourceDeletionManager.instance

if deletion_manager.currently_deleting_resources? ||
(!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?)
# display the same page again and again
# => implicit render "pending_deletion.html.erb"
else
redirect_to_default
end
end

def dismiss_deletion_message
# It is important to reinit the ResourceDeletionManager so that the deletion screens can be available again
ResourceDeletionManager.instance.reinit

redirect_to :action => 'deletion', :id => params[:id]
end

# GET /project/profile?id=<project id>
@@ -227,18 +178,6 @@ class ProjectController < ApplicationController
@project = get_current_project(params[:id])
end

def delete_snapshot_history
@project = get_current_project(params[:id])

sid = params[:snapshot_id]
if sid
Snapshot.update_all("status='U'", ["id=?", sid.to_i])
flash[:notice] = message('project_history.snapshot_deleted')
end

redirect_to :action => 'history', :id => @project.id
end

def links
@project = get_current_project(params[:id])


+ 0
- 34
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_delete_form.html.erb View File

@@ -1,34 +0,0 @@
<% resource_qualifier = message('qualifier.' + @project.qualifier) %>
<form id="delete-project-form" method="post" action="<%= ApplicationController.root_context -%>/project/delete">
<fieldset>
<div class="modal-head">
<h2><%= message('project_deletion.page', :params => resource_qualifier) -%></h2>
</div>
<div class="modal-body">
<%= message('project_deletion.delete_resource_confirmation', :params => resource_qualifier) %>
</div>
<div class="modal-foot">
<span id="delete-project-loading-image" class="loading-image hidden"><%= image_tag 'loading.gif' %></span>
<input type="submit" value="<%= message('delete') -%>" id="delete-project-submit" onclick="return displayLoadingImage()"/>
<a href="#" onclick="return closeModalWindow()" id="delete-project-cancel"><%= message('cancel') -%></a>
</div>
</fieldset>
</form>
<script>
$j("#delete-project-form").modalForm({
success: function () {
$j.ajax({
url: "<%= ApplicationController.root_context-%>/project/delete/<%= h(@project.id) -%>",
success: function (request) {
window.location = '<%= url_for(:action => 'pending_deletion',:id => @project.id)-%>';
},
data: $j(this).serialize(),
type: 'delete'
});
}
});

function displayLoadingImage() {
$j('#delete-project-loading-image').removeClass("hidden");
}
</script>

+ 3
- 18
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/deletion.html.erb View File

@@ -1,18 +1,3 @@
<div class="page">
<%
if !@snapshot || @project.root?
resource_qualifier = message('qualifier.' + @project.qualifier)
delete_resource_message = message('project_deletion.page', :params => resource_qualifier)
%>
<header class="page-header">
<h1 class="page-title"><%= delete_resource_message -%></h1>
<p class="page-description"><%= message('project_deletion.page.description') -%></p>
</header>

<div class="yui-g widget" id="widget_delete_project">
<div class="alert alert-warning spacer-bottom"><%= message('project_deletion.operation_cannot_be_undone') -%></div>
<a id="delete_resource" class="open-modal button button-red"
href="<%= ApplicationController.root_context -%>/project/delete_form/<%= h(@project.id) -%>"><%= delete_resource_message -%></a>
</div>
<% end %>
</div>
<% content_for :extra_script do %>
<script src="<%= ApplicationController.root_context -%>/js/bundles/project-admin.js?v=<%= sonar_version -%>"></script>
<% end %>

+ 0
- 2
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/pending_deletion.html.erb View File

@@ -1,2 +0,0 @@
<%= render :partial => 'bulk_deletion/pending_deletions_screen',
:locals => {:url_after_dismiss => url_for(:action => 'dismiss_deletion_message', :id => params[:id])} -%>

+ 1
- 0
server/sonar-web/webpack.config.js View File

@@ -45,6 +45,7 @@ module.exports = {
'metrics': './src/main/js/apps/metrics/app.js',
'overview': './src/main/js/apps/overview/app.js',
'permission-templates': './src/main/js/apps/permission-templates/app.js',
'project-admin': './src/main/js/apps/project-admin/app.js',
'project-permissions': './src/main/js/apps/permissions/project/app.js',
'projects': './src/main/js/apps/projects/app.js',
'quality-gates': './src/main/js/apps/quality-gates/app.js',

+ 2
- 4
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -508,8 +508,7 @@ bulk_deletion.page.description=Use this page to delete multiple projects at once
update_key.page=Update Key
update_key.page.description=Edit the keys of a project and/or its modules. Key changes must be made here BEFORE analyzing the project with the new keys, otherwise the analysis will simply create another project with the new key, rather than updating the existing project.
deletion.page=Deletion
project_deletion.page=Delete {0}
project_deletion.page.description=Delete this project from the SonarQube database.
project_deletion.page.description=Delete this project from SonarQube. The operation cannot be undone.
provisioning.page=Provisioning
provisioning.page.description=Use this page to initialize projects if you would like to configure them before the first analysis. Once a project is provisioned, you have access to perform all project configurations on it.

@@ -1640,8 +1639,7 @@ project_quality_gate.default_qgate=Default
#
#------------------------------------------------------------------------------

project_deletion.operation_cannot_be_undone=This operation can not be undone.
project_deletion.delete_resource_confirmation=Are you sure you want to delete this {0}?
project_deletion.delete_resource_confirmation=Are you sure you want to delete "{0}"?


#------------------------------------------------------------------------------

Loading…
Cancel
Save