Skip to content

Commit

Permalink
feat: CSV downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
martintomas committed Jan 1, 2025
1 parent 80c1994 commit 9fa0347
Show file tree
Hide file tree
Showing 31 changed files with 765 additions and 1 deletion.
7 changes: 7 additions & 0 deletions backend/app/controllers/backoffice/investments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Backoffice
class InvestmentsController < BaseController
include AsResource

self.includes = [:funder, project: :recipient]
end
end
12 changes: 11 additions & 1 deletion backend/app/controllers/concerns/backoffice/as_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ module AsResource

def index
@q = resource_class.includes(self.class.includes).ransack params[:q]
@pagy_object, @resources = pagy @q.result.order(created_at: :desc), pagy_defaults

respond_to do |format|
format.html do
@pagy_object, @resources = pagy @q.result.order(created_at: :desc), pagy_defaults
end
format.csv do
send_data "Exporters::#{resource_class.to_s.pluralize}".constantize.new(@q.result).call,
filename: "#{resource_class.to_s.underscore.pluralize}.csv",
type: "text/csv; charset=utf-8"
end
end
end

def show
Expand Down
4 changes: 4 additions & 0 deletions backend/app/models/investment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ class Investment < ApplicationRecord

scope :can_show_aggregated_amount, -> { where privacy: %w[all aggregate_amount_funded] }
scope :can_be_shown_without_amount, -> { where privacy: %w[all aggregate_amount_funded amount_funded_visible_only_to_members amount_funded_visible_only_to_staff] }

def to_s
"#{funder} - #{project}"
end
end
4 changes: 4 additions & 0 deletions backend/app/models/subgeographic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ def self.as_geojson(geographic)
end
end

def to_s
name
end

private

def invalidate_cache
Expand Down
15 changes: 15 additions & 0 deletions backend/app/services/exporters/admins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Exporters
class Admins < Base
def call
generate_csv do
column(I18n.t("activerecord.attributes.admin.id")) { |r| r.id }
column(I18n.t("activerecord.attributes.admin.first_name")) { |r| r.first_name }
column(I18n.t("activerecord.attributes.admin.last_name")) { |r| r.last_name }
column(I18n.t("activerecord.attributes.admin.email")) { |r| r.email }
column(I18n.t("activerecord.attributes.admin.is_super_admin")) { |r| I18n.t r.is_super_admin }
column(I18n.t("activerecord.attributes.admin.created_at")) { |r| I18n.l r.created_at }
column(I18n.t("activerecord.attributes.admin.updated_at")) { |r| I18n.l r.updated_at }
end
end
end
end
33 changes: 33 additions & 0 deletions backend/app/services/exporters/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "csv"

module Exporters
class Base
attr_accessor :query, :columns

def initialize(query, include_associations: [])
@query = query.includes include_associations
end

def call
raise NotImplementedError
end

private

def generate_csv
self.columns = []
yield

::CSV.generate do |csv|
csv << columns.map { |column| column[:name] }
query.each do |record|
csv << columns.map { |column| column[:block].call record }
end
end
end

def column(name, &block)
columns << {name: name, block: block}
end
end
end
64 changes: 64 additions & 0 deletions backend/app/services/exporters/funders.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module Exporters
class Funders < Base
def initialize(query)
super query, include_associations: [:primary_office_country, :primary_office_state]
end

