blazer_xlsx 3.0.5

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 (148) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +442 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +1093 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Sortable.js +3709 -0
  13. data/app/assets/javascripts/blazer/ace/ace.js +19630 -0
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1981 -0
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +215 -0
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +16 -0
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +9 -0
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +18 -0
  19. data/app/assets/javascripts/blazer/ace.js +6 -0
  20. data/app/assets/javascripts/blazer/application.js +84 -0
  21. data/app/assets/javascripts/blazer/bootstrap.js +2580 -0
  22. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  23. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +2570 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1578 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.min.js +466 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10872 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  30. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  31. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1548 -0
  32. data/app/assets/javascripts/blazer/moment.js +5685 -0
  33. data/app/assets/javascripts/blazer/queries.js +130 -0
  34. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  35. data/app/assets/javascripts/blazer/routes.js +26 -0
  36. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  37. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  38. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  39. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  40. data/app/assets/stylesheets/blazer/application.css +243 -0
  41. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  42. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  43. data/app/assets/stylesheets/blazer/bootstrap.css +6828 -0
  44. data/app/assets/stylesheets/blazer/daterangepicker.css +410 -0
  45. data/app/assets/stylesheets/blazer/github.css +125 -0
  46. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  47. data/app/controllers/blazer/base_controller.rb +135 -0
  48. data/app/controllers/blazer/checks_controller.rb +56 -0
  49. data/app/controllers/blazer/dashboards_controller.rb +99 -0
  50. data/app/controllers/blazer/queries_controller.rb +472 -0
  51. data/app/controllers/blazer/uploads_controller.rb +147 -0
  52. data/app/helpers/blazer/base_helper.rb +39 -0
  53. data/app/models/blazer/audit.rb +6 -0
  54. data/app/models/blazer/check.rb +104 -0
  55. data/app/models/blazer/connection.rb +5 -0
  56. data/app/models/blazer/dashboard.rb +17 -0
  57. data/app/models/blazer/dashboard_query.rb +9 -0
  58. data/app/models/blazer/query.rb +42 -0
  59. data/app/models/blazer/record.rb +5 -0
  60. data/app/models/blazer/upload.rb +11 -0
  61. data/app/models/blazer/uploads_connection.rb +7 -0
  62. data/app/views/blazer/_nav.html.erb +18 -0
  63. data/app/views/blazer/_variables.html.erb +127 -0
  64. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  65. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  66. data/app/views/blazer/checks/_form.html.erb +79 -0
  67. data/app/views/blazer/checks/edit.html.erb +3 -0
  68. data/app/views/blazer/checks/index.html.erb +72 -0
  69. data/app/views/blazer/checks/new.html.erb +3 -0
  70. data/app/views/blazer/dashboards/_form.html.erb +82 -0
  71. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  72. data/app/views/blazer/dashboards/new.html.erb +3 -0
  73. data/app/views/blazer/dashboards/show.html.erb +53 -0
  74. data/app/views/blazer/queries/_caching.html.erb +16 -0
  75. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  76. data/app/views/blazer/queries/_form.html.erb +255 -0
  77. data/app/views/blazer/queries/docs.html.erb +147 -0
  78. data/app/views/blazer/queries/edit.html.erb +2 -0
  79. data/app/views/blazer/queries/home.html.erb +169 -0
  80. data/app/views/blazer/queries/new.html.erb +2 -0
  81. data/app/views/blazer/queries/run.html.erb +183 -0
  82. data/app/views/blazer/queries/schema.html.erb +55 -0
  83. data/app/views/blazer/queries/show.html.erb +72 -0
  84. data/app/views/blazer/uploads/_form.html.erb +27 -0
  85. data/app/views/blazer/uploads/edit.html.erb +3 -0
  86. data/app/views/blazer/uploads/index.html.erb +55 -0
  87. data/app/views/blazer/uploads/new.html.erb +3 -0
  88. data/app/views/layouts/blazer/application.html.erb +25 -0
  89. data/config/routes.rb +25 -0
  90. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  91. data/lib/blazer/adapters/base_adapter.rb +76 -0
  92. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  93. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  94. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  95. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  96. data/lib/blazer/adapters/elasticsearch_adapter.rb +61 -0
  97. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  98. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  99. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  100. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  101. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  102. data/lib/blazer/adapters/presto_adapter.rb +54 -0
  103. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  104. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  105. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  106. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  107. data/lib/blazer/adapters/sql_adapter.rb +353 -0
  108. data/lib/blazer/adapters.rb +17 -0
  109. data/lib/blazer/anomaly_detectors.rb +22 -0
  110. data/lib/blazer/check_mailer.rb +27 -0
  111. data/lib/blazer/data_source.rb +266 -0
  112. data/lib/blazer/engine.rb +42 -0
  113. data/lib/blazer/forecasters.rb +7 -0
  114. data/lib/blazer/result.rb +178 -0
  115. data/lib/blazer/result_cache.rb +71 -0
  116. data/lib/blazer/run_statement.rb +45 -0
  117. data/lib/blazer/run_statement_job.rb +20 -0
  118. data/lib/blazer/slack_notifier.rb +94 -0
  119. data/lib/blazer/statement.rb +77 -0
  120. data/lib/blazer/version.rb +3 -0
  121. data/lib/blazer.rb +282 -0
  122. data/lib/generators/blazer/install_generator.rb +22 -0
  123. data/lib/generators/blazer/templates/config.yml.tt +79 -0
  124. data/lib/generators/blazer/templates/install.rb.tt +47 -0
  125. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  126. data/lib/generators/blazer/uploads_generator.rb +18 -0
  127. data/lib/tasks/blazer.rake +20 -0
  128. data/licenses/LICENSE-ace.txt +24 -0
  129. data/licenses/LICENSE-bootstrap.txt +21 -0
  130. data/licenses/LICENSE-chart.js.txt +9 -0
  131. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  132. data/licenses/LICENSE-chartkick.js.txt +22 -0
  133. data/licenses/LICENSE-date-fns.txt +21 -0
  134. data/licenses/LICENSE-daterangepicker.txt +21 -0
  135. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  136. data/licenses/LICENSE-highlight.js.txt +29 -0
  137. data/licenses/LICENSE-jquery.txt +20 -0
  138. data/licenses/LICENSE-kurkle-color.txt +9 -0
  139. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  140. data/licenses/LICENSE-moment-timezone.txt +20 -0
  141. data/licenses/LICENSE-moment.txt +22 -0
  142. data/licenses/LICENSE-rails-ujs.txt +20 -0
  143. data/licenses/LICENSE-selectize.txt +202 -0
  144. data/licenses/LICENSE-sortable.txt +21 -0
  145. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  146. data/licenses/LICENSE-stupidtable.txt +19 -0
  147. data/licenses/LICENSE-vue.txt +21 -0
  148. metadata +271 -0
