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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.travis.yml +20 -0
  4. data/README.md +17 -19
  5. data/app/assets/javascripts/reports_kit/lib/_init.js +2 -1
  6. data/app/assets/javascripts/reports_kit/lib/chart.js +25 -16
  7. data/app/assets/javascripts/reports_kit/lib/report.js +42 -25
  8. data/app/assets/javascripts/reports_kit/lib/table.js +37 -2
  9. data/app/assets/stylesheets/reports_kit/reports.css.sass +3 -0
  10. data/docs/dimensions.md +26 -34
  11. data/docs/display_options.md +12 -15
  12. data/docs/filters.md +54 -63
  13. data/docs/measures.md +3 -4
  14. data/lib/reports_kit.rb +12 -10
  15. data/lib/reports_kit/base_controller.rb +1 -2
  16. data/lib/reports_kit/configuration.rb +19 -4
  17. data/lib/reports_kit/entity.rb +3 -0
  18. data/lib/reports_kit/helper.rb +17 -21
  19. data/lib/reports_kit/model_configuration.rb +1 -1
  20. data/lib/reports_kit/report_builder.rb +11 -11
  21. data/lib/reports_kit/reports/{abstract_measure.rb → abstract_series.rb} +1 -1
  22. data/lib/reports_kit/reports/{composite_measure.rb → composite_series.rb} +12 -8
  23. data/lib/reports_kit/reports/data/add_table_aggregations.rb +105 -0
  24. data/lib/reports_kit/reports/data/{composite_aggregation.rb → aggregate_composite.rb} +28 -27
  25. data/lib/reports_kit/reports/data/{one_dimension.rb → aggregate_one_dimension.rb} +9 -7
  26. data/lib/reports_kit/reports/data/{two_dimensions.rb → aggregate_two_dimensions.rb} +9 -8
  27. data/lib/reports_kit/reports/data/chart_options.rb +6 -11
  28. data/lib/reports_kit/reports/data/format_one_dimension.rb +56 -36
  29. data/lib/reports_kit/reports/data/format_table.rb +65 -0
  30. data/lib/reports_kit/reports/data/format_two_dimensions.rb +10 -8
  31. data/lib/reports_kit/reports/data/generate.rb +51 -28
  32. data/lib/reports_kit/reports/data/generate_for_properties.rb +52 -30
  33. data/lib/reports_kit/reports/data/populate_one_dimension.rb +30 -12
  34. data/lib/reports_kit/reports/data/populate_two_dimensions.rb +31 -31
  35. data/lib/reports_kit/reports/data/utils.rb +28 -24
  36. data/lib/reports_kit/reports/dimension.rb +4 -0
  37. data/lib/reports_kit/reports/{dimension_with_measure.rb → dimension_with_series.rb} +8 -9
  38. data/lib/reports_kit/reports/filter.rb +4 -0
  39. data/lib/reports_kit/reports/filter_types/base.rb +4 -3
  40. data/lib/reports_kit/reports/filter_types/boolean.rb +4 -4
  41. data/lib/reports_kit/reports/filter_types/datetime.rb +13 -3
  42. data/lib/reports_kit/reports/filter_types/number.rb +5 -5
  43. data/lib/reports_kit/reports/{filter_with_measure.rb → filter_with_series.rb} +7 -7
  44. data/lib/reports_kit/reports/generate_autocomplete_results.rb +2 -2
  45. data/lib/reports_kit/reports/inferrable_configuration.rb +6 -6
  46. data/lib/reports_kit/reports/{measure.rb → series.rb} +28 -15
  47. data/lib/reports_kit/reports_controller.rb +25 -5
  48. data/lib/reports_kit/value.rb +3 -0
  49. data/lib/reports_kit/version.rb +1 -1
  50. data/spec/fixtures/generate_inputs.yml +116 -63
  51. data/spec/fixtures/generate_outputs.yml +64 -0
  52. data/spec/reports_kit/report_builder_spec.rb +10 -12
  53. data/spec/reports_kit/reports/data/generate_spec.rb +559 -140
  54. data/spec/reports_kit/reports/{dimension_with_measure_spec.rb → dimension_with_series_spec.rb} +5 -7
  55. data/spec/reports_kit/reports/{filter_with_measure_spec.rb → filter_with_series_spec.rb} +3 -3
  56. data/spec/spec_helper.rb +5 -5
  57. data/spec/support/config.rb +31 -1
  58. data/spec/support/helpers.rb +6 -2
  59. metadata +17 -14
  60. data/lib/reports_kit/reports/data/entity.rb +0 -7
  61. data/lib/reports_kit/reports/data/value.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be78c8c9d993eed5a9a37b88549beae7c7aa07af
