adds save PDF report to inventory feature [fixes SCI-2371]

This commit is contained in:
zmagod 2018-05-16 09:31:19 +02:00
parent 8650edbbb9
commit 4573655ec8
16 changed files with 630 additions and 5 deletions

View file

@ -212,6 +212,7 @@ var HelperModule = (function(){
helpers.flashAlertMsg = function(message, type) {
var alertType;
var glyphSign;
$('#notifications').html('');
if (type === 'success') {
alertType = ' alert-success ';

View file

@ -0,0 +1,287 @@
(function() {
'use strict';
var INVENTORY_PICKER, COLUMN_PICKER, ITEM_PICKER;
var SELECTED_IDS = {
repository_id: null,
respository_column_id: null,
repository_item_id: null,
};
function clearErrors() {
var $columnsAlertSection = $('#save-PDF-to-inventory-column-warnings');
var $itemsAlertSection = $('#save-PDF-to-inventory-warnings');
$itemsAlertSection.empty();
$columnsAlertSection.empty();
}
function toggleHasFileErrorMessage(value) {
var element = $('#selectInventoryItem [value="' + value + '"]');
var $alertSection = $('#save-PDF-to-inventory-warnings');
$alertSection.empty();
if(element.data('hasfile')) {
$alertSection.append(
'<div class="alert alert-danger">' +
'<%=I18n.t("projects.reports.new.save_PDF_to_inventory_modal.asset_present_warning_html") %>' +
'</div>'
)
}
}
function appendSearchResults(data) {
var items = [];
if(data.hasOwnProperty('results')){
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false,
data: {
hasFile: el.hasOwnProperty('has_file_attached') ?
el.has_file_attached :
null
}
}
)
});
}
return items;
}
function appendSearchResultsForItems(data) {
var items = [];
if(data.hasOwnProperty('results')){
$('#selectInventoryItem').parent().find('button').removeAttr('disabled');
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false,
data: {
hasFile: el.hasOwnProperty('has_file_attached') ?
el.has_file_attached :
null
}
}
)
});
} else {
$('#selectInventoryItem').parent().find('button').attr('disabled', true);
clearErrors();
$('#save-PDF-to-inventory-warnings').append(
'<strong class="danger">' + data.no_items + '</strong>'
)
}
return items;
}
function appendSearchResultsForColumns(data) {
var items = [];
if(data.hasOwnProperty('results')){
$('#selectInventoryColumn').parent().find('button').removeAttr('disabled');
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false
}
)
});
} else {
$('#selectInventoryColumn').parent().find('button').attr('disabled', true);
clearErrors();
$('#save-PDF-to-inventory-column-warnings').append(
'<div class="save-PDF-to-inventory-alerts"><strong class="danger">' +
data.no_items + '</strong></div>'
)
}
return items;
}
function submitButtonEnableToggle(status) {
var button = $('#savePDFtoInventorySubmit');
if(status) {
button.removeAttr('disabled');
} else {
button.attr('disabled', true);
}
}
function deselect(element) {
if(element) {
element.selectpicker('val', null)
element.selectpicker('render');
element.attr('disabled', true);
}
}
// triggers first request and cleans the dropdown selectpicker
function clearDropdownResultsCallback(element) {
element.parent().find('button').on('click', function(el) {
$(this).parent().find('input').trigger('keyup');
});
}
function initInventoryItemSelectPicker() {
ITEM_PICKER =
$('#selectInventoryItem')
.removeAttr('disabled')
.selectpicker({liveSearch: true})
.ajaxSelectPicker({
ajax: {
url: '<%= Rails.application.routes.url_helpers.available_rows_path %>',
type: 'POST',
dataType: 'json',
data: function () {
return {
q: '{{{q}}}',
repository_id: SELECTED_IDS.repository_id,
repository_column_id: SELECTED_IDS.respository_column_id
};
}
},
locale: {
emptyTitle: '<%= I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected') %>'
},
preprocessData: appendSearchResultsForItems,
emptyRequest: true,
clearOnEmpty: true,
cache: false,
preserveSelected: false
}).on('change.bs.select', function(el) {
var value = el.target.value;
toggleHasFileErrorMessage(value);
submitButtonEnableToggle(true);
SELECTED_IDS.repository_item_id = value;
});
clearDropdownResultsCallback(ITEM_PICKER);
}
function initInventoryColumnSelectPicker() {
COLUMN_PICKER =
$('#selectInventoryColumn')
.removeAttr('disabled')
.selectpicker({liveSearch: true})
.ajaxSelectPicker({
ajax: {
url: '<%= Rails.application.routes.url_helpers.file_columns_path %>',
type: 'POST',
dataType: 'json',
data: function () {
return {
q: '{{{q}}}',
repository_id: SELECTED_IDS.repository_id
};
}
},
locale: {
emptyTitle: '<%= I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected') %>'
},
preprocessData: appendSearchResultsForColumns,
emptyRequest: true,
clearOnEmpty: true,
cache: false,
preserveSelected: false
}).on('change.bs.select', function(el) {
SELECTED_IDS.respository_column_id = el.target.value;
deselect(ITEM_PICKER);
submitButtonEnableToggle(false);
initInventoryItemSelectPicker();
// refresh the dropdown state
$('#selectInventoryItem').parent().find('input').trigger('keyup');
clearErrors();
});
clearDropdownResultsCallback(COLUMN_PICKER);
}
function initInvenoriesSelectPicker() {
INVENTORY_PICKER =
$('#selectInventory')
.selectpicker({liveSearch: true})
.ajaxSelectPicker({
ajax: {
url: '<%= Rails.application.routes.url_helpers.reports_available_repositories_path %>',
type: 'POST',
dataType: 'json',
data: function () {
return { q: '{{{q}}}' };
}
},
locale: {
emptyTitle: '<%= I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected') %>'
},
preprocessData: appendSearchResults,
emptyRequest: true,
clearOnEmpty: false,
cache: false,
preserveSelected: false
}).on('change.bs.select', function(el) {
SELECTED_IDS.repository_id = el.target.value;
deselect(COLUMN_PICKER);
deselect(ITEM_PICKER);
submitButtonEnableToggle(false);
initInventoryColumnSelectPicker();
clearErrors();
// refresh the dropdown state
$('#selectInventoryColumn').parent().find('input').trigger('keyup');
});
clearDropdownResultsCallback(INVENTORY_PICKER);
}
function initializeSubmitAction() {
$('#savePDFtoInventorySubmit').off().on('click', function() {
animateSpinner();
$.ajax({
url: '<%= Rails.application.routes.url_helpers.reports_save_pdf_to_inventory_item_path %>',
data: Object.assign(SELECTED_IDS, { html: $(REPORT_CONTENT).html() }, {}),
type: 'POST',
success: function(data) {
animateSpinner(null, false);
HelperModule.flashAlertMsg(data.message, 'success');
$('#savePDFtoInventory').modal('hide');
},
error: function(xhr) {
animateSpinner(null, false);
HelperModule.flashAlertMsg(xhr.responseJSON.message, 'danger');
$('#savePDFtoInventory').modal('hide');
}
});
});
}
/*
* INITIALIZERS
*/
function initializeSavePDFtoInvenotryModal() {
$('#savePDFtoInventory').off().on('shown.bs.modal', function() {
initInvenoriesSelectPicker();
initializeSubmitAction();
clearErrors();
// refresh the dropdown state
$('#selectInventory').parent().find('input').trigger('keyup');
}).on('hidden.bs.modal', function() {
// clear ids
SELECTED_IDS = {
repository_id: null,
respository_column_id: null,
repository_item_id: null,
}
// clear select picker objects
if(COLUMN_PICKER) {
deselect(COLUMN_PICKER);
}
if(ITEM_PICKER) {
deselect(ITEM_PICKER);
}
submitButtonEnableToggle(false);
});
}
$(document).ready(initializeSavePDFtoInvenotryModal);
})();

