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.
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