data/README.md ADDED
@@ -0,0 +1,1093 @@
1
+ # Blazer
2
+
3
+ Explore your data with SQL. Easily create charts and dashboards, and share them with your team.
4
+
5
+ [Try it out](https://blazer.dokkuapp.com)
6
+
7
+ [![Screenshot](https://blazer.dokkuapp.com/assets/blazer-a10baa40fef1ca2f5bb25fc97bcf261a6a54192fb1ad0f893c0f562b8c7c4697.png)](https://blazer.dokkuapp.com)
8
+
9
+ Blazer is also available as a [Docker image](https://github.com/ankane/blazer-docker).
10
+
11
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
12
+
13
+ [![Build Status](https://github.com/ankane/blazer/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/blazer/actions)
14
+
15
+ ## Features
16
+
17
+ - **Multiple data sources** - PostgreSQL, MySQL, Redshift, and [many more](#full-list)
18
+ - **Variables** - run the same queries with different values
19
+ - **Checks & alerts** - get emailed when bad data appears
20
+ - **Audits** - all queries are tracked
21
+ - **Security** - works with your authentication system
22
+
23
+ ## Docs
24
+
25
+ - [Installation](#installation)
26
+ - [Queries](#queries)
27
+ - [Charts](#charts)
28
+ - [Dashboards](#dashboards)
29
+ - [Checks](#checks)
30
+ - [Cohorts](#cohorts)
31
+ - [Anomaly Detection](#anomaly-detection)
32
+ - [Forecasting](#forecasting)
33
+ - [Uploads](#uploads)
34
+ - [Data Sources](#data-sources)
35
+ - [Query Permissions](#query-permissions)
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application’s Gemfile:
40
+
41
+ ```ruby
42
+ gem "blazer"
43
+ ```
44
+
45
+ Run:
46
+
47
+ ```sh
48
+ rails generate blazer:install
49
+ rails db:migrate
50
+ ```
51
+
52
+ And mount the dashboard in your `config/routes.rb`:
53
+
54
+ ```ruby
55
+ mount Blazer::Engine, at: "blazer"
56
+ ```
57
+
58
+ For production, specify your database:
59
+
60
+ ```ruby
61
+ ENV["BLAZER_DATABASE_URL"] = "postgres://user:password@hostname:5432/database"
62
+ ```
63
+
64
+ When possible, Blazer tries to protect against queries which modify data by running each query in a transaction and rolling it back, but a safer approach is to use a read-only user. [See how to create one](#permissions).
65
+
66
+ #### Checks (optional)
67
+
68
+ Be sure to set a host in `config/environments/production.rb` for emails to work.
69
+
70
+ ```ruby
71
+ config.action_mailer.default_url_options = {host: "blazer.dokkuapp.com"}
72
+ ```
73
+
74
+ Schedule checks to run (with cron, [Heroku Scheduler](https://elements.heroku.com/addons/scheduler), etc). The default options are every 5 minutes, 1 hour, or 1 day, which you can customize. For each of these options, set up a task to run.
75
+
76
+ ```sh
77
+ rake blazer:run_checks SCHEDULE="5 minutes"
78
+ rake blazer:run_checks SCHEDULE="1 hour"
79
+ rake blazer:run_checks SCHEDULE="1 day"
80
+ ```
81
+
82
+ You can also set up failing checks to be sent once a day (or whatever you prefer).
83
+
84
+ ```sh
85
+ rake blazer:send_failing_checks
86
+ ```
87
+
88
+ Here’s what it looks like with cron.
89
+
90
+ ```
91
+ */5 * * * * rake blazer:run_checks SCHEDULE="5 minutes"
92
+ 0 * * * * rake blazer:run_checks SCHEDULE="1 hour"
93
+ 30 7 * * * rake blazer:run_checks SCHEDULE="1 day"
94
+ 0 8 * * * rake blazer:send_failing_checks
95
+ ```
96
+
97
+ For Slack notifications, create an [incoming webhook](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks) and set:
98
+
99
+ ```sh
100
+ BLAZER_SLACK_WEBHOOK_URL=https://hooks.slack.com/...
101
+ ```
102
+
103
+ Name the webhook “Blazer” and add a cool icon.
104
+
105
+ ## Authentication
106
+
107
+ Don’t forget to protect the dashboard in production.
108
+
109
+ ### Basic Authentication
110
+
111
+ Set the following variables in your environment or an initializer.
112
+
113
+ ```ruby
114
+ ENV["BLAZER_USERNAME"] = "andrew"
115
+ ENV["BLAZER_PASSWORD"] = "secret"
116
+ ```
117
+
118
+ ### Devise
119
+
120
+ ```ruby
121
+ authenticate :user, ->(user) { user.admin? } do
122
+ mount Blazer::Engine, at: "blazer"
123
+ end
124
+ ```
125
+
126
+ ### Other
127
+
128
+ Specify a `before_action` method to run in `blazer.yml`.
129
+
130
+ ```yml
131
+ before_action_method: require_admin
132
+ ```
133
+
134
+ You can define this method in your `ApplicationController`.
135
+
136
+ ```ruby
137
+ def require_admin
138
+ # depending on your auth, something like...
139
+ redirect_to root_path unless current_user && current_user.admin?
140
+ end
141
+ ```
142
+
143
+ Be sure to render or redirect for unauthorized users.
144
+
145
+ ## Permissions
146
+
147
+ ### PostgreSQL
148
+
149
+ Create a user with read-only permissions:
150
+
151
+ ```sql
152
+ BEGIN;
153
+ CREATE ROLE blazer LOGIN PASSWORD 'secret';
154
+ GRANT CONNECT ON DATABASE dbname TO blazer;
155
+ GRANT USAGE ON SCHEMA public TO blazer;
156
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO blazer;
157
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO blazer;
158
+ COMMIT;
159
+ ```
160
+
161
+ ### MySQL
162
+
163
+ Create a user with read-only permissions:
164
+
165
+ ```sql
166
+ CREATE USER 'blazer'@'127.0.0.1' IDENTIFIED BY 'secret';
167
+ GRANT SELECT, SHOW VIEW ON dbname.* TO 'blazer'@'127.0.0.1';
168
+ FLUSH PRIVILEGES;
169
+ ```
170
+
171
+ ## Sensitive Data
172
+
173
+ If your database contains sensitive or personal data, check out [Hypershield](https://github.com/ankane/hypershield) to shield it.
174
+
175
+ ## Encrypted Data
176
+
177
+ If you need to search encrypted data, use [blind indexing](https://github.com/ankane/blind_index).
178
+
179
+ You can have Blazer transform specific variables with:
180
+
181
+ ```ruby
182
+ Blazer.transform_variable = lambda do |name, value|
183
+ value = User.generate_email_bidx(value) if name == "email_bidx"
184
+ value
185
+ end
186
+ ```
187
+
188
+ ## Queries
189
+
190
+ ### Variables
191
+
192
+ Create queries with variables.
193
+
194
+ ```sql
195
+ SELECT * FROM users WHERE gender = {gender}
196
+ ```
197
+
198
+ Use `{start_time}` and `{end_time}` for time ranges. [Example](https://blazer.dokkuapp.com/queries/9-time-range-selector?start_time=1997-10-03T05%3A00%3A00%2B00%3A00&end_time=1997-10-04T04%3A59%3A59%2B00%3A00)
199
+
200
+ ```sql
201
+ SELECT * FROM ratings WHERE rated_at >= {start_time} AND rated_at <= {end_time}
202
+ ```
203
+
204
+ ### Smart Variables
205
+
206
+ [Example](https://blazer.dokkuapp.com/queries/1-smart-variable)
207
+
208
+ Suppose you have the query:
209
+
210
+ ```sql
211
+ SELECT * FROM users WHERE occupation_id = {occupation_id}
212
+ ```
213
+
214
+ Instead of remembering each occupation’s id, users can select occupations by name.
215
+
216
+ Add a smart variable with:
217
+
218
+ ```yml
219
+ smart_variables:
220
+ occupation_id: "SELECT id, name FROM occupations ORDER BY name ASC"
221
+ ```
222
+
223
+ The first column is the value of the variable, and the second column is the label.
224
+
225
+ You can also use an array or hash for static data and enums.
226
+
227
+ ```yml
228
+ smart_variables:
229
+ period: ["day", "week", "month"]
230
+ status: {0: "Active", 1: "Archived"}
231
+ ```
232
+
233
+ ### Linked Columns
234
+
235
+ [Example](https://blazer.dokkuapp.com/queries/3-linked-column) - title column
236
+
237
+ Link results to other pages in your apps or around the web. Specify a column name and where it should link to. You can use the value of the result with `{value}`.
238
+
239
+ ```yml
240
+ linked_columns:
241
+ user_id: "/admin/users/{value}"
242
+ ip_address: "https://www.infosniper.net/index.php?ip_address={value}"
243
+ ```
244
+
245
+ ### Smart Columns
246
+
247
+ [Example](https://blazer.dokkuapp.com/queries/2-smart-column) - occupation_id column
248
+
249
+ Suppose you have the query:
250
+
251
+ ```sql
252
+ SELECT name, city_id FROM users
253
+ ```
254
+
255
+ See which city the user belongs to without a join.
256
+
257
+ ```yml
258
+ smart_columns:
259
+ city_id: "SELECT id, name FROM cities WHERE id IN {value}"
260
+ ```
261
+
262
+ You can also use a hash for static data and enums.
263
+
264
+ ```yml
265
+ smart_columns:
266
+ status: {0: "Active", 1: "Archived"}
267
+ ```
268
+
269
+ ### Caching
270
+
271
+ Blazer can automatically cache results to improve speed. It can cache slow queries:
272
+
273
+ ```yml
274
+ cache:
275
+ mode: slow
276
+ expires_in: 60 # min
277
+ slow_threshold: 15 # sec
278
+ ```
279
+
280
+ Or it can cache all queries:
281
+
282
+ ```yml
283
+ cache:
284
+ mode: all
285
+ expires_in: 60 # min
286
+ ```
287
+
288
+ Of course, you can force a refresh at any time.
289
+
290
+ ## Charts
291
+
292
+ Blazer will automatically generate charts based on the types of the columns returned in your query.
293
+
294
+ **Note:** The order of columns matters.
295
+
296
+ ### Line Chart
297
+
298
+ There are two ways to generate line charts.
299
+
300
+ 2+ columns - timestamp, numeric(s) - [Example](https://blazer.dokkuapp.com/queries/4-line-chart-format-1)
301
+
302
+ ```sql
303
+ SELECT date_trunc('week', created_at), COUNT(*) FROM users GROUP BY 1
304
+ ```
305
+
306
+ 3 columns - timestamp, string, numeric - [Example](https://blazer.dokkuapp.com/queries/5-line-chart-format-2)
307
+
308
+
309
+ ```sql
310
+ SELECT date_trunc('week', created_at), gender, COUNT(*) FROM users GROUP BY 1, 2
311
+ ```
312
+
313
+ ### Column Chart
314
+
315
+ There are also two ways to generate column charts.
316
+
317
+ 2+ columns - string, numeric(s) - [Example](https://blazer.dokkuapp.com/queries/6-column-chart-format-1)
318
+
319
+ ```sql
320
+ SELECT gender, COUNT(*) FROM users GROUP BY 1
321
+ ```
322
+
323
+ 3 columns - string, string, numeric - [Example](https://blazer.dokkuapp.com/queries/7-column-chart-format-2)
324
+
325
+ ```sql
326
+ SELECT gender, zip_code, COUNT(*) FROM users GROUP BY 1, 2
327
+ ```
328
+
329
+ ### Scatter Chart
330
+
331
+ 2 columns - both numeric - [Example](https://blazer.dokkuapp.com/queries/16-scatter-chart)
332
+
333
+ ```sql
334
+ SELECT x, y FROM table
335
+ ```
336
+
337
+ ### Pie Chart
338
+
339
+ 2 columns - string, numeric - and last column named `pie` - [Example](https://blazer.dokkuapp.com/queries/17-pie-chart)
340
+
341
+ ```sql
342
+ SELECT gender, COUNT(*) AS pie FROM users GROUP BY 1
343
+ ```
344
+
345
+ ### Maps
346
+
347
+ Columns named `latitude` and `longitude` or `lat` and `lon` or `lat` and `lng` - [Example](https://blazer.dokkuapp.com/queries/15-map)
348
+
349
+ ```sql
350
+ SELECT name, latitude, longitude FROM cities
351
+ ```
352
+
353
+ or a column named `geojson`
354
+
355
+ ```sql
356
+ SELECT name, geojson FROM counties
357
+ ```
358
+
359
+ To enable, get an access token from [Mapbox](https://www.mapbox.com/) and set `ENV["MAPBOX_ACCESS_TOKEN"]`.
360
+
361
+ ### Targets
362
+
363
+ Use the column name `target` to draw a line for goals. [Example](https://blazer.dokkuapp.com/queries/8-target-line)
364
+
365
+ ```sql
366
+ SELECT date_trunc('week', created_at), COUNT(*) AS new_users, 100000 AS target FROM users GROUP BY 1
367
+ ```
368
+
369
+ ## Dashboards
370
+
371
+ Create a dashboard with multiple queries. [Example](https://blazer.dokkuapp.com/dashboards/1-dashboard-demo)
372
+
373
+ If the query has a chart, the chart is shown. Otherwise, you’ll see a table.
374
+
375
+ If any queries have variables, they will show up on the dashboard.
376
+
377
+ ## Checks
378
+
379
+ Checks give you a centralized place to see the health of your data. [Example](https://blazer.dokkuapp.com/checks)
380
+
381
+ Create a query to identify bad rows.
382
+
383
+ ```sql
384
+ SELECT * FROM ratings WHERE user_id IS NULL /* all ratings should have a user */
385
+ ```
386
+
387
+ Then create check with optional emails if you want to be notified. Emails are sent when a check starts failing, and when it starts passing again.
388
+
389
+ ## Cohorts
390
+
391
+ Create a cohort analysis from a simple SQL query. [Example](https://blazer.dokkuapp.com/queries/19-cohort-analysis-from-first-order)
392
+
393
+ Create a query with the comment `/* cohort analysis */`. The result should have columns named `user_id` and `conversion_time` and optionally `cohort_time`.
394
+
395
+ You can generate cohorts from the first conversion time:
396
+
397
+ ```sql
398
+ /* cohort analysis */
399
+ SELECT user_id, created_at AS conversion_time FROM orders
400
+ ```
401
+
402
+ (the first conversion isn’t counted in the first time period with this format)
403
+
404
+ Or from another time, like sign up:
405
+
406
+ ```sql
407
+ /* cohort analysis */
408
+ SELECT users.id AS user_id, orders.created_at AS conversion_time, users.created_at AS cohort_time
409
+ FROM users LEFT JOIN orders ON orders.user_id = users.id
410
+ ```
411
+
412
+ This feature requires PostgreSQL or MySQL 8.
413
+
414
+ ## Anomaly Detection
415
+
416
+ Blazer supports three different approaches to anomaly detection.
417
+
418
+ ### Prophet
419
+
420
+ Add [prophet-rb](https://github.com/ankane/prophet) to your Gemfile:
421
+
422
+ ```ruby
423
+ gem "prophet-rb"
424
+ ```
425
+
426
+ And add to `config/blazer.yml`:
427
+
428
+ ```yml
429
+ anomaly_checks: prophet
430
+ ```
431
+
432
+ ### Trend
433
+
434
+ [Trend](https://trendapi.org/) uses an external service by default, but you can run it on your own infrastructure as well.
435
+
436
+ Add [trend](https://github.com/ankane/trend) to your Gemfile:
437
+
438
+ ```ruby
439
+ gem "trend"
440
+ ```
441
+
442
+ And add to `config/blazer.yml`:
443
+
444
+ ```yml
445
+ anomaly_checks: trend
446
+ ```
447
+
448
+ For the [self-hosted API](https://github.com/ankane/trend-api), create an initializer with:
449
+
450
+ ```ruby
451
+ Trend.url = "http://localhost:8000"
452
+ ```
453
+
454
+ ### AnomalyDetection.rb
455
+
456
+ Add [anomaly_detection](https://github.com/ankane/AnomalyDetection.rb) to your Gemfile:
457
+
458
+ ```ruby
459
+ gem "anomaly_detection"
460
+ ```
461
+
462
+ And add to `config/blazer.yml`:
463
+
464
+ ```yml
465
+ anomaly_checks: anomaly_detection
466
+ ```
467
+
468
+ ## Forecasting
469
+
470
+ Blazer supports for two different forecasting methods. [Example](https://blazer.dokkuapp.com/queries/18-forecast?forecast=t)
471
+
472
+ A forecast link will appear for queries that return 2 columns with types timestamp and numeric.
473
+
474
+ ### Prophet
475
+
476
+ Add [prophet-rb](https://github.com/ankane/prophet) to your Gemfile:
477
+
478
+ ```ruby
479
+ gem "prophet-rb", ">= 0.2.1"
480
+ ```
481
+
482
+ And add to `config/blazer.yml`:
483
+
484
+ ```yml
485
+ forecasting: prophet
486
+ ```
487
+
488
+ ### Trend
489
+
490
+ [Trend](https://trendapi.org/) uses an external service by default, but you can run it on your own infrastructure as well.
491
+
492
+ Add [trend](https://github.com/ankane/trend) to your Gemfile:
493
+
494
+ ```ruby
495
+ gem "trend"
496
+ ```
497
+
498
+ And add to `config/blazer.yml`:
499
+
500
+ ```yml
501
+ forecasting: trend
502
+ ```
503
+
504
+ For the [self-hosted API](https://github.com/ankane/trend-api), create an initializer with:
505
+
506
+ ```ruby
507
+ Trend.url = "http://localhost:8000"
508
+ ```
509
+
510
+ ## Uploads
511
+
512
+ Create database tables from CSV files. [Example](https://blazer.dokkuapp.com/uploads)
513
+
514
+ Run:
515
+
516
+ ```sh
517
+ rails generate blazer:uploads
518
+ rails db:migrate
519
+ ```
520
+
521
+ And add to `config/blazer.yml`:
522
+
523
+ ```yml
524
+ uploads:
525
+ url: postgres://...
526
+ schema: uploads
527
+ data_source: main
528
+ ```
529
+
530
+ This feature requires PostgreSQL. Create a new schema just for uploads.
531
+
532
+ ```sql
533
+ CREATE SCHEMA uploads;
534
+ ```
535
+
536
+ ## Data Sources
537
+
538
+ Blazer supports multiple data sources :tada:
539
+
540
+ Add additional data sources in `config/blazer.yml`:
541
+
542
+ ```yml
543
+ data_sources:
544
+ main:
545
+ url: <%= ENV["BLAZER_DATABASE_URL"] %>
546
+ # timeout, smart_variables, linked_columns, smart_columns
547
+ catalog:
548
+ url: <%= ENV["CATALOG_DATABASE_URL"] %>
549
+ # ...
550
+ redshift:
551
+ url: <%= ENV["REDSHIFT_DATABASE_URL"] %>
552
+ # ...
553
+ ```
554
+
555
+ ### Full List
556
+
557
+ - [Amazon Athena](#amazon-athena)
558
+ - [Amazon Redshift](#amazon-redshift)
559
+ - [Apache Drill](#apache-drill)
560
+ - [Apache Hive](#apache-hive)
561
+ - [Apache Ignite](#apache-ignite)
562
+ - [Apache Spark](#apache-spark)
563
+ - [Cassandra](#cassandra)
564
+ - [Druid](#druid)
565
+ - [Elasticsearch](#elasticsearch)
566
+ - [Google BigQuery](#google-bigquery)
567
+ - [IBM DB2 and Informix](#ibm-db2-and-informix)
568
+ - [InfluxDB](#influxdb)
569
+ - [MySQL](#mysql-1)
570
+ - [Neo4j](#neo4j)
571
+ - [OpenSearch](#opensearch)
572
+ - [Oracle](#oracle)
573
+ - [PostgreSQL](#postgresql-1)
574
+ - [Presto](#presto)
575
+ - [Salesforce](#salesforce)
576
+ - [Socrata Open Data API (SODA)](#socrata-open-data-api-soda)
577
+ - [Snowflake](#snowflake)
578
+ - [SQLite](#sqlite)
579
+ - [SQL Server](#sql-server)
580
+
581
+ You can also [create an adapter](#creating-an-adapter) for any other data store.
582
+
583
+ **Note:** In the examples below, we recommend using environment variables for urls.
584
+
585
+ ```yml
586
+ data_sources:
587
+ my_source:
588
+ url: <%= ENV["BLAZER_MY_SOURCE_URL"] %>
589
+ ```
590
+
591
+ ### Amazon Athena
592
+
593
+ Add [aws-sdk-athena](https://github.com/aws/aws-sdk-ruby) and [aws-sdk-glue](https://github.com/aws/aws-sdk-ruby) to your Gemfile and set:
594
+
595
+ ```yml
596
+ data_sources:
597
+ my_source:
598
+ adapter: athena
599
+ database: database
600
+
601
+ # optional settings
602
+ output_location: s3://some-bucket/
603
+ workgroup: primary
604
+ access_key_id: ...
605
+ secret_access_key: ...
606
+ region: ...
607
+ ```
608
+
609
+ Here’s an example IAM policy:
610
+
611
+ ```json
612
+ {
613
+ "Version": "2012-10-17",
614
+ "Statement": [
615
+ {
616
+ "Effect": "Allow",
617
+ "Action": [
618
+ "athena:GetQueryExecution",
619
+ "athena:GetQueryResults",
620
+ "athena:StartQueryExecution"
621
+ ],
622
+ "Resource": [
623
+ "arn:aws:athena:region:account-id:workgroup/primary"
624
+ ]
625
+ },
626
+ {
627
+ "Effect": "Allow",
628
+ "Action": [
629
+ "glue:GetTable",
630
+ "glue:GetTables"
631
+ ],
632
+ "Resource": [
633
+ "arn:aws:glue:region:account-id:catalog",
634
+ "arn:aws:glue:region:account-id:database/default",
635
+ "arn:aws:glue:region:account-id:table/default/*"
636
+ ]
637
+ }
638
+ ]
639
+ }
640
+ ```
641
+
642
+ You also need to configure [S3 permissions](https://aws.amazon.com/premiumsupport/knowledge-center/access-denied-athena/).
643
+
644
+ ### Amazon Redshift
645
+
646
+ Add [activerecord6-redshift-adapter](https://github.com/kwent/activerecord6-redshift-adapter) or [activerecord5-redshift-adapter](https://github.com/ConsultingMD/activerecord5-redshift-adapter) to your Gemfile and set:
647
+
648
+ ```yml
649
+ data_sources:
650
+ my_source:
651
+ url: redshift://user:password@hostname:5439/database
652
+ ```
653
+
654
+ Use a [read-only user](https://docs.aws.amazon.com/redshift/latest/dg/r_GRANT.html).
655
+
656
+ ### Apache Drill
657
+
658
+ Add [drill-sergeant](https://github.com/ankane/drill-sergeant) to your Gemfile and set:
659
+
660
+ ```yml
661
+ data_sources:
662
+ my_source:
663
+ adapter: drill
664
+ url: http://hostname:8047
665
+ ```
666
+
667
+ Use a [read-only user](https://drill.apache.org/docs/roles-and-privileges/).
668
+
669
+ ### Apache Hive
670
+
671
+ Add [hexspace](https://github.com/ankane/hexspace) to your Gemfile and set:
672
+
673
+ ```yml
674
+ data_sources:
675
+ my_source:
676
+ adapter: hive
677
+ url: sasl://user:password@hostname:10000/database
678
+ ```
679
+
680
+ Use a [read-only user](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Authorization). Requires [HiveServer2](https://cwiki.apache.org/confluence/display/Hive/Setting+Up+HiveServer2).
681
+
682
+ ### Apache Ignite
683
+
684
+ Add [ignite-client](https://github.com/ankane/ignite-ruby) to your Gemfile and set:
685
+
686
+ ```yml
687
+ data_sources:
688
+ my_source:
689
+ url: ignite://user:password@hostname:10800
690
+ ```
691
+
692
+ Use a [read-only user](https://www.gridgain.com/docs/latest/administrators-guide/security/authorization-permissions) (requires a third-party plugin).
693
+
694
+ ### Apache Spark
695
+
696
+ Add [hexspace](https://github.com/ankane/hexspace) to your Gemfile and set:
697
+
698
+ ```yml
699
+ data_sources:
700
+ my_source:
701
+ adapter: spark
702
+ url: sasl://user:password@hostname:10000/database
703
+ ```
704
+
705
+ Use a read-only user. Requires the [Thrift server](https://spark.apache.org/docs/latest/sql-distributed-sql-engine.html).
706
+
707
+ ### Cassandra
708
+
709
+ Add [cassandra-driver](https://github.com/datastax/ruby-driver) (and [sorted_set](https://github.com/knu/sorted_set) for Ruby 3+) to your Gemfile and set:
710
+
711
+ ```yml
712
+ data_sources:
713
+ my_source:
714
+ url: cassandra://user:password@hostname:9042/keyspace
715
+ ```
716
+
717
+ Use a [read-only role](https://docs.datastax.com/en/cql-oss/3.3/cql/cql_using/useSecurePermission.html).
718
+
719
+ ### Druid
720
+
721
+ Enable [SQL support](http://druid.io/docs/latest/querying/sql.html#configuration) on the broker and set:
722
+
723
+ ```yml
724
+ data_sources:
725
+ my_source:
726
+ adapter: druid
727
+ url: http://hostname:8082
728
+ ```
729
+
730
+ Use a [read-only role](https://druid.apache.org/docs/latest/development/extensions-core/druid-basic-security.html).
731
+
732
+ ### Elasticsearch
733
+
734
+ Add [elasticsearch](https://github.com/elastic/elasticsearch-ruby) to your Gemfile and set:
735
+
736
+ ```yml
737
+ data_sources:
738
+ my_source:
739
+ adapter: elasticsearch
740
+ url: http://user:password@hostname:9200
741
+ ```
742
+
743
+ Use a [read-only role](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html).
744
+
745
+ ### Google BigQuery
746
+
747
+ Add [google-cloud-bigquery](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-bigquery) to your Gemfile and set:
748
+
749
+ ```yml
750
+ data_sources:
751
+ my_source:
752
+ adapter: bigquery
753
+ project: your-project
754
+ keyfile: path/to/keyfile.json
755
+ ```
756
+
757
+ ### IBM DB2 and Informix
758
+
759
+ Add [ibm_db](https://github.com/ibmdb/ruby-ibmdb) to your Gemfile and set:
760
+
761
+ ```yml
762
+ data_sources:
763
+ my_source:
764
+ url: ibm-db://user:password@hostname:50000/database
765
+ ```
766
+
767
+ Use a [read-only user](https://www.ibm.com/support/pages/creating-read-only-database-permissions-user).
768
+
769
+ ### InfluxDB
770
+
771
+ Add [influxdb](https://github.com/influxdata/influxdb-ruby) to your Gemfile and set:
772
+
773
+ ```yml
774
+ data_sources:
775
+ my_source:
776
+ adapter: influxdb
777
+ url: http://user:password@hostname:8086/database
778
+ ```
779
+
780
+ Use a [read-only user](https://docs.influxdata.com/influxdb/v1.8/administration/authentication_and_authorization/). Supports [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/explore-data/).
781
+
782
+ ### MySQL
783
+
784
+ Add [mysql2](https://github.com/brianmario/mysql2) to your Gemfile (if it’s not there) and set:
785
+
786
+ ```yml
787
+ data_sources:
788
+ my_source:
789
+ url: mysql2://user:password@hostname:3306/database
790
+ ```
791
+
792
+ Use a [read-only user](#mysql).
793
+
794
+ ### Neo4j
795
+
796
+ Add [neo4j-core](https://github.com/neo4jrb/neo4j-core) to your Gemfile and set:
797
+
798
+ ```yml
799
+ data_sources:
800
+ my_source:
801
+ adapter: neo4j
802
+ url: http://user:password@hostname:7474
803
+ ```
804
+
805
+ Use a [read-only user](https://neo4j.com/docs/cypher-manual/current/access-control/manage-privileges/).
806
+
807
+ ### OpenSearch
808
+
809
+ Add [opensearch-ruby](https://github.com/opensearch-project/opensearch-ruby) to your Gemfile and set:
810
+
811
+ ```yml
812
+ data_sources:
813
+ my_source:
814
+ adapter: opensearch
815
+ url: http://user:password@hostname:9200
816
+ ```
817
+
818
+ Use a [read-only user](https://opensearch.org/docs/latest/security-plugin/access-control/permissions/).
819
+
820
+ ### Oracle
821
+
822
+ Add [activerecord-oracle_enhanced-adapter](https://github.com/rsim/oracle-enhanced) and [ruby-oci8](https://github.com/kubo/ruby-oci8) to your Gemfile and set:
823
+
824
+ ```yml
825
+ data_sources:
826
+ my_source:
827
+ url: oracle-enhanced://user:password@hostname:1521/database
828
+ ```
829
+
830
+ Use a [read-only user](https://docs.oracle.com/cd/B19306_01/network.102/b14266/authoriz.htm).
831
+
832
+ ### PostgreSQL
833
+
834
+ Add [pg](https://github.com/ged/ruby-pg) to your Gemfile (if it’s not there) and set:
835
+
836
+ ```yml
837
+ data_sources:
838
+ my_source:
839
+ url: postgres://user:password@hostname:5432/database
840
+ ```
841
+
842
+ Use a [read-only user](#postgresql).
843
+
844
+ ### Presto
845
+
846
+ Add [presto-client](https://github.com/treasure-data/presto-client-ruby) to your Gemfile and set:
847
+
848
+ ```yml
849
+ data_sources:
850
+ my_source:
851
+ url: presto://user@hostname:8080/catalog
852
+ ```
853
+
854
+ Use a [read-only user](https://prestodb.io/docs/current/security/built-in-system-access-control.html).
855
+
856
+ ### Salesforce
857
+
858
+ Add [restforce](https://github.com/restforce/restforce) to your Gemfile and set:
859
+
860
+ ```yml
861
+ data_sources:
862
+ my_source:
863
+ adapter: salesforce
864
+ ```
865
+
866
+ And set the appropriate environment variables:
867
+
868
+ ```sh
869
+ SALESFORCE_USERNAME="username"
870
+ SALESFORCE_PASSWORD="password"
871
+ SALESFORCE_SECURITY_TOKEN="security token"
872
+ SALESFORCE_CLIENT_ID="client id"
873
+ SALESFORCE_CLIENT_SECRET="client secret"
874
+ SALESFORCE_API_VERSION="41.0"
875
+ ```
876
+
877
+ Use a read-only user. Supports [SOQL](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm).
878
+
879
+ ### Socrata Open Data API (SODA)
880
+
881
+ Set:
882
+
883
+ ```yml
884
+ data_sources:
885
+ my_source:
886
+ adapter: soda
887
+ url: https://soda.demo.socrata.com/resource/4tka-6guv.json
888
+ app_token: ...
889
+ ```
890
+
891
+ Supports [SoQL](https://dev.socrata.com/docs/functions/).
892
+
893
+ ### Snowflake
894
+
895
+ First, install ODBC. For Homebrew, use:
896
+
897
+ ```sh
898
+ brew install unixodbc
899
+ ```
900
+
901
+ For Ubuntu, use:
902
+
903
+ ```sh
904
+ sudo apt-get install unixodbc-dev
905
+ ```
906
+
907
+ For Heroku, use the [Apt buildpack](https://github.com/heroku/heroku-buildpack-apt) and create an `Aptfile` with:
908
+
909
+ ```text
910
+ unixodbc-dev
911
+ https://sfc-repo.snowflakecomputing.com/odbc/linux/2.21.5/snowflake-odbc-2.21.5.x86_64.deb
912
+ ```
913
+
914
+ > This installs the driver at `/app/.apt/usr/lib/snowflake/odbc/lib/libSnowflake.so`
915
+
916
+ Then, download the [Snowflake ODBC driver](https://docs.snowflake.net/manuals/user-guide/odbc-download.html). Add [odbc_adapter](https://github.com/localytics/odbc_adapter) to your Gemfile and set:
917
+
918
+ ```yml
919
+ data_sources:
920
+ my_source:
921
+ adapter: snowflake
922
+ conn_str: Driver=/path/to/libSnowflake.so;uid=user;pwd=password;server=host.snowflakecomputing.com
923
+ ```
924
+
925
+ Use a [read-only role](https://docs.snowflake.com/en/user-guide/security-access-control-configure.html).
926
+
927
+ ### SQLite
928
+
929
+ Add [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) to your Gemfile and set:
930
+
931
+ ```yml
932
+ data_sources:
933
+ my_source:
934
+ url: sqlite3:path/to/database.sqlite3
935
+ ```
936
+
937
+ ### SQL Server
938
+
939
+ Add [tiny_tds](https://github.com/rails-sqlserver/tiny_tds) and [activerecord-sqlserver-adapter](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter) to your Gemfile and set:
940
+
941
+ ```yml
942
+ data_sources:
943
+ my_source:
944
+ url: sqlserver://user:password@hostname:1433/database
945
+ ```
946
+
947
+ Use a [read-only user](https://docs.microsoft.com/en-us/sql/relational-databases/security/authentication-access/getting-started-with-database-engine-permissions?view=sql-server-ver15).
948
+
949
+ ## Creating an Adapter
950
+
951
+ Create an adapter for any data store with:
952
+
953
+ ```ruby
954
+ class FooAdapter < Blazer::Adapters::BaseAdapter
955
+ # code goes here
956
+ end
957
+
958
+ Blazer.register_adapter "foo", FooAdapter
959
+ ```
960
+
961
+ See the [Presto adapter](https://github.com/ankane/blazer/blob/master/lib/blazer/adapters/presto_adapter.rb) for a good example. Then use:
962
+
963
+ ```yml
964
+ data_sources:
965
+ my_source:
966
+ adapter: foo
967
+ url: http://user:password@hostname:9200/
968
+ ```
969
+
970
+ ## Query Permissions
971
+
972
+ Blazer supports a basic permissions model.
973
+
974
+ 1. Queries without a name are unlisted
975
+ 2. Queries whose name starts with `#` are only listed to the creator
976
+ 3. Queries whose name starts with `*` can only be edited by the creator
977
+
978
+ ## Learn SQL
979
+
980
+ Have team members who want to learn SQL? Here are a few great, free resources.
981
+
982
+ - [The Data School](https://dataschool.com/learn-sql/)
983
+ - [SQLBolt](https://sqlbolt.com/)
984
+
985
+ ## Useful Tools
986
+
987
+ For an easy way to group by day, week, month, and more with correct time zones, check out [Groupdate.sql](https://github.com/ankane/groupdate.sql).
988
+
989
+ ## Standalone Version
990
+
991
+ Looking for a standalone version? Check out [Ghost Blazer](https://github.com/buren/ghost_blazer).
992
+
993
+ ## Performance
994
+
995
+ By default, queries take up a request while they are running. To run queries asynchronously, add to your config:
996
+
997
+ ```yml
998
+ async: true
999
+ ```
1000
+
1001
+ **Note:** Requires caching to be enabled. If you have multiple web processes, your app must use a centralized cache store like Memcached or Redis.
1002
+
1003
+ ```ruby
1004
+ config.cache_store = :mem_cache_store
1005
+ ```
1006
+
1007
+ ## Archiving
1008
+
1009
+ Archive queries that haven’t been viewed in over 90 days.
1010
+
1011
+ ```sh
1012
+ rake blazer:archive_queries
1013
+ ```
1014
+
1015
+ ## Content Security Policy
1016
+
1017
+ If views are stuck with a `Loading...` message, there might be a problem with strict CSP settings in your app. This can be checked with Firefox or Chrome dev tools. You can allow Blazer to override these settings for its controllers with:
1018
+
1019
+ ```yml
1020
+ override_csp: true
1021
+ ```
1022
+
1023
+ ## Upgrading
1024
+
1025
+ ### 3.0
1026
+
1027
+ Maps now use Mapbox GL JS v1 instead of Mapbox.js, which affects Mapbox billing.
1028
+
1029
+ ### 2.6
1030
+
1031
+ Custom adapters now need to specify how to quote variables in queries (there is no longer a default)
1032
+
1033
+ ```ruby
1034
+ class FooAdapter < Blazer::Adapters::BaseAdapter
1035
+ def quoting
1036
+ :backslash_escape # single quote strings and convert ' to \' and \ to \\
1037
+ # or
1038
+ :single_quote_escape # single quote strings and convert ' to ''
1039
+ # or
1040
+ ->(value) { ... } # custom method
1041
+ end
1042
+ end
1043
+ ```
1044
+
1045
+ ### 2.3
1046
+
1047
+ To archive queries, create a migration
1048
+
1049
+ ```sh
1050
+ rails g migration add_status_to_blazer_queries
1051
+ ```
1052
+
1053
+ with:
1054
+
1055
+ ```ruby
1056
+ add_column :blazer_queries, :status, :string
1057
+ Blazer::Query.update_all(status: "active")
1058
+ ```
1059
+
1060
+ ### 2.0
1061
+
1062
+ To use Slack notifications, create a migration
1063
+
1064
+ ```sh
1065
+ rails g migration add_slack_channels_to_blazer_checks
1066
+ ```
1067
+
1068
+ with:
1069
+
1070
+ ```ruby
1071
+ add_column :blazer_checks, :slack_channels, :text
1072
+ ```
1073
+
1074
+ ## History
1075
+
1076
+ View the [changelog](https://github.com/ankane/blazer/blob/master/CHANGELOG.md)
1077
+
1078
+ ## Thanks
1079
+
1080
+ Blazer uses a number of awesome open source projects, including [Rails](https://github.com/rails/rails/), [Vue.js](https://github.com/vuejs/vue), [jQuery](https://github.com/jquery/jquery), [Bootstrap](https://github.com/twbs/bootstrap), [Selectize](https://github.com/brianreavis/selectize.js), [StickyTableHeaders](https://github.com/jmosbech/StickyTableHeaders), [Stupid jQuery Table Sort](https://github.com/joequery/Stupid-Table-Plugin), and [Date Range Picker](https://github.com/dangrossman/bootstrap-daterangepicker).
1081
+
1082
+ Demo data from [MovieLens](https://grouplens.org/datasets/movielens/).
1083
+
1084
+ ## Want to Make Blazer Better?
1085
+
1086
+ That’s awesome! Here are a few ways you can help:
1087
+
1088
+ - [Report bugs](https://github.com/ankane/blazer/issues)
1089
+ - Fix bugs and [submit pull requests](https://github.com/ankane/blazer/pulls)
1090
+ - Write, clarify, or fix documentation
1091
+ - Suggest or add new features
1092
+
1093
+ Check out the [dev app](https://github.com/ankane/blazer-dev) to get started.