finery 3.0.0

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