4
- data.tar.gz: c786cb1e077f885ab17ed25756030f9dc7f05569
3
+ metadata.gz: 50df5df0e40d9cd127c03bacec3905c401be05ab
4
+ data.tar.gz: 071d750fe875bcdaa4308fde9e6bf280b18c5d3e
5
5
  SHA512:
6
- metadata.gz: afccc60cd1df78ddb7c686c89ee73ca43e666a377e3b7c2c5b5c466547177969e8b9c709c51be60a1382769d95d220aa1ebcf25d68bbac8257cd38b429a07e24
7
- data.tar.gz: ebeeeeefab073d568870b1d6b93af59e7ef0bf305ad0f2463ceb31cac144d600b1263d69b143be84ef754a03d70a531409b950a43bf03d81811aff91d01b80f1
6
+ metadata.gz: a6fbfb97c8e5f30a23d1260c73046e5f1e19941b0cdaad796ae490c8ebcbf73eeacc3eb3cfc1cd974f80e58b7c31e56d7366e3311e6af9146da055a64c410f25
7
+ data.tar.gz: 57c6cdba6764b876efbe81d85f98285c5945b85f0571d8fa715806f2210b94b317698b06a957171ef78ea15fc23d2c21e904eb6f2f250e9036270f5217d26fd7
@@ -75,6 +75,8 @@ Style/CaseIndentation:
75
75
  - case
76
76
  - end
77
77
  IndentOneStep: false
78
+ Style/IndentHash:
79
+ EnforcedStyle: consistent
78
80
  Style/RaiseArgs:
79
81
  Enabled: false
80
82
  Style/SignalException:
@@ -0,0 +1,20 @@
1
+ cache: bundler
2
+
3
+ language: ruby
4
+
5
+ services:
6
+ - mysql
7
+ - postgresql
8
+
9
+ rvm:
10
+ - '2.2.7'
11
+ - '2.3.4'
12
+ - '2.4.1'
13
+
14
+ before_install: gem update bundler
15
+ before_script:
16
+ - psql -c 'CREATE DATABASE reports_kit_test;' -U postgres
17
+ - mysql -e 'CREATE DATABASE IF NOT EXISTS reports_kit_test;'
18
+ - bundle exec appraisal install
19
+
20
+ script: bundle exec appraisal rspec
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ReportsKit
2
2
  =====
