resque-reports 0.0.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/app/jobs/resque/reports/report_job.rb +48 -8
- data/lib/resque/reports/base_report.rb +151 -98
- data/lib/resque/reports/cache_file.rb +22 -10
- data/lib/resque/reports/csv_report.rb +54 -30
- data/lib/resque/reports/extensions/const.rb +11 -0
- data/lib/resque/reports/extensions/encodings.rb +11 -0
- data/lib/resque/reports/extensions/event_callbacks.rb +64 -0
- data/lib/resque/reports/extensions/event_templates.rb +22 -0
- data/lib/resque/reports/extensions/filename_gen.rb +29 -0
- data/lib/resque/reports/extensions/table_building.rb +117 -0
- data/lib/resque/reports/extensions.rb +37 -0
- data/lib/resque/reports/version.rb +2 -1
- data/lib/resque/reports.rb +6 -5
- data/lib/resque-reports.rb +1 -0
- data/resque-reports.gemspec +7 -5
- data/spec/resque/reports/base_report_spec.rb +196 -0
- data/spec/resque/reports/csv_report_spec.rb +89 -0
- data/spec/resque/reports/report_job_spec.rb +140 -0
- data/spec/spec_helper.rb +8 -73
- metadata +63 -13
- data/init.rb +0 -4
- data/lib/resque/reports/callbacks.rb +0 -50
- data/lib/resque/reports/encodings.rb +0 -13
data/lib/resque-reports.rb
CHANGED
data/resque-reports.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Resque::Reports::VERSION
|
9
9
|
gem.authors = ['Sergey D.']
|
10
10
|
gem.email = ['sclinede@gmail.com']
|
11
|
-
gem.description = 'Make your custom reports to CSV
|
12
|
-
gem.summary = 'resque-reports
|
11
|
+
gem.description = 'Make your custom reports to CSV in background using Resque with simple DSL'
|
12
|
+
gem.summary = 'resque-reports'
|
13
13
|
gem.homepage = 'https://github.com/sclinede/resque-reports'
|
14
14
|
gem.license = "MIT"
|
15
15
|
|
@@ -18,10 +18,12 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ['lib']
|
20
20
|
|
21
|
-
gem.add_dependency 'resque-integration', '
|
22
|
-
gem.add_dependency '
|
21
|
+
gem.add_dependency 'resque-integration', '>= 0.2.9'
|
22
|
+
gem.add_dependency 'activesupport' # избавиться от зависимости
|
23
23
|
|
24
24
|
gem.add_development_dependency "bundler", "~> 1.3"
|
25
25
|
gem.add_development_dependency "rake"
|
26
|
-
gem.add_development_dependency 'rspec'
|
26
|
+
gem.add_development_dependency 'rspec', '>= 2.14.0'
|
27
|
+
gem.add_development_dependency 'rspec-given', '~> 3.0'
|
28
|
+
gem.add_development_dependency 'simplecov'
|
27
29
|
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'resque-reports'
|
6
|
+
|
7
|
+
class MyTypeReport < Resque::Reports::BaseReport
|
8
|
+
extension :type
|
9
|
+
|
10
|
+
def write(io, force = false)
|
11
|
+
write_line io, build_table_header
|
12
|
+
|
13
|
+
data_each(true) do |element|
|
14
|
+
write_line io, build_table_row(element)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_line(io, row)
|
19
|
+
io << "#{row.nil? ? 'nil' : row.join('|')}\r\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class MyReport < MyTypeReport
|
24
|
+
queue :my_type_reports
|
25
|
+
source :select_data
|
26
|
+
encoding UTF8
|
27
|
+
|
28
|
+
directory File.join(Dir.tmpdir, 'resque-reports')
|
29
|
+
|
30
|
+
table do |element|
|
31
|
+
column 'First one', :decorate_first
|
32
|
+
column 'Second', decorate_second(element[:two])
|
33
|
+
end
|
34
|
+
|
35
|
+
create do |param|
|
36
|
+
@main_param = param
|
37
|
+
end
|
38
|
+
|
39
|
+
def decorate_first(element)
|
40
|
+
"decorated: #{element[:one]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def decorate_second(text)
|
44
|
+
"#{text} - is second"
|
45
|
+
end
|
46
|
+
|
47
|
+
def select_data
|
48
|
+
[{one: 'one', two: 'one'}, {one: @main_param, two: @main_param}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'Resque::Reports::BaseReport successor' do
|
53
|
+
let(:io) { StringIO.new }
|
54
|
+
let(:my_report) { MyReport.new('test') }
|
55
|
+
let(:dummy) { Resque::Reports::Extensions::Dummy.new }
|
56
|
+
|
57
|
+
describe '#extension' do
|
58
|
+
before { File.stub(:exists? => true) }
|
59
|
+
|
60
|
+
it { File.extname(my_report.filename).should eq '.type' }
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#source' do
|
64
|
+
before { my_report.stub(select_data: [dummy]) }
|
65
|
+
|
66
|
+
it { my_report.should_receive(:select_data) }
|
67
|
+
|
68
|
+
after { my_report.build true }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#directory' do
|
72
|
+
subject { my_report.instance_variable_get(:@cache_file) }
|
73
|
+
let(:tmpdir) { File.join(Dir.tmpdir, 'resque-reports') }
|
74
|
+
|
75
|
+
it { subject.instance_variable_get(:@dir).should eq tmpdir }
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#create' do
|
79
|
+
it { my_report.instance_variable_get(:@main_param).should eq 'test' }
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#encoding' do
|
83
|
+
subject { my_report.instance_variable_get(:@cache_file) }
|
84
|
+
let(:utf_coding) { Resque::Reports::Extensions::Encodings::UTF8 }
|
85
|
+
|
86
|
+
it { subject.instance_variable_get(:@coding).should eq utf_coding }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#write' do
|
90
|
+
subject { MyReport.new('#write test') }
|
91
|
+
|
92
|
+
before do
|
93
|
+
subject.stub(:data_each) { |&block| block.call(dummy) }
|
94
|
+
subject.stub(build_table_row: ['row'])
|
95
|
+
subject.stub(build_table_header: ['header'])
|
96
|
+
|
97
|
+
subject.write(io)
|
98
|
+
end
|
99
|
+
|
100
|
+
it { io.string.should eq "header\r\nrow\r\n" }
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#bg_build' do
|
104
|
+
let(:job_class) { Resque::Reports::ReportJob }
|
105
|
+
|
106
|
+
context 'when report is building twice' do
|
107
|
+
subject { MyReport.new('#bg_build test') }
|
108
|
+
|
109
|
+
before { job_class.stub(enqueue: 'job_id') }
|
110
|
+
|
111
|
+
it do
|
112
|
+
job_class
|
113
|
+
.should_receive(:enqueue)
|
114
|
+
.with('MyReport', '["#bg_build test",true]')
|
115
|
+
.twice
|
116
|
+
end
|
117
|
+
|
118
|
+
after do
|
119
|
+
2.times { subject.bg_build true }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when report is building' do
|
124
|
+
subject { MyReport.new('#bg_build test') }
|
125
|
+
|
126
|
+
before { job_class.stub(enqueue: 'job_id') }
|
127
|
+
|
128
|
+
it do
|
129
|
+
job_class
|
130
|
+
.should_receive(:enqueue)
|
131
|
+
.with('MyReport', '["#bg_build test",true]')
|
132
|
+
end
|
133
|
+
|
134
|
+
after { subject.bg_build true }
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'when report is not build yet' do
|
138
|
+
subject { MyReport.new('#bg_build test') }
|
139
|
+
|
140
|
+
before do
|
141
|
+
job_class.stub(enqueued?: double('Meta', meta_id: 'enqueued_job_id'))
|
142
|
+
end
|
143
|
+
|
144
|
+
it { subject.bg_build(true).should eq 'enqueued_job_id' }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#build' do
|
149
|
+
subject { MyReport.new('#build test') }
|
150
|
+
|
151
|
+
it { subject.should_receive(:decorate_first).twice }
|
152
|
+
|
153
|
+
after { subject.build true }
|
154
|
+
|
155
|
+
context 'when report was built' do
|
156
|
+
subject { MyReport.new('was built test') }
|
157
|
+
|
158
|
+
before { subject.build true }
|
159
|
+
|
160
|
+
its(:exists?) { should be_true }
|
161
|
+
it do
|
162
|
+
File.read(subject.filename)
|
163
|
+
.should eq <<-REPORT.gsub(/^ {12}/, '')
|
164
|
+
First one|Second\r
|
165
|
+
decorated: one|one - is second\r
|
166
|
+
decorated: was built test|was built test - is second\r
|
167
|
+
REPORT
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '#data_each' do
|
173
|
+
subject { MyReport.new('#data_each test') }
|
174
|
+
|
175
|
+
it { subject.should_receive(:data_each) }
|
176
|
+
|
177
|
+
after { subject.write(io) }
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '#build_table_header' do
|
181
|
+
subject { MyReport.new('#build_table_header test') }
|
182
|
+
|
183
|
+
it { subject.should_receive(:build_table_header) }
|
184
|
+
|
185
|
+
after { subject.write(io) }
|
186
|
+
end
|
187
|
+
|
188
|
+
describe '#build_table_row' do
|
189
|
+
subject { MyReport.new('#build_table_row test') }
|
190
|
+
|
191
|
+
it { subject.should_receive(:build_table_row).twice }
|
192
|
+
|
193
|
+
after { subject.write(io) }
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'resque/reports/csv_report'
|
6
|
+
|
7
|
+
class MyCsvReport < Resque::Reports::CsvReport
|
8
|
+
queue :csv_reports
|
9
|
+
source :select_data
|
10
|
+
encoding UTF8
|
11
|
+
|
12
|
+
csv_options col_sep: ',', row_sep: "\n"
|
13
|
+
|
14
|
+
directory File.join(Dir.home, '.resque-reports')
|
15
|
+
|
16
|
+
table do |element|
|
17
|
+
column 'First one', :decorate_first
|
18
|
+
column 'Second', "#{element} - is second"
|
19
|
+
end
|
20
|
+
|
21
|
+
create do |param|
|
22
|
+
@main_param = param
|
23
|
+
end
|
24
|
+
|
25
|
+
def decorate_first(element)
|
26
|
+
"decorated: #{element}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def select_data
|
30
|
+
[:one, @main_param]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class MyCsvDefaultsReport < Resque::Reports::CsvReport
|
35
|
+
source :select_data
|
36
|
+
encoding UTF8
|
37
|
+
|
38
|
+
directory File.join(Dir.tmpdir, 'resque-reports')
|
39
|
+
|
40
|
+
table do |element|
|
41
|
+
column 'Uno', "#{element} - is value"
|
42
|
+
end
|
43
|
+
|
44
|
+
def select_data
|
45
|
+
[:one, @main_param]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'Resque::Reports::CsvReport successor' do
|
50
|
+
describe '.csv_options' do
|
51
|
+
context 'when custom options not set' do
|
52
|
+
subject { MyCsvDefaultsReport.new }
|
53
|
+
|
54
|
+
it 'sets csv_options defaults' do
|
55
|
+
subject.options.should eq MyCsvReport::DEFAULT_CSV_OPTIONS
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when custom options are set' do
|
60
|
+
subject { MyCsvReport.new('csv_options test') }
|
61
|
+
|
62
|
+
let(:my_options) do
|
63
|
+
MyCsvReport::DEFAULT_CSV_OPTIONS.merge(col_sep: ',', row_sep: "\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'merges csv_options with defaults' do
|
67
|
+
subject.options.should eq my_options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#build' do
|
73
|
+
context 'when report was built' do
|
74
|
+
subject { MyCsvReport.new('was built test') }
|
75
|
+
|
76
|
+
before { subject.build true }
|
77
|
+
|
78
|
+
its(:exists?) { should be_true }
|
79
|
+
it do
|
80
|
+
File.read(subject.filename)
|
81
|
+
.should eq <<-CSV.gsub(/^ {12}/, "")
|
82
|
+
First one,Second
|
83
|
+
decorated: one,one - is second
|
84
|
+
decorated: was built test,was built test - is second
|
85
|
+
CSV
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'resque-reports'
|
4
|
+
|
5
|
+
module Reports
|
6
|
+
class MyCsvReport < Resque::Reports::CsvReport
|
7
|
+
queue :csv_reports
|
8
|
+
source :select_data
|
9
|
+
encoding UTF8
|
10
|
+
|
11
|
+
directory File.join(Dir.home, '.resque-reports')
|
12
|
+
|
13
|
+
table do |element|
|
14
|
+
column 'First', "#{element} - is first"
|
15
|
+
end
|
16
|
+
|
17
|
+
create do |param1, param2|
|
18
|
+
@main_param = param1
|
19
|
+
@secondary_param = param2
|
20
|
+
end
|
21
|
+
|
22
|
+
def select_data
|
23
|
+
[:one, @main_param]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Resque::Reports::ReportJob do
|
29
|
+
let(:my_report) { Reports::MyCsvReport.new('.execute test', 'test') }
|
30
|
+
let(:exec_params) do
|
31
|
+
['Reports::MyCsvReport', '[".execute test", "test",true]']
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.execute' do
|
35
|
+
before { Reports::MyCsvReport.stub(new: my_report) }
|
36
|
+
|
37
|
+
context 'when building report' do
|
38
|
+
before { my_report.stub(build: nil) }
|
39
|
+
|
40
|
+
it do
|
41
|
+
expect(Reports::MyCsvReport)
|
42
|
+
.to receive(:new).with('.execute test', 'test')
|
43
|
+
end
|
44
|
+
it { expect(my_report).to receive(:build).with(true) }
|
45
|
+
|
46
|
+
after { described_class.execute(*exec_params) }
|
47
|
+
|
48
|
+
end
|
49
|
+
context 'when wrong class given' do
|
50
|
+
it 'sends invalid class name' do
|
51
|
+
expect { described_class.execute('MyWrongReport', '[true]') }
|
52
|
+
.to raise_error(NameError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'sends class that is not BaseReport successor' do
|
56
|
+
expect { described_class.execute('Object', '[true]') }
|
57
|
+
.to raise_error(RuntimeError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when events are firing' do
|
62
|
+
before do
|
63
|
+
described_class.stub(get_meta: {})
|
64
|
+
described_class.get_meta.stub(save: true)
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when progress total is zero' do
|
68
|
+
before do
|
69
|
+
my_report.stub(select_data: [])
|
70
|
+
my_report.stub(data_size: 0)
|
71
|
+
end
|
72
|
+
|
73
|
+
it { described_class.should_not_receive(:at) }
|
74
|
+
|
75
|
+
after { described_class.execute(*exec_params) }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when works default handlers' do
|
79
|
+
context 'when error occurs' do
|
80
|
+
before { my_report.stub(:build_table_row) { fail 'Custom error' } }
|
81
|
+
|
82
|
+
it do
|
83
|
+
expect { described_class.execute(*exec_params) }
|
84
|
+
.to raise_error('Custom error')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when progress is changed' do
|
89
|
+
it { described_class.should_receive(:at).with(2, 2, nil) }
|
90
|
+
|
91
|
+
after { described_class.execute(*exec_params) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when works custom handlers' do
|
96
|
+
context 'when error occurs' do
|
97
|
+
before do
|
98
|
+
my_report.stub(:error_message) { |e| fail "Boom! #{e.message}" }
|
99
|
+
my_report.stub(:build_table_row) { fail 'Custom error' }
|
100
|
+
end
|
101
|
+
|
102
|
+
it do
|
103
|
+
expect { described_class.execute(*exec_params) }
|
104
|
+
.to raise_error('Boom! Custom error')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when progress is changed' do
|
109
|
+
before do
|
110
|
+
my_report.stub(:progress_message) do |progress, total|
|
111
|
+
"my progress: #{progress} / #{total}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it do
|
116
|
+
described_class
|
117
|
+
.should_receive(:at)
|
118
|
+
.with(2, 2, 'my progress: 2 / 2')
|
119
|
+
end
|
120
|
+
|
121
|
+
after { described_class.execute(*exec_params) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when task is performed by resque' do
|
126
|
+
context 'when error occurs' do
|
127
|
+
before do
|
128
|
+
my_report.stub(:error_message) { |e| fail "Boom! #{e.message}" }
|
129
|
+
my_report.stub(:build_table_row) { fail 'Custom error' }
|
130
|
+
end
|
131
|
+
|
132
|
+
it do
|
133
|
+
expect { described_class.execute(*exec_params) }
|
134
|
+
.to raise_error('Boom! Custom error')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|