bogdan / datagrid

Gem to create tables grids with sortable columns and filters
MIT License
1.02k stars 115 forks source link

Downloading filtered CSV after moving from Rails 4 - 6 #288

Closed cmcguff closed 4 years ago

cmcguff commented 4 years ago

I have upgraded from rails 4-6 and having an issue with downloading filtered data to csv using the example in the docs - everything else is working fine. I'm using datagrid 1.6.0

The code I have here used to work in rails 4, but now no longer filters - I assume it's related to strong parameters but I'm not clear on exactly how to resolve it with the datagrid.

the download option on the form:

<%= content_for :adhoc_download do %>
  <%= link_to url_for(format: 'csv', leave_request_report: params.permit(:leave_request_report)[:leave_request_report]), class: "btn btn-secondary" do %>
    <i class="<%= view_icon('csv') %>"></i>
    <span>Download CSV</span>
  <% end %>
<% end %>

The backing controller:

class LeaveRequestReportsController < ApplicationController
  before_action :authenticate_user!
  load_and_authorize_resource

  def index
    # force scope to current users reports only
    inputs = (params[:leave_request_report] || {}).merge(current_user: current_user)
    @leave_request_report = LeaveRequestReport.new(inputs) { |scope| scope.where("users.id in (?)", current_user.me_and_reports) }

    respond_to do |f|
      f.html do
        # responding to html make sure to page it!
        @leave_request_report.scope { |scope| scope.page(params[:page]) }
      end
      f.csv do
        # csv is unpaged
        send_data @leave_request_report.to_csv,
          type: "text/csv",
          disposition: "attachment",
          filename: "leave_request_report-#{Time.zone.now.strftime("%Y%m%d%H%M%S")}.csv"
      end
    end
  end
end

and the datagrid classs:

# frozen_string_literal: true

# non-database model, only used for reporting
class LeaveRequestReport
  include Datagrid

  attr_accessor :current_user

  #
  # Scope
  #

  scope do
    LeaveRequest.select('
      users.remote_id as employee_payroll_code,
      users.last_name as employee_last_name,
      users.first_name as employee_first_name,
      users.known_as as employee_known_as,
      users.work_area as employee_work_area,
      managers_leave_requests.remote_id as manager_payroll_code,
      managers_leave_requests.last_name as manager_last_name,
      managers_leave_requests.first_name as manager_first_name,
      leave_types.name as leave_type_name,
      leave_types.units as leave_type_units,
      leave_requests.id,
      leave_requests.updated_at,
      leave_requests.created_at,
      leave_requests.comment as comment,
      leave_requests.status as leave_request_status,
      leave_requests.part_day as part_day,
      leave_dates.date_requested as day_requested,
      leave_dates.hours as hours,
      leave_dates.status as day_status
    ').joins(:user, :manager, :leave_type, :leave_dates)
  end

  #
  # Filters
  #

  # filter(:remote_id, :string) do |value|
  #  self.where(["users.remote_id = ?", value])
  # end

  filter(:user_id, :enum, select: :available_users, multiple: true, include_blank: false) do |value|
    where(users: {id: value})
  end

  def available_users
    # User.where(id: current_user.me_and_reports)
    current_user.me_and_reports.map { |u| [u.optional_formal_name.to_s, u.id] }
  end

  filter(:leave_type_id, :enum,
    select: lambda { LeaveType.where(transaction_type: "Leave").order(:name).map { |lt| [lt.name, lt.id] } },
    multiple: true,
    include_blank: false) do |value|
    where(leave_types: {id: value})
  end

  filter(:status, :enum,
    select: [%w[Requested New], %w[Exported Paid], "Approved", "Escalated", "Withdrawn", "Declined"],
    multiple: true)

  filter(:work_area, :enum, select: :work_areas, multiple: true, include_blank: false) do |value|
    where(["users.work_area in (?)", value])
  end

  def work_areas
    current_user.me_and_reports.map(&:work_area).compact.sort.uniq
  end

  filter(:created_at, :date, range: true)
  filter(:date_requested, :date, range: true)

  # Show list of non-mandatory columns
  column_names_filter(header: "Extra Columns", checkboxes: false)

  #
  # Columns
  #

  column(:employee_payroll_code, mandatory: true, order: "users.remote_id")
  column(:employee_last_name, mandatory: true, order: "users.last_name")
  column(:employee_first_name, mandatory: true, order: "users.first_name")
  column(:employee_known_as, order: "users.known_as")
  column(:employee_work_area, order: "users.work_area")
  column(:manager_payroll_code, order: "managers_leave_requests.remote_id")
  column(:manager_last_name, order: "managers_leave_requests.last_name")
  column(:manager_first_name, order: "managers_leave_requests.first_name")
  column(:leave_type_name, mandatory: true, order: "leave_types.name")
  column(:leave_type_units, mandatory: true, order: "leave_types.units")
  column(:comment, order: "leave_requests.comment")
  column(:leave_request_status, mandatory: true, order: "leave_requests.status")
  column(:part_day, mandatory: true) do |record|
    record.part_day? ? "Yes" : "No"
  end
  column(:hours, mandatory: true, order: "leave_dates.hours")
  column(:day_status, mandatory: true, order: "leave_dates.status")

  column(:day_requested, mandatory: true, order: "leave_dates.date_requested") do |record|
    record.day_requested.strftime("%d/%m/%Y")
  end

  # Optional Columns
  column(:created_at) do |record|
    record.created_at.strftime("%d/%m/%Y %H:%M")
  end
  column(:updated_at) do |record|
    record.updated_at.to_date.strftime("%d/%m/%Y %H:%M")
  end
end
bogdan commented 4 years ago

I don't think params.permit(:leave_request_report)[:leave_request_report] is going to work. Make sure your a[href] has the value with required parameters. I think it should be params.require(:leave_request_report).permit!.

cmcguff commented 4 years ago

If I change it use permit! I'm getting an error:

param is missing or the value is empty: leave_request_report Which I guess is coming from the index action on first load there are no params:

inputs = (params[:leave_request_report] || {}).merge(current_user: current_user)

I've been able to work around it now by calling the index with an empty object to seed it - not sure if it's the rails way, but it's working! Happy to close this.

link_to leave_request_reports_path(leave_request_report: { user_id: "" } )

bogdan commented 4 years ago

Good to know. I think better workarounds exist though.

You may do it like this too:

url_for(format: 'csv', leave_request_report: @grid.attributes)