View file

@ -533,3 +533,13 @@ label {
}
}
}
#save-PDF-to-inventory-warnings {
margin-top: 30px;
}
.save-PDF-to-inventory-alerts {
.danger {
color: $brand-danger;
}
}

View file

@ -26,6 +26,8 @@ class ReportsController < ApplicationController
before_action :load_vars, only: %i(edit update)
before_action :load_vars_nested, only: BEFORE_ACTION_METHODS
before_action :load_visible_projects, only: %i(index visible_projects)
before_action :load_available_repositories,
only: %i(new edit available_repositories)
before_action :check_manage_permissions, only: BEFORE_ACTION_METHODS
@ -169,7 +171,7 @@ class ReportsController < ApplicationController
respond_to do |format|
format.pdf do
@html = params[:html]
@html = '<h1>No content</h1>' if @html.blank?
@html = I18n.t('projects.reports.new.no_content_for_PDF_html') if @html.blank?
render pdf: 'report',
header: { right: '[page] of [topage]' },
template: 'reports/report.pdf.erb',
@ -178,6 +180,30 @@ class ReportsController < ApplicationController
end
end
def save_pdf_to_inventory_item
report_service = ReportActions::SavePdfToInventoryItem.new(
current_user, current_team, save_PDF_params
)
report_service.call
cell_value = report_service.cell_value
if cell_value.save
render json: {
message: I18n.t(
'projects.reports.new.save_PDF_to_inventory_modal.success_flash'
)
}, status: :ok
else
render json: { message: cell_value.errors.full_messages.join },
status: :unprocessable_entity
end
rescue ReportActions::RepostioryPermissionError => error
render json: { message: error },
status: :unprocessable_entity
rescue Exception => error
render json: { message: error.message },
status: :unprocessable_entity
end
# Modal for saving the existsing/new report
def save_modal
# Assume user is updating existing report
@ -428,10 +454,15 @@ class ReportsController < ApplicationController
render json: { projects: @visible_projects }, status: :ok
end
def available_repositories
render json: { results: @available_repositories }, status: :ok
end
private
include StringUtility
VisibleProject = Struct.new(:path, :name)
AvailableRepository = Struct.new(:id, :name)
def load_vars
@report = Report.find_by_id(params[:id])
@ -460,6 +491,16 @@ class ReportsController < ApplicationController
end
end
def load_available_repositories
repositories = current_team.repositories
.name_like(search_params[:q])
.select(:id, :name)
@available_repositories = repositories.collect do |repository|
AvailableRepository.new(repository.id,
ellipsisize(repository.name, 75, 50))
end
end
def report_params
params.require(:report)
.permit(:name, :description, :grouped_by, :report_contents)
@ -468,4 +509,11 @@ class ReportsController < ApplicationController
def search_params
params.permit(:q)
end
def save_PDF_params
params.permit(:repository_id,
:respository_column_id,
:repository_item_id,
:html)
end
end