def call
generate_csv do
column(I18n.t("activerecord.attributes.funder.id")) { |r| r.id }
column(I18n.t("activerecord.attributes.funder.name")) { |r| r.name }
column(I18n.t("activerecord.attributes.funder.description")) { |r| r.description }
column(I18n.t("activerecord.attributes.funder.primary_office_country")) { |r| r.primary_office_country&.name }
column(I18n.t("activerecord.attributes.funder.primary_office_state")) { |r| r.primary_office_state&.name }
column(I18n.t("activerecord.attributes.funder.primary_office_city")) { |r| r.primary_office_city }
column(I18n.t("activerecord.attributes.funder.primary_office_address")) { |r| r.primary_office_address }
column(I18n.t("activerecord.attributes.funder.primary_contact_first_name")) { |r| r.primary_contact_first_name }
column(I18n.t("activerecord.attributes.funder.primary_contact_last_name")) { |r| r.primary_contact_last_name }
column(I18n.t("activerecord.attributes.funder.primary_contact_email")) { |r| r.primary_contact_email }
column(I18n.t("activerecord.attributes.funder.show_primary_email")) { |r| I18n.t r.show_primary_email }
column(I18n.t("activerecord.attributes.funder.primary_contact_phone")) { |r| r.primary_contact_phone }
column(I18n.t("activerecord.attributes.funder.primary_contact_location")) { |r| r.primary_contact_location }
column(I18n.t("activerecord.attributes.funder.primary_contact_role")) { |r| r.primary_contact_role }
column(I18n.t("activerecord.attributes.funder.secondary_email_which_can_be_shared")) { |r| r.secondary_email_which_can_be_shared }
column(I18n.t("activerecord.attributes.funder.website")) { |r| r.website }
column(I18n.t("activerecord.attributes.funder.date_joined_fora")) { |r| I18n.l r.date_joined_fora }
column(I18n.t("activerecord.attributes.funder.funder_type")) { |r| FunderType.find(r.funder_type)&.name }
column(I18n.t("activerecord.attributes.funder.funder_type_other")) { |r| r.funder_type_other }
column(I18n.t("activerecord.attributes.funder.capital_acceptances")) do |r|
CapitalAcceptance.find_many(r.capital_acceptances).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.funder.capital_acceptances_other")) { |r| r.capital_acceptances_other }
column(I18n.t("activerecord.attributes.funder.leadership_demographics")) do |r|
Demographic.find_many(r.leadership_demographics).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.funder.leadership_demographics_other")) { |r| r.leadership_demographics_other }
column(I18n.t("activerecord.attributes.funder.number_staff_employees")) { |r| r.number_staff_employees }
column(I18n.t("activerecord.attributes.funder.application_status")) do |r|
ApplicationStatus.find(r.application_status)&.name
end
column(I18n.t("activerecord.attributes.funder.funder_legal_status")) do |r|
FunderLegalStatus.find(r.funder_legal_status)&.name
end
column(I18n.t("activerecord.attributes.funder.funder_legal_status_other")) { |r| r.funder_legal_status_other }
column(I18n.t("activerecord.attributes.funder.new_to_regenerative_ag")) { |r| I18n.t r.new_to_regenerative_ag }
column(I18n.t("activerecord.attributes.funder.networks")) { |r| r.networks }
column(I18n.t("activerecord.attributes.funder.capital_types")) do |r|
CapitalType.find_many(r.capital_types).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.funder.capital_types_other")) { |r| r.capital_types_other }
column(I18n.t("activerecord.attributes.funder.spend_down_strategy")) { |r| I18n.t r.spend_down_strategy }
column(I18n.t("activerecord.attributes.funder.areas")) do |r|
Area.find_many(r.areas).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.funder.areas_other")) { |r| r.areas_other }
column(I18n.t("activerecord.attributes.funder.demographics")) do |r|
Demographic.find_many(r.demographics).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.funder.demographics_other")) { |r| r.demographics_other }
column(I18n.t("activerecord.attributes.funder.created_at")) { |r| I18n.l r.created_at }
column(I18n.t("activerecord.attributes.funder.updated_at")) { |r| I18n.l r.updated_at }
end
end
end
end
46 changes: 46 additions & 0 deletions backend/app/services/exporters/investments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Exporters
class Investments < Base
def initialize(query)
super query, include_associations: [:funder, project: [:recipient]]
end

