reports_kit 0.1.0 → 0.2.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/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
|