reports_kit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +83 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +468 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/reports_kit/application.js +14 -0
- data/app/assets/javascripts/reports_kit/lib/_init.js +8 -0
- data/app/assets/javascripts/reports_kit/lib/chart.js +39 -0
- data/app/assets/javascripts/reports_kit/lib/report.js +119 -0
- data/app/assets/javascripts/reports_kit/vendor/chart.js +12269 -0
- data/app/assets/javascripts/reports_kit/vendor/daterangepicker.js +1627 -0
- data/app/assets/javascripts/reports_kit/vendor/moment.js +4040 -0
- data/app/assets/javascripts/reports_kit/vendor/select2.full.js +6436 -0
- data/app/assets/stylesheets/reports_kit/application.css.scss +3 -0
- data/app/assets/stylesheets/reports_kit/reports.css.sass +7 -0
- data/app/assets/stylesheets/reports_kit/select2_overrides.css.sass +7 -0
- data/app/assets/stylesheets/reports_kit/vendor/daterangepicker.css +269 -0
- data/app/assets/stylesheets/reports_kit/vendor/select2-bootstrap.css +721 -0
- data/app/assets/stylesheets/reports_kit/vendor/select2.css +484 -0
- data/config/routes.rb +10 -0
- data/docs/images/chart_options.png +0 -0
- data/docs/images/dashed_line.png +0 -0
- data/docs/images/flights_by_carrier.png +0 -0
- data/docs/images/flights_by_carrier_and_flight_at.png +0 -0
- data/docs/images/flights_by_delay.png +0 -0
- data/docs/images/flights_by_flight_at.png +0 -0
- data/docs/images/flights_by_hours_delayed.png +0 -0
- data/docs/images/flights_with_check_box.png +0 -0
- data/docs/images/flights_with_configured_boolean.png +0 -0
- data/docs/images/flights_with_configured_datetime.png +0 -0
- data/docs/images/flights_with_configured_number.png +0 -0
- data/docs/images/flights_with_configured_string.png +0 -0
- data/docs/images/flights_with_date_range.png +0 -0
- data/docs/images/flights_with_filters.png +0 -0
- data/docs/images/flights_with_multi_autocomplete.png +0 -0
- data/docs/images/flights_with_string_filter.png +0 -0
- data/docs/images/horizontal_bar.png +0 -0
- data/docs/images/legend_right.png +0 -0
- data/docs/images/users_by_created_at.png +0 -0
- data/gists/doc.txt +58 -0
- data/lib/reports_kit.rb +17 -0
- data/lib/reports_kit/base_controller.rb +11 -0
- data/lib/reports_kit/configuration.rb +10 -0
- data/lib/reports_kit/engine.rb +21 -0
- data/lib/reports_kit/helper.rb +19 -0
- data/lib/reports_kit/model.rb +16 -0
- data/lib/reports_kit/model_configuration.rb +23 -0
- data/lib/reports_kit/rails.rb +5 -0
- data/lib/reports_kit/report_builder.rb +76 -0
- data/lib/reports_kit/reports/data/chart_options.rb +132 -0
- data/lib/reports_kit/reports/data/generate.rb +65 -0
- data/lib/reports_kit/reports/data/one_dimension.rb +71 -0
- data/lib/reports_kit/reports/data/two_dimensions.rb +129 -0
- data/lib/reports_kit/reports/data/utils.rb +79 -0
- data/lib/reports_kit/reports/dimension.rb +78 -0
- data/lib/reports_kit/reports/filter.rb +84 -0
- data/lib/reports_kit/reports/filter_types/base.rb +47 -0
- data/lib/reports_kit/reports/filter_types/boolean.rb +30 -0
- data/lib/reports_kit/reports/filter_types/datetime.rb +27 -0
- data/lib/reports_kit/reports/filter_types/number.rb +28 -0
- data/lib/reports_kit/reports/filter_types/records.rb +26 -0
- data/lib/reports_kit/reports/filter_types/string.rb +38 -0
- data/lib/reports_kit/reports/generate_autocomplete_results.rb +55 -0
- data/lib/reports_kit/reports/inferrable_configuration.rb +113 -0
- data/lib/reports_kit/reports/measure.rb +58 -0
- data/lib/reports_kit/reports_controller.rb +15 -0
- data/lib/reports_kit/resources_controller.rb +8 -0
- data/lib/reports_kit/version.rb +3 -0
- data/reports_kit.gemspec +23 -0
- data/spec/factories/issue_factory.rb +4 -0
- data/spec/factories/issues_label_factory.rb +4 -0
- data/spec/factories/label_factory.rb +4 -0
- data/spec/factories/repo_factory.rb +5 -0
- data/spec/factories/tag_factory.rb +4 -0
- data/spec/fixtures/generate_inputs.yml +35 -0
- data/spec/fixtures/generate_outputs.yml +208 -0
- data/spec/reports_kit/reports/data/generate_spec.rb +275 -0
- data/spec/reports_kit/reports/dimension_spec.rb +38 -0
- data/spec/reports_kit/reports/filter_spec.rb +38 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/helpers.rb +13 -0
- data/spec/support/models/issue.rb +6 -0
- data/spec/support/models/issues_label.rb +4 -0
- data/spec/support/models/label.rb +5 -0
- data/spec/support/models/repo.rb +7 -0
- data/spec/support/models/tag.rb +4 -0
- data/spec/support/schema.rb +38 -0
- metadata +232 -0
data/config/routes.rb
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/gists/doc.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
1. Add ReportsKit
|
2
|
+
|
3
|
+
Gemfile
|
4
|
+
|
5
|
+
source 'https://my-api-key@gems.reportskit.co' do
|
6
|
+
gem 'reportskit'
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
2. Configure Models
|
11
|
+
|
12
|
+
Configure the filters and dimensions that can be used in your reports:
|
13
|
+
|
14
|
+
app/models/task.rb
|
15
|
+
|
16
|
+
class Task < ActiveRecord::Base
|
17
|
+
belongs_to :assignee
|
18
|
+
belongs_to :project
|
19
|
+
|
20
|
+
reportskit do
|
21
|
+
filter :assignee
|
22
|
+
filter :project
|
23
|
+
filter :completed_at
|
24
|
+
filter :is_completed, :boolean, conditions: -> { where.not(completed_at: nil) }
|
25
|
+
|
26
|
+
dimension :assignee
|
27
|
+
dimension :project
|
28
|
+
dimension :completed_at
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
3. Configure Reports
|
34
|
+
|
35
|
+
config/reportskit/reports/completed_tasks.yml
|
36
|
+
|
37
|
+
name: Completed tasks
|
38
|
+
measures:
|
39
|
+
- tasks
|
40
|
+
dimensions:
|
41
|
+
- completed_at
|
42
|
+
display_format: area
|
43
|
+
|
44
|
+
|
45
|
+
4. Add Report to a View
|
46
|
+
|
47
|
+
app/views/reports/my_view.html.haml
|
48
|
+
|
49
|
+
= render_report 'completed_tasks'
|
50
|
+
|
51
|
+
|
52
|
+
5. Add Routes:
|
53
|
+
|
54
|
+
config/routes.rb
|
55
|
+
|
56
|
+
mount ReportsKit::Engine, at: '/reports'
|
57
|
+
|
58
|
+
That's it! You can now visit the view in step 4. to see the report that you've configured.
|
data/lib/reports_kit.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails/all'
|
2
|
+
|
3
|
+
directory = File.dirname(File.absolute_path(__FILE__))
|
4
|
+
Dir.glob("#{directory}/reports_kit/*.rb") { |file| require file }
|
5
|
+
Dir.glob("#{directory}/reports_kit/reports/data/*.rb") { |file| require file }
|
6
|
+
Dir.glob("#{directory}/reports_kit/reports/filter_types/*.rb") { |file| require file }
|
7
|
+
Dir.glob("#{directory}/reports_kit/reports/*.rb") { |file| require file }
|
8
|
+
|
9
|
+
module ReportsKit
|
10
|
+
def self.configure
|
11
|
+
yield(configuration)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
class BaseController < ActionController::Base
|
3
|
+
private
|
4
|
+
|
5
|
+
def context_record
|
6
|
+
context_record_method = ReportsKit.configuration.context_record_method
|
7
|
+
return unless context_record_method
|
8
|
+
instance_eval(&context_record_method)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
engine_name 'reports_kit'
|
4
|
+
|
5
|
+
initializer 'helper' do
|
6
|
+
ActiveSupport.on_load(:action_view) do
|
7
|
+
include Helper
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer 'precompile', group: :all do |app|
|
12
|
+
if app.config.respond_to?(:assets)
|
13
|
+
if defined?(Sprockets) && Gem::Version.new(Sprockets::VERSION) >= Gem::Version.new('4.0.0.beta1')
|
14
|
+
app.config.assets.precompile += %w(reports_kit.js reports_kit.css)
|
15
|
+
else
|
16
|
+
app.config.assets.precompile << proc { |path| path.start_with?('reports_kit.') }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Helper
|
3
|
+
def render_report(properties, &block)
|
4
|
+
raise ArgumentError.new('`properties` must be a Hash or String') if properties.blank?
|
5
|
+
if properties.is_a?(String)
|
6
|
+
path = Rails.root.join('config', 'reports_kit', 'reports', "#{properties}.yml")
|
7
|
+
properties = YAML.load_file(path)
|
8
|
+
end
|
9
|
+
builder = ReportsKit::ReportBuilder.new(properties)
|
10
|
+
content_tag :div, nil, class: 'reports_kit_report', data: { properties: builder.properties, path: reports_kit_path } do
|
11
|
+
if block_given?
|
12
|
+
form_tag reports_kit_path, method: 'get', class: 'reports_kit_report_form form-inline' do
|
13
|
+
capture(builder, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class << self
|
7
|
+
attr_accessor :reports_kit_configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.reports_kit(&block)
|
11
|
+
self.reports_kit_configuration = ModelConfiguration.new
|
12
|
+
reports_kit_configuration.instance_eval(&block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
class ModelConfiguration
|
3
|
+
attr_accessor :dimensions, :filters, :autocomplete_scopes
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
self.dimensions = []
|
7
|
+
self.filters = []
|
8
|
+
self.autocomplete_scopes = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def dimension(key, properties)
|
12
|
+
dimensions << { key: key.to_s }.merge(properties).symbolize_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(key, type_key, properties)
|
16
|
+
filters << { key: key.to_s, type_key: type_key }.merge(properties).symbolize_keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def autocomplete_scope(*scopes)
|
20
|
+
self.autocomplete_scopes += scopes.map(&:to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
class ReportBuilder
|
3
|
+
include ActionView::Helpers
|
4
|
+
|
5
|
+
attr_accessor :properties
|
6
|
+
|
7
|
+
def initialize(properties)
|
8
|
+
self.properties = normalize_properties(properties)
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_box(filter_key, options={})
|
12
|
+
filter = validate_filter!(filter_key)
|
13
|
+
checked = filter.properties[:criteria][:operator] == 'true'
|
14
|
+
check_box_tag(filter_key, '1', checked, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def date_range(filter_key, options={})
|
18
|
+
filter = validate_filter!(filter_key)
|
19
|
+
defaults = { class: 'form-control input-sm date_range_picker' }
|
20
|
+
options = defaults.deep_merge(options)
|
21
|
+
text_field_tag(filter_key, filter.properties[:criteria][:value], options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def multi_autocomplete(filter_key, options={})
|
25
|
+
validate_filter!(filter_key)
|
26
|
+
reports_kit_path = Rails.application.routes.url_helpers.reports_kit_path
|
27
|
+
path = "#{reports_kit_path}reports_kit/resources/measures/#{measure.key}/filters/#{filter_key}/autocomplete"
|
28
|
+
scope = options.delete(:scope)
|
29
|
+
params = {}
|
30
|
+
params[:scope] = scope if scope.present?
|
31
|
+
|
32
|
+
defaults = {
|
33
|
+
class: 'form-control input-sm select2',
|
34
|
+
multiple: 'multiple',
|
35
|
+
data: {
|
36
|
+
placeholder: options[:placeholder],
|
37
|
+
path: path,
|
38
|
+
params: params
|
39
|
+
}
|
40
|
+
}
|
41
|
+
options = defaults.deep_merge(options)
|
42
|
+
select_tag(filter_key, nil, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def string_filter(filter_key, options={})
|
46
|
+
filter = validate_filter!(filter_key)
|
47
|
+
defaults = { class: 'form-control input-sm' }
|
48
|
+
options = defaults.deep_merge(options)
|
49
|
+
text_field_tag(filter_key, filter.properties[:criteria][:value], options)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_filter!(filter_key)
|
55
|
+
filter_key = filter_key.to_s
|
56
|
+
filter = filters.find { |f| f.key == filter_key }
|
57
|
+
raise ArgumentError.new("A filter with key '#{filter_key}' is not configured in this report") unless filter
|
58
|
+
filter
|
59
|
+
end
|
60
|
+
|
61
|
+
def filters
|
62
|
+
measure.filters
|
63
|
+
end
|
64
|
+
|
65
|
+
def measure
|
66
|
+
Reports::Measure.new(properties[:measure])
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_properties(properties)
|
70
|
+
properties = properties.deep_symbolize_keys
|
71
|
+
measure = Reports::Measure.new(properties[:measure])
|
72
|
+
properties[:measure] = measure.properties_with_filters
|
73
|
+
properties
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class ChartOptions
|
5
|
+
DEFAULT_COLORS = %w(
|
6
|
+
#1f77b4
|
7
|
+
#aec7e8
|
8
|
+
#ff7f0e
|
9
|
+
#ffbb78
|
10
|
+
#2ca02c
|
11
|
+
#98df8a
|
12
|
+
#d62728
|
13
|
+
#ff9896
|
14
|
+
#9467bd
|
15
|
+
#c5b0d5
|
16
|
+
#8c564b
|
17
|
+
#c49c94
|
18
|
+
#e377c2
|
19
|
+
#f7b6d2
|
20
|
+
#7f7f7f
|
21
|
+
#c7c7c7
|
22
|
+
#bcbd22
|
23
|
+
#dbdb8d
|
24
|
+
#17becf
|
25
|
+
#9edae5
|
26
|
+
).freeze
|
27
|
+
DEFAULT_OPTIONS = {
|
28
|
+
scales: {
|
29
|
+
xAxes: [{
|
30
|
+
gridLines: {
|
31
|
+
display: false
|
32
|
+
},
|
33
|
+
barPercentage: 0.9,
|
34
|
+
categoryPercentage: 0.9
|
35
|
+
}],
|
36
|
+
yAxes: [{
|
37
|
+
ticks: {
|
38
|
+
beginAtZero: true
|
39
|
+
}
|
40
|
+
}]
|
41
|
+
},
|
42
|
+
legend: {
|
43
|
+
labels: {
|
44
|
+
usePointStyle: true
|
45
|
+
}
|
46
|
+
},
|
47
|
+
tooltips: {
|
48
|
+
xPadding: 8,
|
49
|
+
yPadding: 7
|
50
|
+
}
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
attr_accessor :data, :options, :chart_options, :inferred_options, :dataset_options, :type
|
54
|
+
|
55
|
+
def initialize(data, options:, inferred_options: {})
|
56
|
+
self.data = data
|
57
|
+
self.options = options.try(:except, :options) || {}
|
58
|
+
self.chart_options = options.try(:[], :options) || {}
|
59
|
+
self.dataset_options = options.try(:[], :datasets)
|
60
|
+
self.type = options.try(:[], :type) || 'bar'
|
61
|
+
|
62
|
+
self.options = inferred_options.deep_merge(self.options) if inferred_options.present?
|
63
|
+
end
|
64
|
+
|
65
|
+
def perform
|
66
|
+
set_colors
|
67
|
+
set_chart_options
|
68
|
+
set_dataset_options
|
69
|
+
set_type
|
70
|
+
data
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def set_colors
|
76
|
+
self.data[:chart_data][:datasets] = data[:chart_data][:datasets].map.with_index do |dataset, index|
|
77
|
+
color = DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
78
|
+
dataset[:backgroundColor] = color
|
79
|
+
dataset[:borderColor] = color
|
80
|
+
dataset
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_options
|
85
|
+
@default_options ||= begin
|
86
|
+
default_options = DEFAULT_OPTIONS.deep_dup
|
87
|
+
|
88
|
+
x_axis_label = options[:x_axis_label]
|
89
|
+
if x_axis_label
|
90
|
+
default_options[:scales] ||= {}
|
91
|
+
default_options[:scales][:xAxes] ||= []
|
92
|
+
default_options[:scales][:xAxes][0] ||= {}
|
93
|
+
default_options[:scales][:xAxes][0][:scaleLabel] ||= {}
|
94
|
+
default_options[:scales][:xAxes][0][:scaleLabel][:display] ||= true
|
95
|
+
default_options[:scales][:xAxes][0][:scaleLabel][:labelString] ||= x_axis_label
|
96
|
+
end
|
97
|
+
|
98
|
+
y_axis_label = options[:y_axis_label]
|
99
|
+
if y_axis_label
|
100
|
+
default_options[:scales] ||= {}
|
101
|
+
default_options[:scales][:yAxes] ||= []
|
102
|
+
default_options[:scales][:yAxes][0] ||= {}
|
103
|
+
default_options[:scales][:yAxes][0][:scaleLabel] ||= {}
|
104
|
+
default_options[:scales][:yAxes][0][:scaleLabel][:display] ||= true
|
105
|
+
default_options[:scales][:yAxes][0][:scaleLabel][:labelString] ||= y_axis_label
|
106
|
+
end
|
107
|
+
|
108
|
+
default_options
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def set_chart_options
|
113
|
+
merged_options = default_options
|
114
|
+
merged_options = merged_options.deep_merge(chart_options) if chart_options
|
115
|
+
self.data[:chart_data][:options] = merged_options
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_dataset_options
|
119
|
+
return if self.data[:chart_data][:datasets].blank? || dataset_options.blank?
|
120
|
+
self.data[:chart_data][:datasets] = self.data[:chart_data][:datasets].map do |dataset|
|
121
|
+
dataset.merge(dataset_options)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_type
|
126
|
+
return if type.blank?
|
127
|
+
self.data[:type] = type
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|