def call
generate_csv do
column(I18n.t("activerecord.attributes.investment.id")) { |r| r.id }
column(I18n.t("activerecord.attributes.investment.funder")) { |r| r.funder&.name }
column(I18n.t("activerecord.attributes.investment.project")) { |r| r.project&.recipient&.name }
column(I18n.t("activerecord.attributes.investment.amount")) { |r| r.amount }
column(I18n.t("activerecord.attributes.investment.year_invested")) { |r| r.year_invested }
column(I18n.t("activerecord.attributes.investment.initial_funded_year")) { |r| r.initial_funded_year }
column(I18n.t("activerecord.attributes.investment.funding_type")) do |r|
FundingType.find(r.funding_type)&.name
end
column(I18n.t("activerecord.attributes.investment.funding_type_other")) { |r| r.funding_type_other }
column(I18n.t("activerecord.attributes.investment.areas")) do |r|
Area.find_many(r.areas).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.investment.areas_other")) { |r| r.areas_other }
column(I18n.t("activerecord.attributes.investment.grant_duration")) do |r|
GrantDuration.find(r.grant_duration)&.name
end
column(I18n.t("activerecord.attributes.investment.number_of_grant_years")) { |r| r.number_of_grant_years }
column(I18n.t("activerecord.attributes.investment.demographics")) do |r|
Demographic.find_many(r.demographics).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.investment.demographics_other")) { |r| r.demographics_other }
column(I18n.t("activerecord.attributes.investment.capital_type")) do |r|
CapitalType.find(r.capital_type)&.name
end
column(I18n.t("activerecord.attributes.investment.capital_type_other")) { |r| r.capital_type_other }
column(I18n.t("activerecord.attributes.investment.submitting_organization_contact_name")) do |r|
r.submitting_organization_contact_name
end
column(I18n.t("activerecord.attributes.investment.privacy")) do |r|
InvestmentPrivacy.find(r.privacy)&.name
end
column(I18n.t("activerecord.attributes.investment.created_at")) { |r| I18n.l r.created_at }
column(I18n.t("activerecord.attributes.investment.updated_at")) { |r| I18n.l r.updated_at }
end
end
end
end
19 changes: 19 additions & 0 deletions backend/app/services/exporters/members.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Exporters
class Members < Base
def initialize(query)
super query, include_associations: [:funder]
end

def call
generate_csv do
column(I18n.t("activerecord.attributes.member.id")) { |r| r.id }
column(I18n.t("activerecord.attributes.member.funder")) { |r| r.funder.name }
column(I18n.t("activerecord.attributes.member.first_name")) { |r| r.first_name }
column(I18n.t("activerecord.attributes.member.last_name")) { |r| r.last_name }
column(I18n.t("activerecord.attributes.member.email")) { |r| r.email }
column(I18n.t("activerecord.attributes.member.created_at")) { |r| I18n.l r.created_at }
column(I18n.t("activerecord.attributes.member.updated_at")) { |r| I18n.l r.updated_at }
end
end
end
end
33 changes: 33 additions & 0 deletions backend/app/services/exporters/projects.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Exporters
class Projects < Base
def initialize(query)
super query, include_associations: [:member, recipient: %i[country state]]
end

def call
generate_csv do
column(I18n.t("activerecord.attributes.project.id")) { |r| r.id }
column(I18n.t("activerecord.attributes.project.member")) { |r| r.member&.full_name }
column(I18n.t("activerecord.attributes.recipient.name")) { |r| r.recipient.name }
column(I18n.t("activerecord.attributes.recipient.description")) { |r| r.recipient.description }
column(I18n.t("activerecord.attributes.recipient.contact_first_name")) { |r| r.recipient.contact_first_name }
column(I18n.t("activerecord.attributes.recipient.contact_last_name")) { |r| r.recipient.contact_last_name }
column(I18n.t("activerecord.attributes.recipient.website")) { |r| r.recipient.website }
column(I18n.t("activerecord.attributes.recipient.country")) { |r| r.recipient.country.to_s }
column(I18n.t("activerecord.attributes.recipient.state")) { |r| r.recipient.state.to_s }
column(I18n.t("activerecord.attributes.recipient.city")) { |r| r.recipient.city }
column(I18n.t("activerecord.attributes.recipient.leadership_demographics")) do |r|
Demographic.find_many(r.recipient.leadership_demographics).map(&:name).join(", ")
end
column(I18n.t("activerecord.attributes.recipient.leadership_demographics_other")) do |r|
r.recipient.leadership_demographics_other
end
column(I18n.t("activerecord.attributes.recipient.recipient_legal_status")) do |r|
RecipientLegalStatus.find(r.recipient.recipient_legal_status)&.name
end
column(I18n.t("activerecord.attributes.project.created_at")) { |r| I18n.l r.created_at }
column(I18n.t("activerecord.attributes.project.updated_at")) { |r| I18n.l r.updated_at }
end
end
end
end
3 changes: 3 additions & 0 deletions backend/app/views/backoffice/admins/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<%= render "new_record", text: t("backoffice.messages.create_new_record", model: resource_class.model_name.human), url: new_backoffice_admin_path %>
<div class="flex justify-end mb-4 mr-2">
<%= render "total_records" %>
</div>

