reports_kit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rubocop.yml +83 -0
  4. data/Gemfile +3 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +468 -0
  7. data/Rakefile +2 -0
  8. data/app/assets/javascripts/reports_kit/application.js +14 -0
  9. data/app/assets/javascripts/reports_kit/lib/_init.js +8 -0
  10. data/app/assets/javascripts/reports_kit/lib/chart.js +39 -0
  11. data/app/assets/javascripts/reports_kit/lib/report.js +119 -0
  12. data/app/assets/javascripts/reports_kit/vendor/chart.js +12269 -0
  13. data/app/assets/javascripts/reports_kit/vendor/daterangepicker.js +1627 -0
  14. data/app/assets/javascripts/reports_kit/vendor/moment.js +4040 -0
  15. data/app/assets/javascripts/reports_kit/vendor/select2.full.js +6436 -0
  16. data/app/assets/stylesheets/reports_kit/application.css.scss +3 -0
  17. data/app/assets/stylesheets/reports_kit/reports.css.sass +7 -0
  18. data/app/assets/stylesheets/reports_kit/select2_overrides.css.sass +7 -0
  19. data/app/assets/stylesheets/reports_kit/vendor/daterangepicker.css +269 -0
  20. data/app/assets/stylesheets/reports_kit/vendor/select2-bootstrap.css +721 -0
  21. data/app/assets/stylesheets/reports_kit/vendor/select2.css +484 -0
  22. data/config/routes.rb +10 -0
  23. data/docs/images/chart_options.png +0 -0
  24. data/docs/images/dashed_line.png +0 -0
  25. data/docs/images/flights_by_carrier.png +0 -0
  26. data/docs/images/flights_by_carrier_and_flight_at.png +0 -0
  27. data/docs/images/flights_by_delay.png +0 -0
  28. data/docs/images/flights_by_flight_at.png +0 -0
  29. data/docs/images/flights_by_hours_delayed.png +0 -0
  30. data/docs/images/flights_with_check_box.png +0 -0
  31. data/docs/images/flights_with_configured_boolean.png +0 -0
  32. data/docs/images/flights_with_configured_datetime.png +0 -0
  33. data/docs/images/flights_with_configured_number.png +0 -0
  34. data/docs/images/flights_with_configured_string.png +0 -0
  35. data/docs/images/flights_with_date_range.png +0 -0
  36. data/docs/images/flights_with_filters.png +0 -0
  37. data/docs/images/flights_with_multi_autocomplete.png +0 -0
  38. data/docs/images/flights_with_string_filter.png +0 -0
  39. data/docs/images/horizontal_bar.png +0 -0
  40. data/docs/images/legend_right.png +0 -0
  41. data/docs/images/users_by_created_at.png +0 -0
  42. data/gists/doc.txt +58 -0
  43. data/lib/reports_kit.rb +17 -0
  44. data/lib/reports_kit/base_controller.rb +11 -0
  45. data/lib/reports_kit/configuration.rb +10 -0
  46. data/lib/reports_kit/engine.rb +21 -0
  47. data/lib/reports_kit/helper.rb +19 -0
  48. data/lib/reports_kit/model.rb +16 -0
  49. data/lib/reports_kit/model_configuration.rb +23 -0
  50. data/lib/reports_kit/rails.rb +5 -0
  51. data/lib/reports_kit/report_builder.rb +76 -0
  52. data/lib/reports_kit/reports/data/chart_options.rb +132 -0
  53. data/lib/reports_kit/reports/data/generate.rb +65 -0
  54. data/lib/reports_kit/reports/data/one_dimension.rb +71 -0
  55. data/lib/reports_kit/reports/data/two_dimensions.rb +129 -0
  56. data/lib/reports_kit/reports/data/utils.rb +79 -0
  57. data/lib/reports_kit/reports/dimension.rb +78 -0
  58. data/lib/reports_kit/reports/filter.rb +84 -0
  59. data/lib/reports_kit/reports/filter_types/base.rb +47 -0
  60. data/lib/reports_kit/reports/filter_types/boolean.rb +30 -0
  61. data/lib/reports_kit/reports/filter_types/datetime.rb +27 -0
  62. data/lib/reports_kit/reports/filter_types/number.rb +28 -0
  63. data/lib/reports_kit/reports/filter_types/records.rb +26 -0
  64. data/lib/reports_kit/reports/filter_types/string.rb +38 -0
  65. data/lib/reports_kit/reports/generate_autocomplete_results.rb +55 -0
  66. data/lib/reports_kit/reports/inferrable_configuration.rb +113 -0
  67. data/lib/reports_kit/reports/measure.rb +58 -0
  68. data/lib/reports_kit/reports_controller.rb +15 -0
  69. data/lib/reports_kit/resources_controller.rb +8 -0
  70. data/lib/reports_kit/version.rb +3 -0
  71. data/reports_kit.gemspec +23 -0
  72. data/spec/factories/issue_factory.rb +4 -0
  73. data/spec/factories/issues_label_factory.rb +4 -0
  74. data/spec/factories/label_factory.rb +4 -0
  75. data/spec/factories/repo_factory.rb +5 -0
  76. data/spec/factories/tag_factory.rb +4 -0
  77. data/spec/fixtures/generate_inputs.yml +35 -0
  78. data/spec/fixtures/generate_outputs.yml +208 -0
  79. data/spec/reports_kit/reports/data/generate_spec.rb +275 -0
  80. data/spec/reports_kit/reports/dimension_spec.rb +38 -0
  81. data/spec/reports_kit/reports/filter_spec.rb +38 -0
  82. data/spec/spec_helper.rb +58 -0
  83. data/spec/support/factory_girl.rb +5 -0
  84. data/spec/support/helpers.rb +13 -0
  85. data/spec/support/models/issue.rb +6 -0
  86. data/spec/support/models/issues_label.rb +4 -0
  87. data/spec/support/models/label.rb +5 -0
  88. data/spec/support/models/repo.rb +7 -0
  89. data/spec/support/models/tag.rb +4 -0
  90. data/spec/support/schema.rb +38 -0
  91. metadata +232 -0