View file

@ -1,11 +1,14 @@
class RepositoryColumnsController < ApplicationController
include InputSanitizeHelper
before_action :load_vars, except: %i(create index create_html)
before_action :load_vars_nested, only: %i(create index create_html)
before_action :load_vars, except: %i(create index create_html file_columns)
before_action :load_vars_nested,
only: %i(create index create_html file_columns)
before_action :check_create_permissions, only: :create
before_action :check_manage_permissions, except: %i(create index create_html)
before_action :check_manage_permissions,
except: %i(create index create_html file_columns)
before_action :load_repository_columns, only: :index
before_action :load_asset_type_columns, only: :file_columns
def index; end
@ -139,8 +142,25 @@ class RepositoryColumnsController < ApplicationController
end
end
def file_columns
if @asset_columns.empty?
render json: {
no_items: t(
'projects.reports.new.save_PDF_to_inventory_modal.no_columns'
)
}, status: :ok
else
render json: { results: @asset_columns }, status: :ok
end
end
private
include StringUtility
AvailableRepositoryColumn = Struct.new(:id, :name)
def load_vars
@repository = Repository.find_by_id(params[:repository_id])
render_404 unless @repository
@ -158,6 +178,11 @@ class RepositoryColumnsController < ApplicationController
.order(created_at: :desc)
end
def load_asset_type_columns
render_403 unless can_read_team?(@repository.team)
@asset_columns = load_asset_columns(search_params[:q])
end
def check_create_permissions
render_403 unless can_create_repository_columns?(@repository.team)
end
@ -170,6 +195,23 @@ class RepositoryColumnsController < ApplicationController
params.require(:repository_column).permit(:name, :data_type)
end
def search_params
params.permit(:q, :repository_id)
end
def load_asset_columns(query)
@repository.repository_columns
.asset_type.name_like(query)
.limit(Constants::SEARCH_LIMIT)
.select(:id, :name)
.collect do |column|
AvailableRepositoryColumn.new(
column.id,
ellipsisize(column.name)
)
end
end
def generate_repository_list_items(item_names)
return true unless @repository_column.data_type == 'RepositoryListValue'
column_items = @repository_column.repository_list_items.size