<table class="backoffice-table">
<thead>
Expand Down
1 change: 1 addition & 0 deletions backend/app/views/backoffice/base/_navigation.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<nav class="flex gap-4 py-3">
<%= nav_link_to t("activerecord.models.funder.few"), backoffice_funders_url %>
<%= nav_link_to t("activerecord.models.project.few"), backoffice_projects_url %>
<%= nav_link_to t("activerecord.models.investment.few"), backoffice_investments_url %>
<%= nav_link_to t("activerecord.models.member.few"), backoffice_members_url %>
<%= nav_link_to t("activerecord.models.admin.few"), backoffice_admins_url %>
<%= nav_link_to t("activerecord.models.upload.few"), backoffice_uploads_url %>
Expand Down
7 changes: 7 additions & 0 deletions backend/app/views/backoffice/base/_total_records.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="min-w-fit flex items-center font-sans pagination-total-number">
<div class="text-gray-800">
<span class="mr-1"><%= t("backoffice.actions.total") %></span>
<span class="font-semibold"><%= @pagy_object.count %></span>
<div class="text-xs text-center"><%= link_to t("backoffice.actions.export_csv"), request.query_parameters.merge(format: :csv) %></div>
</div>
</div>
3 changes: 3 additions & 0 deletions backend/app/views/backoffice/funders/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<%= render "new_record", text: t("backoffice.messages.create_new_record", model: resource_class.model_name.human), url: new_backoffice_funder_path %>
<div class="flex justify-end mb-4 mr-2">
<%= render "total_records" %>
</div>

<table class="backoffice-table">
<thead>
Expand Down
39 changes: 39 additions & 0 deletions backend/app/views/backoffice/investments/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<div class="flex justify-end mb-4 mr-2">
<%= render "total_records" %>
</div>

<table class="backoffice-table">
<thead>
<tr>
<th>
<%= sort_link @q, :id, t("activerecord.attributes.funder.id") %>
</th>
<th>
<%= sort_link @q, :funder_name , t("activerecord.attributes.investment.funder") %>
</th>
<th>
<%= sort_link @q, :project_recipient_name , t("activerecord.attributes.investment.project") %>
</th>
<th>
<%= sort_link @q, :updated_at, t("activerecord.attributes.investment.updated_at") %>
</th>
<th>
<%= sort_link @q, :created_at, t("activerecord.attributes.investment.created_at") %>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
<% @resources.each do |resource| %>
<tr>
<td><%= resource.id %></td>
<td><%= resource.funder.name %></td>
<td><%= resource.project.name %></td>
<td><%= I18n.l resource.updated_at.to_date %></td>
<td><%= I18n.l resource.created_at.to_date %></td>
<td><%= link_to t("backoffice.actions.show"), backoffice_investment_path(resource), class: "link-button" %></td>
</tr>
<% end %>
</tbody>
</table>
24 changes: 24 additions & 0 deletions backend/app/views/backoffice/investments/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="relative w-full bg-white text-base rounded-xl p-3">
<h1 class="font-semibold text-xl text-black mb-6">
<%= t "backoffice.messages.show_record", record: @resource.to_s %>
</h1>

<table>
<tr>
<td class="p-2"><%= @resource.class.human_attribute_name(:funder) %>:</td>
<td><%= @resource.funder.name %></td>
</tr>
<tr>
<td class="p-2"><%= @resource.class.human_attribute_name(:project) %>:</td>
<td><%= @resource.project.name %></td>
</tr>
<tr>
<td class="p-2"><%= @resource.class.human_attribute_name(:created_at) %>:</td>
<td><%= I18n.l @resource.created_at %></td>
</tr>
<tr>
<td class="p-2"><%= @resource.class.human_attribute_name(:updated_at) %>:</td>
<td><%= I18n.l @resource.updated_at %></td>
</tr>
</table>
</div>
3 changes: 3 additions & 0 deletions backend/app/views/backoffice/members/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<%= render "new_record", text: t("backoffice.messages.create_new_record", model: resource_class.model_name.human), url: new_backoffice_member_path %>
<div class="flex justify-end mb-4 mr-2">
<%= render "total_records" %>
</div>

<table class="backoffice-table">
<thead>
Expand Down
3 changes: 3 additions & 0 deletions backend/app/views/backoffice/projects/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<%#= render "new_record", text: t("backoffice.messages.create_new_record", model: resource_class.model_name.human), url: new_backoffice_project_path %>
<div class="flex justify-end mb-4 mr-2">
<%= render "total_records" %>
</div>

<table class="backoffice-table">
<thead>
Expand Down
Loading

0 comments on commit 9fa0347

Please sign in to comment.