Skip to content

Commit

Permalink
Merge 9346ed2 into f4825c1
Browse files Browse the repository at this point in the history
  • Loading branch information
wenderjean committed Mar 11, 2019
2 parents f4825c1 + 9346ed2 commit d9a9852
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 36 deletions.
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base

include Pundit
include SidebarController
include Renderers::CSV

before_action :authenticate_user!, unless: :devise_controller?
before_action :check_team_presence, if: :need_check_team?
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ def index
respond_to do |format|
format.json { render json: @stories }
format.csv do
render csv: @stories.order(:position), filename: @project.csv_filename
render(csv: @stories.order(:position),
exporter: Exporters::Stories,
filename: @project.csv_filename)
end
end
end
Expand Down
31 changes: 0 additions & 31 deletions config/initializers/csv_renderer.rb

This file was deleted.

25 changes: 25 additions & 0 deletions lib/exporters/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Exporters
class Default
attr_reader :collection

def initialize(collection = [])
@collection = collection
end

def generate
raise NotImplementedError
end

class << self
def content(collection)
new(collection).generate
end

def filename
'export.csv'
end
end
end
end
49 changes: 49 additions & 0 deletions lib/exporters/stories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module Exporters
class Stories < Default
EXTRA_COLUMNS = {
note: 'Note', document: 'Document', task: ['Task', 'Task Status']
}.freeze

alias stories collection

def generate
columns = {
notes: note_columns_size, documents: document_columns_size, tasks: task_columns_size
}

CSV.generate do |csv|
csv << headers

stories.each do |story|
csv << story.to_csv(columns)
end
end
end

private

def headers
notes_columns = Array.new(note_columns_size, EXTRA_COLUMNS[:note])
documents_columns = Array.new(document_columns_size, EXTRA_COLUMNS[:document])
tasks_columns = Array.new(task_columns_size, EXTRA_COLUMNS[:task]).flatten

@headers ||= Story.csv_headers + [
notes_columns, documents_columns, tasks_columns
].flatten
end

def note_columns_size
@note_columns_size ||= stories.map { |story| story.notes.size }.max || 0
end

def document_columns_size
@document_columns_size ||= stories.map { |story| story.documents.size }.max || 0
end

def task_columns_size
@task_columns_size ||= stories.map { |story| story.tasks.size }.max || 0
end
end
end
12 changes: 12 additions & 0 deletions lib/renderers/csv.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Renderers
module CSV
ActionController::Renderers.add :csv do |collection, options|
exporter, filename, type = Renderers::CSVOptions.new(options)
.attributes

send_data(exporter.content(collection), type: type, filename: filename)
end
end
end
29 changes: 29 additions & 0 deletions lib/renderers/csv_options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Renderers
class CSVOptions
def initialize(options = {})
@options = options
end

def exporter
@exporter ||= options[:exporter] || ::Exporters::Default
end

def filename
@filename ||= options[:filename] || exporter.filename
end

def type
Mime[:csv]
end

def attributes
[exporter, filename, type]
end

private

attr_reader :options
end
end
41 changes: 37 additions & 4 deletions spec/controllers/stories_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,43 @@
end

describe '#index' do
specify do
get :index, xhr: true, params: { project_id: project.id }
expect(response).to be_successful
expect(response.body).to eq(project.stories.to_json)
context 'when responding to json' do
specify do
get :index, xhr: true, params: { project_id: project.id }

expect(response).to be_successful
expect(response.body).to eq(project.stories.to_json)
end
end

context 'when responding to csv' do
let(:active_story) { create(:story, :active, project: project, requested_by: user) }

before do
create(:note, story: active_story)

Timecop.freeze Time.utc(2019, 1, 1, 12, 0, 0, 0).in_time_zone do
get :index, format: :csv, params: { project_id: project.id }
end
end

it 'responds correct Content-Type' do
expect(response.headers['Content-Type']).to eq 'text/csv'
end

it 'responds correct filename' do
expect(response.headers['Content-Disposition']).to eq "attachment; filename=\"Test Project-20190101_1200.csv\""
end

it 'responds correct content' do
expect(response.body).to eq(
[
(Story.csv_headers << 'Note').to_csv,
project.stories.map { |story| story.to_csv({ notes: 1, documents: 0, tasks: 0 }) }
.map(&:to_csv)
].flatten.join
)
end
end
end

