reports_kit 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.travis.yml +20 -0
- data/README.md +17 -19
- data/app/assets/javascripts/reports_kit/lib/_init.js +2 -1
- data/app/assets/javascripts/reports_kit/lib/chart.js +25 -16
- data/app/assets/javascripts/reports_kit/lib/report.js +42 -25
- data/app/assets/javascripts/reports_kit/lib/table.js +37 -2
- data/app/assets/stylesheets/reports_kit/reports.css.sass +3 -0
- data/docs/dimensions.md +26 -34
- data/docs/display_options.md +12 -15
- data/docs/filters.md +54 -63
- data/docs/measures.md +3 -4
- data/lib/reports_kit.rb +12 -10
- data/lib/reports_kit/base_controller.rb +1 -2
- data/lib/reports_kit/configuration.rb +19 -4
- data/lib/reports_kit/entity.rb +3 -0
- data/lib/reports_kit/helper.rb +17 -21
- data/lib/reports_kit/model_configuration.rb +1 -1
- data/lib/reports_kit/report_builder.rb +11 -11
- data/lib/reports_kit/reports/{abstract_measure.rb → abstract_series.rb} +1 -1
- data/lib/reports_kit/reports/{composite_measure.rb → composite_series.rb} +12 -8
- data/lib/reports_kit/reports/data/add_table_aggregations.rb +105 -0
- data/lib/reports_kit/reports/data/{composite_aggregation.rb → aggregate_composite.rb} +28 -27
- data/lib/reports_kit/reports/data/{one_dimension.rb → aggregate_one_dimension.rb} +9 -7
- data/lib/reports_kit/reports/data/{two_dimensions.rb → aggregate_two_dimensions.rb} +9 -8
- data/lib/reports_kit/reports/data/chart_options.rb +6 -11
- data/lib/reports_kit/reports/data/format_one_dimension.rb +56 -36
- data/lib/reports_kit/reports/data/format_table.rb +65 -0
- data/lib/reports_kit/reports/data/format_two_dimensions.rb +10 -8
- data/lib/reports_kit/reports/data/generate.rb +51 -28
- data/lib/reports_kit/reports/data/generate_for_properties.rb +52 -30
- data/lib/reports_kit/reports/data/populate_one_dimension.rb +30 -12
- data/lib/reports_kit/reports/data/populate_two_dimensions.rb +31 -31
- data/lib/reports_kit/reports/data/utils.rb +28 -24
- data/lib/reports_kit/reports/dimension.rb +4 -0
- data/lib/reports_kit/reports/{dimension_with_measure.rb → dimension_with_series.rb} +8 -9
- data/lib/reports_kit/reports/filter.rb +4 -0
- data/lib/reports_kit/reports/filter_types/base.rb +4 -3
- data/lib/reports_kit/reports/filter_types/boolean.rb +4 -4
- data/lib/reports_kit/reports/filter_types/datetime.rb +13 -3
- data/lib/reports_kit/reports/filter_types/number.rb +5 -5
- data/lib/reports_kit/reports/{filter_with_measure.rb → filter_with_series.rb} +7 -7
- data/lib/reports_kit/reports/generate_autocomplete_results.rb +2 -2
- data/lib/reports_kit/reports/inferrable_configuration.rb +6 -6
- data/lib/reports_kit/reports/{measure.rb → series.rb} +28 -15
- data/lib/reports_kit/reports_controller.rb +25 -5
- data/lib/reports_kit/value.rb +3 -0
- data/lib/reports_kit/version.rb +1 -1
- data/spec/fixtures/generate_inputs.yml +116 -63
- data/spec/fixtures/generate_outputs.yml +64 -0
- data/spec/reports_kit/report_builder_spec.rb +10 -12
- data/spec/reports_kit/reports/data/generate_spec.rb +559 -140
- data/spec/reports_kit/reports/{dimension_with_measure_spec.rb → dimension_with_series_spec.rb} +5 -7
- data/spec/reports_kit/reports/{filter_with_measure_spec.rb → filter_with_series_spec.rb} +3 -3
- data/spec/spec_helper.rb +5 -5
- data/spec/support/config.rb +31 -1
- data/spec/support/helpers.rb +6 -2
- metadata +17 -14
- data/lib/reports_kit/reports/data/entity.rb +0 -7
- data/lib/reports_kit/reports/data/value.rb +0 -7
@@ -1,6 +1,6 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
|
-
class
|
3
|
+
class FilterWithSeries
|
4
4
|
CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES = {
|
5
5
|
association: FilterTypes::Records,
|
6
6
|
boolean: FilterTypes::Boolean,
|
@@ -15,19 +15,19 @@ module ReportsKit
|
|
15
15
|
string: FilterTypes::String
|
16
16
|
}
|
17
17
|
|
18
|
-
attr_accessor :filter, :
|
18
|
+
attr_accessor :filter, :series, :configuration
|
19
19
|
|
20
|
-
delegate :key, :properties, :label, to: :filter
|
20
|
+
delegate :key, :expression, :properties, :label, to: :filter
|
21
21
|
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
22
22
|
:settings_from_model, :configuration_strategy, :instance_class, :column_type, :column,
|
23
23
|
to: :configuration
|
24
24
|
|
25
|
-
def initialize(filter:,
|
25
|
+
def initialize(filter:, series:)
|
26
26
|
self.filter = filter
|
27
|
-
self.
|
27
|
+
self.series = series
|
28
28
|
self.configuration = InferrableConfiguration.new(self, :filters)
|
29
29
|
|
30
|
-
|
30
|
+
properties[:criteria] = filter_type.default_criteria unless properties[:criteria]
|
31
31
|
end
|
32
32
|
|
33
33
|
def normalized_properties
|
@@ -64,7 +64,7 @@ module ReportsKit
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def filter_type
|
67
|
-
type_klass.new(settings, properties)
|
67
|
+
type_klass.new(settings, properties, primary_dimension: series.dimensions.first)
|
68
68
|
end
|
69
69
|
|
70
70
|
def filter_type_class_from_model
|
@@ -41,8 +41,8 @@ module ReportsKit
|
|
41
41
|
|
42
42
|
def model
|
43
43
|
@model ||= begin
|
44
|
-
|
45
|
-
filter =
|
44
|
+
series = Series.new(measure_key, context_record: context_record)
|
45
|
+
filter = FilterWithSeries.new(filter: Filter.new(filter_key), series: series)
|
46
46
|
filter.instance_class
|
47
47
|
end
|
48
48
|
end
|
@@ -11,7 +11,7 @@ module ReportsKit
|
|
11
11
|
|
12
12
|
attr_accessor :inferrable, :inferrable_type
|
13
13
|
|
14
|
-
delegate :key, :
|
14
|
+
delegate :key, :expression, :series, to: :inferrable
|
15
15
|
|
16
16
|
def initialize(inferrable, inferrable_type)
|
17
17
|
self.inferrable = inferrable
|
@@ -57,7 +57,7 @@ module ReportsKit
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def reflection
|
60
|
-
model_class.reflect_on_association(
|
60
|
+
model_class.reflect_on_association(expression.to_sym)
|
61
61
|
end
|
62
62
|
|
63
63
|
def instance_class
|
@@ -71,7 +71,7 @@ module ReportsKit
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def inferred_settings
|
74
|
-
return { column: "#{model_class.table_name}.#{
|
74
|
+
return { column: "#{model_class.table_name}.#{expression}" } if configured_by_column?
|
75
75
|
if configured_by_association?
|
76
76
|
return inferred_settings_from_belongs_to_or_has_one if inferred_settings_from_belongs_to_or_has_one
|
77
77
|
return inferred_settings_from_has_many if inferred_settings_from_has_many
|
@@ -115,7 +115,7 @@ module ReportsKit
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def column_type
|
118
|
-
column_type = model_class.columns_hash[
|
118
|
+
column_type = model_class.columns_hash[expression.to_s].try(:type)
|
119
119
|
return column_type if SUPPORTED_COLUMN_TYPES.include?(column_type)
|
120
120
|
end
|
121
121
|
|
@@ -125,8 +125,8 @@ module ReportsKit
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def model_class
|
128
|
-
return unless
|
129
|
-
|
128
|
+
return unless series
|
129
|
+
series.model_class
|
130
130
|
end
|
131
131
|
end
|
132
132
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
|
-
class
|
3
|
+
class Series < AbstractSeries
|
4
|
+
VALID_KEYS = [:measure, :dimensions, :filters, :limit, :report_options]
|
5
|
+
|
4
6
|
attr_accessor :properties, :dimensions, :filters, :context_record
|
5
7
|
|
6
8
|
def initialize(properties, context_record: nil)
|
7
|
-
properties = properties.
|
8
|
-
properties =
|
9
|
+
properties = { measure: properties } if properties.is_a?(String)
|
10
|
+
properties = properties.deep_symbolize_keys.dup
|
11
|
+
measure_properties = properties[:measure]
|
12
|
+
properties[:measure] = measure_properties
|
13
|
+
properties[:measure] = { key: properties[:measure] } if properties[:measure].is_a?(String)
|
9
14
|
raise ArgumentError.new("Measure properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
|
10
|
-
properties = properties.deep_symbolize_keys
|
11
15
|
|
12
16
|
dimension_hashes = properties[:dimensions] || []
|
13
17
|
dimension_hashes = dimension_hashes.values if dimension_hashes.is_a?(Hash) && dimension_hashes.key?(:'0')
|
@@ -15,17 +19,25 @@ module ReportsKit
|
|
15
19
|
filter_hashes = filter_hashes.values if filter_hashes.is_a?(Hash) && filter_hashes.key?(:'0')
|
16
20
|
|
17
21
|
self.properties = properties
|
18
|
-
self.dimensions = dimension_hashes.map { |dimension_hash|
|
19
|
-
self.filters = filter_hashes.map { |filter_hash|
|
22
|
+
self.dimensions = dimension_hashes.map { |dimension_hash| DimensionWithSeries.new(dimension: Dimension.new(dimension_hash), series: self) }
|
23
|
+
self.filters = filter_hashes.map { |filter_hash| FilterWithSeries.new(filter: Filter.new(filter_hash), series: self) }
|
20
24
|
self.context_record = context_record
|
21
25
|
end
|
22
26
|
|
23
27
|
def key
|
24
|
-
properties[:key].underscore
|
28
|
+
properties[:measure][:key].underscore
|
25
29
|
end
|
26
30
|
|
27
31
|
def label
|
28
|
-
properties[:name].presence || key.pluralize.titleize
|
32
|
+
properties[:measure][:name].presence || key.pluralize.titleize
|
33
|
+
end
|
34
|
+
|
35
|
+
def limit
|
36
|
+
properties[:limit]
|
37
|
+
end
|
38
|
+
|
39
|
+
def edit_relation_method
|
40
|
+
ReportsKit.configuration.custom_method(properties[:report_options].try(:[], :edit_relation_method))
|
29
41
|
end
|
30
42
|
|
31
43
|
def relation_name
|
@@ -47,7 +59,7 @@ module ReportsKit
|
|
47
59
|
end
|
48
60
|
|
49
61
|
def aggregation_key
|
50
|
-
properties[:aggregation]
|
62
|
+
properties[:measure][:aggregation]
|
51
63
|
end
|
52
64
|
|
53
65
|
def aggregation_config
|
@@ -82,14 +94,15 @@ module ReportsKit
|
|
82
94
|
end
|
83
95
|
|
84
96
|
def self.new_from_properties!(properties, context_record:)
|
85
|
-
|
86
|
-
|
97
|
+
series_hashes = properties[:series].presence || properties.slice(*Series::VALID_KEYS)
|
98
|
+
series_hashes = [series_hashes] if series_hashes.is_a?(Hash)
|
99
|
+
raise ArgumentError.new('At least one series must be configured') if series_hashes.blank?
|
87
100
|
|
88
|
-
|
89
|
-
if
|
90
|
-
|
101
|
+
series_hashes.map do |series_hash|
|
102
|
+
if series_hash[:composite_operator].present?
|
103
|
+
CompositeSeries.new(series_hash)
|
91
104
|
else
|
92
|
-
new(
|
105
|
+
new(series_hash, context_record: context_record)
|
93
106
|
end
|
94
107
|
end
|
95
108
|
end
|
@@ -3,35 +3,55 @@ require 'spreadsheet'
|
|
3
3
|
|
4
4
|
module ReportsKit
|
5
5
|
class ReportsController < ReportsKit::BaseController
|
6
|
+
VALID_PARAMS_PROPERTIES_KEYS = [:ui_filters]
|
7
|
+
|
6
8
|
def index
|
7
9
|
respond_to do |format|
|
8
10
|
format.json do
|
9
11
|
render json: { data: report_data }
|
10
12
|
end
|
11
13
|
format.csv do
|
12
|
-
properties[:format] = '
|
14
|
+
properties[:format] = 'csv'
|
13
15
|
csv = CSV.generate do |csv|
|
14
16
|
report_data[:table_data].each do |row|
|
15
17
|
csv << row
|
16
18
|
end
|
17
19
|
end
|
18
|
-
send_data csv, filename: "
|
20
|
+
send_data csv, filename: "#{report_filename}.csv"
|
19
21
|
end
|
20
22
|
format.xls do
|
21
|
-
properties[:format] = '
|
22
|
-
send_data xls_string, filename:
|
23
|
+
properties[:format] = 'csv'
|
24
|
+
send_data xls_string, filename: "#{report_filename}.xls", type: 'application/vnd.ms-excel'
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
29
|
private
|
28
30
|
|
31
|
+
def report_filename
|
32
|
+
report_filename_method = ReportsKit.configuration.report_filename_method
|
33
|
+
return 'Report' unless report_filename_method
|
34
|
+
instance_eval(&report_filename_method)
|
35
|
+
end
|
36
|
+
|
29
37
|
def report_data
|
30
38
|
Reports::Data::Generate.new(properties, context_record: context_record).perform
|
31
39
|
end
|
32
40
|
|
41
|
+
def report_key
|
42
|
+
params[:report_params][:key]
|
43
|
+
end
|
44
|
+
|
33
45
|
def properties
|
34
|
-
@properties ||=
|
46
|
+
@properties ||= begin
|
47
|
+
properties_method = ReportsKit.configuration.properties_method
|
48
|
+
properties = instance_eval(&properties_method)
|
49
|
+
properties.merge(params_properties).deep_symbolize_keys
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def params_properties
|
54
|
+
@params_properties ||= ActiveSupport::JSON.decode(params[:properties]).with_indifferent_access.slice(*VALID_PARAMS_PROPERTIES_KEYS)
|
35
55
|
end
|
36
56
|
|
37
57
|
def xls_string
|
data/lib/reports_kit/version.rb
CHANGED
@@ -1,164 +1,217 @@
|
|
1
|
-
-
|
2
|
-
|
1
|
+
- series:
|
2
|
+
measure: issue
|
3
3
|
dimensions:
|
4
4
|
- repo
|
5
|
-
-
|
6
|
-
|
5
|
+
- series:
|
6
|
+
measure: issue
|
7
7
|
dimensions:
|
8
8
|
- repo
|
9
9
|
chart:
|
10
10
|
options:
|
11
11
|
foo: bar
|
12
|
-
-
|
13
|
-
|
12
|
+
- series:
|
13
|
+
measure: issue
|
14
14
|
dimensions:
|
15
15
|
- repo
|
16
16
|
chart:
|
17
17
|
foo: bar
|
18
|
-
-
|
19
|
-
|
18
|
+
- series:
|
19
|
+
measure: issue
|
20
20
|
dimensions:
|
21
21
|
- repo
|
22
22
|
chart:
|
23
23
|
type: pie
|
24
|
-
-
|
25
|
-
|
24
|
+
- series:
|
25
|
+
measure: issue
|
26
26
|
dimensions:
|
27
27
|
- key: repo
|
28
|
-
-
|
29
|
-
|
28
|
+
- series:
|
29
|
+
measure: issue
|
30
30
|
dimensions:
|
31
31
|
- key: opened_at
|
32
|
-
-
|
33
|
-
|
32
|
+
- series:
|
33
|
+
measure: issue
|
34
34
|
dimensions:
|
35
35
|
- key: opened_at
|
36
36
|
- key: repo
|
37
|
-
-
|
38
|
-
|
37
|
+
- series:
|
38
|
+
measure: issue
|
39
39
|
filters:
|
40
40
|
- key: locked
|
41
41
|
- key: title
|
42
42
|
- key: opened_at
|
43
43
|
dimensions:
|
44
44
|
- key: repo
|
45
|
-
-
|
46
|
-
-
|
45
|
+
- series:
|
46
|
+
- measure: issue
|
47
47
|
dimensions:
|
48
48
|
- repo
|
49
|
-
-
|
49
|
+
- measure: label
|
50
50
|
dimensions:
|
51
51
|
- repo
|
52
|
-
-
|
53
|
-
-
|
52
|
+
- series:
|
53
|
+
- measure: issue
|
54
54
|
dimensions:
|
55
55
|
- repo
|
56
|
-
-
|
56
|
+
- measure: label
|
57
57
|
dimensions:
|
58
58
|
- repo
|
59
|
-
-
|
59
|
+
- measure: tag
|
60
60
|
dimensions:
|
61
61
|
- repo
|
62
|
-
-
|
63
|
-
-
|
64
|
-
|
62
|
+
- series:
|
63
|
+
- measure:
|
64
|
+
key: issue
|
65
|
+
name: Repo Issues
|
65
66
|
dimensions:
|
66
67
|
- repo
|
67
|
-
-
|
68
|
+
- measure: label
|
68
69
|
dimensions:
|
69
70
|
- repo
|
70
|
-
-
|
71
|
-
-
|
72
|
-
|
71
|
+
- series:
|
72
|
+
- measure:
|
73
|
+
key: issue
|
74
|
+
name: Repo Issues
|
73
75
|
dimensions:
|
74
76
|
- key: repo
|
75
77
|
label:
|
76
|
-
-
|
78
|
+
- measure: label
|
77
79
|
dimensions:
|
78
80
|
- repo
|
79
81
|
format: table
|
80
|
-
-
|
81
|
-
|
82
|
+
- series:
|
83
|
+
- measure:
|
84
|
+
key: issue
|
85
|
+
name: Repo Issues
|
86
|
+
dimensions:
|
87
|
+
- key: repo
|
88
|
+
label:
|
89
|
+
- measure: label
|
90
|
+
dimensions:
|
91
|
+
- repo
|
92
|
+
format: table
|
93
|
+
report_options:
|
94
|
+
aggregations:
|
95
|
+
- from: columns
|
96
|
+
operator: sum
|
97
|
+
label: Total
|
98
|
+
- series:
|
99
|
+
- measure:
|
100
|
+
key: issue
|
101
|
+
name: Repo Issues
|
102
|
+
dimensions:
|
103
|
+
- key: repo
|
104
|
+
label:
|
105
|
+
- measure: label
|
106
|
+
dimensions:
|
107
|
+
- repo
|
108
|
+
format: table
|
109
|
+
report_options:
|
110
|
+
aggregations:
|
111
|
+
- from: rows
|
112
|
+
operator: sum
|
113
|
+
label: Total
|
114
|
+
- series:
|
115
|
+
- measure:
|
116
|
+
key: issue
|
117
|
+
name: Repo Issues
|
118
|
+
dimensions:
|
119
|
+
- key: repo
|
120
|
+
label:
|
121
|
+
- measure: label
|
122
|
+
dimensions:
|
123
|
+
- repo
|
124
|
+
format: table
|
125
|
+
report_options:
|
126
|
+
aggregations:
|
127
|
+
- from: columns
|
128
|
+
operator: sum
|
129
|
+
label: Total
|
130
|
+
- from: rows
|
131
|
+
operator: sum
|
132
|
+
label: Total
|
133
|
+
- series:
|
134
|
+
measure: label
|
82
135
|
dimensions:
|
83
136
|
- repo
|
84
137
|
order: dimension1.label
|
85
|
-
-
|
86
|
-
|
138
|
+
- series:
|
139
|
+
measure: label
|
87
140
|
dimensions:
|
88
141
|
- repo
|
89
142
|
order: dimension1.label desc
|
90
|
-
-
|
91
|
-
|
143
|
+
- series:
|
144
|
+
measure: label
|
92
145
|
dimensions:
|
93
146
|
- repo
|
94
147
|
order: count
|
95
|
-
-
|
96
|
-
|
148
|
+
- series:
|
149
|
+
measure: label
|
97
150
|
dimensions:
|
98
151
|
- repo
|
99
152
|
order: count desc
|
100
|
-
-
|
101
|
-
|
153
|
+
- series:
|
154
|
+
measure: issue
|
102
155
|
dimensions:
|
103
156
|
- opened_at
|
104
157
|
order: dimension1
|
105
|
-
-
|
106
|
-
|
158
|
+
- series:
|
159
|
+
measure: issue
|
107
160
|
dimensions:
|
108
161
|
- opened_at
|
109
162
|
order: dimension1 desc
|
110
|
-
-
|
111
|
-
|
163
|
+
- series:
|
164
|
+
measure: label
|
112
165
|
dimensions:
|
113
166
|
- repo
|
114
167
|
order: 0
|
115
|
-
-
|
116
|
-
|
168
|
+
- series:
|
169
|
+
measure: label
|
117
170
|
dimensions:
|
118
171
|
- repo
|
119
172
|
order: 0 desc
|
120
|
-
-
|
121
|
-
|
173
|
+
- series:
|
174
|
+
measure: label
|
122
175
|
dimensions:
|
123
176
|
- repo
|
124
177
|
order: 1
|
125
|
-
-
|
126
|
-
|
178
|
+
- series:
|
179
|
+
measure: label
|
127
180
|
dimensions:
|
128
181
|
- repo
|
129
182
|
order: 1 desc
|
130
|
-
-
|
131
|
-
|
183
|
+
- series:
|
184
|
+
measure: issue
|
132
185
|
dimensions:
|
133
186
|
- key: opened_at
|
134
187
|
- key: repo
|
135
188
|
order: dimension1
|
136
|
-
-
|
137
|
-
|
189
|
+
- series:
|
190
|
+
measure: issue
|
138
191
|
dimensions:
|
139
192
|
- key: opened_at
|
140
193
|
- key: repo
|
141
194
|
order: dimension1 desc
|
142
|
-
-
|
143
|
-
|
195
|
+
- series:
|
196
|
+
measure: label
|
144
197
|
dimensions:
|
145
198
|
- key: repo
|
146
199
|
- key: created_at
|
147
200
|
order: dimension1.label
|
148
|
-
-
|
149
|
-
|
201
|
+
- series:
|
202
|
+
measure: label
|
150
203
|
dimensions:
|
151
204
|
- key: repo
|
152
205
|
- key: created_at
|
153
206
|
order: dimension1.label desc
|
154
|
-
-
|
155
|
-
|
207
|
+
- series:
|
208
|
+
measure: issue
|
156
209
|
dimensions:
|
157
210
|
- key: opened_at
|
158
211
|
- key: repo
|
159
212
|
order: count
|
160
|
-
-
|
161
|
-
|
213
|
+
- series:
|
214
|
+
measure: issue
|
162
215
|
dimensions:
|
163
216
|
- key: opened_at
|
164
217
|
- key: repo
|