diff --git a/Gemfile b/Gemfile index 710cbf891d..e68cc23127 100644 --- a/Gemfile +++ b/Gemfile @@ -47,8 +47,7 @@ gem 'aspector' # Aspect-oriented programming for Rails gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR gem 'bcrypt', '~> 3.1.10' # gem 'caracal' -# gem 'caracal', git: 'https://github.com/scinote-eln/caracal.git', branch: 'rubyzip2' # Build docx report -gem 'caracal_the_curve', '~> 1.4', '>= 1.4.6' +gem 'caracal', git: 'https://github.com/scinote-eln/caracal.git', branch: 'custom-docx-reports' # Build docx report gem 'caxlsx' # Build XLSX files gem 'deface', '~> 1.9' gem 'down', '~> 5.0' diff --git a/Gemfile.lock b/Gemfile.lock index 956bc482e4..4ed5dbebe6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,16 @@ GIT docile (>= 1.1.0) rails (>= 4) +GIT + remote: https://github.com/scinote-eln/caracal.git + revision: 54c21353798569476a1eaa73b5fd3e275ac85419 + branch: custom-docx-reports + specs: + caracal (1.4.2) + nokogiri (~> 1.6) + rubyzip (>= 2.3) + tilt (>= 1.4) + GIT remote: https://github.com/scinote-eln/img2zpl revision: 23d61cfc3e90ac4caa62dd08546fa0d7590a5140 @@ -210,10 +220,6 @@ GEM capybara-email (3.0.2) capybara (>= 2.4, < 4.0) mail - caracal_the_curve (1.4.6) - nokogiri (~> 1.6) - rubyzip (>= 1.1.0, < 3.0) - tilt (>= 1.4) case_transform (0.2) activesupport caxlsx (4.0.0) @@ -790,7 +796,7 @@ DEPENDENCIES canaid! capybara capybara-email - caracal_the_curve (~> 1.4, >= 1.4.6) + caracal! caxlsx cssbundling-rails cucumber-rails diff --git a/app/assets/javascripts/reports/new.js b/app/assets/javascripts/reports/new.js index ad280af9f7..bcc8ad5d39 100644 --- a/app/assets/javascripts/reports/new.js +++ b/app/assets/javascripts/reports/new.js @@ -968,6 +968,16 @@ function reportHandsonTableConverter() { } (function() { + function getSelectedRepositoryColumnValues(element, selectedAll = false) { + const values = []; + $(element).find('option').each((_, option) => { + if ($(option).attr('selected-value') || selectedAll) { + values.push(option.value); + } + }); + return values; + } + function getReportData() { var reportData = {}; @@ -982,7 +992,7 @@ function reportHandsonTableConverter() { // Template values reportData.template_values = {}; - $.each($('.report-template-values-container').find('.sci-input-field'), function(i, field) { + $.each($('.report-template-values-container').find('.sci-input-field').not('.report-template-value-dropdown'), (_, field) => { if (field.value.length === 0) return; reportData.template_values[field.name] = { @@ -1046,12 +1056,24 @@ function reportHandsonTableConverter() { reportData.report.settings.task[e.value] = e.checked; }); reportData.report.settings.task.repositories = []; - $.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), function(i, e) { - reportData.report.settings.task.repositories.push(parseInt(e.value, 10)); + reportData.report.settings.task.excluded_repository_columns = {}; + + $.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), (_, e) => { + const value = parseInt(e.value, 10); + const $repositoryColumn = $(e).parent().siblings('.repository-columns')[0]; + const selectedValues = dropdownSelector.getValues($repositoryColumn); + const excludedValues = getSelectedRepositoryColumnValues($repositoryColumn, true) + .filter((item) => !selectedValues.includes(item)) + .map((el) => parseInt(el, 10)); + reportData.report.settings.task.repositories.push(value); + reportData.report.settings.task.excluded_repository_columns[value] = excludedValues; }); reportData.report.settings.task.result_order = dropdownSelector.getValues('#taskResultsOrder'); + reportData.report.settings.exclude_task_metadata = $('.exclude-task-metadata-setting')[0].checked; + reportData.report.settings.exclude_timestamps = $('.exclude-timestamps-setting')[0].checked; + return reportData; } @@ -1256,7 +1278,8 @@ function reportHandsonTableConverter() { function reCheckContinueButton() { if (dropdownSelector.getValues('#projectSelector').length > 0 && dropdownSelector.getValues('#templateSelector').length > 0 - && dropdownSelector.getValues('#docxTemplateSelector').length > 0) { + && (dropdownSelector.getValues('#docxTemplateSelector').length > 0 + || $('#docxTemplateSelector').closest('.hidden').length > 0)) { $('.continue-button').attr('disabled', false); } else { $('.continue-button').attr('disabled', true); @@ -1279,6 +1302,12 @@ function reportHandsonTableConverter() { if (dropdownSelector.getValues('#projectSelector').length > 0) { dropdownSelector.enableSelector('#templateSelector'); dropdownSelector.enableSelector('#docxTemplateSelector'); + if ($('#templateSelector').data('defaultTemplate')) { + dropdownSelector.selectValues('#templateSelector', $('#templateSelector').data('defaultTemplate')); + } + if ($('#docxTemplateSelector').data('defaultTemplate')) { + dropdownSelector.selectValues('#docxTemplateSelector', $('#docxTemplateSelector').data('defaultTemplate')); + } } else { dropdownSelector.selectValues('#templateSelector', ''); dropdownSelector.disableSelector('#templateSelector'); @@ -1349,10 +1378,24 @@ function reportHandsonTableConverter() { if (dropdownSelector.getValues('#docxTemplateSelector').length > 0) { loadDocxTemplate(); } + + $('.repository-columns').each((_, element) => { + const elementId = `#${$(element).attr('id')}`; + const elements = getSelectedRepositoryColumnValues(elementId); + + dropdownSelector.init(elementId, { + selectAppearance: 'simple', + optionClass: 'checkbox-icon' + }); + + if (elements.length) { + dropdownSelector.selectValues(elementId, elements); + } + }); } function loadTemplate() { - let template = $('#templateSelector').val(); + const template = dropdownSelector.getValues('#templateSelector'); let params = { project_id: dropdownSelector.getValues('#projectSelector'), template: template @@ -1382,7 +1425,7 @@ function reportHandsonTableConverter() { } function loadDocxTemplate() { - let template = $('#docxTemplateSelector').val(); + const template = dropdownSelector.getValues('#docxTemplateSelector'); let params = { project_id: dropdownSelector.getValues('#projectSelector'), template: template diff --git a/app/assets/javascripts/sitewide/dropdown_selector.js b/app/assets/javascripts/sitewide/dropdown_selector.js index b78704270b..41902a5046 100644 --- a/app/assets/javascripts/sitewide/dropdown_selector.js +++ b/app/assets/javascripts/sitewide/dropdown_selector.js @@ -353,7 +353,7 @@ var dropdownSelector = (function() { // If we setup Select All we draw it and add correspond logic if (selectElement.data('select-all-button')) { - $(``) + $(``) .appendTo(dropdownContainer.find('.dropdown-container')) .click(() => { // For AJAX dropdown we will use only "Deselect All" diff --git a/app/assets/stylesheets/reports/new.scss b/app/assets/stylesheets/reports/new.scss index 0355c40640..8b50742d8b 100644 --- a/app/assets/stylesheets/reports/new.scss +++ b/app/assets/stylesheets/reports/new.scss @@ -250,6 +250,25 @@ } } + // scss-lint:disable ImportantRule + .dropdown-selector-container { + .dropdown-container { + left: auto !important; + margin: auto !important; + position: absolute !important; + } + } + // scss-lint:enable ImportantRule + + .repositories-contents { + .dropdown-selector-container { + display: inline-flex; + flex-shrink: 0; + margin-left: auto; + width: 200px; + } + } + .project-selector-container { background: $color-white; box-shadow: $modal-shadow; diff --git a/app/assets/stylesheets/shared/dropdown_selector.scss b/app/assets/stylesheets/shared/dropdown_selector.scss index ef119838a8..21c9ff9e8c 100644 --- a/app/assets/stylesheets/shared/dropdown_selector.scss +++ b/app/assets/stylesheets/shared/dropdown_selector.scss @@ -175,6 +175,10 @@ top: 0; width: 100%; z-index: 5; + + &:hover { + background: $color-concrete; + } } .dropdown-blank { diff --git a/app/components/reports/repositories_input_component.rb b/app/components/reports/repositories_input_component.rb index c7909d8c7d..f168cc3966 100644 --- a/app/components/reports/repositories_input_component.rb +++ b/app/components/reports/repositories_input_component.rb @@ -2,10 +2,9 @@ module Reports class RepositoriesInputComponent < TemplateValueComponent - def initialize(report:, name:, label:, placeholder: nil, editing: true, displayed_field: :name) + def initialize(report:, name:, label:, placeholder: nil, editing: true, displayed_field: :name, user: nil) super(report: report, name: name, label: label, placeholder: placeholder, editing: editing) - - live_repositories = Repository.accessible_by_teams(report.team).sort_by { |r| r.name.downcase } + live_repositories = Repository.viewable_by_user(user, report.team).sort_by { |r| r.name.downcase } snapshots_of_deleted = RepositorySnapshot.left_outer_joins(:original_repository) .where(team: report.team) .where.not(original_repository: live_repositories) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 3618b9cfb6..3a0dcbc736 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -18,7 +18,8 @@ class ReportsController < ApplicationController before_action :check_create_permissions, only: %i(new create) before_action :check_manage_permissions, only: %i(edit update generate_pdf generate_docx) before_action :switch_team_with_param, only: :index - after_action :generate_pdf_report, only: %i(create update generate_pdf) + after_action :generate_pdf_report, only: %i(generate_pdf) + after_action :generate_report, only: %i(create update) # Index showing all reports of a single project def index @@ -44,6 +45,8 @@ def new def new_template_values if Extends::REPORT_TEMPLATES.key?(params[:template]&.to_sym) template = params[:template] + @type = :pdf + @template_name = Extends::REPORT_TEMPLATES[params[:template].to_sym] else return render_404 end @@ -69,6 +72,7 @@ def new_template_values else render json: { html: render_to_string(partial: 'reports/wizard/no_template_values', + locals: { type: @type, template: @template_name }, formats: :html) } end @@ -77,6 +81,8 @@ def new_template_values def new_docx_template_values if Extends::DOCX_REPORT_TEMPLATES.key?(params[:template]&.to_sym) template = params[:template] + @type = :docx + @template_name = Extends::DOCX_REPORT_TEMPLATES[params[:template].to_sym] else return render_404 end @@ -102,6 +108,7 @@ def new_docx_template_values else render json: { html: render_to_string(partial: 'reports/wizard/no_template_values', + locals: { type: @type, template: @template_name }, formats: :html) } end @@ -363,6 +370,9 @@ def load_wizard_vars .merge(MyModule.active) .group(:id) .select(:id, :name) + @default_template = Extends::REPORT_TEMPLATES.keys.first.to_s if Extends::REPORT_TEMPLATES.one? + + @default_docx_template = Extends::DOCX_REPORT_TEMPLATES.keys.first.to_s if Extends::DOCX_REPORT_TEMPLATES.one? && custom_templates(Extends::DOCX_REPORT_TEMPLATES) end def check_project_read_permissions @@ -430,6 +440,26 @@ def generate_pdf_report Rails.logger.error e.message end + def generate_docx_report + return unless @report.persisted? + + @report.docx_processing! + log_activity(:generate_docx_report) + + ensure_report_template! + Reports::DocxJob.perform_later(@report.id, user_id: current_user.id, root_url: root_url) + rescue ActiveRecord::ActiveRecordError => e + Rails.logger.error e.message + end + + def generate_report + return unless @report.persisted? + + generate_pdf_report + + generate_docx_report if @report.settings['docx_template'].present? && custom_templates(Extends::DOCX_REPORT_TEMPLATES) + end + def ensure_report_template! return if @report.settings['template'].present? diff --git a/app/helpers/input_sanitize_helper.rb b/app/helpers/input_sanitize_helper.rb index 73c9eaca28..55c3ff38e6 100644 --- a/app/helpers/input_sanitize_helper.rb +++ b/app/helpers/input_sanitize_helper.rb @@ -40,11 +40,12 @@ def custom_auto_link(text, options = {}) preview_repository = options.fetch(:preview_repository, false) format_opt = wrapper_tag.merge(sanitize: false) base64_encoded_imgs = options.fetch(:base64_encoded_imgs, false) - text = simple_format(text, {}, format_opt) if simple_f # allow base64 images when sanitizing if base64_encoded_imgs is true sanitizer_config = Constants::INPUT_SANITIZE_CONFIG.deep_dup + text = sanitize_input(text, tags, sanitizer_config: sanitizer_config) + text = simple_format(text, {}, format_opt) if simple_f text = smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository) if text.match?(SmartAnnotations::TagToHtml::ALL_REGEX) diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index 2abd4fdc2c..47a49e6cf4 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -119,4 +119,8 @@ def permit_report_settings_structure(settings_definition) end end end + + def custom_templates(templates) + templates.any? { |template, _| template != :scinote_template } + end end diff --git a/app/models/my_module.rb b/app/models/my_module.rb index 690427c269..5ded2c691d 100644 --- a/app/models/my_module.rb +++ b/app/models/my_module.rb @@ -391,17 +391,17 @@ def repository_json_hot(repository, order) { data: data, headers: headers } end - def repository_docx_json(repository) - headers = [ - I18n.t('repositories.table.id'), - I18n.t('repositories.table.row_name'), - I18n.t('repositories.table.added_on'), - I18n.t('repositories.table.added_by') - ] + def repository_docx_json(repository, excluded_columns) + headers = Report.default_repository_columns.filter_map do |key, value| + value unless excluded_columns.include?(key.to_s.to_i) + end + custom_columns = [] return false unless repository repository.repository_columns.order(:id).each do |column| + next if excluded_columns.include?(column.id) + if column.data_type == 'RepositoryStockValue' if repository.has_stock_consumption? headers.push(I18n.t('repositories.table.row_consumption')) @@ -416,7 +416,7 @@ def repository_docx_json(repository) records = repository.assigned_rows(self) .select(:id, :name, :created_at, :created_by_id, :repository_id, :parent_id, :archived) - { headers: headers, rows: records, custom_columns: custom_columns } + { headers: headers, rows: records, custom_columns: custom_columns, excluded_columns: excluded_columns } end def deep_clone(current_user) diff --git a/app/models/report.rb b/app/models/report.rb index 0d1fbc97da..61f092e33d 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -43,6 +43,8 @@ class Report < ApplicationRecord DEFAULT_SETTINGS = { all_tasks: true, + exclude_task_metadata: false, + exclude_timestamps: false, task: { protocol: { description: true, @@ -62,7 +64,8 @@ class Report < ApplicationRecord result_comments: true, result_order: 'new', activities: true, - repositories: [] + repositories: [], + excluded_repository_columns: {} } }.freeze @@ -124,4 +127,13 @@ def self.generate_whole_project_report(project, current_user, current_team) ReportActions::ReportContent.new(report, content, {}, current_user).save_with_content report end + + def self.default_repository_columns + { + '-1': I18n.t('repositories.table.id'), + '-2': I18n.t('repositories.table.row_name'), + '-3': I18n.t('repositories.table.added_on'), + '-4': I18n.t('repositories.table.added_by') + } + end end diff --git a/app/services/reports/docx.rb b/app/services/reports/docx.rb index 46445f338c..72e6ce3398 100644 --- a/app/services/reports/docx.rb +++ b/app/services/reports/docx.rb @@ -30,7 +30,7 @@ def initialize(report, docx, options) @link_style = {} @color = {} @scinote_url = options[:scinote_url][0..-2] - @template = @settings[:docx_template] || 'scinote_template' + @template = @settings[:docx_template].presence || 'scinote_template' extend "#{@template.camelize}Docx".constantize end diff --git a/app/services/reports/docx/draw_experiment.rb b/app/services/reports/docx/draw_experiment.rb index e02d018a7a..ebe3ae7ca3 100644 --- a/app/services/reports/docx/draw_experiment.rb +++ b/app/services/reports/docx/draw_experiment.rb @@ -4,6 +4,7 @@ module Reports::Docx::DrawExperiment def draw_experiment(subject) color = @color link_style = @link_style + settings = @settings scinote_url = @scinote_url experiment = subject.experiment return unless can_read_experiment?(@user, experiment) @@ -14,12 +15,15 @@ def draw_experiment(subject) link_style end - @docx.p do - text I18n.t('projects.reports.elements.experiment.user_time', - code: experiment.code, timestamp: I18n.l(experiment.created_at, format: :full)), color: color[:gray] - if experiment.archived? - text ' | ' - text I18n.t('search.index.archived'), color: color[:gray] + if !settings['exclude_timestamps'] || experiment.archived? + @docx.p do + unless settings['exclude_timestamps'] + text I18n.t('projects.reports.elements.experiment.user_time', + code: experiment.code, + timestamp: I18n.l(experiment.created_at, format: :full)), color: color[:gray] + text ' | ' if experiment.archived? + end + text I18n.t('search.index.archived'), color: color[:gray] if experiment.archived? end end html = custom_auto_link(experiment.description, team: @report_team) diff --git a/app/services/reports/docx/draw_my_module.rb b/app/services/reports/docx/draw_my_module.rb index e85ccf7014..bd859baab4 100644 --- a/app/services/reports/docx/draw_my_module.rb +++ b/app/services/reports/docx/draw_my_module.rb @@ -4,6 +4,7 @@ module Reports::Docx::DrawMyModule def draw_my_module(subject, without_results: false, without_repositories: false) color = @color link_style = @link_style + settings = @settings scinote_url = @scinote_url my_module = subject.my_module tags = my_module.tags.order(:id) @@ -15,45 +16,50 @@ def draw_my_module(subject, without_results: false, without_repositories: false) link_style end - @docx.p do - text I18n.t('projects.reports.elements.module.user_time', code: my_module.code, - timestamp: I18n.l(my_module.created_at, format: :full)), color: color[:gray] - if my_module.archived? - text ' | ' - text I18n.t('search.index.archived'), color: color[:gray] - end - end - - if my_module.started_on.present? + if my_module.archived? || !settings['exclude_timestamps'] @docx.p do - text I18n.t('projects.reports.elements.module.started_on', - started_on: I18n.l(my_module.started_on, format: :full)) + unless settings['exclude_timestamps'] + text I18n.t('projects.reports.elements.module.user_time', code: my_module.code, + timestamp: I18n.l(my_module.created_at, format: :full)), color: color[:gray] + text ' | ' if my_module.archived? + end + + text I18n.t('search.index.archived'), color: color[:gray] if my_module.archived? end end - if my_module.due_date.present? - @docx.p do - text I18n.t('projects.reports.elements.module.due_date', - due_date: I18n.l(my_module.due_date, format: :full)) + unless settings['exclude_task_metadata'] + if my_module.started_on.present? + @docx.p do + text I18n.t('projects.reports.elements.module.started_on', + started_on: I18n.l(my_module.started_on, format: :full)) + end end - end - status = my_module.my_module_status - @docx.p do - text I18n.t('projects.reports.elements.module.status') - text ' ' - text "[#{status.name}]", color: (status.light_color? ? '000000' : status.color.delete('#')) - if my_module.completed? - text " #{I18n.t('my_modules.states.completed')} #{I18n.l(my_module.completed_on, format: :full)}" + if my_module.due_date.present? + @docx.p do + text I18n.t('projects.reports.elements.module.due_date', + due_date: I18n.l(my_module.due_date, format: :full)) + end end - end - if tags.present? + status = my_module.my_module_status @docx.p do - text I18n.t('projects.reports.elements.module.tags_header') - tags.each do |tag| - text ' ' - text "[#{tag.name}]", color: tag.color.delete('#') + text I18n.t('projects.reports.elements.module.status') + text ' ' + text "[#{status.name}]", color: (status.light_color? ? '000000' : status.color.delete('#')) + if my_module.completed? + text " #{I18n.t('my_modules.states.completed')} #{I18n.l(my_module.completed_on, format: :full)}" + end + end + + if tags.present? + @docx.p do + text I18n.t('projects.reports.elements.module.tags_header') + tags.each do |tag| + text ' ' + text "[#{tag.name}]", color: tag.color.delete('#') + end end end end @@ -69,10 +75,13 @@ def draw_my_module(subject, without_results: false, without_repositories: false) filter_steps_for_report(my_module.protocol.steps, @settings).order(:position).each do |step| draw_step(step) end + @docx.p - draw_results(my_module) unless without_results + unless without_results + draw_results(my_module) + @docx.p + end - @docx.p subject.children.active.each do |child| next if without_repositories && child.type_of == 'my_module_repository' diff --git a/app/services/reports/docx/draw_my_module_protocol.rb b/app/services/reports/docx/draw_my_module_protocol.rb index 2096569ffe..0f3b56dbea 100644 --- a/app/services/reports/docx/draw_my_module_protocol.rb +++ b/app/services/reports/docx/draw_my_module_protocol.rb @@ -12,13 +12,14 @@ def draw_my_module_protocol(my_module) end if @settings.dig('task', 'protocol', 'description') && protocol.description.present? - @docx.p I18n.t('projects.reports.elements.module.protocol.user_time', code: protocol.original_code, - timestamp: I18n.l(protocol.created_at, format: :full)), color: @color[:gray] + unless @settings['exclude_timestamps'] + @docx.p I18n.t('projects.reports.elements.module.protocol.user_time', code: protocol.original_code, + timestamp: I18n.l(protocol.created_at, format: :full)), color: @color[:gray] + end html = custom_auto_link(protocol.description, team: @report_team) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url, link_style: @link_style }).html_to_word_converter(html) @docx.p - @docx.p end end end diff --git a/app/services/reports/docx/draw_my_module_repository.rb b/app/services/reports/docx/draw_my_module_repository.rb index b552211d18..11f1ed0c84 100644 --- a/app/services/reports/docx/draw_my_module_repository.rb +++ b/app/services/reports/docx/draw_my_module_repository.rb @@ -5,11 +5,12 @@ def draw_my_module_repository(subject) my_module = subject.my_module repository = subject.repository repository = assigned_repository_or_snapshot(my_module, repository) + excluded_repository_columns = @settings.dig(:task, :excluded_repository_columns, repository.id.to_s) || {} return unless repository && can_read_experiment?(@user, my_module.experiment) && (repository.is_a?(RepositorySnapshot) || can_read_repository?(@user, repository)) - repository_data = my_module.repository_docx_json(repository) + repository_data = my_module.repository_docx_json(repository, excluded_repository_columns) return false unless repository_data[:rows].any? && can_read_repository?(@user, repository) @@ -19,7 +20,12 @@ def draw_my_module_repository(subject) @docx.p I18n.t('projects.reports.elements.module_repository.name', repository: repository.name, my_module: my_module.name), bold: true, size: Constants::REPORT_DOCX_STEP_ELEMENTS_TITLE_SIZE - @docx.table table, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE + + if table.present? + @docx.table table, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE + else + @docx.p I18n.t('projects.reports.elements.module_repository.no_columns'), italic: true + end @docx.p @docx.p diff --git a/app/services/reports/docx/draw_project_header.rb b/app/services/reports/docx/draw_project_header.rb index defa8e36ea..c0bbffeef4 100644 --- a/app/services/reports/docx/draw_project_header.rb +++ b/app/services/reports/docx/draw_project_header.rb @@ -15,10 +15,12 @@ def draw_project_header(subject) link_style end - @docx.p do - text I18n.t('projects.reports.elements.project_header.user_time', code: project.code, - timestamp: I18n.l(project.created_at, format: :full)), color: color[:gray] - br + unless @settings['exclude_timestamps'] + @docx.p do + text I18n.t('projects.reports.elements.project_header.user_time', code: project.code, + timestamp: I18n.l(project.created_at, format: :full)), color: color[:gray] + br + end end end end diff --git a/app/services/reports/docx/draw_result_asset.rb b/app/services/reports/docx/draw_result_asset.rb index 030de2591a..fc690977f9 100644 --- a/app/services/reports/docx/draw_result_asset.rb +++ b/app/services/reports/docx/draw_result_asset.rb @@ -25,11 +25,9 @@ def draw_result_asset(result, settings) end text " #{I18n.t('search.index.archived')} ", bold: true if result.archived? text ' ' + I18n.t('projects.reports.elements.result_asset.file_name', file: asset.file_name) - text ' ' + I18n.t('projects.reports.elements.result_asset.user_time', - user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] - - if settings.dig(:task, :file_results_previews) && ActiveStorageFileUtil.previewable_document?(asset&.file&.blob) - text " #{I18n.t('projects.reports.elements.result_asset.full_preview_attached')}", color: color[:gray] + unless settings['exclude_timestamps'] + text ' ' + I18n.t('projects.reports.elements.result_asset.user_time', + user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] end end diff --git a/app/services/reports/docx/draw_result_comments.rb b/app/services/reports/docx/draw_result_comments.rb index 3fc68ef1f3..ebec9ad139 100644 --- a/app/services/reports/docx/draw_result_comments.rb +++ b/app/services/reports/docx/draw_result_comments.rb @@ -8,8 +8,10 @@ def draw_result_comments(result) @docx.p @docx.p I18n.t('projects.reports.elements.result_comments.name', result: result.name), bold: true, size: Constants::REPORT_DOCX_STEP_ELEMENTS_TITLE_SIZE - comments.each do |comment| + comments.find_each.with_index do |comment, index| comment_ts = comment.created_at + + @docx.p unless index.zero? @docx.p I18n.t('projects.reports.elements.result_comments.comment_prefix', user: comment.user.full_name, date: I18n.l(comment_ts, format: :full_date), @@ -17,7 +19,6 @@ def draw_result_comments(result) html = custom_auto_link(comment.message, team: @report_team) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url, link_style: @link_style }).html_to_word_converter(html) - @docx.p end end end diff --git a/app/services/reports/docx/draw_result_table.rb b/app/services/reports/docx/draw_result_table.rb index 661d41a466..f919cc8a40 100644 --- a/app/services/reports/docx/draw_result_table.rb +++ b/app/services/reports/docx/draw_result_table.rb @@ -5,6 +5,7 @@ def draw_result_table(element) result = element.result table = element.orderable.table timestamp = table.created_at + settings = @settings color = @color obj = self table_data = JSON.parse(table.contents_utf_8)['data'] @@ -39,9 +40,11 @@ def draw_result_table(element) end @docx.p do text I18n.t 'projects.reports.elements.result_table.table_name', name: table.name - text ' ' - text I18n.t('projects.reports.elements.result_table.user_time', - timestamp: I18n.l(timestamp, format: :full), user: result.user.full_name), color: color[:gray] + unless settings['exclude_timestamps'] + text ' ' + text I18n.t('projects.reports.elements.result_table.user_time', + timestamp: I18n.l(timestamp, format: :full), user: result.user.full_name), color: color[:gray] + end end end end diff --git a/app/services/reports/docx/draw_result_text.rb b/app/services/reports/docx/draw_result_text.rb index 2ab606a7e6..baf9bc9f3d 100644 --- a/app/services/reports/docx/draw_result_text.rb +++ b/app/services/reports/docx/draw_result_text.rb @@ -4,12 +4,18 @@ module Reports::Docx::DrawResultText def draw_result_text(element) result_text = element.orderable timestamp = element.created_at + settings = @settings color = @color - @docx.p do - text result_text.name.presence || '', italic: true - text ' ' - text I18n.t('projects.reports.elements.result_text.user_time', - timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + if result_text.name.present? || !settings['exclude_timestamps'] + @docx.p do + text result_text.name.to_s, italic: true + text ' ' if result_text.name.present? + + unless settings['exclude_timestamps'] + text I18n.t('projects.reports.elements.result_text.user_time', + timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + end + end end html = custom_auto_link(result_text.text, team: @report_team) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url, diff --git a/app/services/reports/docx/draw_results.rb b/app/services/reports/docx/draw_results.rb index 0baedb9cf8..a708c262e0 100644 --- a/app/services/reports/docx/draw_results.rb +++ b/app/services/reports/docx/draw_results.rb @@ -1,19 +1,31 @@ # frozen_string_literal: true module Reports::Docx::DrawResults - def draw_results(my_module) + def draw_results(my_module, with_my_module_name: false) color = @color + settings = @settings + scinote_url = @scinote_url + link_style = @link_style return unless can_read_my_module?(@user, my_module) if my_module.results.any? && (%w(file_results table_results text_results).any? { |k| @settings.dig('task', k) }) + if with_my_module_name + @docx.h3 do + link my_module.name, + scinote_url + Rails.application.routes.url_helpers.protocols_my_module_path(my_module), + link_style + end + end @docx.h4 I18n.t('Results') order_results_for_report(my_module.results, @settings.dig('task', 'result_order')).each do |result| @docx.p do text result.name.presence || I18n.t('projects.reports.unnamed'), italic: true text " #{I18n.t('search.index.archived')} ", bold: true if result.archived? - text I18n.t('projects.reports.elements.result.user_time', - timestamp: I18n.l(result.created_at, format: :full), - user: result.user.full_name), color: color[:gray] + unless settings['exclude_timestamps'] + text I18n.t('projects.reports.elements.result.user_time', + timestamp: I18n.l(result.created_at, format: :full), + user: result.user.full_name), color: color[:gray] + end end draw_result_asset(result, @settings) if @settings.dig('task', 'file_results') result.result_orderable_elements.each do |element| diff --git a/app/services/reports/docx/draw_step.rb b/app/services/reports/docx/draw_step.rb index 6d4e27da6d..579a24c4e5 100644 --- a/app/services/reports/docx/draw_step.rb +++ b/app/services/reports/docx/draw_step.rb @@ -6,22 +6,30 @@ def draw_step(step) step_type_str = step.completed ? 'completed' : 'uncompleted' user = (step.completed? && step.last_modified_by) || step.user timestamp = step.completed ? step.completed_on : step.created_at + settings = @settings @docx.p @docx.h4( "#{I18n.t('projects.reports.elements.step.step_pos', pos: step.position_plus_one)} #{step.name}" ) - @docx.p do - if step.completed - text I18n.t('protocols.steps.completed'), color: color[:green], bold: true - else - text I18n.t('protocols.steps.uncompleted'), color: color[:gray], bold: true + + unless settings['exclude_task_metadata'] || settings['exclude_timestamps'] + @docx.p do + unless settings['exclude_task_metadata'] + if step.completed + text I18n.t('protocols.steps.completed'), color: color[:green], bold: true + else + text I18n.t('protocols.steps.uncompleted'), color: color[:gray], bold: true + end + end + unless settings['exclude_timestamps'] + text ' | ' unless settings['exclude_task_metadata'] + text I18n.t( + "projects.reports.elements.step.#{step_type_str}.user_time", + user: user.full_name, + timestamp: I18n.l(timestamp, format: :full) + ), color: color[:gray] + end end - text ' | ' - text I18n.t( - "projects.reports.elements.step.#{step_type_str}.user_time", - user: user.full_name, - timestamp: I18n.l(timestamp, format: :full) - ), color: color[:gray] end step.step_orderable_elements.order(:position).each do |element| @@ -41,9 +49,6 @@ def draw_step(step) end draw_step_comments(step) if @settings.dig('task', 'protocol', 'step_comments') - - @docx.p - @docx.p end def handle_step_table(table) diff --git a/app/services/reports/docx/draw_step_asset.rb b/app/services/reports/docx/draw_step_asset.rb index 8d83ca221b..5b6569919e 100644 --- a/app/services/reports/docx/draw_step_asset.rb +++ b/app/services/reports/docx/draw_step_asset.rb @@ -5,6 +5,7 @@ def draw_step_asset(asset) timestamp = asset.created_at asset_url = Rails.application.routes.url_helpers.asset_download_url(asset) color = @color + settings = @settings @docx.p begin @@ -22,9 +23,11 @@ def draw_step_asset(asset) link I18n.t('projects.reports.elements.download'), asset_url do italic true end - text ' ' - text I18n.t('projects.reports.elements.step_asset.user_time', - timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + unless settings['exclude_timestamps'] + text ' ' + text I18n.t('projects.reports.elements.step_asset.user_time', + timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + end end end end diff --git a/app/services/reports/docx/draw_step_checklist.rb b/app/services/reports/docx/draw_step_checklist.rb index e2662e60ec..5c510c9f98 100644 --- a/app/services/reports/docx/draw_step_checklist.rb +++ b/app/services/reports/docx/draw_step_checklist.rb @@ -4,6 +4,7 @@ module Reports::Docx::DrawStepChecklist def draw_step_checklist(checklist) team = @report_team user = @user + settings = @settings items = checklist.checklist_items timestamp = checklist.created_at @@ -15,9 +16,11 @@ def draw_step_checklist(checklist) team, I18n.t('projects.reports.elements.step_checklist.checklist_name', name: checklist.name) ).text, italic: true - text ' ' - text I18n.t('projects.reports.elements.step_checklist.user_time', - timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + unless settings['exclude_timestamps'] + text ' ' + text I18n.t('projects.reports.elements.step_checklist.user_time', + timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + end end if items.any? @docx.ul do diff --git a/app/services/reports/docx/draw_step_comments.rb b/app/services/reports/docx/draw_step_comments.rb index 87d0abc5f6..f9733eaeb1 100644 --- a/app/services/reports/docx/draw_step_comments.rb +++ b/app/services/reports/docx/draw_step_comments.rb @@ -8,8 +8,10 @@ def draw_step_comments(step) @docx.p @docx.p I18n.t('projects.reports.elements.step_comments.name', step: step.name), bold: true, size: Constants::REPORT_DOCX_STEP_ELEMENTS_TITLE_SIZE - comments.each do |comment| + comments.find_each.with_index do |comment, index| comment_ts = comment.created_at + + @docx.p unless index.zero? @docx.p I18n.t('projects.reports.elements.step_comments.comment_prefix', user: comment.user.full_name, date: I18n.l(comment_ts, format: :full_date), @@ -17,7 +19,6 @@ def draw_step_comments(step) html = custom_auto_link(comment.message, team: @report_team) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url, link_style: @link_style }).html_to_word_converter(html) - @docx.p end end end diff --git a/app/services/reports/docx/draw_step_table.rb b/app/services/reports/docx/draw_step_table.rb index a0826741af..3565c200e6 100644 --- a/app/services/reports/docx/draw_step_table.rb +++ b/app/services/reports/docx/draw_step_table.rb @@ -4,6 +4,7 @@ module Reports::Docx::DrawStepTable def draw_step_table(table, table_type) color = @color timestamp = table.created_at + settings = @settings obj = self table_data = JSON.parse(table.contents_utf_8)['data'] table_data = obj.add_headers_to_table(table_data, table_type == 'step_well_plates_table') @@ -38,9 +39,11 @@ def draw_step_table(table, table_type) end @docx.p do text I18n.t("projects.reports.elements.#{table_type}.table_name", name: table.name), italic: true - text ' ' - text I18n.t("projects.reports.elements.#{table_type}.user_time", - timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + unless settings['exclude_timestamps'] + text ' ' + text I18n.t("projects.reports.elements.#{table_type}.user_time", + timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + end end end end diff --git a/app/services/reports/docx/draw_step_text.rb b/app/services/reports/docx/draw_step_text.rb index 978386a947..5fc3646393 100644 --- a/app/services/reports/docx/draw_step_text.rb +++ b/app/services/reports/docx/draw_step_text.rb @@ -5,11 +5,18 @@ def draw_step_text(element) step_text = element.orderable timestamp = element.created_at color = @color - @docx.p do - text step_text.name.presence || '', italic: true - text ' ' - text I18n.t('projects.reports.elements.result_text.user_time', - timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + settings = @settings + + if step_text.name.present? || !settings['exclude_timestamps'] + @docx.p do + text step_text.name.to_s, italic: true + text ' ' if step_text.name.present? + + unless settings['exclude_timestamps'] + text I18n.t('projects.reports.elements.result_text.user_time', + timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] + end + end end if step_text.text.present? html = custom_auto_link(step_text.text, team: @report_team) diff --git a/app/services/reports/docx/repository_helper.rb b/app/services/reports/docx/repository_helper.rb index 159ba8dbf7..d8166ca9bd 100644 --- a/app/services/reports/docx/repository_helper.rb +++ b/app/services/reports/docx/repository_helper.rb @@ -4,15 +4,20 @@ module Reports::Docx::RepositoryHelper include InputSanitizeHelper include ActionView::Helpers::NumberHelper - def prepare_row_columns_for_docx(repository_data, my_module = nil, repository = nil) + return if repository_data[:headers].blank? + result = [repository_data[:headers]] + excluded_columns = repository_data[:excluded_columns] + repository_data[:rows].each do |record| row = [] - row.push(record.code) - row.push(escape_input(record.archived ? "#{record.name} [#{I18n.t('general.archived')}]" : record.name)) - row.push(I18n.l(record.created_at, format: :full)) - row.push(escape_input(record.created_by.full_name)) + row.push(record.code) unless excluded_columns.include?(-1) + unless excluded_columns.include?(-2) + row.push(escape_input(record.archived ? "#{record.name} [#{I18n.t('general.archived')}]" : record.name)) + end + row.push(I18n.l(record.created_at, format: :full)) unless excluded_columns.include?(-3) + row.push(escape_input(record.created_by.full_name)) unless excluded_columns.include?(-4) cell_values = {} custom_cells = record.repository_cells @@ -39,6 +44,8 @@ def prepare_row_columns_for_docx(repository_data, my_module = nil, repository = end repository_data[:custom_columns].each do |column_id| + next if excluded_columns.include?(column_id) + value = cell_values[column_id] row.push(value) end diff --git a/app/services/reports/docx_renderer.rb b/app/services/reports/docx_renderer.rb index 91872dc46e..dd027bc4bb 100644 --- a/app/services/reports/docx_renderer.rb +++ b/app/services/reports/docx_renderer.rb @@ -130,6 +130,13 @@ def self.render_table_element(docx, element, options = {}) row[:data].each do |cell| docx_cell = Caracal::Core::Models::TableCellModel.new do |c| cell.each do |content| + c.background content[:style][:background] if content.dig(:style, :background).present? + if content.dig(:style, :vertical_align).present? && content[:style][:vertical_align] != :middle + c.vertical_align content[:style][:vertical_align] + else + c.vertical_align :center + end + if content[:type] == 'p' Reports::DocxRenderer.render_p_element(c, content, options.merge({ skip_br: true })) elsif content[:type] == 'table' diff --git a/app/services/reports/html_to_word_converter.rb b/app/services/reports/html_to_word_converter.rb index f4e312f8e8..6d589a020e 100644 --- a/app/services/reports/html_to_word_converter.rb +++ b/app/services/reports/html_to_word_converter.rb @@ -208,7 +208,7 @@ def paragraph_styling(elem) if style style_keys.each do |key| - style_el = style.value.split(';').select { |i| (i.include? key) }[0] + style_el = style.value.split(';').find { |i| i.strip.start_with?(key) } next unless style_el value = style_el.split(':')[1].strip if style_el @@ -259,6 +259,29 @@ def image_styling(elem, dimension) } end + def table_cell_styling(elem) + style = elem.attributes['style'] + result = {} + style_keys = %w(background-color vertical-align background) + + if style + style_keys.each do |key| + style_el = style.value.split(';').find { |i| (i.include? key) } + next unless style_el + + value = style_el.split(':')[1].strip if style_el + + case key + when 'background-color', 'background' + result[:background] = normalized_hex_color(value) + when 'vertical-align' + result[:vertical_align] = value.to_sym + end + end + end + result + end + def tiny_mce_table_element(table_element) # array of elements rows = table_element.css('tbody').first.children.map do |row| @@ -267,11 +290,13 @@ def tiny_mce_table_element(table_element) cells = row.children.map do |cell| next unless cell.name == 'td' + style = table_cell_styling(cell) # Parse cell content formated_cell = recursive_children(cell.children, [], true) # Combine text elements to single paragraph formated_cell = combine_docx_elements(formated_cell) + formated_cell.each { |element| element[:style] = style } if style.present? formated_cell end.reject(&:blank?) { type: 'tr', data: cells } diff --git a/app/views/layouts/reports/template_values_editor.html.erb b/app/views/layouts/reports/template_values_editor.html.erb index 225c32a4a1..841b024efb 100644 --- a/app/views/layouts/reports/template_values_editor.html.erb +++ b/app/views/layouts/reports/template_values_editor.html.erb @@ -1,6 +1,10 @@

- <%= t('projects.reports.wizard.first_step.values_editor.title') %> + <% if @type == :pdf %> + <%= t('projects.reports.wizard.first_step.values_editor.title_pdf', template: @template_name) %> + <% else %> + <%= t('projects.reports.wizard.first_step.values_editor.title_docx', template: @template_name) %> + <% end %>