Merge pull request #649 from mz3944/mz-SCI-1269

Global repositories screen - rename, delete (just core)
This commit is contained in:
mz3944 2017-06-05 17:59:10 +02:00 committed by GitHub
commit 876fcfc979
13 changed files with 296 additions and 15 deletions

View file

@ -90,8 +90,7 @@ function animateLoading(start){
* Optional parameter options for spin.js options.
*/
function animateSpinner(el, start, options) {
// If overlaying the whole page,
// put the spinner in the middle of the page
// If overlaying the whole page, put the spinner in the middle of the page
var overlayPage = false;
if (_.isUndefined(el) || el === null) {
overlayPage = true;
@ -123,6 +122,30 @@ function animateSpinner(el, start, options) {
}
}
/**
* Automatic handling of show/hide spinner.
* @param {boolean} redirection Whether page is refreshed/redirected on success
* @param {boolean} onElement Whether spinner is fixed on the center of fn
* element or it's positions on the center of whole page
*/
$.fn.animateSpinner = function(redirection, onElement) {
redirection = _.isUndefined(redirection) ? false : redirection;
onElement = _.isUndefined(onElement) ? false : onElement;
$(this)
.on('ajax:beforeSend', function() {
onElement ? animateSpinner($(this)) : animateSpinner();
})
.on('ajax:error', function(e, data) {
animateSpinner(null, false);
})
.on('ajax:success', function(e, data) {
if (!redirection) {
animateSpinner(null, false);
}
});
}
/*
* Prevents user from accidentally leaving page when server is busy
* and notifies him with a message.

View file

@ -0,0 +1,6 @@
(function() {
'use strict';
$('.delete-repo-option').initializeModal('#delete-repo-modal');
$('.rename-repo-option').initializeModal('#rename-repo-modal');
})();

View file

@ -60,6 +60,7 @@ var renderFormError = function(ev, input, errMsgs, clearErr, errAttributes) {
// Don't submit form
ev.preventDefault();
ev.stopPropagation();
ev.stopImmediatePropagation();
}
};
@ -67,11 +68,11 @@ var renderFormError = function(ev, input, errMsgs, clearErr, errAttributes) {
* Render errors specified in JSON format for many form elements.
*/
$.fn.renderFormErrors = function(modelName, errors, clear, ev) {
clear = ((typeof clear) === 'undefined') ? true : clear;
if (clear || _.isUndefined(clear)) {
clear = _.isUndefined(clear) ? true : clear;
if (clear) {
this.clearFormErrors();
}
var form = $(this);
var $form = $(this);
$.each(errors, function(field, messages) {
// Special exception for file uploads in steps and results
if (field === 'assets.file') {
@ -80,7 +81,7 @@ $.fn.renderFormErrors = function(modelName, errors, clear, ev) {
field = 'asset_attribute';
}
var types = 'input, file, select, textarea';
var $input = $(_.filter(form.find(types), function(el) {
var $input = $(_.filter($form.find(types), function(el) {
var name = $(el).attr('name');
if (name) {
return name.match(new RegExp(modelName + '\\[' + field + '\\(?'));

View file

@ -239,3 +239,57 @@ $.fn.checkboxTreeLogic = function(dependencies, checkAll) {
});
}).trigger('change');
};
/**
* Show modal on link click and handle its' submition and validation.
*
* On link click it gets HTTP reponse with modal partial, shows it, and then on
* submit gets JSON response, displays errors if any or either refreshes the
* page or redirects it (if 'url' parameter is specified in JSON response).
* @param {string} modalID Modal ID
* @param {object} $fn Link objects for opening the modal (can have more
* links for same modal)
*/
$.fn.initializeModal = function(modalID) {
/**
* Popup modal validator
* @param {object} $modal Modal object
*/
function modalResponse($modal) {
var $modalForm = $modal.find('form');
$modalForm
.on('ajax:success', function(ev, data) {
if (_.isUndefined(data)) {
location.reload();
} else {
$(location).attr('href', data.url);
}
})
.on('ajax:error', function(e, data) {
$(this).renderFormErrors('repository', data.responseJSON);
})
.animateSpinner(true);
}
var $linksToModal = $(this);
$linksToModal
.on('ajax:success', function(e, data) {
// Add and show modal
$('body').append($.parseHTML(data.html));
$(modalID).modal('show', {
backdrop: true,
keyboard: false
});
modalResponse($(modalID));
// Remove modal when it gets closed
$(modalID).on('hidden.bs.modal', function() {
$(modalID).remove();
});
})
.on('ajax:error', function() {
// TODO
})
.animateSpinner();
};

View file

@ -1,11 +1,67 @@
class RepositoriesController < ApplicationController
before_action :load_vars
before_action :check_view_all_permissions, only: :index
before_action :check_edit_and_destroy_permissions, only:
%(destroy destroy_modal rename_modal update)
def index
render('repositories/index')
end
def destroy_modal
@repository = Repository.find(params[:repository_id])
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'delete_repository_modal.html.erb'
)
}
end
end
end
def destroy
@repository = Repository.find(params[:id])
flash[:success] = t('repositories.index.delete_flash',
name: @repository.name)
@repository.destroy
redirect_to team_repositories_path
end
def rename_modal
@repository = Repository.find(params[:repository_id])
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'rename_repository_modal.html.erb'
)
}
end
end
end
def update
@repository = Repository.find(params[:id])
old_name = @repository.name
@repository.update_attributes(repository_params)
respond_to do |format|
format.json do
if @repository.save
flash[:success] = t('repositories.index.rename_flash',
old_name: old_name, new_name: @repository.name)
render json: {
url: team_repositories_path(repository: @repository)
}, status: :ok
else
render json: @repository.errors, status: :unprocessable_entity
end
end
end
end
private
def load_vars
@ -17,4 +73,12 @@ class RepositoriesController < ApplicationController
def check_view_all_permissions
render_403 unless can_view_team_repositories(@team)
end
def check_edit_and_destroy_permissions
render_403 unless can_edit_and_destroy_repository(@repository)
end
def repository_params
params.require(:repository).permit(:name)
end
end

View file

@ -1060,4 +1060,8 @@ module PermissionHelper
def can_view_repository(repository)
is_normal_user_or_admin_of_team(repository.team)
end
def can_edit_and_destroy_repository(repository)
is_admin_of_team(repository.team)
end
end

View file

@ -7,7 +7,7 @@ class Repository < ActiveRecord::Base
auto_strip_attributes :name, nullify: false
validates :name,
presence: true,
uniqueness: { scope: :team },
uniqueness: { scope: :team, case_sensitive: false },
length: { maximum: Constants::NAME_MAX_LENGTH }
validates :team, presence: true
validates :created_by, presence: true

View file

@ -1,4 +1,4 @@
<ol class="breadcrumb breadcrumb-protocols-manager">
<li><%= link_to current_team.name, projects_path(team: current_team) %></li>
<li class="active"><%= t("repositories.nav.breadcrumbs.repositories") %></li>
</ol>
</ol>

View file

@ -0,0 +1,33 @@
<div class="modal fade" id="delete-repo-modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title"><%= t("repositories.index.modal_delete.title_html", name: @repository.name) %></h4>
</div>
<div class="modal-body">
<p><%= t("repositories.index.modal_delete.message_html", name: @repository.name) %></p>
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
&nbsp;
<%= t("repositories.index.modal_delete.alert_heading") %>
<ul>
<li><%= t("repositories.index.modal_delete.alert_line_1") %></li>
<li><%= t("repositories.index.modal_delete.alert_line_2") %></li>
</ul>
</div>
</div>
<div class="modal-footer">
<%= link_to t('repositories.index.modal_delete.delete'),
team_repository_path(id: @repository),
id: "confirm-repo-delete",
method: :delete,
type: 'button',
class: 'btn btn-primary' %>
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel")%></button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,32 @@
<div class="modal fade" id="rename-repo-modal" tabindex="-1" role="dialog">
<%= bootstrap_form_for @repository,
url: team_repository_path(id: @repository, format: :json),
remote: true do |f| %>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title"><%= t("repositories.index.modal_rename.title_html", name: @repository.name ) %></h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div class="form-group">
<%= f.text_field :name,
label: t("repositories.index.modal_rename.name"),
autofocus: true,
placeholder: t("repositories.index.modal_rename.name_placeholder") %>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<%= f.submit t("repositories.index.modal_rename.rename"), class: "btn btn-primary" %>
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t "general.cancel" %></button>
</div>
</div>
</div>
<% end %>
</div>

View file

@ -1,13 +1,17 @@
<% provide(:head_title, t("repositories.index.head_title")) %>
<% if current_team %>
<%= render partial: "repositories/breadcrumbs.html.erb", locals: { teams: @teams, current_team: current_team, type: @type } %>
<%= render partial: "repositories/breadcrumbs.html.erb",
locals: { teams: @teams, current_team: current_team, type: @type } %>
<!-- Nav tabs -->
<% if @repositories.present? %>
<% active_repo = @repositories.find_by_id(params[:repository]) %>
<% active_repo = @repositories.first if !active_repo %>
<ul class="nav nav-tabs nav-settings" role="tablist" id="tabs">
<% @repositories.each.with_index do |repo, i| %>
<li role="presentation" class="<%= 'active' if i == 0 %>">
<% @repositories.each do |repo| %>
<li role="presentation" class="<%= 'active' if repo == active_repo %>">
<a class="repository-nav-tab"
href="#custom_repo_<%= repo.id %>"
data-toggle="tab"
@ -18,9 +22,44 @@
</ul>
<!-- Tab panes -->
<div class="tab-content">
<% @repositories.each.with_index do |repo, i| %>
<div class="tab-pane tab-pane-settings <%= 'active' if i == 0 %>" id="custom_repo_<%= repo.id %>">
<% @repositories.each do |repo| %>
<div class="tab-pane tab-pane-settings <%= 'active' if repo == active_repo %>" id="custom_repo_<%= repo.id %>">
<!-- Tab Content -->
<div id="repository-toolbar">
<div class="dropdown text-right">
<div class="btn btn-default btn-xs"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
<%= "disabled='disabled'" if !can_edit_and_destroy_repository repo %>>
<span class="glyphicon glyphicon-cog"></span>
<span class="caret"></span>
</div>
<% if can_edit_and_destroy_repository repo %>
<ul class="dropdown-menu pull-right">
<li class="dropdown-header">
<%= t("repositories.index.options_dropdown.header") %>
</li>
<li>
<%= link_to t('repositories.index.options_dropdown.rename'),
team_repository_rename_modal_path(repository_id: repo),
class: "rename-repo-option",
remote: true %>
</li>
<li role="separator" class="divider"></li>
<li>
<%= link_to t('repositories.index.modal_delete.delete'),
team_repository_destroy_modal_path(repository_id: repo),
class: "delete-repo-option",
remote: true %>
</li>
</ul>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
@ -35,3 +74,5 @@
<p><%=t 'repositories.index.no_teams.text' %></p>
</div>
<% end %>
<%= javascript_include_tag "repositories/index", "data-turbolinks-track" => true %>

View file

@ -153,9 +153,27 @@ en:
head_title: "Repositories"
title: "Repositories"
no_repositories: "No repositories"
delete_flash: "\"%{name}\" repository was successfully deleted!"
rename_flash: "\"%{old_name}\" repository was successfully renamed to \"%{new_name}\"!"
no_teams:
title: "Your dashboard is empty!"
text: "It seems you're not a member of any team. See team management to sort it out."
options_dropdown:
header: "Edit repository"
rename: "Rename"
delete: "Delete"
modal_delete:
title_html: "Delete repository <em>%{name}</em>"
message_html: "Are you sure you want to delete repository <em>%{name}</em>? This action is irreversible."
alert_heading: "Deleting repository has following consequences:"
alert_line_1: "all data inside the repository will be lost;"
alert_line_2: "all references to repository items will be rendered as invalid."
delete: "Delete repository"
modal_rename:
title_html: "Rename repository <em>%{name}</em>"
name: "New repository name"
name_placeholder: "My repository"
rename: "Rename repository"
nav:
breadcrumbs:
repositories: "Repositories"
@ -895,7 +913,7 @@ en:
modal_delete_custom_field:
title: "Delete a column"
message: "Are you sure you wish to permanently delete selected column %{cf}? This action is irreversible."
alert_heading: "Deleting a column has following consequences:"
alert_heading: "Deleting column has following consequences:"
alert_line_1: "you will lose information in this column for %{nr} samples;"
alert_line_2: "the column will be deleted for all team members."
delete: "Delete column"

View file

@ -123,7 +123,12 @@ Rails.application.routes.draw do
as: 'file_expired'
resources :teams do
resources :repositories, only: [:index]
resources :repositories, only: %i(index destroy update) do
get 'destroy_modal', to: 'repositories#destroy_modal',
defaults: { format: 'json' }
get 'rename_modal', to: 'repositories#rename_modal',
defaults: { format: 'json' }
end
resources :samples, only: [:new, :create]
resources :sample_types, except: [:show, :new] do
get 'sample_type_element', to: 'sample_types#sample_type_element'