Expand Down
14 changes: 14 additions & 0 deletions spec/factories/attachinary_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FactoryBot.define do
factory :attachinary_file, class: Attachinary::File do
public_id { 'public_id' }
version { Time.now.to_i }
format { 'png' }
resource_type { 'image' }
attachinariable_id { nil }

trait :story do
scope { 'documents' }
attachinariable_type { 'Story' }
end
end
end
15 changes: 15 additions & 0 deletions spec/lib/exporters/default_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'rails_helper'

describe Exporters::Default do
it { is_expected.to respond_to(:collection) }

describe '.filename' do
subject { described_class.filename }

it { is_expected.to eq 'export.csv' }
end

describe '.content' do
it { expect { described_class.content([]) }.to raise_error NotImplementedError }
end
end
121 changes: 121 additions & 0 deletions spec/lib/exporters/stories_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'rails_helper'

describe Exporters::Stories do
describe '.content' do
let(:user) { create(:user) }
let(:project) { create(:project, users: [user]) }

let(:csv) do
[
csv_headers.to_csv,
project.stories.map { |story| story.to_csv(csv_extra_columns) }
.map(&:to_csv)
].flatten.join
end

subject { described_class.content(stories) }

context 'when stories is empty' do
let(:stories) { [] }

it { is_expected.to eq Story.csv_headers.to_csv }
end

context 'when stories has not notes nor documents nor tasks' do
let(:stories) { create_list(:story, 3, :done, project: project, requested_by: user) }

let(:csv_headers) { Story.csv_headers }
let(:csv_extra_columns) { { notes: 0, documents: 0, tasks: 0 } }

it { is_expected.to eq csv }
end

context 'when only one story has notes' do
let(:csv_headers) { Story.csv_headers << 'Note' }
let(:csv_extra_columns) { { notes: 1, documents: 0, tasks: 0 } }

let(:stories) { create_list(:story, 1, :active, project: project, requested_by: user) }

before { create(:note, story: stories.first) }

it { is_expected.to eq csv }
end

context 'when more than one story has notes' do
let(:csv_headers) { Story.csv_headers.concat Array.new(2, 'Note') }
let(:csv_extra_columns) { { notes: 2, documents: 0, tasks: 0 } }

let(:stories) { create_list(:story, 1, :active, project: project, requested_by: user) }

before { create_list(:note, 2, story: stories.first) }

it { is_expected.to eq csv }
end

context 'when only one story has documents' do
let(:csv_headers) { Story.csv_headers << 'Document' }
let(:csv_extra_columns) { { notes: 0, documents: 1, tasks: 0 } }

let(:stories) { create_list(:story, 3, :done, project: project, requested_by: user) }
let(:story) { stories.first }

before do
Attachinary::File.skip_callback(:create, :after, :remove_temporary_tag)

create(:attachinary_file, :story, attachinariable_id: story.id)
end

after do
Attachinary::File.set_callback(:create, :after, :remove_temporary_tag)
end

it { is_expected.to eq csv }
end

context 'when more than one story has documents' do
let(:csv_headers) { Story.csv_headers.concat Array.new(2, 'Document') }
let(:csv_extra_columns) { { notes: 0, documents: 2, tasks: 0 } }

let(:stories) { create_list(:story, 3, :done, project: project, requested_by: user) }
let(:story) { stories.first }

before do
Attachinary::File.skip_callback(:create, :after, :remove_temporary_tag)

create_list(:attachinary_file, 2, :story, attachinariable_id: story.id)
end

after do
Attachinary::File.set_callback(:create, :after, :remove_temporary_tag)
end

it { is_expected.to eq csv }
end

context 'when only one story has tasks' do
let(:csv_headers) { Story.csv_headers.concat(['Task', 'Task Status']) }
let(:csv_extra_columns) { { notes: 0, documents: 0, tasks: 1 } }

let(:stories) { create_list(:story, 1, :active, project: project, requested_by: user) }

before { create(:task, story: stories.first) }

it { is_expected.to eq csv }
end

context 'when more than one story has tasks' do
let(:csv_headers) do
Story.csv_headers.concat(
Array.new(2, ['Task', 'Task Status']).flatten
)
end
let(:csv_extra_columns) { { notes: 0, documents: 0, tasks: 2 } }

let(:stories) { create_list(:story, 1, :active, project: project, requested_by: user) }

before { create_list(:task, 2, story: stories.first) }

it { is_expected.to eq csv }
end
end
end

0 comments on commit d9a9852

Please sign in to comment.