Merge pull request #665 from rekonder/aj-SCI-1275

Repositories UI - refactor export (background job) [SCI-1275]
This commit is contained in:
Luka Murn 2017-06-13 08:06:35 +02:00 committed by GitHub
commit cc8878433d
8 changed files with 211 additions and 30 deletions

View file

@ -1,8 +1,8 @@
GIT
remote: git://github.com/einzige/sneaky-save.git
revision: 03866e838f62a4b13e15784974fcc13e14cd9502
remote: https://github.com/einzige/sneaky-save
revision: e7c77674abe74d598dfd58db7c680dd85936f207
specs:
sneaky-save (0.1.1)
sneaky-save (0.1.2)
activerecord (>= 3.2.0)
GEM

View file

@ -214,6 +214,56 @@ setTimeout(function() {
// Enables noSearchHidden plugin
$.fn.dataTable.defaults.noSearchHidden = true;
$('form#form-export').submit(function() {
var form = this;
if (currentMode === 'viewMode') {
// Remove all hidden fields
$(form).find('input[name=row_ids\\[\\]]').remove();
$(form).find('input[name=header_ids\\[\\]]').remove();
// Append visible column information
$('.active table#repository-table thead tr th').each(function() {
var th = $(this);
var val;
switch ($(th).attr('id')) {
case 'checkbox':
val = -1;
break;
case 'row-name':
val = -2;
break;
case 'added-by':
val = -3;
break;
case 'added-on':
val = -4;
break;
default:
val = th.attr('id');
}
if (val) {
appendInput(form, val, 'header_ids[]');
}
});
// Append records
$.each(rowsSelected, function(index, rowId) {
appendInput(form, rowId, 'row_ids[]');
});
}
});
function appendInput(form, val, name) {
$(form).append(
$('<input>')
.attr('type', 'hidden')
.attr('name', name)
.val(val)
);
}
function initRowSelection() {
// Handle clicks on checkbox
$('.dt-body-center .repository-row-selector').change(function(e) {
@ -637,29 +687,38 @@ function updateButtons() {
$('th').removeClass('disable-click');
$('.repository-row-selector').removeClass('disabled');
$('.repository-row-selector').prop('disabled', false);
if (rowsSelected.length === 1) {
$('#editRepositoryRecord').prop('disabled', false);
$('#editRepositoryRecord').removeClass('disabled');
$('#deleteRepositoryRecordsButton').prop('disabled', false);
$('#deleteRepositoryRecordsButton').removeClass('disabled');
$('#assignRepositoryRecords').removeClass('disabled');
$('#assignRepositoryRecords').prop('disabled', false);
$('#unassignRepositoryRecords').removeClass('disabled');
$('#unassignRepositoryRecords').prop('disabled', false);
} else if (rowsSelected.length === 0) {
if (rowsSelected.length === 0) {
$('#editRepositoryRecord').prop('disabled', true);
$('#editRepositoryRecord').addClass('disabled');
$('#deleteRepositoryRecordsButton').prop('disabled', true);
$('#deleteRepositoryRecordsButton').addClass('disabled');
$('#exportRepositoriesButton').addClass('disabled');
$('#exportRepositoriesButton').prop('disabled', true);
$('#exportRepositoriesButton').off('click');
$('#export-repositories').off('click');
$('#assignRepositoryRecords').addClass('disabled');
$('#assignRepositoryRecords').prop('disabled', true);
$('#unassignRepositoryRecords').addClass('disabled');
$('#unassignRepositoryRecords').prop('disabled', true);
} else {
$('#editRepositoryRecord').prop('disabled', true);
$('#editRepositoryRecord').addClass('disabled');
if (rowsSelected.length === 1) {
$('#editRepositoryRecord').prop('disabled', false);
$('#editRepositoryRecord').removeClass('disabled');
} else {
$('#editRepositoryRecord').prop('disabled', true);
$('#editRepositoryRecord').addClass('disabled');
}
$('#deleteRepositoryRecordsButton').prop('disabled', false);
$('#deleteRepositoryRecordsButton').removeClass('disabled');
$('#exportRepositoriesButton').removeClass('disabled');
$('#exportRepositoriesButton').prop('disabled', false);
$('#exportRepositoriesButton').on('click', function() {
$('#exportRepositoryModal').modal('show');
});
$('#export-repositories').on('click', function() {
animateSpinner(null, true);
$('#form-export').submit();
});
$('#assignRepositoryRecords').removeClass('disabled');
$('#assignRepositoryRecords').prop('disabled', false);
$('#unassignRepositoryRecords').removeClass('disabled');
@ -674,6 +733,9 @@ function updateButtons() {
$('#addNewColumn').prop('disabled', true);
$('#deleteRepositoryRecordsButton').addClass('disabled');
$('#deleteRepositoryRecordsButton').prop('disabled', true);
$('#exportRepositoriesButton').addClass('disabled');
$('#exportRepositoriesButton').off('click');
$('#export-repositories').off('click');
$('#assignRepositoryRecords').addClass('disabled');
$('#assignRepositoryRecords').prop('disabled', true);
$('#unassignRepositoryRecords').addClass('disabled');
@ -971,6 +1033,7 @@ function changeToEditMode() {
if (!_.isEmpty(searchText)) {
table.search(searchText).draw();
}
initRowSelection();
});
}

View file

@ -1,19 +1,21 @@
class RepositoriesController < ApplicationController
before_action :load_vars, except: :repository_table_index
before_action :load_vars, except: %i(index create create_modal)
before_action :load_parent_vars, except:
%i(repository_table_index export_repository)
before_action :check_view_all_permissions, only: :index
before_action :check_view_permissions, only: :export_repository
before_action :check_edit_and_destroy_permissions, only:
%(destroy destroy_modal rename_modal update)
%i(destroy destroy_modal rename_modal update)
before_action :check_copy_permissions, only:
%(copy_modal copy)
%i(copy_modal copy)
before_action :check_create_permissions, only:
%(create_new_modal create)
%i(create_new_modal create)
def index
render('repositories/index')
end
def show_tab
@repository = Repository.find_by_id(params[:repository_id])
respond_to do |format|
format.json do
render json: {
@ -62,7 +64,6 @@ class RepositoriesController < ApplicationController
end
def destroy_modal
@repository = Repository.find(params[:repository_id])
respond_to do |format|
format.json do
render json: {
@ -75,7 +76,6 @@ class RepositoriesController < ApplicationController
end
def destroy
@repository = Repository.find(params[:id])
flash[:success] = t('repositories.index.delete_flash',
name: @repository.name)
@repository.destroy
@ -83,7 +83,6 @@ class RepositoriesController < ApplicationController
end
def rename_modal
@repository = Repository.find(params[:repository_id])
respond_to do |format|
format.json do
render json: {
@ -96,7 +95,6 @@ class RepositoriesController < ApplicationController
end
def update
@repository = Repository.find(params[:id])
old_name = @repository.name
@repository.update_attributes(repository_params)
@ -116,7 +114,6 @@ class RepositoriesController < ApplicationController
end
def copy_modal
@repository = Repository.find(params[:repository_id])
@tmp_repository = Repository.new(
team: @team,
created_by: current_user,
@ -134,7 +131,6 @@ class RepositoriesController < ApplicationController
end
def copy
@repository = Repository.find(params[:repository_id])
@tmp_repository = Repository.new(
team: @team,
created_by: current_user
@ -169,7 +165,6 @@ class RepositoriesController < ApplicationController
# AJAX actions
def repository_table_index
@repository = Repository.find_by_id(params[:repository_id])
if @repository.nil? || !can_view_repository(@repository)
render_403
else
@ -185,9 +180,24 @@ class RepositoriesController < ApplicationController
end
end
def export_repository
if params[:row_ids] && params[:header_ids]
generate_zip
else
flash[:alert] = t('zip_export.export_error')
end
redirect_to :back
end
private
def load_vars
repository_id = params[:id] || params[:repository_id]
@repository = Repository.find_by_id(repository_id)
render_404 unless @repository
end
def load_parent_vars
@team = Team.find_by_id(params[:team_id])
render_404 unless @team
@repositories = @team.repositories.order(created_at: :asc)
@ -197,6 +207,10 @@ class RepositoriesController < ApplicationController
render_403 unless can_view_team_repositories(@team)
end
def check_view_permissions
render_403 unless can_view_repository(@repository)
end
def check_create_permissions
render_403 unless can_create_repository(@team)
end
@ -212,4 +226,66 @@ class RepositoriesController < ApplicationController
def repository_params
params.require(:repository).permit(:name)
end
def generate_zip
# Fetch rows in the same order as in the currently viewed datatable
ordered_row_ids = params[:row_ids]
id_row_map = RepositoryRow.where(id: ordered_row_ids,
repository: @repository)
.index_by(&:id)
ordered_rows = ordered_row_ids.collect { |id| id_row_map[id.to_i] }
zip = ZipExport.create(user: current_user)
zip.generate_exportable_zip(
current_user,
to_csv(ordered_rows, params[:header_ids]),
:repositories
)
end
def to_csv(rows, column_ids)
require 'csv'
# Parse column names
csv_header = []
column_ids.each do |c_id|
csv_header << case c_id.to_i
when -1
next
when -2
I18n.t('repositories.table.row_name')
when -3
I18n.t('repositories.table.added_by')
when -4
I18n.t('repositories.table.added_on')
else
column = RepositoryColumn.find_by_id(c_id)
column ? column.name : nil
end
end
CSV.generate do |csv|
csv << csv_header
rows.each do |row|
csv_row = []
column_ids.each do |c_id|
csv_row << case c_id.to_i
when -1
next
when -2
row.name
when -3
row.created_by.full_name
when -4
I18n.l(row.created_at, format: :full)
else
cell = row.repository_cells
.find_by(repository_column_id: c_id)
cell ? cell.value.data : nil
end
end
csv << csv_row
end
end
end
end

View file

@ -102,8 +102,12 @@ class ZipExport < ActiveRecord::Base
end
end
# generates zip export file for samples
def generate_samples_zip(tmp_dir, data, options = {})
def generate_samples_zip(tmp_dir, data, _options = {})
file = FileUtils.touch("#{tmp_dir}/export.csv").first
File.open(file, 'wb') { |f| f.write(data) }
end
def generate_repositories_zip(tmp_dir, data, _options = {})
file = FileUtils.touch("#{tmp_dir}/export.csv").first
File.open(file, 'wb') { |f| f.write(data) }
end

View file

@ -0,0 +1,24 @@
<div class="modal fade"
id="exportRepositoryModal"
tabindex="-1"
role="dialog"
aria-labelledby="modal-export-repository-label">
<%= bootstrap_form_tag(url: export_repository_team_path(repository),
html: { id: 'form-export' }) 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 'zip_export.modal_label' %></h4>
</div>
<div class="modal-body">
<%=t('zip_export.repository_html', repository: repository.name) %>
</div>
<div class="modal-footer">
<button type='button' class='btn btn-primary' data-dismiss='modal' id='export-repositories'> <%=t 'my_modules.repository.export' %> </button>
<button type='button' class='btn btn-default' data-dismiss='modal' id='close-modal-export-repositories'><%= t('general.close')%></button>
</div>
</div>
</div>
<% end %>
</div>

View file

@ -1,5 +1,7 @@
<%= render partial: "repositories/delete_record_modal.html.erb" %>
<%= render partial: "repositories/delete_column_modal.html.erb" %>
<%= render partial: 'repositories/export_repository_modal.html.erb',
locals: { repository: repository } %>
<div id="alert-container"></div>
@ -12,6 +14,13 @@
</button>
<% end %>
<% if can_view_repository(repository) %>
<a href="#" class="btn btn-default" id="exportRepositoriesButton">
<span class="glyphicon glyphicon-cloud-download"></span>
<span class="hidden-xs"><%= t("my_modules.repository.export") %></span>
</a>
<% end %>
<div id="datatables-buttons" style="display: inline;">
<div id="repository-columns-dropdown" class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">

View file

@ -612,6 +612,7 @@ en:
more_activities: "Load older activities"
repository:
head_title: "%{project} | %{module} | Custom repository %{repository}"
export: 'Export'
experiments:
new:
@ -1692,12 +1693,15 @@ en:
deleted: "(deleted)"
zip_export:
modal_label: 'Export repository'
notification_title: 'Your package is ready to be exported!'
expired_title: 'The required file was expired!'
expired_description: 'The downloadable file expires in 7 days after its creation.'
export_error: 'An error occured.'
modal_label: 'Export request received'
modal_html: "<p>Your export request is being processed.</p><p>When completed we will <strong>send an email to %{email}</strong> inbox with a link to your exported samples. Note that the link will expire in 7 days.</p>"
repository_html: '<p>You are about to export selected items in repository %{repository}</p> <br> Repository will be exported in a .csv file format. You will receive <strong>email with a link</strong> where you can download it.'
export_error: "Error when creating zip export."
# This section contains general words that can be used in any parts of
# application.
tiny_mce:

View file

@ -155,6 +155,7 @@ Rails.application.routes.draw do
post 'parse_sheet'
post 'import_samples'
post 'export_samples'
post 'export_repository', to: 'repositories#export_repository'
# Used for atwho (smart annotations)
get 'atwho_users', to: 'at_who#users'
get 'atwho_samples', to: 'at_who#samples'