mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Add report name and details to CSV files
This commit is contained in:
committed by
Maikel Linke
parent
f5a9ec7fa9
commit
0a9eb173ea
@@ -22,14 +22,12 @@ module Admin
|
||||
def show
|
||||
@report = report_class.new(spree_current_user, params, render: false)
|
||||
@rendering_options = rendering_options
|
||||
|
||||
show_report
|
||||
end
|
||||
|
||||
def create
|
||||
@report = report_class.new(spree_current_user, params, render: true)
|
||||
update_rendering_options
|
||||
|
||||
render_in_background
|
||||
end
|
||||
|
||||
@@ -61,7 +59,9 @@ module Admin
|
||||
@blob = ReportBlob.create_for_upload_later!(report_filename)
|
||||
|
||||
ReportJob.perform_later(
|
||||
report_class:, user: spree_current_user, params:,
|
||||
report_class:,
|
||||
user: spree_current_user,
|
||||
params:,
|
||||
format: report_format,
|
||||
blob: @blob,
|
||||
channel: ScopedChannel.for_id(params[:uuid]),
|
||||
|
||||
@@ -84,6 +84,7 @@ module ReportsActions
|
||||
else
|
||||
params[:fields_to_show]
|
||||
end,
|
||||
display_metadata_rows: false,
|
||||
display_summary_row: request.get?,
|
||||
display_header_row: false
|
||||
}
|
||||
@@ -94,6 +95,7 @@ module ReportsActions
|
||||
rendering_options.update(
|
||||
options: {
|
||||
fields_to_show: params[:fields_to_show],
|
||||
display_metadata_rows: params[:display_metadata_rows].present?,
|
||||
display_summary_row: params[:display_summary_row].present?,
|
||||
display_header_row: params[:display_header_row].present?
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
- if @report_subtypes.present? && @report_subtypes.count > 1
|
||||
%input{type: 'hidden', name: 'report_subtype', value: @report_subtype}
|
||||
|
||||
.row.rendering-options{ "data-controller": "csv-select" }
|
||||
.row.rendering-options{ "data-controller": "csv-select metadata-toggle" }
|
||||
.alpha.two.columns
|
||||
= label_tag :report_format, t(".generate_report")
|
||||
.omega.fourteen.columns{ style: "margin-bottom: 1.5em;" }
|
||||
= select_tag :report_format, grouped_options_for_select({ |
|
||||
t('.formatted_data') => { t('.on_screen') => '', "PDF" => 'pdf', t('.spreadsheet') => 'xlsx' }, |
|
||||
t('.raw_data') => { "CSV" => 'csv' }, |
|
||||
}), { "data-csv-select-target": "reportType", "data-action": "csv-select#handleSelectChange" }
|
||||
}), { "data-csv-select-target": "reportType", "data-metadata-toggle-target": "reportType", "data-action": "csv-select#handleSelectChange metadata-toggle#handleSelectChange" }
|
||||
|
||||
- if @report.header_option? || @report.summary_row_option?
|
||||
- if @report.header_option? || @report.summary_row_option? || @report.metadata_option?
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(".display")
|
||||
.omega.fourteen.columns
|
||||
- if @report.metadata_option?
|
||||
%span.inline-checkbox{ style: "margin-right: 1rem;" }
|
||||
= check_box_tag :display_metadata_rows, true, @rendering_options.options[:display_metadata_rows], { "disabled": "true", "data-metadata-toggle-target": "checkbox" }
|
||||
= label_tag :display_metadata_rows, t(".metadata_rows"), {"class": "disabled", "data-metadata-toggle-target": "label" }
|
||||
- if @report.header_option?
|
||||
%span.inline-checkbox{ style: "margin-right: 1rem;" }
|
||||
= check_box_tag :display_header_row, true, @rendering_options.options[:display_header_row]
|
||||
|
||||
@@ -4,9 +4,9 @@ export default class extends Controller {
|
||||
static targets = ["reportType", "checkbox", "label"];
|
||||
|
||||
handleSelectChange() {
|
||||
this.reportTypeTarget.value == "csv"
|
||||
? this.disableField()
|
||||
: this.enableField();
|
||||
this.reportTypeTarget.value == "csv" ?
|
||||
this.disableField():
|
||||
this.enableField();
|
||||
}
|
||||
|
||||
disableField() {
|
||||
|
||||
31
app/webpacker/controllers/metadata_toggle_controller.js
Normal file
31
app/webpacker/controllers/metadata_toggle_controller.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["reportType", "checkbox", "label"];
|
||||
|
||||
handleSelectChange() {
|
||||
this.reportTypeTarget.value == "csv" ?
|
||||
this.enableField():
|
||||
this.disableField();
|
||||
}
|
||||
|
||||
disableField() {
|
||||
if (this.hasCheckboxTarget) {
|
||||
this.checkboxTarget.checked = false;
|
||||
this.checkboxTarget.disabled = true;
|
||||
}
|
||||
if (this.hasLabelTarget) {
|
||||
this.labelTarget.classList.add("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
enableField() {
|
||||
if (this.hasCheckboxTarget) {
|
||||
this.checkboxTarget.checked = true;
|
||||
this.checkboxTarget.disabled = false;
|
||||
}
|
||||
if (this.hasLabelTarget) {
|
||||
this.labelTarget.classList.remove("disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1798,6 +1798,8 @@ en:
|
||||
not_visible: "%{enterprise} is not visible and so cannot be found on the map or in searches"
|
||||
reports:
|
||||
none: none
|
||||
metadata:
|
||||
report_title: Report Title
|
||||
deprecated: "This report is deprecated and will be removed in a future release."
|
||||
hidden_field: "< Hidden >"
|
||||
unitsize: UNITSIZE
|
||||
@@ -1900,6 +1902,7 @@ en:
|
||||
display: Display
|
||||
summary_row: Summary Row
|
||||
header_row: Header Row
|
||||
metadata_rows: Metadata Rows
|
||||
raw_data: Raw Data
|
||||
formatted_data: Formatted Data
|
||||
packing:
|
||||
@@ -2514,12 +2517,14 @@ en:
|
||||
email_confirmed: "Thank you for confirming your email address."
|
||||
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
|
||||
email_confirmation_greeting: "Hi, %{contact}!"
|
||||
email_confirmation_profile_created: "A profile for %{name} has been successfully created!
|
||||
To activate your Profile we need to confirm this email address."
|
||||
email_confirmation_profile_created: >
|
||||
A profile for %{name} has been successfully created!
|
||||
To activate your Profile we need to confirm this email address.
|
||||
email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile."
|
||||
email_confirmation_link_label: "Confirm this email address »"
|
||||
email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise.
|
||||
See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store."
|
||||
email_confirmation_help_html: >
|
||||
After confirming your email you can access your administration account for this enterprise.
|
||||
See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store."
|
||||
email_confirmation_notice_unexpected: "You received this message because you signed up on %{sitename}, or were invited to sign up by someone you probably know. If you don't understand why you are receiving this email, please write to %{contact}."
|
||||
email_social: "Connect with Us:"
|
||||
email_contact: "Email us:"
|
||||
|
||||
78
lib/reporting/report_metadata_builder.rb
Normal file
78
lib/reporting/report_metadata_builder.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
class ReportMetadataBuilder
|
||||
attr_reader :report, :current_user
|
||||
|
||||
def initialize(report, current_user = nil)
|
||||
@report = report
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def rows
|
||||
rows = []
|
||||
rows.concat(title_rows)
|
||||
rows.concat(date_range_rows)
|
||||
rows.concat(printed_rows)
|
||||
rows.concat(other_filter_rows)
|
||||
rows << [] if rows.any? # spacer only if something was added
|
||||
rows
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
DATE_FROM_KEYS = %i[completed_at_gt created_at_gt updated_at_gt].freeze
|
||||
DATE_TO_KEYS = %i[completed_at_lt created_at_lt updated_at_lt].freeze
|
||||
|
||||
def title_rows
|
||||
type = params[:report_type]
|
||||
sub = params[:report_subtype]
|
||||
return [] if type.blank?
|
||||
|
||||
label = I18n.t("admin.reports.metadata.report_title", default: "Report Title")
|
||||
type_name = I18n.t("admin.reports.#{type}.name",
|
||||
default: I18n.t("admin.reports.#{type}",
|
||||
default: type.to_s.tr('_', ' ').titleize))
|
||||
|
||||
sub_name = sub.present? ? sub.to_s.tr('_', ' ').titleize : nil
|
||||
|
||||
title = [type_name, sub_name].compact.join(' - ')
|
||||
[[label, title]]
|
||||
end
|
||||
|
||||
def date_range_rows
|
||||
q = indifferent_ransack
|
||||
from = first_present(q, DATE_FROM_KEYS)
|
||||
to = first_present(q, DATE_TO_KEYS)
|
||||
return [] unless from || to
|
||||
|
||||
label = I18n.t("date_range", default: "Date Range")
|
||||
[[label, [from, to].compact.join(' - ')]] # en dash
|
||||
end
|
||||
|
||||
def first_present(hash, keys)
|
||||
keys.map { |k| hash[k] }.find(&:present?)
|
||||
end
|
||||
|
||||
def indifferent_ransack
|
||||
(report.ransack_params || {}).with_indifferent_access
|
||||
end
|
||||
|
||||
def printed_rows
|
||||
[[I18n.t("printed", default: "Printed"), Time.now.utc.strftime('%F %T %Z')]]
|
||||
end
|
||||
|
||||
def other_filter_rows
|
||||
q = indifferent_ransack.except(*DATE_FROM_KEYS, *DATE_TO_KEYS)
|
||||
q.each_with_object([]) do |(k, v), rows|
|
||||
next if v.blank?
|
||||
|
||||
rows << [k.to_s.humanize, v.is_a?(Array) ? v.join(', ') : v.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def params
|
||||
(report.params || {}).with_indifferent_access
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
require 'spreadsheet_architect'
|
||||
|
||||
module Reporting
|
||||
@@ -24,6 +25,10 @@ module Reporting
|
||||
@report.params[:report_format].in?([nil, '', 'pdf'])
|
||||
end
|
||||
|
||||
def display_metadata_rows?
|
||||
@report.params[:display_metadata_rows].present? && raw_render?
|
||||
end
|
||||
|
||||
def display_header_row?
|
||||
@report.params[:display_header_row].present? && !raw_render?
|
||||
end
|
||||
@@ -33,13 +38,22 @@ module Reporting
|
||||
end
|
||||
|
||||
def table_headers
|
||||
@report.table_headers || []
|
||||
base = @report.table_headers || []
|
||||
return base unless display_metadata_rows?
|
||||
|
||||
[*metadata_headers, base]
|
||||
end
|
||||
|
||||
def table_rows
|
||||
@report.table_rows || []
|
||||
end
|
||||
|
||||
def metadata_headers
|
||||
return [] unless display_metadata_rows?
|
||||
|
||||
Reporting::ReportMetadataBuilder.new(@report, @report.try(:user)).rows
|
||||
end
|
||||
|
||||
def as_json(_context_controller = nil)
|
||||
@report.rows.map(&:to_h).as_json
|
||||
end
|
||||
|
||||
@@ -10,6 +10,10 @@ module Reporting
|
||||
@formatted_rules ||= @report.rules.map { |rule| format_rule(rule) }
|
||||
end
|
||||
|
||||
def metadata_option?
|
||||
true
|
||||
end
|
||||
|
||||
def header_option?
|
||||
formatted_rules.find { |rule| rule[:header].present? }
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ module Reporting
|
||||
delegate :available_headers, :table_headers, :fields_to_hide, :fields_to_show,
|
||||
to: :headers_builder
|
||||
|
||||
delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler
|
||||
delegate :formatted_rules, :header_option?, :summary_row_option?, :metadata_option?, to: :ruler
|
||||
|
||||
def initialize(user, params = {}, render: false)
|
||||
unless render
|
||||
|
||||
48
spec/lib/reports/report_metadata_builder_spec.rb
Normal file
48
spec/lib/reports/report_metadata_builder_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Reporting::ReportMetadataBuilder do
|
||||
let(:from_key) { described_class::DATE_FROM_KEYS.first }
|
||||
let(:to_key) { described_class::DATE_TO_KEYS.first }
|
||||
|
||||
let(:params) do
|
||||
{ report_type: :order_cycle_customer_totals, report_subtype: 'by_distributor' }
|
||||
end
|
||||
|
||||
let(:ransack_params) do
|
||||
{
|
||||
from_key => '2025-01-01',
|
||||
to_key => '2025-01-31',
|
||||
:status_in => %w[paid shipped],
|
||||
:hub_id_eq => '42'
|
||||
}
|
||||
end
|
||||
|
||||
let(:report) { instance_double('Report', params:, ransack_params:) }
|
||||
|
||||
subject(:builder) { described_class.new(report, nil) }
|
||||
|
||||
it 'assembles rows with title, date range, printed, other filters, and spacer' do
|
||||
travel_to(Time.zone.parse('2025-06-13 10:20:30 UTC')) do
|
||||
rows = builder.rows
|
||||
|
||||
# Title
|
||||
expect(rows).to include(['Report Title', 'Order Cycle Customer Totals - By Distributor'])
|
||||
|
||||
# Date range
|
||||
expect(rows).to include(['Date Range', '2025-01-01 - 2025-01-31'])
|
||||
|
||||
# Printed timestamp
|
||||
printed = rows.find { |r| r.first == 'Printed' }
|
||||
expect(printed).to eq(['Printed', '2025-06-13 10:20:30 UTC'])
|
||||
|
||||
# Other filters (humanized keys)
|
||||
expect(rows).to include(['Status in', 'paid, shipped'])
|
||||
expect(rows).to include(['Hub id eq', '42'])
|
||||
|
||||
# Spacer
|
||||
expect(rows.last).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,4 +34,48 @@ RSpec.describe Reporting::ReportRenderer do
|
||||
expect { subject.render_as("give_me_everything") }.to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
# metadata headers
|
||||
|
||||
describe '#metadata_headers' do
|
||||
let(:user) { create(:user) }
|
||||
let(:from_key) { Reporting::ReportMetadataBuilder::DATE_FROM_KEYS.first }
|
||||
let(:to_key) { Reporting::ReportMetadataBuilder::DATE_TO_KEYS.first }
|
||||
|
||||
let(:meta_report) do
|
||||
instance_double(
|
||||
'MetaReport',
|
||||
rows: data,
|
||||
params: {
|
||||
display_metadata_rows: true,
|
||||
report_type: :order_cycle_customer_totals,
|
||||
report_subtype: 'by_distributor',
|
||||
report_format: 'csv'
|
||||
},
|
||||
ransack_params: {
|
||||
from_key => '2025-01-01',
|
||||
to_key => '2025-01-31',
|
||||
:status_in => %w[paid shipped]
|
||||
},
|
||||
user:
|
||||
)
|
||||
end
|
||||
|
||||
let(:renderer) { described_class.new(meta_report) }
|
||||
|
||||
it 'builds rows via ReportMetadataBuilder when display_metadata_rows?
|
||||
is true and report_format is csv' do
|
||||
rows = renderer.metadata_headers
|
||||
|
||||
labels = rows.map(&:first)
|
||||
expect(labels).to include('Report Title')
|
||||
expect(labels).to include('Date Range')
|
||||
expect(labels).to include('Printed')
|
||||
|
||||
values = rows.map(&:second)
|
||||
expect(values).to include('Order Cycle Customer Totals - By Distributor')
|
||||
expect(values).to include('2025-01-01 - 2025-01-31')
|
||||
expect(values).to include(Time.now.utc.strftime('%F %T %Z'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user