diff --git a/Gemfile.lock b/Gemfile.lock index f0a7e09df..40a51e8f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js index 042d8b83b..865200116 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js +++ b/app/assets/javascripts/repositories/repository_datatable.js @@ -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( + $('') + .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(); }); } diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index b1724734b..97713e9e6 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -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 diff --git a/app/models/zip_export.rb b/app/models/zip_export.rb index 38e92d44e..ad637b5c5 100644 --- a/app/models/zip_export.rb +++ b/app/models/zip_export.rb @@ -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 diff --git a/app/views/repositories/_export_repository_modal.html.erb b/app/views/repositories/_export_repository_modal.html.erb new file mode 100644 index 000000000..c6cd2ce0b --- /dev/null +++ b/app/views/repositories/_export_repository_modal.html.erb @@ -0,0 +1,24 @@ +