View file

@ -6,7 +6,11 @@ class RepositoryRowsController < ApplicationController
before_action :load_info_modal_vars, only: :show
before_action :load_vars, only: %i(edit update)
before_action :load_repository,
only: %i(create delete_records index copy_records)
only: %i(create
delete_records
index
copy_records
available_rows)
before_action :check_create_permissions, only: :create
before_action :check_manage_permissions,
only: %i(edit update delete_records copy_records)
@ -296,8 +300,26 @@ class RepositoryRowsController < ApplicationController
}, status: :ok
end
def available_rows
if @repository.repository_rows.empty?
no_items_string =
"#{t('projects.reports.new.save_PDF_to_inventory_modal.no_items')} " \
"#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'),
repository_path(@repository),
data: { 'no-turbolink' => true })}"
render json: { no_items: no_items_string },
status: :ok
else
render json: { results: load_available_rows(search_params[:q]) },
status: :ok
end
end
private
include StringUtility
AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached)
def load_info_modal_vars
@repository_row = RepositoryRow.eager_load(:created_by, repository: [:team])
.find_by_id(params[:id])
@ -342,6 +364,27 @@ class RepositoryRowsController < ApplicationController
params.permit(selected_rows: []).to_h[:selected_rows]
end
def load_available_rows(query)
@repository.repository_rows
.includes(:repository_cells)
.name_like(search_params[:q])
.limit(Constants::SEARCH_LIMIT)
.select(:id, :name)
.collect do |row|
with_asset_cell = row.repository_cells.where(
'repository_cells.repository_column_id = ?',
search_params[:repository_column_id]
)
AvailableRepositoryRow.new(row.id,
ellipsisize(row.name),
with_asset_cell.present?)
end
end
def search_params
params.permit(:q, :repository_id, :repository_column_id)
end
def record_annotation_notification(record, cell, old_text = nil)
table_url = params.fetch(:request_url) { :request_url_must_be_present }
smart_annotation_notification(

View file

@ -57,6 +57,10 @@ class Repository < ApplicationRecord
end
end
def self.name_like(query)
where('repositories.name ILIKE ?', "%#{query}%")
.limit(Constants::SEARCH_LIMIT)
end
def importable_repository_fields
fields = {}
# First and foremost add record name

View file

@ -23,6 +23,11 @@ class RepositoryColumn < ApplicationRecord
around_destroy :update_repository_table_states_with_removed_column
scope :list_type, -> { where(data_type: 'RepositoryListValue') }
scope :asset_type, -> { where(data_type: 'RepositoryAssetValue') }
def self.name_like(query)
where('repository_columns.name ILIKE ?', "%#{query}%")
end
def update_repository_table_states_with_new_column
service = RepositoryTableStateColumnUpdateService.new

View file

@ -26,4 +26,8 @@ class RepositoryRow < ApplicationRecord
where(id: ids).joins(:my_module_repository_rows)
.where('my_module_repository_rows.my_module' => my_module)
end
def self.name_like(query)
where('repository_rows.name ILIKE ?', "%#{query}%")
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
module ReportActions
class SavePdfToInventoryItem
attr_reader :cell_value
def initialize(user, team, params)
@user = user
@team = team
@params = params
load_repository_collaborators
end
def call
file = generate_pdf(@params[:html])
asset = create_new_asset(file)
cell = fetch_repository_cell
cell.destroy if cell
@cell_value = create_new_cell_value(asset)
end
private
include Canaid::Helpers::PermissionsHelper
def load_repository_collaborators
@repository = Repository.find_by_id(@params[:repository_id])
@repository_column = RepositoryColumn.find_by_id(
@params[:respository_column_id]
)
@repository_item = RepositoryRow.find_by_id(
@params[:repository_item_id]
)
unless can_create_repository_rows?(@user, @repository.team)
raise ReportActions::RepostioryPermissionError,
I18n.t('projects.reports.new.no_permissions')
end
end
def generate_pdf(html)
ac = ActionView::Base.new(ActionController::Base.view_paths, {})
ac.extend ReportsHelper # include reports helper methods to view
ac.extend InputSanitizeHelper # include input sanitize methods to view
no_content_label = I18n.t('projects.reports.new.no_content_for_PDF_html')
save_path = Tempfile.open('report', Rails.root.join('tmp'))
@html = html
@html = no_content_label if @html.blank?
pdf_file = WickedPdf.new.pdf_from_string(
ac.render(template: 'reports/report.pdf.erb'),
header: { right: '[page] of [topage]' }, disable_javascript: true
)
File.open(save_path, 'wb') do |file|
file << pdf_file
end
save_path
end
def create_new_asset(file)
asset = Asset.new(
file: file, created_by: @user, last_modified_by: @user, team: @team
)
asset.post_process_file(@team) if asset.save
asset
end
def fetch_repository_cell
RepositoryCell.where(repository_row: @repository_item,
repository_column: @repository_column,
value_type: 'RepositoryAssetValue').first
end
def create_new_cell_value(asset)
RepositoryAssetValue.new(
asset: asset,
created_by: @user,
last_modified_by: @user,
repository_cell_attributes: {
repository_row: @repository_item,
repository_column: @repository_column
}
)
end
end
RepostioryPermissionError = Class.new(StandardError)
end

View file

@ -2,6 +2,7 @@
<% provide(:body_class, "report-body") %>
<% provide(:container_class, "report-container") %>
<div id="notifications"></div>
<%= render partial: "reports/new/report_navigation" %>
<div
@ -71,8 +72,11 @@
</div>
</div>
<%= render partial: 'reports/new/save_PDF_to_inventory_modal' %>
<%= javascript_include_tag "handsontable.full.min" %>
<%= javascript_include_tag("reports/new") %>
<%= javascript_include_tag 'reports/save_pdf_to_inventory' %>
<!-- Libraries for formulas -->
<%= javascript_include_tag "lodash" %>

View file

@ -27,6 +27,12 @@
</div>
<% end %>
<button
onclick="$('#savePDFtoInventory').modal('show')"
class="btn btn-default">
<%=t 'projects.reports.new.save_PDF_to_inventory'%>
</button>
<div class="pull-right">
<%= link_to reports_path, data: { no_turbolink: false }, id: "cancel-report-link", class: "btn btn-default" do %>
<span class="hidden-xs"><%=t "projects.reports.new.nav_close" %></span>

View file

@ -0,0 +1,57 @@
<div
id="savePDFtoInventory"
class="modal fade" 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 'projects.reports.new.save_PDF_to_inventory' %></h4>
</div>
<div class="modal-body">
<p><%=t 'projects.reports.new.save_PDF_to_inventory_modal.description_one' %></p>
<br />
<p><%=t 'projects.reports.new.save_PDF_to_inventory_modal.description_two' %></p>
<% if @available_repositories && @available_repositories.length > 0 %>
<label><%=t 'projects.reports.new.save_PDF_to_inventory_modal.inventory' %></label>
<select
id="selectInventory"
class="form-control selectpicker"
data-live-search="true">
<option></option>
<% @available_repositories.each do |repository| %>
<option value="<%= repository.id %>"><%= repository.name %></option>
<% end %>
</select>
<label><%=t 'projects.reports.new.save_PDF_to_inventory_modal.inventory_column' %></label>
<select
id="selectInventoryColumn"
class="form-control selectpicker"
data-live-search="true"
disabled>
</select>
<div id="save-PDF-to-inventory-column-warnings"></div>
<label><%=t 'projects.reports.new.save_PDF_to_inventory_modal.inventory_column' %></label>
<select
id="selectInventoryItem"
class="form-control selectpicker"
data-live-search="true"
disabled>
</select>
<% else %>
<p><%=t 'projects.reports.new.save_PDF_to_inventory_modal.no_inventories' %></p>
<% end %>
<div class="save-PDF-to-inventory-alerts" id="save-PDF-to-inventory-warnings"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t 'general.close' %></button>
<button
id="savePDFtoInventorySubmit"
type="button"
class="btn btn-primary"
disabled
>Save changes</button>
</div>
</div>
</div>
</div>

View file

@ -87,6 +87,7 @@ Rails.application.config.assets.precompile += %w(repository_columns/index.js)
Rails.application.config.assets.precompile += %w(repositories/show.js)
Rails.application.config.assets.precompile += %w(sidebar_toggle.js)
Rails.application.config.assets.precompile += %w(reports/reports_datatable.js)
Rails.application.config.assets.precompile += %w(reports/save_pdf_to_inventory.js)
# Libraries needed for Handsontable formulas
Rails.application.config.assets.precompile += %w(lodash.js)

View file

@ -315,6 +315,7 @@ en:
nav_title: "Report for: "
nav_print: "Print"
nav_pdf: "Download PDF"
save_PDF_to_inventory: "Save PDF to Inventory"
nav_save: "Save"
nav_close: "Close"
nav_sort_by: "Sort by"
@ -324,6 +325,21 @@ en:
sidebar_title: "Navigation"
global_sort: "Sorting whole report will undo any custom sorting you might have done. Proceed?"
unsaved_work: "Are you sure you want to leave this page? All unsaved data will be lost."
no_content_for_PDF_html: "<h1>No content</h1>"
no_permissions: "You don't have permissions on that repository"
save_PDF_to_inventory_modal:
description_one: "Here you can save PDF report to an inventory item."
description_two: "First select an inventory, then a column and finally the item within tne inventory to which you would like to save the PDF report to. Note that the column has to be of type \"file\"."
inventory: "Select inventory:"
inventory_column: "Select inventory column:"
inventory_item: "Select inventory item:"
no_inventories: "No inventories available!"
success_flash: "Report successfully saved to Inventory item."
asset_present_warning_html: "The selected cell already contains a file. If you would like to replace the file click Save. Replacing the file will have the following consequences: <ul><li>previous file will be permanently deleted;</li><li>new file will be added to the Inventory item.</li></ul>"
no_items: "Selected Inventory does not contain any items yet. Add the first item"
here: "here"
no_columns: "You do not have any Inventories with File columns. Add a File column to existing Inventory or create a new Inventory."
nothing_selected: "Nothing selected"
elements:
modals:
project_contents:

View file

@ -193,6 +193,13 @@ Rails.application.routes.draw do
get 'reports/datatable', to: 'reports#datatable'
post 'reports/visible_projects', to: 'reports#visible_projects',
defaults: { format: 'json' }
post 'reports/available_repositories', to: 'reports#available_repositories',
defaults: { format: 'json' }
post 'reports/save_pdf_to_inventory_item',
to: 'reports#save_pdf_to_inventory_item',
defaults: { format: 'json' }
post 'file_columns', to: 'repository_columns#file_columns',
defaults: { format: 'json' }
post 'reports/destroy', to: 'reports#destroy'
resources :projects, except: [:new, :destroy] do
@ -491,6 +498,9 @@ Rails.application.routes.draw do
end
end
post 'available_rows', to: 'repository_rows#available_rows',
defaults: { format: 'json' }
post 'repository_list_items', to: 'repository_list_items#search',
defaults: { format: 'json' }