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
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ ReportsKit::Engine.routes.draw do
2
+ namespace :reports_kit do
3
+ resources :reports, only: [:index]
4
+ resources :resources, only: [] do
5
+ collection do
6
+ get 'measures/:measure_key/filters/:filter_key/autocomplete' => :autocomplete
7
+ end
8
+ end
9
+ end
10
+ end
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.
@@ -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,10 @@
1
+ module ReportsKit
2
+ class Configuration
3
+ attr_accessor :context_record_method, :first_day_of_week
4
+
5
+ def initialize
6
+ self.context_record_method = nil
7
+ self.first_day_of_week = :sunday
8
+ end
9
+ end
10
+ 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,5 @@
1
+ if Rails.version >= '3.1'
2
+ require 'reports_kit/engine'
3
+ else
4
+ ActionView::Base.send :include, Chartkick::Helper
5
+ 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