Skip to content

Commit 8e3efcb

Browse files
committed
Adds ability to CSV export collections from the ActionMenu
1 parent d344309 commit 8e3efcb

File tree

5 files changed

+144
-6
lines changed

5 files changed

+144
-6
lines changed

app/controllers/api/v1/collections_controller.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ class Api::V1::CollectionsController < Api::V1::BaseController
22
deserializable_resource :collection, class: DeserializableCollection, only: %i[update]
33
load_and_authorize_resource :collection_card, only: [:create]
44
load_and_authorize_resource except: %i[update destroy in_my_collection clear_collection_cover clear_background_image
5-
challenge_submission_boxes next_available_submission_test insert_row remove_row]
5+
challenge_submission_boxes next_available_submission_test insert_row remove_row csv]
66
skip_before_action :check_api_authentication!, only: %i[show]
77

88
before_action :join_collection_group, only: :show, if: :join_collection_group?
99
before_action :switch_to_organization, only: :show, if: :user_signed_in?
1010
before_action :load_and_authorize_collection_layout_update, only: %i[insert_row remove_row]
11-
before_action :load_collection_with_roles, only: %i[show update]
11+
before_action :load_collection_with_roles, only: %i[show update csv]
1212
before_action :load_and_authorize_collection_update, only: %i[update clear_collection_cover
1313
clear_background_image collection_challenge_setup]
1414
before_action :load_and_authorize_parent_challenge, only: %i[challenge_submission_boxes next_available_submission_test]
@@ -33,6 +33,16 @@ def show
3333
)
3434
end
3535

36+
before_action :load_and_authorize_collection_view, only: %i[csv]
37+
def csv
38+
csv_data = CollectionCSVBuilder.call(@collection)
39+
filename = "#{@collection.id}-#{@collection.name.parameterize}-#{Date.today}.csv"
40+
41+
respond_to do |format|
42+
format.any { send_data csv_data, filename: filename }
43+
end
44+
end
45+
3646
before_action :load_and_authorize_template_and_parent, only: %i[create_template]
3747
def create_template
3848
builder = CollectionTemplateBuilder.new(
@@ -291,6 +301,11 @@ def log_viewing_activities
291301
log_collection_activity(:viewed)
292302
end
293303

304+
def load_and_authorize_collection_view
305+
@collection = Collection.find(params[:id])
306+
authorize! :read, @collection
307+
end
308+
294309
def load_and_authorize_template_and_parent
295310
@parent_collection = Collection.find(json_api_params[:parent_id])
296311
@template_collection = Collection.find(json_api_params[:template_id])

app/helpers/application_helper.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module ApplicationHelper
22
# NOTE: this is somewhat similar to RoutingStore.js by necessity
3-
def frontend_url_for(obj, with_id: true)
3+
def frontend_url_for(obj, with_id: true, slug: nil)
44
return admin_root_url if obj == Role::SHAPE_ADMIN.to_s.titleize
55

6-
"#{root_url.chomp('/')}#{frontend_path_for(obj, with_id: with_id)}"
6+
"#{root_url.chomp('/')}#{frontend_path_for(obj, with_id: with_id, slug: slug)}"
77
end
88

9-
def frontend_path_for(obj, with_id: true)
10-
slug = obj&.organization&.slug
9+
def frontend_path_for(obj, with_id: true, slug: nil)
10+
slug ||= obj&.organization&.slug
1111
url = ''
1212
url += "/#{slug}" if slug
1313
obj_id = with_id ? "/#{obj.id}" : ''

app/javascript/ui/grid/ActionMenu.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,21 @@ class ActionMenu extends React.Component {
118118
downloadCard = () => {
119119
const { card } = this.props
120120
const { record } = card
121+
if (record.isCollection) {
122+
return this.downloadCsv()
123+
}
121124
if (record.filestack_file) {
122125
Activity.trackActivity('downloaded', record)
123126
window.open(record.filestack_file.url, '_blank')
124127
}
125128
}
126129

130+
downloadCsv = () => {
131+
const { card } = this.props
132+
const { record } = card
133+
window.open(`/api/v1/collections/${record.id}/csv`, '_blank')
134+
}
135+
127136
showRolesMenu = () => {
128137
const { uiStore, card } = this.props
129138
uiStore.update('rolesMenuOpen', card.record)
@@ -309,6 +318,11 @@ class ActionMenu extends React.Component {
309318
iconRight: <PrintIcon />,
310319
onClick: this.printPage,
311320
})
321+
items.push({
322+
name: 'Export',
323+
iconRight: <DownloadIcon />,
324+
onClick: this.downloadCsv,
325+
})
312326
}
313327

314328
// if record is system required, we always remove these actions
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require 'csv'
2+
3+
class CollectionCSVBuilder < SimpleService
4+
include Rails.application.routes.url_helpers
5+
include ApplicationHelper
6+
7+
def initialize(collection)
8+
@collection = collection
9+
end
10+
11+
def call
12+
CSV.generate do |csv|
13+
csv << %w[
14+
id
15+
type
16+
name
17+
created_by_name
18+
created_by_email
19+
content
20+
data_content
21+
col
22+
row
23+
height
24+
width
25+
url
26+
image_url
27+
shape_url
28+
shape_csv_url
29+
]
30+
31+
row = [
32+
@collection.id,
33+
@collection.type || 'Collection',
34+
@collection.name,
35+
@collection.created_by&.name,
36+
@collection.created_by&.email,
37+
nil,
38+
nil,
39+
nil,
40+
nil,
41+
nil,
42+
nil,
43+
nil,
44+
nil,
45+
url_for(@collection),
46+
csv_url_for(@collection),
47+
]
48+
49+
csv << row
50+
51+
@collection.collection_cards.find_each do |card|
52+
csv << card_row(card)
53+
end
54+
55+
next unless @collection.is_a?(Collection::SubmissionBox)
56+
57+
@collection.submissions_collection.collection_cards.find_each do |card|
58+
csv << card_row(card)
59+
end
60+
end
61+
end
62+
63+
private
64+
65+
def card_row(card)
66+
record = card.record
67+
record_type = record&.type
68+
record_type = record.class.name if record_type.nil? && record.present?
69+
70+
[
71+
card.id,
72+
record_type,
73+
card.name,
74+
record.try(:created_by)&.name,
75+
record.try(:created_by)&.email,
76+
record.try(:content),
77+
record.try(:data_content),
78+
card.col,
79+
card.row,
80+
card.height,
81+
card.width,
82+
record.try(:url),
83+
record.try(:image_url),
84+
url_for(record),
85+
csv_url_for(record),
86+
]
87+
end
88+
89+
def org_slug
90+
@org_slug ||= @collection.organization.slug
91+
end
92+
93+
def url_for(obj)
94+
return nil unless obj.present?
95+
96+
frontend_url_for(obj, slug: org_slug)
97+
end
98+
99+
def csv_url_for(coll)
100+
return nil unless coll&.is_a?(Collection)
101+
102+
"#{root_url}api/v1/collections/#{coll.id}/csv"
103+
end
104+
105+
def default_url_options
106+
Rails.application.config.action_mailer.default_url_options
107+
end
108+
end

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
get 'phase_sub_collections'
3333
get 'next_available_submission_test'
3434
get 'challenge_phase_collections'
35+
get 'csv'
3536
post 'clear_collection_cover'
3637
post 'clear_background_image'
3738
patch 'submit'

0 commit comments

Comments
 (0)