3
+ [![Build Status](https://travis-ci.org/tombenner/reports_kit.svg?branch=master)](https://travis-ci.org/tombenner/reports_kit)
4
+
3
5
  ReportsKit lets you easily create beautiful charts with customizable, interactive filters.
4
6
 
5
7
  For interactive examples, see [reportskit.co](https://www.reportskit.co/).
@@ -63,35 +65,27 @@ end
63
65
  Quick Start
64
66
  -----------
65
67
 
66
- After installation, you can create your first chart with a single line!
67
-
68
- In any view, create a chart that shows the number of records of a model (e.g. `user`) created over time:
69
-
70
- `app/views/users/index.html.haml`
71
- ```haml
72
- = render_report measure: { key: 'user', dimensions: ['created_at'] }
73
- ```
74
-
75
- You're done! `render_report` will render the following chart:
76
-
77
- [<img src="docs/images/users_by_created_at.png?raw=true" width="500" />](docs/images/users_by_created_at.png?raw=true)
68
+ After installation, you can create your first chart with just a YAML file and a single line in any view.
78
69
 
79
- Instead of passing a hash to `render_report`, you can alternatively configure your charts using YAML and then pass the filename to `render_report`:
70
+ Configure the chart in the YAML file:
80
71
 
81
72
  `config/reports_kit/reports/my_users.yml`
82
73
  ```yaml
83
- measure:
84
- key: user
85
- dimensions:
86
- - created_at
74
+ measure: user
75
+ dimensions:
76
+ - created_at
87
77
  ```
88
78
 
79
+ Then pass that filename to `render_report` in a view:
80
+
89
81
  `app/views/users/index.html.haml`
90
82
  ```haml
91
83
  = render_report 'my_users'
92
84
  ```
93
85
 
94
- The YAML approach is more maintainable and readable, so we'll use it in the rest of the documentation.
86
+ You're done! `render_report` will render the following chart:
87
+
88
+ [<img src="docs/images/users_by_created_at.png?raw=true" width="500" />](docs/images/users_by_created_at.png?raw=true)
95
89
 
96
90
  ### Form Controls
97
91
 
@@ -109,7 +103,11 @@ Many other form controls are available; see [Filters](docs/filters.md) for more.
109
103
 
110
104
  ### How It Works
111
105
 
112
- In the Quick Start chart, `key: 'user'` tells ReportsKit to count the number of `User` records, and `dimensions: ['created_at']` tells it to group by the week of the `created_at` column. Since `created_at` is a `datetime` column, ReportsKit knows that it should sort the results chronologically.
106
+ In the Quick Start chart, `measure: user` tells ReportsKit to count the number of `User` records, and `dimensions: ['created_at']` tells it to group by the week of the `created_at` column. Since `created_at` is a `datetime` column, ReportsKit knows that it should group the counts by week (the granularity is configurable), sort them chronologically, and add in zeros for any missing weeks.
107
+
108
+ ReportsKit infers sane defaults from your ActiveRecord model configurations. If there was a `belongs_to :company` association on `User` and you used `dimensions: ['company']`, then ReportsKit would count users grouped by the `company_id` column and show company names on the x-axis.
109
+
110
+ If you need more customization (e.g. custom filters, custom dimensions, custom aggregation functions, custom orders, aggregations of aggregations, etc), ReportsKit is very flexible and powerful and supports all of these with a simple syntax. It lets you use SQL, too.
113
111
 
114
112
  To learn how to use more of ReportsKit's features, check out the following resources:
115
113
 
@@ -3,6 +3,7 @@ window.ReportsKit = {};
3
3
  $(document).ready(function() {
4
4
  $('.reports_kit_report').each(function(index, el) {
5
5
  var el = $(el)
6
- new ReportsKit.Report({ 'el': el });
6
+ var reportClass = el.data('report-class');
7
+ new ReportsKit[reportClass]().render({ 'el': el });
7
8
  });
8
9
  });
@@ -6,7 +6,8 @@ ReportsKit.Chart = (function(options) {
6
6
  self.report = options.report;
7
7
  self.el = self.report.el;
8
8
 
9
- self.noResultsEl = $('<div>No data was found</div>').appendTo(self.report.visualizationEl).hide();
9
+ self.defaultEmptyStateText = 'No data was found';
10
+ self.emptyStateEl = $('<div>' + self.defaultEmptyStateText + '</div>').appendTo(self.report.visualizationEl).hide();
10
11
  self.loadingIndicatorEl = $('<div class="loading_indicator"></div>').appendTo(self.report.visualizationEl).hide();
11
12
  self.canvas = $('<canvas />').appendTo(self.report.visualizationEl);
12
13
  };
@@ -15,19 +16,32 @@ ReportsKit.Chart = (function(options) {
15
16
  var path = self.el.data('path');
16
17
  var separator = path.indexOf('?') === -1 ? '?' : '&';
17
18
  path += separator + 'properties=' + encodeURIComponent(JSON.stringify(self.report.properties()));
18
- self.loadingIndicatorEl.fadeIn(5000);
19
+ self.loadingIndicatorEl.fadeIn(1000);
20
+ if (self.canvas.is(':visible')) {
21
+ self.canvas.fadeTo(300, 0.1);
22
+ }
19
23
  $.getJSON(path, function(response) {
20
24
  var data = response.data;
21
25
  var chartData = data.chart_data;
26
+ var isEmptyState = chartData.datasets.length === 0 ||
27
+ (chartData.datasets.length === 1 && chartData.datasets[0].data.length === 0);
28
+ var emptyStateText = (data.report_options && data.report_options.empty_state_text) || self.defaultEmptyStateText;
22
29
  var options = chartData.options;
23
- options = self.addAdditionalOptions(options, chartData.standard_options)
30
+ options = self.addAdditionalOptions(options, data.report_options);
31
+
32
+ self.loadingIndicatorEl.stop(true, true).hide();
33
+ self.emptyStateEl.html(emptyStateText).toggle(isEmptyState);
34
+ if (isEmptyState) {
35
+ self.canvas.hide();
36
+ return;
37
+ }
38
+ self.canvas.show().fadeTo(300, 1);
24
39
 
25
40
  var args = {
26
41
  type: data.type,
27
42
  data: chartData,
28
43
  options: options
29
44
  };
30
- self.loadingIndicatorEl.stop(true, true).hide();
31
45
 
32
46
  if (self.chart) {
33
47
  self.chart.data.datasets = chartData.datasets;
@@ -36,24 +50,19 @@ ReportsKit.Chart = (function(options) {
36
50
  } else {
37
51
  self.chart = new Chart(self.canvas, args);
38
52
  }
39
- self.noResultsEl.toggle(self.chart.data.labels.length === 0);
40
53
  });
41
54
  };
42
55
 
43
- self.addAdditionalOptions = function(options, standardOptions) {
56
+ self.addAdditionalOptions = function(options, reportOptions) {
44
57
  var additionalOptions = {};
45
- var maxItems = standardOptions && standardOptions.legend && standardOptions.legend.max_items;
58
+ var maxItems = reportOptions && reportOptions.legend && reportOptions.legend.max_items;
46
59
  if (maxItems) {
47
- additionalOptions = {
48
- legend: {
49
- labels: {
50
- filter: function(item) {
51
- return item.index < maxItems;
52
- }
53
- }
54
- }
60
+ options.legend = options.legend || {};
61
+ options.legend.labels = options.legend.labels || {};
62
+ options.legend.labels.filter = options.legend.labels.filter || function(item) {
63
+ var index = (typeof item.datasetIndex === 'undefined') ? item.index : item.datasetIndex;
64
+ return index < maxItems;
55
65
  };
56
- options = $.extend(true, options, additionalOptions);
57
66
  }
58
67
  return options;
59
68
  };
@@ -1,8 +1,7 @@
1
- ReportsKit.Report = (function(options) {
1
+ ReportsKit.Report = (function() {
2
2
  var self = this;
3
3
 
4
- self.initialize = function(options) {
5
- self.options = options;
4
+ self.render = function(options) {
6
5
  self.el = options.el;
7
6
  self.visualizationEl = self.el.find('.reports_kit_visualization');
8
7
 
@@ -11,33 +10,25 @@ ReportsKit.Report = (function(options) {
11
10
 
12
11
  self.initializeElements();
13
12
  self.initializeEvents();
13
+ self.initializeVisualization();
14
+ self.renderVisualization();
15
+ };
14
16
 
17
+ self.initializeVisualization = function() {
15
18
  if (self.defaultProperties.format == 'table') {
16
19
  self.visualization = new ReportsKit.Table({ report: self });
17
20
  } else {
18
21
  self.visualization = new ReportsKit.Chart({ report: self });
19
22
  }
20
- self.render();
21
23
  };
22
24
 
23
25
  self.initializeElements = function() {
24
26
  self.exportButtons = self.el.find('[data-role=reports_kit_export_button]');
25
- self.form.find('.date_range_picker').daterangepicker({
26
- locale: {
27
- format: 'MMM D, YYYY'
28
- },
29
- ranges: {
30
- 'Today': [moment(), moment()],
31
- 'Last 7 Days': [moment().subtract(7, 'days'), moment()],
32
- 'Last 30 Days': [moment().subtract(30, 'days'), moment()],
33
- 'Last 2 Months': [moment().subtract(2, 'months'), moment()],
34
- 'Last 3 Months': [moment().subtract(3, 'months'), moment()],
35
- 'Last 4 Months': [moment().subtract(4, 'months'), moment()],
36
- 'Last 6 Months': [moment().subtract(6, 'months'), moment()],
37
- 'Last 12 Months': [moment().subtract(12, 'months'), moment()],
38
- 'Year To Date': [moment().startOf('year'), moment()]
39
- }
40
- });
27
+ self.initializeAutocompletes();
28
+ self.initializeDateRangePickers();
29
+ };
30
+
31
+ self.initializeAutocompletes = function() {
41
32
  self.form.find('.select2').each(function(index, el) {
42
33
  el = $(el);
43
34
  var path = el.data('path');
@@ -67,12 +58,40 @@ ReportsKit.Report = (function(options) {
67
58
  });
68
59
  };
69
60
 
61
+ self.initializeDateRangePickers = function() {
62
+ self.form.find('.date_range_picker').daterangepicker({
63
+ opens: 'left',
64
+ drops: 'down',
65
+ showDropdowns: false,
66
+ showWeekNumbers: false,
67
+ timePicker: false,
68
+ buttonClasses: ['btn', 'btn-sm'],
69
+ applyClass: 'btn-primary btn-daterange-submit',
70
+ cancelClass: 'btn-default',
71
+ maxDate: moment(),
72
+ locale: {
73
+ format: 'MMM D, YYYY'
74
+ },
75
+ ranges: {
76
+ 'Today': [moment(), moment()],
77
+ 'Last 7 Days': [moment().subtract(7, 'days'), moment()],
78
+ 'Last 30 Days': [moment().subtract(30, 'days'), moment()],
79
+ 'Last 2 Months': [moment().subtract(2, 'months'), moment()],
80
+ 'Last 3 Months': [moment().subtract(3, 'months'), moment()],
81
+ 'Last 4 Months': [moment().subtract(4, 'months'), moment()],
82
+ 'Last 6 Months': [moment().subtract(6, 'months'), moment()],
83
+ 'Last 12 Months': [moment().subtract(12, 'months'), moment()],
84
+ 'Year To Date': [moment().startOf('year'), moment()]
85
+ }
86
+ });
87
+ };
88
+
70
89
  self.initializeEvents = function() {
71
90
  self.form.find('select,input').on('change', function() {
72
- self.render();
91
+ self.renderVisualization();
73
92
  })
74
93
  self.form.on('submit', function() {
75
- self.render();
94
+ self.renderVisualization();
76
95
  return false;
77
96
  })
78
97
  self.exportButtons.on('click', self.onClickExportButton);
@@ -110,11 +129,9 @@ ReportsKit.Report = (function(options) {
110
129
  return false;
111
130
  };
112
131
 
113
- self.render = function() {
132
+ self.renderVisualization = function() {
114
133
  self.visualization.render();
115
134
  };
116
135
 
117
- self.initialize(options);
118
-
119
136
  return self;
120
137
  });
@@ -6,6 +6,8 @@ ReportsKit.Table = (function(options) {
6
6
  self.report = options.report;
7
7
  self.el = self.report.el;
8
8
 
9
+ self.defaultEmptyStateText = 'No data was found';
10
+ self.emptyStateEl = $('<div>' + self.defaultEmptyStateText + '</div>').appendTo(self.report.visualizationEl).hide();
9
11
  self.loadingIndicatorEl = $('<div class="loading_indicator"></div>').appendTo(self.report.visualizationEl).hide();
10
12
  self.table = $('<table />', { 'class': 'table table-striped table-hover' }).appendTo(self.report.visualizationEl);
11
13
  };
@@ -14,19 +16,37 @@ ReportsKit.Table = (function(options) {
14
16
  var path = self.el.data('path');
15
17
  var separator = path.indexOf('?') === -1 ? '?' : '&';
16
18
  path += separator + 'properties=' + encodeURIComponent(JSON.stringify(self.report.properties()));
17
- self.loadingIndicatorEl.fadeIn(100);
19
+ self.loadingIndicatorEl.fadeIn(1000);
20
+ if (self.table.is(':visible')) {
21
+ self.table.fadeTo(300, 0.1);
22
+ }
18
23
  $.getJSON(path, function(response) {
19
24
  var data = response.data;
20
25
  var tableData = data.table_data;
26
+ var reportOptions = data.report_options || {};
27
+ // If the data only includes column headers, then it we have an empty state.
28
+ var isEmptyState = tableData.length <= 1;
29
+ var emptyStateText = reportOptions.empty_state_text || self.defaultEmptyStateText;
30
+ self.emptyStateEl.html(emptyStateText);
21
31
 
22
32
  self.loadingIndicatorEl.stop(true, true).hide();
33
+ self.emptyStateEl.toggle(isEmptyState);
34
+ if (isEmptyState) {
35
+ self.table.hide();
36
+ return;
37
+ }
38
+ self.table.show().fadeTo(300, 1);
23
39
 
40
+ var rowAggregationsCount = self.rowAggregationsCount(reportOptions);
41
+ var rowAggregationsStartIndex = rowAggregationsCount ? (tableData.length - rowAggregationsCount) : null;
24
42
  var html = '';
25
43
  for(var i = 0; i < tableData.length; i++) {
26
44
  if (i == 0) {
27
45
  html += '<thead><tr>';
28
46
  } else if (i == 1) {
29
47
  html += '<tbody><tr>';
48
+ } else if (rowAggregationsCount && i == rowAggregationsStartIndex) {
49
+ html += '<tfoot><tr>';
30
50
  } else {
31
51
  html += '<tr>';
32
52
  }
@@ -35,12 +55,14 @@ ReportsKit.Table = (function(options) {
35
55
  if (i == 0 || j == 0) {
36
56
  html += '<th>' + (tableData[i][j] || '') + '</th>';
37
57
  } else {
38
- html += '<td>' + tableData[i][j] + '</td>';
58
+ html += '<td>' + ((tableData[i][j] === null) ? '' : tableData[i][j]) + '</td>';
39
59
  }
40
60
  }
41
61
 
42
62
  if (i == 0) {
43
63
  html += '</tr></thead>';
64
+ } else if (i == tableData.length && rowAggregationsCount) {
65
+ html += '</tfoot></tbody>';
44
66
  } else if (i == tableData.length) {
45
67
  html += '</tr></tbody>';
46
68
  } else {
@@ -52,6 +74,19 @@ ReportsKit.Table = (function(options) {
52
74
  });
53
75
  };
54
76
 
77
+ self.rowAggregationsCount = function(reportOptions) {
78
+ if (!reportOptions.aggregations) {
79
+ return 0;
80
+ };
81
+ var rowAggregationsCount = 0;
82
+ for(var i = 0; i < reportOptions.aggregations.length; i++) {
83
+ if (reportOptions.aggregations[i].from === 'columns') {
84
+ rowAggregationsCount += 1;
85
+ }
86
+ }
87
+ return rowAggregationsCount;
88
+ };
89
+
55
90
  self.initialize(options);
56
91
 
57
92
  return self;
@@ -7,6 +7,9 @@
7
7
  width: 180px
8
8
  .loading_indicator
9
9
  position: absolute
10
+ margin: 5px auto
11
+ left: 0
12
+ right: 0
10
13
  padding: 22px
11
14
  background: url(data:image/gif;base64,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) no-repeat center
12
15
 
@@ -15,71 +15,64 @@ end
15
15
  You can then use `dimensions: ['carrier']` to count the number of Flights per Carrier:
16
16
 
17
17
  ```yaml
18
- measure:
19
- key: flight
20
- dimensions:
21
- - carrier
18
+ measure: flight
19
+ dimensions:
20
+ - carrier
22
21
  ```
23
22
  [<img src="images/flights_by_carrier.png?raw=true" width="500" />](images/flights_by_carrier.png?raw=true)
24
23
 
25
24
  You can also use two dimensions:
26
25
 
27
26
  ```yaml
28
- measure:
29
- key: flight
30
- dimensions:
31
- - carrier
32
- - flight_at
27
+ measure: flight
28
+ dimensions:
29
+ - carrier
30
+ - flight_at
33
31
  ```
34
32
  [<img src="images/flights_by_carrier_and_flight_at.png?raw=true" width="500" />](images/flights_by_carrier_and_flight_at.png?raw=true)
35
33
 
36
34
  Dimensions can be configured using a string (`carrier`):
37
35
 
38
36
  ```yaml
39
- measure:
40
- key: flight
41
- dimensions:
42
- - carrier
37
+ measure: flight
38
+ dimensions:
39
+ - carrier
43
40
  ```
44
41
 
45
42
  Or, if you need to use options, you can configure them using a hash:
46
43
 
47
44
  ```yaml
48
- measure:
49
- key: flight
50
- dimensions:
51
- - key: carrier
52
- limit: 5
45
+ measure: flight
46
+ dimensions:
47
+ - key: carrier
48
+ limit: 5
53
49
  ```
54
50
  #### Types
55
51
 
56
52
  ##### Association
57
53
 
58
54
  ```yaml
59
- measure:
60
- key: flight
61
- dimensions:
62
- - carrier
55
+ measure: flight
56
+ dimensions:
57
+ - carrier
63
58
  ```
64
59
  [<img src="images/flights_by_carrier.png?raw=true" width="500" />](images/flights_by_carrier.png?raw=true)
65
60
 
66
61
  ##### Datetime Column
67
62
 
68
63
  ```yaml
69
- measure:
70
- key: flight
71
- dimensions:
72
- - flight_at
64
+ measure: flight
65
+ dimensions:
66
+ - flight_at
73
67
  ```
74
68
  [<img src="images/flights_by_flight_at.png?raw=true" width="500" />](images/flights_by_flight_at.png?raw=true)
75
69
 
76
70
  ##### Integer Column
77
71
 
78
72
  ```yaml
79
- measure:
80
- key: flight
81
- dimensions:
82
- - delay
73
+ measure: flight
74
+ dimensions:
75
+ - delay
83
76
  ```
84
77
  [<img src="images/flights_by_delay.png?raw=true" width="500" />](images/flights_by_delay.png?raw=true)
85
78
 
@@ -100,10 +93,9 @@ end
100
93
  We can then use the `hours_delayed` dimension:
101
94
 
102
95
  ```yaml
103
- measure:
104
- key: flight
105
- dimensions:
106
- - hours_delayed
96
+ measure: flight
97
+ dimensions:
98
+ - hours_delayed
107
99
  ```
108
100
  [<img src="images/flights_by_hours_delayed.png?raw=true" width="500" />](images/flights_by_hours_delayed.png?raw=true)
109
101