reports_kit 0.0.1

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.
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