reports_kit 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/app/assets/javascripts/reports_kit/lib/chart.js +33 -8
- data/app/assets/javascripts/reports_kit/lib/report.js +27 -26
- data/app/assets/javascripts/reports_kit/lib/table.js +58 -0
- data/app/assets/javascripts/reports_kit/vendor/jquery.tablesorter.min.js +4 -0
- data/app/assets/stylesheets/reports_kit/reports.css.sass +20 -0
- data/config/initializers/mime_types.rb +1 -0
- data/docs/dimensions.md +34 -26
- data/docs/display_options.md +15 -12
- data/docs/filters.md +2 -2
- data/docs/measures.md +4 -3
- data/gemfiles/mysql.gemfile.lock +14 -1
- data/gemfiles/postgresql.gemfile.lock +14 -1
- data/lib/reports_kit.rb +15 -0
- data/lib/reports_kit/cache.rb +37 -0
- data/lib/reports_kit/configuration.rb +13 -1
- data/lib/reports_kit/helper.rb +54 -3
- data/lib/reports_kit/model_configuration.rb +6 -1
- data/lib/reports_kit/order.rb +33 -0
- data/lib/reports_kit/relative_time.rb +42 -0
- data/lib/reports_kit/report_builder.rb +34 -15
- data/lib/reports_kit/reports/abstract_measure.rb +9 -0
- data/lib/reports_kit/reports/adapters/mysql.rb +8 -1
- data/lib/reports_kit/reports/adapters/postgresql.rb +8 -1
- data/lib/reports_kit/reports/composite_measure.rb +43 -0
- data/lib/reports_kit/reports/data/chart_options.rb +5 -0
- data/lib/reports_kit/reports/data/composite_aggregation.rb +96 -0
- data/lib/reports_kit/reports/data/entity.rb +7 -0
- data/lib/reports_kit/reports/data/format_one_dimension.rb +120 -0
- data/lib/reports_kit/reports/data/format_two_dimensions.rb +141 -0
- data/lib/reports_kit/reports/data/generate.rb +72 -25
- data/lib/reports_kit/reports/data/generate_for_properties.rb +75 -0
- data/lib/reports_kit/reports/data/one_dimension.rb +15 -49
- data/lib/reports_kit/reports/data/populate_one_dimension.rb +36 -0
- data/lib/reports_kit/reports/data/populate_two_dimensions.rb +104 -0
- data/lib/reports_kit/reports/data/two_dimensions.rb +15 -110
- data/lib/reports_kit/reports/data/utils.rb +77 -12
- data/lib/reports_kit/reports/data/value.rb +7 -0
- data/lib/reports_kit/reports/dimension.rb +4 -110
- data/lib/reports_kit/reports/dimension_with_measure.rb +137 -0
- data/lib/reports_kit/reports/filter.rb +5 -64
- data/lib/reports_kit/reports/filter_types/base.rb +1 -1
- data/lib/reports_kit/reports/filter_types/boolean.rb +9 -7
- data/lib/reports_kit/reports/filter_types/datetime.rb +7 -5
- data/lib/reports_kit/reports/filter_types/number.rb +2 -0
- data/lib/reports_kit/reports/filter_with_measure.rb +84 -0
- data/lib/reports_kit/reports/generate_autocomplete_results.rb +1 -1
- data/lib/reports_kit/reports/inferrable_configuration.rb +32 -13
- data/lib/reports_kit/reports/measure.rb +48 -12
- data/lib/reports_kit/reports_controller.rb +42 -3
- data/lib/reports_kit/version.rb +1 -1
- data/reports_kit.gemspec +2 -0
- data/spec/fixtures/generate_inputs.yml +146 -21
- data/spec/fixtures/generate_outputs.yml +768 -17
- data/spec/reports_kit/relative_time_spec.rb +29 -0
- data/spec/reports_kit/report_builder_spec.rb +28 -0
- data/spec/reports_kit/reports/data/generate_spec.rb +614 -27
- data/spec/reports_kit/reports/dimension_with_measure_spec.rb +69 -0
- data/spec/reports_kit/reports/{filter_spec.rb → filter_with_measure_spec.rb} +4 -3
- data/spec/spec_helper.rb +7 -2
- data/spec/support/config.rb +11 -0
- data/spec/support/helpers.rb +3 -3
- data/spec/support/models/issue.rb +7 -0
- metadata +53 -4
- data/spec/reports_kit/reports/dimension_spec.rb +0 -54
@@ -3,7 +3,7 @@ module ReportsKit
|
|
3
3
|
module FilterTypes
|
4
4
|
class Boolean < Base
|
5
5
|
DEFAULT_CRITERIA = {
|
6
|
-
|
6
|
+
value: nil
|
7
7
|
}
|
8
8
|
|
9
9
|
def apply_conditions(records)
|
@@ -11,29 +11,31 @@ module ReportsKit
|
|
11
11
|
when ::String
|
12
12
|
records.where("(#{conditions}) #{sql_operator} true")
|
13
13
|
when ::Hash
|
14
|
-
|
14
|
+
boolean_value ? records.where(conditions) : records.not.where(conditions)
|
15
|
+
when ::Proc
|
16
|
+
conditions.call(records)
|
15
17
|
else
|
16
18
|
raise ArgumentError.new("Unsupported conditions type: '#{conditions}'")
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
21
|
-
case criteria[:
|
22
|
+
def boolean_value
|
23
|
+
case criteria[:value]
|
22
24
|
when true, 'true'
|
23
25
|
true
|
24
26
|
when false, 'false'
|
25
27
|
false
|
26
28
|
else
|
27
|
-
raise ArgumentError.new("Unsupported
|
29
|
+
raise ArgumentError.new("Unsupported value: '#{criteria[:value]}'")
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
33
|
def sql_operator
|
32
|
-
|
34
|
+
boolean_value ? '=' : '!='
|
33
35
|
end
|
34
36
|
|
35
37
|
def valid?
|
36
|
-
criteria[:
|
38
|
+
criteria[:value].present?
|
37
39
|
end
|
38
40
|
|
39
41
|
def conditions
|
@@ -5,6 +5,7 @@ module ReportsKit
|
|
5
5
|
DEFAULT_CRITERIA = {
|
6
6
|
operator: 'between'
|
7
7
|
}
|
8
|
+
SEPARATOR = ' - '
|
8
9
|
|
9
10
|
def apply_conditions(records)
|
10
11
|
case criteria[:operator]
|
@@ -17,19 +18,20 @@ module ReportsKit
|
|
17
18
|
|
18
19
|
def start_at_end_at
|
19
20
|
@start_at_end_at ||= begin
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
return unless valid?
|
22
|
+
start_string, end_string = value.split(SEPARATOR)
|
23
|
+
start_at = ReportsKit::Reports::Data::Utils.parse_date_string(start_string)
|
24
|
+
end_at = ReportsKit::Reports::Data::Utils.parse_date_string(end_string).end_of_day
|
23
25
|
[start_at, end_at]
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
29
|
def start_at
|
28
|
-
start_at_end_at[0
|
30
|
+
start_at_end_at.try(:[], 0)
|
29
31
|
end
|
30
32
|
|
31
33
|
def end_at
|
32
|
-
start_at_end_at[1
|
34
|
+
start_at_end_at.try(:[], 1)
|
33
35
|
end
|
34
36
|
|
35
37
|
def valid?
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
class FilterWithMeasure
|
4
|
+
CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES = {
|
5
|
+
association: FilterTypes::Records,
|
6
|
+
boolean: FilterTypes::Boolean,
|
7
|
+
datetime: FilterTypes::Datetime,
|
8
|
+
integer: FilterTypes::Number,
|
9
|
+
string: FilterTypes::String
|
10
|
+
}
|
11
|
+
COLUMN_TYPES_FILTER_TYPE_CLASSES = {
|
12
|
+
boolean: FilterTypes::Boolean,
|
13
|
+
datetime: FilterTypes::Datetime,
|
14
|
+
integer: FilterTypes::Number,
|
15
|
+
string: FilterTypes::String
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_accessor :filter, :measure, :configuration
|
19
|
+
|
20
|
+
delegate :key, :properties, :label, to: :filter
|
21
|
+
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
22
|
+
:settings_from_model, :configuration_strategy, :instance_class, :column_type, :column,
|
23
|
+
to: :configuration
|
24
|
+
|
25
|
+
def initialize(filter:, measure:)
|
26
|
+
self.filter = filter
|
27
|
+
self.measure = measure
|
28
|
+
self.configuration = InferrableConfiguration.new(self, :filters)
|
29
|
+
|
30
|
+
self.properties[:criteria] = filter_type.default_criteria unless self.properties[:criteria]
|
31
|
+
end
|
32
|
+
|
33
|
+
def normalized_properties
|
34
|
+
return properties unless configured_by_time?
|
35
|
+
criteria = properties[:criteria]
|
36
|
+
return properties if criteria.blank? || criteria[:value].blank?
|
37
|
+
values = criteria[:value].split(ReportsKit::Reports::FilterTypes::Datetime::SEPARATOR)
|
38
|
+
values = values.map { |value| ReportsKit::Reports::Data::Utils.format_time_value(value) }
|
39
|
+
properties[:criteria][:value] = values.join(ReportsKit::Reports::FilterTypes::Datetime::SEPARATOR)
|
40
|
+
properties
|
41
|
+
end
|
42
|
+
|
43
|
+
def settings
|
44
|
+
inferred_settings.merge(settings_from_model)
|
45
|
+
end
|
46
|
+
|
47
|
+
def inferred_settings
|
48
|
+
configuration.inferred_settings.merge(inferred_filter_settings)
|
49
|
+
end
|
50
|
+
|
51
|
+
def inferred_filter_settings
|
52
|
+
{
|
53
|
+
column: column
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def type_klass
|
58
|
+
type_klass_for_configuration_strategy = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[configuration_strategy]
|
59
|
+
return type_klass_for_configuration_strategy if type_klass_for_configuration_strategy
|
60
|
+
type_klass_for_column_type = COLUMN_TYPES_FILTER_TYPE_CLASSES[column_type]
|
61
|
+
return type_klass_for_column_type if type_klass_for_column_type
|
62
|
+
return filter_type_class_from_model if configured_by_model?
|
63
|
+
raise ArgumentError.new("No configuration found for filter with key: '#{key}'")
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_type
|
67
|
+
type_klass.new(settings, properties)
|
68
|
+
end
|
69
|
+
|
70
|
+
def filter_type_class_from_model
|
71
|
+
return unless settings
|
72
|
+
type_key = settings[:type_key]
|
73
|
+
raise ArgumentError.new("No type specified for filter with key: '#{key}'") unless type_key
|
74
|
+
type_class = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[type_key]
|
75
|
+
raise ArgumentError.new("Invalid type ('#{type_key}') specified for filter with key: '#{key}'") unless type_class
|
76
|
+
type_class
|
77
|
+
end
|
78
|
+
|
79
|
+
def apply(relation)
|
80
|
+
filter_type.apply_filter(relation)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -42,7 +42,7 @@ module ReportsKit
|
|
42
42
|
def model
|
43
43
|
@model ||= begin
|
44
44
|
measure = Measure.new(measure_key, context_record: context_record)
|
45
|
-
filter = Filter.new(filter_key, measure: measure)
|
45
|
+
filter = FilterWithMeasure.new(filter: Filter.new(filter_key), measure: measure)
|
46
46
|
filter.instance_class
|
47
47
|
end
|
48
48
|
end
|
@@ -73,25 +73,44 @@ module ReportsKit
|
|
73
73
|
def inferred_settings
|
74
74
|
return { column: "#{model_class.table_name}.#{key}" } if configured_by_column?
|
75
75
|
if configured_by_association?
|
76
|
-
return
|
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
|
78
78
|
end
|
79
79
|
{}
|
80
80
|
end
|
81
81
|
|
82
|
+
def inferred_settings_from_belongs_to_or_has_one
|
83
|
+
@inferred_settings_from_belongs_to_or_has_one ||= begin
|
84
|
+
return unless reflection.macro.in?([:belongs_to, :has_one])
|
85
|
+
through_reflection = reflection.through_reflection
|
86
|
+
if through_reflection
|
87
|
+
{
|
88
|
+
joins: through_reflection.name,
|
89
|
+
column: "#{through_reflection.table_name}.#{reflection.source_reflection.foreign_key}"
|
90
|
+
}
|
91
|
+
else
|
92
|
+
{
|
93
|
+
column: "#{model_class.table_name}.#{reflection.foreign_key}"
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
82
99
|
def inferred_settings_from_has_many
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
100
|
+
@inferred_settings_from_has_many ||= begin
|
101
|
+
return unless reflection.macro == :has_many
|
102
|
+
through_reflection = reflection.through_reflection
|
103
|
+
if through_reflection
|
104
|
+
{
|
105
|
+
joins: through_reflection.name,
|
106
|
+
column: "#{through_reflection.table_name}.#{reflection.source_reflection.foreign_key}"
|
107
|
+
}
|
108
|
+
else
|
109
|
+
{
|
110
|
+
joins: reflection.name,
|
111
|
+
column: "#{reflection.klass.table_name}.#{reflection.klass.primary_key}"
|
112
|
+
}
|
113
|
+
end
|
95
114
|
end
|
96
115
|
end
|
97
116
|
|
@@ -1,17 +1,22 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
|
-
class Measure
|
4
|
-
attr_accessor :properties, :filters, :context_record
|
3
|
+
class Measure < AbstractMeasure
|
4
|
+
attr_accessor :properties, :dimensions, :filters, :context_record
|
5
5
|
|
6
6
|
def initialize(properties, context_record: nil)
|
7
|
+
properties = properties.dup
|
7
8
|
properties = { key: properties } if properties.is_a?(String)
|
8
9
|
raise ArgumentError.new("Measure properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
|
9
10
|
properties = properties.deep_symbolize_keys
|
10
|
-
|
11
|
+
|
12
|
+
dimension_hashes = properties[:dimensions] || []
|
13
|
+
dimension_hashes = dimension_hashes.values if dimension_hashes.is_a?(Hash) && dimension_hashes.key?(:'0')
|
14
|
+
filter_hashes = properties[:filters] || []
|
11
15
|
filter_hashes = filter_hashes.values if filter_hashes.is_a?(Hash) && filter_hashes.key?(:'0')
|
12
16
|
|
13
17
|
self.properties = properties
|
14
|
-
self.
|
18
|
+
self.dimensions = dimension_hashes.map { |dimension_hash| DimensionWithMeasure.new(dimension: Dimension.new(dimension_hash), measure: self) }
|
19
|
+
self.filters = filter_hashes.map { |filter_hash| FilterWithMeasure.new(filter: Filter.new(filter_hash), measure: self) }
|
15
20
|
self.context_record = context_record
|
16
21
|
end
|
17
22
|
|
@@ -20,7 +25,7 @@ module ReportsKit
|
|
20
25
|
end
|
21
26
|
|
22
27
|
def label
|
23
|
-
key.pluralize.titleize
|
28
|
+
properties[:name].presence || key.pluralize.titleize
|
24
29
|
end
|
25
30
|
|
26
31
|
def relation_name
|
@@ -28,11 +33,31 @@ module ReportsKit
|
|
28
33
|
end
|
29
34
|
|
30
35
|
def aggregate_function
|
31
|
-
:count
|
36
|
+
aggregation_expression || :count
|
37
|
+
end
|
38
|
+
|
39
|
+
def aggregation_expression
|
40
|
+
return unless aggregation_config
|
41
|
+
expression = aggregation_config[:expression]
|
42
|
+
if expression.is_a?(Array)
|
43
|
+
expression
|
44
|
+
else
|
45
|
+
raise ArgumentError.new("The '#{aggregation_key}' aggregation on the #{model_class} model isn't valid")
|
46
|
+
end
|
32
47
|
end
|
33
48
|
|
34
|
-
def
|
35
|
-
|
49
|
+
def aggregation_key
|
50
|
+
properties[:aggregation]
|
51
|
+
end
|
52
|
+
|
53
|
+
def aggregation_config
|
54
|
+
@aggregation_config ||= begin
|
55
|
+
return unless aggregation_key
|
56
|
+
raise ArgumentError.new("A '#{aggregation_key}' aggregation on the #{model_class} model hasn't been configured") unless model_class.respond_to?(:reports_kit_configuration)
|
57
|
+
config = model_class.reports_kit_configuration.aggregations.find { |aggregation| aggregation[:key] == aggregation_key }
|
58
|
+
raise ArgumentError.new("A '#{aggregation_key}' aggregation on the #{model_class} model hasn't been configured") unless config
|
59
|
+
config
|
60
|
+
end
|
36
61
|
end
|
37
62
|
|
38
63
|
def base_relation
|
@@ -52,10 +77,21 @@ module ReportsKit
|
|
52
77
|
relation
|
53
78
|
end
|
54
79
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
80
|
+
def has_two_dimensions?
|
81
|
+
dimensions.length == 2
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.new_from_properties!(properties, context_record:)
|
85
|
+
measure_hashes = [properties[:measure]].compact + Array(properties[:measures])
|
86
|
+
raise ArgumentError.new('At least one measure must be configured') if measure_hashes.blank?
|
87
|
+
|
88
|
+
measure_hashes.map do |measure_hash|
|
89
|
+
if measure_hash[:composite_operator].present?
|
90
|
+
CompositeMeasure.new(measure_hash)
|
91
|
+
else
|
92
|
+
new(measure_hash, context_record: context_record)
|
93
|
+
end
|
94
|
+
end
|
59
95
|
end
|
60
96
|
end
|
61
97
|
end
|
@@ -1,9 +1,48 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'spreadsheet'
|
3
|
+
|
1
4
|
module ReportsKit
|
2
5
|
class ReportsController < ReportsKit::BaseController
|
3
6
|
def index
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
+
respond_to do |format|
|
8
|
+
format.json do
|
9
|
+
render json: { data: report_data }
|
10
|
+
end
|
11
|
+
format.csv do
|
12
|
+
properties[:format] = 'table'
|
13
|
+
csv = CSV.generate do |csv|
|
14
|
+
report_data[:table_data].each do |row|
|
15
|
+
csv << row
|
16
|
+
end
|
17
|
+
end
|
18
|
+
send_data csv, filename: "Report.csv"
|
19
|
+
end
|
20
|
+
format.xls do
|
21
|
+
properties[:format] = 'table'
|
22
|
+
send_data xls_string, filename: 'Report.xls', type: 'application/vnd.ms-excel'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def report_data
|
30
|
+
Reports::Data::Generate.new(properties, context_record: context_record).perform
|
31
|
+
end
|
32
|
+
|
33
|
+
def properties
|
34
|
+
@properties ||= ActiveSupport::JSON.decode(params[:properties])
|
35
|
+
end
|
36
|
+
|
37
|
+
def xls_string
|
38
|
+
spreadsheet = Spreadsheet::Workbook.new
|
39
|
+
sheet = spreadsheet.create_worksheet
|
40
|
+
report_data[:table_data].each_with_index do |row, index|
|
41
|
+
sheet.update_row(index, *row)
|
42
|
+
end
|
43
|
+
io = StringIO.new
|
44
|
+
spreadsheet.write(io)
|
45
|
+
io.string
|
7
46
|
end
|
8
47
|
end
|
9
48
|
end
|
data/lib/reports_kit/version.rb
CHANGED
data/reports_kit.gemspec
CHANGED
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.license = 'MIT'
|
15
15
|
|
16
16
|
s.add_dependency 'rails', '>= 3'
|
17
|
+
s.add_dependency 'spreadsheet', '>= 1.1'
|
17
18
|
|
18
19
|
s.add_development_dependency 'appraisal'
|
19
20
|
s.add_development_dependency 'rspec', '~> 3'
|
@@ -21,5 +22,6 @@ Gem::Specification.new do |s|
|
|
21
22
|
s.add_development_dependency 'factory_girl', '~> 4'
|
22
23
|
s.add_development_dependency 'pg', '>= 0.15'
|
23
24
|
s.add_development_dependency 'pry', '~> 0'
|
25
|
+
s.add_development_dependency 'pry-byebug', '~> 1'
|
24
26
|
s.add_development_dependency 'timecop', '~> 0'
|
25
27
|
end
|
@@ -1,40 +1,165 @@
|
|
1
|
-
- measure:
|
2
|
-
|
3
|
-
|
4
|
-
-
|
5
|
-
|
6
|
-
|
1
|
+
- measure:
|
2
|
+
key: issue
|
3
|
+
dimensions:
|
4
|
+
- repo
|
5
|
+
- measure:
|
6
|
+
key: issue
|
7
|
+
dimensions:
|
8
|
+
- repo
|
7
9
|
chart:
|
8
10
|
options:
|
9
11
|
foo: bar
|
10
|
-
- measure:
|
11
|
-
|
12
|
-
|
12
|
+
- measure:
|
13
|
+
key: issue
|
14
|
+
dimensions:
|
15
|
+
- repo
|
13
16
|
chart:
|
14
17
|
foo: bar
|
15
|
-
- measure:
|
16
|
-
|
17
|
-
|
18
|
+
- measure:
|
19
|
+
key: issue
|
20
|
+
dimensions:
|
21
|
+
- repo
|
18
22
|
chart:
|
19
23
|
type: pie
|
20
24
|
- measure:
|
21
25
|
key: issue
|
22
|
-
|
23
|
-
|
26
|
+
dimensions:
|
27
|
+
- key: repo
|
24
28
|
- measure:
|
25
29
|
key: issue
|
26
|
-
|
27
|
-
|
30
|
+
dimensions:
|
31
|
+
- key: opened_at
|
28
32
|
- measure:
|
29
33
|
key: issue
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
dimensions:
|
35
|
+
- key: opened_at
|
36
|
+
- key: repo
|
33
37
|
- measure:
|
34
38
|
key: issue
|
35
39
|
filters:
|
36
40
|
- key: locked
|
37
41
|
- key: title
|
38
42
|
- key: opened_at
|
39
|
-
|
40
|
-
|
43
|
+
dimensions:
|
44
|
+
- key: repo
|
45
|
+
- measures:
|
46
|
+
- key: issue
|
47
|
+
dimensions:
|
48
|
+
- repo
|
49
|
+
- key: label
|
50
|
+
dimensions:
|
51
|
+
- repo
|
52
|
+
- measures:
|
53
|
+
- key: issue
|
54
|
+
dimensions:
|
55
|
+
- repo
|
56
|
+
- key: label
|
57
|
+
dimensions:
|
58
|
+
- repo
|
59
|
+
- key: tag
|
60
|
+
dimensions:
|
61
|
+
- repo
|
62
|
+
- measures:
|
63
|
+
- key: issue
|
64
|
+
name: Repo Issues
|
65
|
+
dimensions:
|
66
|
+
- repo
|
67
|
+
- key: label
|
68
|
+
dimensions:
|
69
|
+
- repo
|
70
|
+
- measures:
|
71
|
+
- key: issue
|
72
|
+
name: Repo Issues
|
73
|
+
dimensions:
|
74
|
+
- key: repo
|
75
|
+
label:
|
76
|
+
- key: label
|
77
|
+
dimensions:
|
78
|
+
- repo
|
79
|
+
format: table
|
80
|
+
- measure:
|
81
|
+
key: label
|
82
|
+
dimensions:
|
83
|
+
- repo
|
84
|
+
order: dimension1.label
|
85
|
+
- measure:
|
86
|
+
key: label
|
87
|
+
dimensions:
|
88
|
+
- repo
|
89
|
+
order: dimension1.label desc
|
90
|
+
- measure:
|
91
|
+
key: label
|
92
|
+
dimensions:
|
93
|
+
- repo
|
94
|
+
order: count
|
95
|
+
- measure:
|
96
|
+
key: label
|
97
|
+
dimensions:
|
98
|
+
- repo
|
99
|
+
order: count desc
|
100
|
+
- measure:
|
101
|
+
key: issue
|
102
|
+
dimensions:
|
103
|
+
- opened_at
|
104
|
+
order: dimension1
|
105
|
+
- measure:
|
106
|
+
key: issue
|
107
|
+
dimensions:
|
108
|
+
- opened_at
|
109
|
+
order: dimension1 desc
|
110
|
+
- measure:
|
111
|
+
key: label
|
112
|
+
dimensions:
|
113
|
+
- repo
|
114
|
+
order: 0
|
115
|
+
- measure:
|
116
|
+
key: label
|
117
|
+
dimensions:
|
118
|
+
- repo
|
119
|
+
order: 0 desc
|
120
|
+
- measure:
|
121
|
+
key: label
|
122
|
+
dimensions:
|
123
|
+
- repo
|
124
|
+
order: 1
|
125
|
+
- measure:
|
126
|
+
key: label
|
127
|
+
dimensions:
|
128
|
+
- repo
|
129
|
+
order: 1 desc
|
130
|
+
- measure:
|
131
|
+
key: issue
|
132
|
+
dimensions:
|
133
|
+
- key: opened_at
|
134
|
+
- key: repo
|
135
|
+
order: dimension1
|
136
|
+
- measure:
|
137
|
+
key: issue
|
138
|
+
dimensions:
|
139
|
+
- key: opened_at
|
140
|
+
- key: repo
|
141
|
+
order: dimension1 desc
|
142
|
+
- measure:
|
143
|
+
key: label
|
144
|
+
dimensions:
|
145
|
+
- key: repo
|
146
|
+
- key: created_at
|
147
|
+
order: dimension1.label
|
148
|
+
- measure:
|
149
|
+
key: label
|
150
|
+
dimensions:
|
151
|
+
- key: repo
|
152
|
+
- key: created_at
|
153
|
+
order: dimension1.label desc
|
154
|
+
- measure:
|
155
|
+
key: issue
|
156
|
+
dimensions:
|
157
|
+
- key: opened_at
|
158
|
+
- key: repo
|
159
|
+
order: count
|
160
|
+
- measure:
|
161
|
+
key: issue
|
162
|
+
dimensions:
|
163
|
+
- key: opened_at
|
164
|
+
- key: repo
|
165
|
+
order: count desc
|