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