Merge pull request #1308 from mz3944/mz-SCI-2641

Export all - PDF (or HTML) report [SCI-2641] [WIP]
This commit is contained in:
Jure Grabnar 2018-10-15 09:59:24 +02:00 committed by GitHub
commit 91f2ab62bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 373 additions and 93 deletions

View file

@ -68,7 +68,7 @@ label {
}
.ht_clone_top,.ht_clone_left,.ht_clone_corner {
display: none !important;
display: none !important;
}
}
@ -323,8 +323,14 @@ label {
// Result table element style
.report-result-table-element {
.report-element-header {
.table-name {
margin-left: 15px;
}
}
.report-element-body {
padding-top: 30px;
padding-top: 15px;
}
}
@ -493,6 +499,14 @@ label {
.repository-name {
margin-left: 5px;
}
.table-name {
margin-left: 15px;
}
}
.report-element-body {
padding-top: 15px;
}
&:hover > .report-element-header {

View file

@ -1,12 +1,25 @@
module ReportsHelper
include StringUtility
def render_new_element(hide)
render partial: 'reports/elements/new_element.html.erb',
locals: { hide: hide }
end
def render_report_element(element, provided_locals = nil)
children_html = ''.html_safe
# Determine partial
file_name = element.type_of
if element.type_of.in? ReportExtends::MY_MODULE_CHILDREN_ELEMENTS
file_name = "my_module_#{element.type_of.singularize}"
end
view = "reports/elements/#{file_name}_element.html.erb"
# Set locals
locals = provided_locals.nil? ? {} : provided_locals.clone
children_html = ''.html_safe
# First, recursively render element's children
if element.comments? || element.project_header?
# Render no children
@ -31,25 +44,54 @@ module ReportsHelper
end
children_html.safe_concat render_new_element(false)
end
file_name = element.type_of
if element.type_of.in? ReportExtends::MY_MODULE_CHILDREN_ELEMENTS
file_name = "my_module_#{element.type_of.singularize}"
end
view = "reports/elements/#{file_name}_element.html.erb"
locals = provided_locals.nil? ? {} : provided_locals.clone
locals[:children] = children_html
# ReportExtends is located in config/initializers/extends/report_extends.rb
if provided_locals[:export_all]
# Set path and filename locals for files and tables in export all ZIP
if element['type_of'] == 'my_module_repository'
obj_id = element[:repository_id]
elsif element['type_of'].in? %w(step_asset step_table result_asset
result_table)
parent_el = ReportElement.find(element['parent_id'])
parent_type = parent_el[:type_of]
parent = parent_type.singularize.classify.constantize
.find(parent_el["#{parent_type}_id"])
if parent.class == Step
obj_id = if element['type_of'] == 'step_asset'
element[:asset_id]
elsif element['type_of'] == 'step_table'
element[:table_id]
end
elsif parent.class == MyModule
result = Result.find(element[:result_id])
obj_id = if element['type_of'] == 'result_asset'
result.asset.id
elsif element['type_of'] == 'result_table'
result.table.id
end
end
end
if obj_id
locals[:path] =
provided_locals[:obj_filenames][element['type_of'].to_sym][obj_id]
.sub(%r{/usr/src/app/tmp/temp-zip-\d+/}, '')
locals[:filename] = locals[:path].split('/').last
end
end
# ReportExtends is located in config/initializers/extends/report_extends.rb
ReportElement.type_ofs.keys.each do |type|
next unless element.public_send("#{type}?")
element.element_references.each do |el_ref|
locals[el_ref.class.name.underscore.to_sym] = el_ref
end
locals[:order] = element
.sort_order if type.in? ReportExtends::SORTED_ELEMENTS
if type.in? ReportExtends::SORTED_ELEMENTS
locals[:order] = element.sort_order
end
end
(render partial: view, locals: locals).html_safe
@ -114,4 +156,19 @@ module ReportsHelper
end
html_doc.to_s
end
private
def obj_name_to_filename(obj, filename_suffix = '')
obj_name = if obj.class == Asset
obj_name, extension = obj.file_file_name.split('.')
extension&.prepend('.')
obj_name
elsif obj.class.in? [Table, Result, Repository]
extension = '.csv'
obj.name.present? ? obj.name : obj.class.name
end
obj_name = to_filesystem_name(obj_name)
obj_name + "#{filename_suffix}#{extension}"
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module StringUtility
def ellipsize(
string,
@ -10,4 +12,25 @@ module StringUtility
mid_length = length - edge_length * 2
string.gsub(/(#{edge}).{#{mid_length},}(#{edge})/, '\1...\2')
end
# Convert string to filesystem compatible file/folder name
def to_filesystem_name(name)
# Handle reserved directories
if name == '..'
return '__'
elsif name == '.'
return '_'
end
# Truncate and replace reserved characters
name = name[0, Constants::EXPORTED_FILENAME_TRUNCATION_LENGTH]
.gsub(%r{[*":<>?/\\|~]}, '_')
# Remove control characters
name = name.chars.map(&:ord).select { |s| (s > 31 && s < 127) || s > 127 }
.pack('U*')
# Remove leading hyphens, trailing dots/spaces
name.gsub(/^-|\.+$| +$/, '_')
end
end

View file

@ -221,4 +221,36 @@ class Project < ApplicationRecord
end
res
end
def generate_report_pdf(user, team, pdf_name, obj_filenames = nil)
ActionController::Renderer::RACK_KEY_TRANSLATION['warden'] ||= 'warden'
proxy = Warden::Proxy.new({}, Warden::Manager.new({}))
renderer = ApplicationController.renderer.new(warden: proxy)
report = Report.generate_whole_project_report(self, user, team)
page_html_string =
renderer.render 'reports/new.html.erb',
locals: { export_all: true,
obj_filenames: obj_filenames },
assigns: { project: self, report: report }
parsed_page_html = Nokogiri::HTML(page_html_string)
parsed_pdf_html = parsed_page_html.at_css('#report-content')
report.destroy
parsed_pdf = ApplicationController.render(
pdf: pdf_name,
header: { right: '[page] of [topage]' },
locals: { content: parsed_pdf_html.to_s },
template: 'reports/report.pdf.erb',
disable_javascript: true,
disable_internal_links: false,
current_user: user,
current_team: team
)
# Dirty workaround to convert absolute links back to relative ones, since
# WickedPdf does the opposite, based on the path where the file parsing is
# done
parsed_pdf.gsub('/URI (file:////tmp/', '/URI (')
end
end

View file

@ -20,7 +20,7 @@ class Report < ApplicationRecord
# Report either has many report elements (if grouped by timestamp),
# or many module elements (if grouped by module)
has_many :report_elements, inverse_of: :report, dependent: :destroy
has_many :report_elements, inverse_of: :report, dependent: :delete_all
after_commit do
Views::Datatables::DatatablesReport.refresh_materialized_view
@ -93,6 +93,103 @@ class Report < ApplicationRecord
end
end
def self.generate_whole_project_report(project, current_user, current_team)
report_contents = gen_element_content(project, nil, 'project_header', true)
project.experiments.each do |exp|
modules = []
exp.my_modules.each do |my_module|
module_children = []
my_module.protocol.steps.each do |step|
step_children =
gen_element_content(step, step.assets, 'step_asset')
step_children +=
gen_element_content(step, step.tables, 'step_table')
step_children +=
gen_element_content(step, step.checklists, 'step_checklist')
step_children +=
gen_element_content(step, nil, 'step_comments', true, 'asc')
module_children +=
gen_element_content(step, nil, 'step', true, nil, step_children)
end
my_module.results.each do |result|
result_children =
gen_element_content(result, nil, 'result_comments', true, 'asc')
result_type = if result.asset
'result_asset'
elsif result.table
'result_table'
elsif result.result_text
'result_text'
end
module_children +=
gen_element_content(result, nil, result_type, true, nil,
result_children)
end
module_children +=
gen_element_content(my_module, nil, 'my_module_activity', true, 'asc')
module_children +=
gen_element_content(my_module,
my_module.repository_rows.select(:repository_id)
.distinct.map(&:repository),
'my_module_repository', true, 'asc')
modules +=
gen_element_content(my_module, nil, 'my_module', true, nil,
module_children)
end
report_contents +=
gen_element_content(exp, nil, 'experiment', true, nil, modules)
end
report = Report.new
report.name = loop do
dummy_name = SecureRandom.hex(10)
break dummy_name unless Report.where(name: dummy_name).exists?
end
report.project = project
report.user = current_user
report.team = current_team
report.last_modified_by = current_user
report.save_with_contents(report_contents)
report
end
def self.gen_element_content(parent_obj, association_objs, type_of,
use_parent_id = false, sort_order = nil,
children = nil)
parent_type = parent_obj.class.name.underscore
type = type_of.split('_').last.singularize
extra_id_needed = use_parent_id && !association_objs.nil?
elements = []
association_objs ||= [nil]
association_objs.each do |obj|
elements << {
'type_of' => type_of,
'id' => {}.tap do |ids_hash|
if use_parent_id
ids_hash["#{parent_type}_id"] = parent_obj.id
else
ids_hash["#{type}_id"] = obj.id
end
ids_hash["#{type}_id"] = obj.id if extra_id_needed
end,
'sort_order' => sort_order.present? ? sort_order : nil,
'children' => children.present? ? children : []
}
end
elements
end
private
# Recursively save a single JSON element

View file

@ -1,17 +1,18 @@
# frozen_string_literal: true
require 'zip'
require 'fileutils'
require 'csv'
class TeamZipExport < ZipExport
include StringUtility
has_attached_file :zip_file,
path: '/zip_exports/:attachment/:id_partition/' \
':hash/:style/:filename'
validates_attachment :zip_file,
content_type: { content_type: 'application/zip' }
# Length of allowed name size
MAX_NAME_SIZE = 20
def generate_exportable_zip(user, data, type, options = {})
@user = user
FileUtils.mkdir_p(File.join(Rails.root, 'tmp/zip-ready'))
@ -34,10 +35,10 @@ class TeamZipExport < ZipExport
private
# Export all functionality
def generate_teams_zip(tmp_dir, data, options = {})
def generate_team_zip(tmp_dir, data, options = {})
# Create team folder
@team = options[:team]
team_path = "#{tmp_dir}/#{handle_name(@team.name)}"
team_path = "#{tmp_dir}/#{to_filesystem_name(@team.name)}"
FileUtils.mkdir_p(team_path)
# Create Projects folders
@ -46,7 +47,10 @@ class TeamZipExport < ZipExport
# Iterate through every project
data.each_with_index do |(_, p), ind|
project_name = handle_name(p.name) + "_#{ind}"
obj_filenames = { my_module_repository: {}, step_asset: {},
step_table: {}, result_asset: {}, result_table: {} }
project_name = to_filesystem_name(p.name) + "_#{ind}"
root =
if p.archived
"#{team_path}/Archived projects"
@ -56,8 +60,6 @@ class TeamZipExport < ZipExport
root += "/#{project_name}"
FileUtils.mkdir_p(root)
FileUtils.touch("#{root}/#{project_name}_Report.pdf").first
inventories = "#{root}/Inventories"
FileUtils.mkdir_p(inventories)
@ -70,18 +72,19 @@ class TeamZipExport < ZipExport
# Iterate through every inventory repo and save it to CSV
repo_rows.map(&:repository).uniq.each_with_index do |repo, repo_idx|
curr_repo_rows = repo_rows.select { |x| x.repository_id == repo.id }
save_inventories_to_csv(inventories, repo, curr_repo_rows, repo_idx)
obj_filenames[:my_module_repository][repo.id] =
save_inventories_to_csv(inventories, repo, curr_repo_rows, repo_idx)
end
# Include all experiments
p.experiments.each_with_index do |ex, ex_ind|
experiment_path = "#{root}/#{handle_name(ex.name)}_#{ex_ind}"
experiment_path = "#{root}/#{to_filesystem_name(ex.name)}_#{ex_ind}"
FileUtils.mkdir_p(experiment_path)
# Include all modules
ex.my_modules.each_with_index do |my_module, mod_ind|
my_module_path = "#{experiment_path}/" \
"#{handle_name(my_module.name)}_#{mod_ind}"
"#{to_filesystem_name(my_module.name)}_#{mod_ind}"
FileUtils.mkdir_p(my_module_path)
# Create upper directories for both elements
@ -92,16 +95,31 @@ class TeamZipExport < ZipExport
# Export protocols
steps = my_module.protocols.map(&:steps).flatten
export_assets(StepAsset.where(step: steps), :step, protocol_path)
export_tables(StepTable.where(step: steps), :step, protocol_path)
obj_filenames[:step_asset].merge!(
export_assets(StepAsset.where(step: steps), :step, protocol_path)
)
obj_filenames[:step_table].merge!(
export_tables(StepTable.where(step: steps), :step, protocol_path)
)
# Export results
export_assets(ResultAsset.where(result: my_module.results),
:result, result_path)
export_tables(ResultTable.where(result: my_module.results),
:result, result_path)
obj_filenames[:result_asset].merge!(
export_assets(ResultAsset.where(result: my_module.results),
:result, result_path)
)
obj_filenames[:result_table].merge!(
export_tables(ResultTable.where(result: my_module.results),
:result, result_path)
)
end
end
# Generate and export whole project report PDF
pdf_name = "#{project_name}_Report.pdf"
project_report_pdf =
p.generate_report_pdf(@user, @team, pdf_name, obj_filenames)
file = FileUtils.touch("#{root}/#{pdf_name}").first
File.open(file, 'wb') { |f| f.write(project_report_pdf) }
end
end
@ -119,25 +137,6 @@ class TeamZipExport < ZipExport
UserNotification.create(notification: notification, user: user)
end
def handle_name(name)
# Handle reserved directories
if name == '..'
return '__'
elsif name == '.'
return '_'
end
# Truncate and replace reserved characters
name = name[0, MAX_NAME_SIZE].gsub(%r{[*":<>?/\\|~]}, '_')
# Remove control characters
name = name.chars.map(&:ord).select { |s| (s > 31 && s < 127) || s > 127 }
.pack('U*')
# Remove leading hyphens, trailing dots/spaces
name.gsub(/^-|\.+$| +$/, '_')
end
# Appends given suffix to file_name and then adds original extension
def append_file_suffix(file_name, suffix)
ext = File.extname(file_name)
@ -146,8 +145,11 @@ class TeamZipExport < ZipExport
# Helper method to extract given assets to the directory
def export_assets(elements, type, directory)
asset_indexes = {}
elements.each_with_index do |element, i|
asset = element.asset
if type == :step
name = "#{directory}/" \
"#{append_file_suffix(asset.file_file_name,
@ -158,37 +160,48 @@ class TeamZipExport < ZipExport
end
file = FileUtils.touch(name).first
File.open(file, 'wb') { |f| f.write(asset.open.read) }
asset_indexes[asset.id] = name
end
asset_indexes
end
# Helper method to extract given tables to the directory
def export_tables(elements, type, directory)
table_indexes = {}
elements.each_with_index do |element, i|
table = element.table
table_name = table.name.presence || 'Table'
table_name += i.to_s
if type == :step
name = "#{directory}/#{handle_name(table_name)}" \
name = "#{directory}/#{to_filesystem_name(table_name)}" \
"_#{i}_Step#{element.step.position + 1}.csv"
elsif type == :result
name = "#{directory}/#{handle_name(table_name)}.csv"
name = "#{directory}/#{to_filesystem_name(table_name)}.csv"
end
file = FileUtils.touch(name).first
File.open(file, 'wb') { |f| f.write(table.to_csv) }
table_indexes[table.id] = name
end
table_indexes
end
# Helper method for saving inventories to CSV
def save_inventories_to_csv(path, repo, repo_rows, id)
repo_name = handle_name(repo.name) + "_#{id}"
file = FileUtils.touch("#{path}/#{repo_name}.csv").first
repo_name = "#{to_filesystem_name(repo.name)}_#{id}"
# Attachment folder
rel_attach_path = "#{repo_name}_attachments"
attach_path = "#{path}/#{rel_attach_path}"
FileUtils.mkdir_p(attach_path)
# CSV file
csv_file_path = "#{path}/#{to_filesystem_name(repo.name)}_#{id}.csv"
csv_file = FileUtils.touch(csv_file_path).first
# Define headers and columns IDs
col_ids = [-3, -4, -5, -6] + repo.repository_columns.map(&:id)
@ -210,13 +223,15 @@ class TeamZipExport < ZipExport
# Generate CSV
csv_data = RepositoryZipExport.to_csv(repo_rows, col_ids, @user, @team,
handle_name_func)
File.open(file, 'wb') { |f| f.write(csv_data) }
File.open(csv_file, 'wb') { |f| f.write(csv_data) }
# Save all attachments (it doesn't work directly in callback function
assets.each do |asset, asset_path|
file = FileUtils.touch(asset_path).first
File.open(file, 'wb') { |f| f.write asset.open.read }
end
repo_name
end
# Recursive zipping

View file

@ -12,7 +12,7 @@ module TeamZipExporter
zip.generate_exportable_zip(
current_user,
ids,
:teams,
:team,
options
)
end

View file

@ -13,6 +13,14 @@
<div class="pull-left repository-name">
<%=t "projects.reports.elements.module_repository.name", repository: repository.name, my_module: my_module.name %>
</div>
<% if defined? export_all and export_all %>
<div class="pull-left table-name">
<a href="<%= path %>">
<em><%=t "projects.reports.elements.module_repository.table_name",
name: filename %></em>
</a>
</div>
<% end %>
<div class="pull-right controls">
<%= render partial: "reports/elements/element_controls.html.erb", locals: { show_sort: true } %>
</div>

View file

@ -1,25 +1,29 @@
<% if result.blank? and @result.present? then result = @result end %>
<% result ||= @result %>
<% asset = result.asset %>
<% is_image = result.asset.is_image? %>
<% comments = result.result_comments %>
<% timestamp = asset.created_at %>
<% name = result.name %>
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
<div class="report-element report-result-element report-result-asset-element" data-ts="<%= timestamp.to_i %>" data-type="result_asset" data-id='{ "result_id": <%= result.id %> }' data-scroll-id="<%= result.id %>" data-modal-title="<%=t "projects.reports.elements.modals.result_contents.head_title", result: result.name %>" data-name="<%= name %>" data-icon-class="<%= icon_class %>">
<div class="report-element report-result-element report-result-asset-element" data-ts="<%= timestamp.to_i %>" data-type="result_asset" data-id='{ "result_id": <%= result.id %> }' data-scroll-id="<%= result.id %>" data-modal-title="<%=t "projects.reports.elements.modals.result_contents.head_title", result: result.name %>" data-name="<%= result.name %>" data-icon-class="<%= icon_class %>">
<div class="report-element-header">
<div class="row">
<div class="pull-left result-icon">
<span class="<%= icon_class %>"></span>
</div>
<div class="pull-left result-name">
<%= name %>
<%= result.name %>
</div>
<div class="pull-left file-name">
<em><%=t "projects.reports.elements.result_asset.file_name",
file: truncate(asset.file_file_name,
length: Constants::FILENAME_TRUNCATION_LENGTH)
%>
</em>
<% if defined? export_all and export_all %>
<a href="<%= path %>">
<em><%=t "projects.reports.elements.result_asset.file_name",
file: filename %></em>
</a>
<% else %>
<em><%=t "projects.reports.elements.result_asset.file_name",
file: truncate(asset.file_file_name,
length: Constants::FILENAME_TRUNCATION_LENGTH) %></em>
<% end %>
</div>
<div class="pull-left user-time">
<%=t "projects.reports.elements.result_asset.user_time", user: result.user.full_name, timestamp: l(timestamp, format: :full) %>

View file

@ -1,24 +1,31 @@
<% if result.blank? and @result.present? then result = @result end %>
<% result ||= @result %>
<% table = result.table %>
<% comments = result.result_comments %>
<% timestamp = table.created_at %>
<% name = result.name %>
<div class="report-element report-result-element report-result-table-element" data-ts="<%= timestamp.to_i %>" data-type="result_table" data-id='{ "result_id": <%= result.id %> }' data-scroll-id="<%= result.id %>" data-modal-title="<%=t "projects.reports.elements.modals.result_contents.head_title", result: result.name %>" data-name="<%= name %>" data-icon-class="fas fa-table">
<div class="report-element report-result-element report-result-table-element" data-ts="<%= timestamp.to_i %>" data-type="result_table" data-id='{ "result_id": <%= result.id %> }' data-scroll-id="<%= result.id %>" data-modal-title="<%=t "projects.reports.elements.modals.result_contents.head_title", result: result.name %>" data-name="<%= result.name %>" data-icon-class="fas fa-table">
<div class="report-element-header">
<div class="row">
<div class="pull-left result-name-container">
<div class="result-icon">
<span class="fas fa-table"></span>
<div class="pull-left result-icon">
<span class="fas fa-table"></span>
</div>
<div class="result-name">
<%= name %>
<div class="pull-left result-name">
<%= result.name %>
</div>
<div class="user-time">
<% if defined? export_all and export_all %>
<div class="pull-left table-name">
<a href="<%= path %>">
<em><%=t "projects.reports.elements.result_table.table_name",
name: filename %></em>
</a>
</div>
<% end %>
<div class="pull-left user-time">
<%=t "projects.reports.elements.result_table.user_time", user: result.user.full_name , timestamp: l(timestamp, format: :full) %>
</div>
</div>
<div class="pull-right controls">
<%= render partial: "reports/elements/element_controls.html.erb" %>
<div class="pull-right controls">
<%= render partial: "reports/elements/element_controls.html.erb" %>
</div>
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
<% if asset.blank? and @asset.present? then asset = @asset end %>
<% asset ||= @asset %>
<% is_image = asset.is_image? %>
<% timestamp = asset.created_at %>
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
@ -9,7 +9,16 @@
<span class="<%= icon_class %>"></span>
</div>
<div class="pull-left file-name">
<em><%=t 'projects.reports.elements.step_asset.file_name', file: truncate( asset.file_file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %></em>
<% if defined? export_all and export_all %>
<a href="<%= path %>">
<em><%=t 'projects.reports.elements.step_asset.file_name',
file: filename %></em>
</a>
<% else %>
<em><%=t 'projects.reports.elements.step_asset.file_name',
file: truncate(asset.file_file_name,
length: Constants::FILENAME_TRUNCATION_LENGTH) %></em>
<% end %>
</div>
<div class="pull-left user-time">
<%=t 'projects.reports.elements.step_asset.user_time', timestamp: l(timestamp, format: :full) %>

View file

@ -1,4 +1,4 @@
<% if table.blank? and @table.present? then table = @table end %>
<% table ||= @table %>
<% timestamp = table.created_at %>
<div class="report-element report-step-attachment-element report-step-table-element" data-ts="<%= timestamp.to_i %>" data-type="step_table" data-id='{ "table_id": <%= table.id %> }' data-scroll-id="<%= table.id %>" data-name="<%= table.name %>" data-icon-class="fas fa-table">
<div class="report-element-header">
@ -6,11 +6,20 @@
<div class="pull-left attachment-icon">
<span class="fas fa-table"></span>
</div>
<% if table && table.name %>
<div class="pull-left table-name">
<em><%=t 'projects.reports.elements.step_table.table_name', name: table.name %></em>
</div>
<% end %>
<div class="pull-left table-name">
<% if defined? export_all and export_all %>
<a href="<%= path %>">
<em><%=t 'projects.reports.elements.step_table.table_name',
name: filename %></em>
</a>
<% else %>
<% if table.try(:name) %>
<em><%=t 'projects.reports.elements.step_table.table_name',
name: truncate(table.name,
length: Constants::FILENAME_TRUNCATION_LENGTH) %></em>
<% end %>
<% end %>
</div>
<div class="pull-left user-time">
<%=t 'projects.reports.elements.step_table.user_time', timestamp: l(timestamp, format: :full) %>
</div>

View file

@ -14,15 +14,15 @@
id="data-holder"
class="hidden"
data-project-modal-title="<%=t "projects.reports.elements.modals.project_contents.head_title" %>"
data-add-project-contents-url="<%= project_contents_modal_project_reports_url %>"
data-add-experiment-contents-url="<%= experiment_contents_modal_project_reports_url %>"
data-add-module-contents-url="<%= module_contents_modal_project_reports_url %>"
data-add-step-contents-url="<%= step_contents_modal_project_reports_url %>"
data-add-result-contents-url="<%= result_contents_modal_project_reports_url %>"
data-add-project-contents-url="<%= project_contents_modal_project_reports_url(@project) %>"
data-add-experiment-contents-url="<%= experiment_contents_modal_project_reports_url(@project) %>"
data-add-module-contents-url="<%= module_contents_modal_project_reports_url(@project) %>"
data-add-step-contents-url="<%= step_contents_modal_project_reports_url(@project) %>"
data-add-result-contents-url="<%= result_contents_modal_project_reports_url(@project) %>"
data-stylesheet-url="<%= stylesheet_path "application" %>"
data-print-title="<%=t "projects.reports.print_title", project: @project.name %>"
data-project-id="<%= @project.id %>"
data-save-report-url="<%= save_modal_project_reports_url %>"
data-save-report-url="<%= save_modal_project_reports_url(@project) %>"
data-report-id="<%= @report.present? ? @report.id : "" %>"
data-unsaved-work-text="<%=t "projects.reports.new.unsaved_work" %>"
data-global-sort-text="<%=t "projects.reports.new.global_sort" %>"></div>
@ -32,7 +32,7 @@
<% if @report.present? %>
<% @report.root_elements.each do |el| %>
<%= render_report_element(el) %>
<%= render_report_element(el, local_assigns) %>
<%= render_new_element(false) %>
<% end %>
<% else %>

View file

@ -25,6 +25,9 @@ class Constants
DROPDOWN_TEXT_MAX_LENGTH = 15
# Max characters for filenames, after which they get truncated
FILENAME_TRUNCATION_LENGTH = 50
# Max characters for names of exported files and folders, after which they get
# truncated
EXPORTED_FILENAME_TRUNCATION_LENGTH = 20
USER_INITIALS_MAX_LENGTH = 4
# Password 'key stretching' factor

View file

@ -459,11 +459,13 @@ en:
no_samples: "No samples"
module_repository:
name: "%{repository} of task %{my_module}"
table_name: "[ %{name} ]"
no_items: "No items"
result_asset:
file_name: "[ %{file} ]"
user_time: "Uploaded by %{user} on %{timestamp}."
result_table:
table_name: "[ %{name} ]"
user_time: "Created by %{user} on %{timestamp}."
result_text:
user_time: "Created by %{user} on %{timestamp}."

View file

@ -24,7 +24,7 @@
"@fortawesome/fontawesome-free": "^5.2.0",
"babel-eslint": "^8.2.6",
"babel-plugin-transform-react-jsx-source": "^6.22.0",
"eslint": "^5.2.0",
"eslint": "^5.3.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-airbnb-base": "^13.0.0",
"eslint-config-google": "^0.9.1",