reports_kit 0.2.0 → 0.3.0
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.
- 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
|