@@ -0,0 +1,8 @@
1
+ module ReportsKit
2
+ class ResourcesController < ReportsKit::BaseController
3
+ def autocomplete
4
+ results = Reports::GenerateAutocompleteResults.new(params, context_record: context_record).perform
5
+ render json: { data: results }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module ReportsKit
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../lib/reports_kit/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.authors = ['Tom Benner']
5
+ s.email = ['tombenner@gmail.com']
6
+ s.description = s.summary = 'Beautiful, interactive charts for Ruby on Rails'
7
+ s.homepage = 'https://github.com/tombenner/reports_kit'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.name = 'reports_kit'
11
+ s.require_paths = ['lib']
12
+ s.version = ReportsKit::VERSION
13
+ s.license = 'MIT'
14
+
15
+ s.add_dependency 'rails'
16
+ s.add_dependency 'pg'
17
+
18
+ s.add_development_dependency 'rspec', '~> 3'
19
+ s.add_development_dependency 'database_cleaner', '~> 1'
20
+ s.add_development_dependency 'factory_girl', '~> 4'
21
+ s.add_development_dependency 'pry', '~> 0'
22
+ s.add_development_dependency 'timecop', '~> 0'
23
+ end
@@ -0,0 +1,4 @@
1
+ FactoryGirl.define do
2
+ factory :issue do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ FactoryGirl.define do
2
+ factory :issues_label do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ FactoryGirl.define do
2
+ factory :label do
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :repo do
3
+ sequence(:full_name) { |i| "foo/bar#{i}" }
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ FactoryGirl.define do
2
+ factory :tag do
3
+ end
4
+ end
@@ -0,0 +1,35 @@
1
+ - measure: issue
2
+ dimensions:
3
+ - repo
4
+ - measure: issue
5
+ dimensions:
6
+ - repo
7
+ chart:
8
+ options:
9
+ foo: bar
10
+ - measure: issue
11
+ dimensions:
12
+ - repo
13
+ chart:
14
+ foo: bar
15
+ - measure:
16
+ key: issue
17
+ dimensions:
18
+ - key: repo
19
+ - measure:
20
+ key: issue
21
+ dimensions:
22
+ - key: opened_at
23
+ - measure:
24
+ key: issue
25
+ dimensions:
26
+ - key: opened_at
27
+ - key: repo
28
+ - measure:
29
+ key: issue
30
+ filters:
31
+ - key: locked
32
+ - key: title
33
+ - key: opened_at
34
+ dimensions:
35
+ - key: repo
@@ -0,0 +1,208 @@
1
+ ---
2
+ - :chart_data:
3
+ :labels: []
4
+ :datasets:
5
+ - :label: Issues
6
+ :data: []
7
+ :backgroundColor: "#1f77b4"
8
+ :borderColor: "#1f77b4"
9
+ :options:
10
+ :scales:
11
+ :xAxes:
12
+ - :gridLines:
13
+ :display: false
14
+ :barPercentage: 0.9
15
+ :categoryPercentage: 0.9
16
+ :scaleLabel:
17
+ :display: true
18
+ :labelString: Repo
19
+ :yAxes:
20
+ - :ticks:
21
+ :beginAtZero: true
22
+ :scaleLabel:
23
+ :display: true
24
+ :labelString: Issues
25
+ :legend:
26
+ :labels:
27
+ :usePointStyle: true
28
+ :tooltips:
29
+ :xPadding: 8
30
+ :yPadding: 7
31
+ :type: bar
32
+ - :chart_data:
33
+ :labels: []
34
+ :datasets:
35
+ - :label: Issues
36
+ :data: []
37
+ :backgroundColor: "#1f77b4"
38
+ :borderColor: "#1f77b4"
39
+ :options:
40
+ :scales:
41
+ :xAxes:
42
+ - :gridLines:
43
+ :display: false
44
+ :barPercentage: 0.9
45
+ :categoryPercentage: 0.9
46
+ :scaleLabel:
47
+ :display: true
48
+ :labelString: Repo
49
+ :yAxes:
50
+ - :ticks:
51
+ :beginAtZero: true
52
+ :scaleLabel:
53
+ :display: true
54
+ :labelString: Issues
55
+ :legend:
56
+ :labels:
57
+ :usePointStyle: true
58
+ :tooltips:
59
+ :xPadding: 8
60
+ :yPadding: 7
61
+ :foo: bar
62
+ :type: bar
63
+ - :chart_data:
64
+ :labels: []
65
+ :datasets:
66
+ - :label: Issues
67
+ :data: []
68
+ :backgroundColor: "#1f77b4"
69
+ :borderColor: "#1f77b4"
70
+ :options:
71
+ :scales:
72
+ :xAxes:
73
+ - :gridLines:
74
+ :display: false
75
+ :barPercentage: 0.9
76
+ :categoryPercentage: 0.9
77
+ :scaleLabel:
78
+ :display: true
79
+ :labelString: Repo
80
+ :yAxes:
81
+ - :ticks:
82
+ :beginAtZero: true
83
+ :scaleLabel:
84
+ :display: true
85
+ :labelString: Issues
86
+ :legend:
87
+ :labels:
88
+ :usePointStyle: true
89
+ :tooltips:
90
+ :xPadding: 8
91
+ :yPadding: 7
92
+ :type: bar
93
+ - :chart_data:
94
+ :labels: []
95
+ :datasets:
96
+ - :label: Issues
97
+ :data: []
98
+ :backgroundColor: "#1f77b4"
99
+ :borderColor: "#1f77b4"
100
+ :options:
101
+ :scales:
102
+ :xAxes:
103
+ - :gridLines:
104
+ :display: false
105
+ :barPercentage: 0.9
106
+ :categoryPercentage: 0.9
107
+ :scaleLabel:
108
+ :display: true
109
+ :labelString: Repo
110
+ :yAxes:
111
+ - :ticks:
112
+ :beginAtZero: true
113
+ :scaleLabel:
114
+ :display: true
115
+ :labelString: Issues
116
+ :legend:
117
+ :labels:
118
+ :usePointStyle: true
119
+ :tooltips:
120
+ :xPadding: 8
121
+ :yPadding: 7
122
+ :type: bar
123
+ - :chart_data:
124
+ :labels: []
125
+ :datasets:
126
+ - :label: Issues
127
+ :data: []
128
+ :backgroundColor: "#1f77b4"
129
+ :borderColor: "#1f77b4"
130
+ :options:
131
+ :scales:
132
+ :xAxes:
133
+ - :gridLines:
134
+ :display: false
135
+ :barPercentage: 0.9
136
+ :categoryPercentage: 0.9
137
+ :scaleLabel:
138
+ :display: true
139
+ :labelString: Opened At
140
+ :yAxes:
141
+ - :ticks:
142
+ :beginAtZero: true
143
+ :scaleLabel:
144
+ :display: true
145
+ :labelString: Issues
146
+ :legend:
147
+ :labels:
148
+ :usePointStyle: true
149
+ :tooltips:
150
+ :xPadding: 8
151
+ :yPadding: 7
152
+ :type: bar
153
+ - :chart_data:
154
+ :labels: []
155
+ :datasets: []
156
+ :options:
157
+ :scales:
158
+ :xAxes:
159
+ - :gridLines:
160
+ :display: false
161
+ :barPercentage: 0.9
162
+ :categoryPercentage: 0.9
163
+ :scaleLabel:
164
+ :display: true
165
+ :labelString: Opened At
166
+ :yAxes:
167
+ - :ticks:
168
+ :beginAtZero: true
169
+ :scaleLabel:
170
+ :display: true
171
+ :labelString: Issues
172
+ :legend:
173
+ :labels:
174
+ :usePointStyle: true
175
+ :tooltips:
176
+ :xPadding: 8
177
+ :yPadding: 7
178
+ :type: bar
179
+ - :chart_data:
180
+ :labels: []
181
+ :datasets:
182
+ - :label: Issues
183
+ :data: []
184
+ :backgroundColor: "#1f77b4"
185
+ :borderColor: "#1f77b4"
186
+ :options:
187
+ :scales:
188
+ :xAxes:
189
+ - :gridLines:
190
+ :display: false
191
+ :barPercentage: 0.9
192
+ :categoryPercentage: 0.9
193
+ :scaleLabel:
194
+ :display: true
195
+ :labelString: Repo
196
+ :yAxes:
197
+ - :ticks:
198
+ :beginAtZero: true
199
+ :scaleLabel:
200
+ :display: true
201
+ :labelString: Issues
202
+ :legend:
203
+ :labels:
204
+ :usePointStyle: true
205
+ :tooltips:
206
+ :xPadding: 8
207
+ :yPadding: 7
208
+ :type: bar
@@ -0,0 +1,275 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReportsKit::Reports::Data::Generate do
4
+ subject { described_class.new(properties, context_record: context_record).perform }
5
+
6
+ let(:repo) { create(:repo) }
7
+ let(:repo2) { create(:repo) }
8
+ let(:context_record) { nil }
9
+ let(:chart_data) do
10
+ chart_data = subject[:chart_data].except(:options)
11
+ chart_data[:datasets] = chart_data[:datasets].map do |dataset|
12
+ dataset.except(:backgroundColor, :borderColor)
13
+ end
14
+ chart_data
15
+ end
16
+
17
+ let(:chart_type) { subject[:type] }
18
+ let(:chart_options) { subject[:chart_data][:options] }
19
+
20
+ context 'with a datetime dimension' do
21
+ let(:properties) do
22
+ {
23
+ measure: 'issue',
24
+ dimensions: %w(opened_at)
25
+ }
26
+ end
27
+ let!(:issues) do
28
+ [
29
+ create(:issue, repo: repo, opened_at: now - 2.weeks),
30
+ create(:issue, repo: repo, opened_at: now - 2.weeks),
31
+ create(:issue, repo: repo, opened_at: now)
32
+ ]
33
+ end
34
+
35
+ it 'returns the type' do
36
+ expect(chart_type).to eq('bar')
37
+ end
38
+
39
+ it 'returns the data' do
40
+ expect(chart_data).to eq({
41
+ labels: [
42
+ format_time(2),
43
+ format_time(1),
44
+ format_time(0)
45
+ ],
46
+ datasets: [
47
+ {
48
+ label: 'Issues',
49
+ data: [2, 0, 1]
50
+ }
51
+ ]
52
+ })
53
+ end
54
+
55
+ context 'with a datetime filter' do
56
+ let(:properties) do
57
+ {
58
+ measure: {
59
+ key: 'issue',
60
+ filters: [
61
+ {
62
+ key: 'opened_at',
63
+ criteria: {
64
+ operator: 'between',
65
+ value: "#{date_string_for_filter(now - 1.week)} - #{date_string_for_filter(now)}"
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ dimensions: %w(opened_at)
71
+ }
72
+ end
73
+
74
+ it 'returns the chart_data' do
75
+ expect(chart_data).to eq({
76
+ labels: [format_time(0)],
77
+ datasets: [
78
+ {
79
+ label: "Issues",
80
+ data: [1.0]
81
+ }
82
+ ]
83
+ })
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'with an association dimension' do
89
+ let(:properties) do
90
+ {
91
+ measure: 'issue',
92
+ dimensions: %w(repo)
93
+ }
94
+ end
95
+ let!(:issues) do
96
+ [
97
+ create(:issue, repo: repo),
98
+ create(:issue, repo: repo),
99
+ create(:issue, repo: repo2)
100
+ ]
101
+ end
102
+
103
+ it 'returns the chart_data' do
104
+ expect(chart_data).to eq({
105
+ labels: [repo.to_s, repo2.to_s],
106
+ datasets: [{ label: 'Issues', data: [2, 1] }]
107
+ })
108
+ end
109
+
110
+ context 'with a context_record' do
111
+ let(:context_record) { repo }
112
+
113
+ it 'returns the chart_data' do
114
+ expect(chart_data).to eq({
115
+ labels: [repo.to_s],
116
+ datasets: [{ label: "Issues", data: [2.0] }]
117
+ })
118
+ end
119
+ end
120
+
121
+ context 'with a belongs_to association filter' do
122
+ let(:properties) do
123
+ {
124
+ measure: {
125
+ key: 'issue',
126
+ filters: [
127
+ {
128
+ key: 'repo',
129
+ criteria: {
130
+ operator: 'include',
131
+ value: [repo.id]
132
+ }
133
+ }
134
+ ]
135
+ },
136
+ dimensions: %w(repo)
137
+ }
138
+ end
139
+
140
+ it 'returns the chart_data' do
141
+ expect(chart_data).to eq({
142
+ labels: [repo.to_s],
143
+ datasets: [{ label: "Issues", data: [2.0] }]
144
+ })
145
+ end
146
+ end
147
+
148
+ context 'with a has_many association filter' do
149
+ let(:properties) do
150
+ {
151
+ measure: {
152
+ key: 'issue',
153
+ filters: [
154
+ {
155
+ key: 'tags',
156
+ criteria: {
157
+ operator: 'include',
158
+ value: [tag.id]
159
+ }
160
+ }
161
+ ]
162
+ },
163
+ dimensions: %w(repo)
164
+ }
165
+ end
166
+ let(:tag) { create(:tag) }
167
+ before(:each) do
168
+ issues[0].tags << tag
169
+ end
170
+
171
+ it 'returns the chart_data' do
172
+ chart_data
173
+ expect(chart_data).to eq({
174
+ labels: [repo.to_s],
175
+ datasets: [{ label: "Issues", data: [1.0] }]
176
+ })
177
+ end
178
+ end
179
+
180
+ context 'with a has_many :through association filter' do
181
+ let(:properties) do
182
+ {
183
+ measure: {
184
+ key: 'issue',
185
+ filters: [
186
+ {
187
+ key: 'labels',
188
+ criteria: {
189
+ operator: 'include',
190
+ value: [label.id]
191
+ }
192
+ }
193
+ ]
194
+ },
195
+ dimensions: %w(repo)
196
+ }
197
+ end
198
+ let(:label) { create(:label) }
199
+ before(:each) do
200
+ issues[0].labels << label
201
+ end
202
+
203
+ it 'returns the chart_data' do
204
+ expect(chart_data).to eq({
205
+ labels: [repo.to_s],
206
+ datasets: [{ label: "Issues", data: [1.0] }]
207
+ })
208
+ end
209
+ end
210
+ end
211
+
212
+ context 'with datetime and association dimensions' do
213
+ let(:properties) do
214
+ {
215
+ measure: 'issue',
216
+ dimensions: %w(opened_at repo)
217
+ }
218
+ end
219
+ let!(:issues) do
220
+ [
221
+ create(:issue, repo: repo, opened_at: now),
222
+ create(:issue, repo: repo, opened_at: now - 2.weeks),
223
+ create(:issue, repo: repo2, opened_at: now)
224
+ ]
225
+ end
226
+
227
+ it 'returns the chart_data' do
228
+ expect(chart_data).to eq({
229
+ labels: [format_time(2), format_time(1), format_time(0)],
230
+ datasets: [
231
+ {
232
+ label: repo.to_s,
233
+ data: [1, 0, 1]
234
+ },
235
+ {
236
+ label: repo2.to_s,
237
+ data: [0, 0, 1]
238
+ }
239
+ ]
240
+ })
241
+ end
242
+ end
243
+
244
+ # These examples allow for quick comparisons of many types of inputs and outputs.
245
+ # When making functional modifications, run these to see whether the modifications impact the output in any cases.
246
+ # If the output effects are desired, run `REWRITE_RESULTS=1 rspec` to write them to the outputs YAML file, then verify that the output
247
+ # modifications are desired, then commit the result and uncomment the `skip`.
248
+ describe 'chart options' do
249
+ FIXTURES_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'fixtures'))
250
+
251
+ let(:inputs) { YAML.load_file("#{FIXTURES_DIRECTORY}/generate_inputs.yml") }
252
+ let(:outputs_path) { "#{FIXTURES_DIRECTORY}/generate_outputs.yml" }
253
+ let(:outputs) { YAML.load_file(outputs_path) }
254
+
255
+ context 'input examples' do
256
+ YAML.load_file("#{FIXTURES_DIRECTORY}/generate_inputs.yml").each.with_index do |inputs, index|
257
+ it 'returns the expected output' do
258
+ expect(described_class.new(inputs).perform).to eq(outputs[index])
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'writing the outputs' do
264
+ # For documentation about this `skip`, see the comment at the top of this `describe` block.
265
+ skip unless ENV['REWRITE_RESULTS'] == '1'
266
+
267
+ it 'writes the outputs' do
268
+ outputs = inputs.map do |example|
269
+ described_class.new(example).perform
270
+ end
271
+ File.write(outputs_path, outputs.to_yaml)
272
+ end
273
+ end
274
+ end
275
+ end