blazer 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +55 -9
- data/app/assets/stylesheets/blazer/application.css +1 -0
- data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
- data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
- data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +0 -6
- data/app/controllers/blazer/base_controller.rb +45 -45
- data/app/controllers/blazer/dashboards_controller.rb +4 -11
- data/app/controllers/blazer/queries_controller.rb +28 -48
- data/app/models/blazer/query.rb +8 -2
- data/app/views/blazer/_variables.html.erb +5 -4
- data/app/views/blazer/dashboards/_form.html.erb +1 -1
- data/app/views/blazer/dashboards/show.html.erb +4 -4
- data/app/views/blazer/queries/_caching.html.erb +1 -1
- data/app/views/blazer/queries/_form.html.erb +3 -3
- data/app/views/blazer/queries/run.html.erb +1 -1
- data/app/views/blazer/queries/show.html.erb +12 -7
- data/app/views/layouts/blazer/application.html.erb +7 -2
- data/lib/blazer/adapters/athena_adapter.rb +51 -15
- data/lib/blazer/adapters/base_adapter.rb +16 -1
- data/lib/blazer/adapters/bigquery_adapter.rb +13 -2
- data/lib/blazer/adapters/cassandra_adapter.rb +15 -4
- data/lib/blazer/adapters/drill_adapter.rb +10 -0
- data/lib/blazer/adapters/druid_adapter.rb +36 -1
- data/lib/blazer/adapters/elasticsearch_adapter.rb +13 -2
- data/lib/blazer/adapters/hive_adapter.rb +10 -0
- data/lib/blazer/adapters/ignite_adapter.rb +12 -2
- data/lib/blazer/adapters/influxdb_adapter.rb +22 -10
- data/lib/blazer/adapters/mongodb_adapter.rb +4 -0
- data/lib/blazer/adapters/neo4j_adapter.rb +17 -2
- data/lib/blazer/adapters/opensearch_adapter.rb +4 -0
- data/lib/blazer/adapters/presto_adapter.rb +9 -0
- data/lib/blazer/adapters/salesforce_adapter.rb +5 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +9 -0
- data/lib/blazer/adapters/soda_adapter.rb +9 -0
- data/lib/blazer/adapters/spark_adapter.rb +5 -0
- data/lib/blazer/adapters/sql_adapter.rb +30 -3
- data/lib/blazer/data_source.rb +85 -5
- data/lib/blazer/engine.rb +0 -4
- data/lib/blazer/run_statement.rb +7 -3
- data/lib/blazer/run_statement_job.rb +4 -2
- data/lib/blazer/slack_notifier.rb +5 -2
- data/lib/blazer/statement.rb +75 -0
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +14 -6
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bdfc1e428f7e01bf9a06461a5f0143a6e940cd1e3d708ba2bd504132bbaa1db
|
4
|
+
data.tar.gz: 729e9a408e7f4fa5203ab4c133800e49087fccf634efcf7c9dd6ca80a610a861
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6c7a7be80246c1030170a5df95656947b2431cf908bb6d37d668cbe3f57006ef096d63186976c63c683f0e2511873cabb5d2be56e389de331487666ba297dc3
|
7
|
+
data.tar.gz: 9feb70216244f77d37357376cd68b9ec85a6d3eeb3c89f3e58196688e81618ca2ae38372e5491e0811bdd7d9e0082317a711040f5782cc54f5d9f99ec57d624a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 2.6.0 (2022-04-20)
|
2
|
+
|
3
|
+
- Fixed quoting issue with variables
|
4
|
+
- Custom adapters now need to specify how to quote variables in queries
|
5
|
+
- Added experimental support for Propshaft
|
6
|
+
- Fixed error with empty results with InfluxDB
|
7
|
+
|
1
8
|
## 2.5.0 (2022-01-04)
|
2
9
|
|
3
10
|
- Added support for Slack OAuth tokens
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ For production, specify your database:
|
|
61
61
|
ENV["BLAZER_DATABASE_URL"] = "postgres://user:password@hostname:5432/database"
|
62
62
|
```
|
63
63
|
|
64
|
-
Blazer tries to protect against queries which modify data
|
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
65
|
|
66
66
|
#### Checks (optional)
|
67
67
|
|
@@ -144,11 +144,9 @@ Be sure to render or redirect for unauthorized users.
|
|
144
144
|
|
145
145
|
## Permissions
|
146
146
|
|
147
|
-
Blazer runs each query in a transaction and rolls it back to prevent queries from modifying data. As an additional line of defense, we recommend using a read only user.
|
148
|
-
|
149
147
|
### PostgreSQL
|
150
148
|
|
151
|
-
Create a user with read
|
149
|
+
Create a user with read-only permissions:
|
152
150
|
|
153
151
|
```sql
|
154
152
|
BEGIN;
|
@@ -162,7 +160,7 @@ COMMIT;
|
|
162
160
|
|
163
161
|
### MySQL
|
164
162
|
|
165
|
-
Create a user with read
|
163
|
+
Create a user with read-only permissions:
|
166
164
|
|
167
165
|
```sql
|
168
166
|
GRANT SELECT, SHOW VIEW ON database_name.* TO blazer@’127.0.0.1′ IDENTIFIED BY ‘secret123‘;
|
@@ -171,7 +169,7 @@ FLUSH PRIVILEGES;
|
|
171
169
|
|
172
170
|
### MongoDB
|
173
171
|
|
174
|
-
Create a user with read
|
172
|
+
Create a user with read-only permissions:
|
175
173
|
|
176
174
|
```
|
177
175
|
db.createUser({user: "blazer", pwd: "password", roles: ["read"]})
|
@@ -656,6 +654,8 @@ data_sources:
|
|
656
654
|
url: redshift://user:password@hostname:5439/database
|
657
655
|
```
|
658
656
|
|
657
|
+
Use a [read-only user](https://docs.aws.amazon.com/redshift/latest/dg/r_GRANT.html).
|
658
|
+
|
659
659
|
### Apache Drill
|
660
660
|
|
661
661
|
Add [drill-sergeant](https://github.com/ankane/drill-sergeant) to your Gemfile and set:
|
@@ -667,6 +667,8 @@ data_sources:
|
|
667
667
|
url: http://hostname:8047
|
668
668
|
```
|
669
669
|
|
670
|
+
Use a [read-only user](https://drill.apache.org/docs/roles-and-privileges/).
|
671
|
+
|
670
672
|
### Apache Hive
|
671
673
|
|
672
674
|
Add [hexspace](https://github.com/ankane/hexspace) to your Gemfile and set:
|
@@ -690,6 +692,8 @@ data_sources:
|
|
690
692
|
url: ignite://user:password@hostname:10800
|
691
693
|
```
|
692
694
|
|
695
|
+
Use a [read-only user](https://www.gridgain.com/docs/latest/administrators-guide/security/authorization-permissions) (requires a third-party plugin).
|
696
|
+
|
693
697
|
### Apache Spark
|
694
698
|
|
695
699
|
Add [hexspace](https://github.com/ankane/hexspace) to your Gemfile and set:
|
@@ -713,6 +717,8 @@ data_sources:
|
|
713
717
|
url: cassandra://user:password@hostname:9042/keyspace
|
714
718
|
```
|
715
719
|
|
720
|
+
Use a [read-only role](https://docs.datastax.com/en/cql-oss/3.3/cql/cql_using/useSecurePermission.html).
|
721
|
+
|
716
722
|
### Druid
|
717
723
|
|
718
724
|
Enable [SQL support](http://druid.io/docs/latest/querying/sql.html#configuration) on the broker and set:
|
@@ -724,6 +730,8 @@ data_sources:
|
|
724
730
|
url: http://hostname:8082
|
725
731
|
```
|
726
732
|
|
733
|
+
Use a [read-only role](https://druid.apache.org/docs/latest/development/extensions-core/druid-basic-security.html).
|
734
|
+
|
727
735
|
### Elasticsearch
|
728
736
|
|
729
737
|
Add [elasticsearch](https://github.com/elastic/elasticsearch-ruby) to your Gemfile and set:
|
@@ -735,6 +743,8 @@ data_sources:
|
|
735
743
|
url: http://user:password@hostname:9200
|
736
744
|
```
|
737
745
|
|
746
|
+
Use a [read-only role](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html).
|
747
|
+
|
738
748
|
### Google BigQuery
|
739
749
|
|
740
750
|
Add [google-cloud-bigquery](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-bigquery) to your Gemfile and set:
|
@@ -757,6 +767,8 @@ data_sources:
|
|
757
767
|
url: ibm-db://user:password@hostname:50000/database
|
758
768
|
```
|
759
769
|
|
770
|
+
Use a [read-only user](https://www.ibm.com/support/pages/creating-read-only-database-permissions-user).
|
771
|
+
|
760
772
|
### InfluxDB
|
761
773
|
|
762
774
|
Add [influxdb](https://github.com/influxdata/influxdb-ruby) to your Gemfile and set:
|
@@ -768,7 +780,7 @@ data_sources:
|
|
768
780
|
url: http://user:password@hostname:8086/database
|
769
781
|
```
|
770
782
|
|
771
|
-
Supports [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/explore-data/)
|
783
|
+
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/).
|
772
784
|
|
773
785
|
### MongoDB
|
774
786
|
|
@@ -782,6 +794,8 @@ data_sources:
|
|
782
794
|
url: mongodb://user:password@hostname:27017/database
|
783
795
|
```
|
784
796
|
|
797
|
+
Use a [read-only user](#mongodb).
|
798
|
+
|
785
799
|
### MySQL
|
786
800
|
|
787
801
|
Add [mysql2](https://github.com/brianmario/mysql2) to your Gemfile (if it’s not there) and set:
|
@@ -792,6 +806,8 @@ data_sources:
|
|
792
806
|
url: mysql2://user:password@hostname:3306/database
|
793
807
|
```
|
794
808
|
|
809
|
+
Use a [read-only user](#mysql).
|
810
|
+
|
795
811
|
### Neo4j
|
796
812
|
|
797
813
|
Add [neo4j-core](https://github.com/neo4jrb/neo4j-core) to your Gemfile and set:
|
@@ -803,6 +819,8 @@ data_sources:
|
|
803
819
|
url: http://user:password@hostname:7474
|
804
820
|
```
|
805
821
|
|
822
|
+
Use a [read-only user](https://neo4j.com/docs/cypher-manual/current/access-control/manage-privileges/).
|
823
|
+
|
806
824
|
### OpenSearch
|
807
825
|
|
808
826
|
Add [opensearch-ruby](https://github.com/opensearch-project/opensearch-ruby) to your Gemfile and set:
|
@@ -814,6 +832,8 @@ data_sources:
|
|
814
832
|
url: http://user:password@hostname:9200
|
815
833
|
```
|
816
834
|
|
835
|
+
Use a [read-only user](https://opensearch.org/docs/latest/security-plugin/access-control/permissions/).
|
836
|
+
|
817
837
|
### Oracle
|
818
838
|
|
819
839
|
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:
|
@@ -824,6 +844,8 @@ data_sources:
|
|
824
844
|
url: oracle-enhanced://user:password@hostname:1521/database
|
825
845
|
```
|
826
846
|
|
847
|
+
Use a [read-only user](https://docs.oracle.com/cd/B19306_01/network.102/b14266/authoriz.htm).
|
848
|
+
|
827
849
|
### PostgreSQL
|
828
850
|
|
829
851
|
Add [pg](https://github.com/ged/ruby-pg) to your Gemfile (if it’s not there) and set:
|
@@ -834,6 +856,8 @@ data_sources:
|
|
834
856
|
url: postgres://user:password@hostname:5432/database
|
835
857
|
```
|
836
858
|
|
859
|
+
Use a [read-only user](#postgresql).
|
860
|
+
|
837
861
|
### Presto
|
838
862
|
|
839
863
|
Add [presto-client](https://github.com/treasure-data/presto-client-ruby) to your Gemfile and set:
|
@@ -844,6 +868,8 @@ data_sources:
|
|
844
868
|
url: presto://user@hostname:8080/catalog
|
845
869
|
```
|
846
870
|
|
871
|
+
Use a [read-only user](https://prestodb.io/docs/current/security/built-in-system-access-control.html).
|
872
|
+
|
847
873
|
### Salesforce
|
848
874
|
|
849
875
|
Add [restforce](https://github.com/restforce/restforce) to your Gemfile and set:
|
@@ -865,7 +891,7 @@ SALESFORCE_CLIENT_SECRET="client secret"
|
|
865
891
|
SALESFORCE_API_VERSION="41.0"
|
866
892
|
```
|
867
893
|
|
868
|
-
Supports [SOQL](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm)
|
894
|
+
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).
|
869
895
|
|
870
896
|
### Socrata Open Data API (SODA)
|
871
897
|
|
@@ -879,7 +905,7 @@ data_sources:
|
|
879
905
|
app_token: ...
|
880
906
|
```
|
881
907
|
|
882
|
-
Supports [SoQL](https://dev.socrata.com/docs/functions/)
|
908
|
+
Supports [SoQL](https://dev.socrata.com/docs/functions/).
|
883
909
|
|
884
910
|
### Snowflake
|
885
911
|
|
@@ -913,6 +939,8 @@ data_sources:
|
|
913
939
|
conn_str: Driver=/path/to/libSnowflake.so;uid=user;pwd=password;server=host.snowflakecomputing.com
|
914
940
|
```
|
915
941
|
|
942
|
+
Use a [read-only role](https://docs.snowflake.com/en/user-guide/security-access-control-configure.html).
|
943
|
+
|
916
944
|
### SQLite
|
917
945
|
|
918
946
|
Add [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) to your Gemfile and set:
|
@@ -933,6 +961,8 @@ data_sources:
|
|
933
961
|
url: sqlserver://user:password@hostname:1433/database
|
934
962
|
```
|
935
963
|
|
964
|
+
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).
|
965
|
+
|
936
966
|
## Creating an Adapter
|
937
967
|
|
938
968
|
Create an adapter for any data store with:
|
@@ -1009,6 +1039,22 @@ override_csp: true
|
|
1009
1039
|
|
1010
1040
|
## Upgrading
|
1011
1041
|
|
1042
|
+
### 2.6
|
1043
|
+
|
1044
|
+
Custom adapters now need to specify how to quote variables in queries (there is no longer a default)
|
1045
|
+
|
1046
|
+
```ruby
|
1047
|
+
class FooAdapter < Blazer::Adapters::BaseAdapter
|
1048
|
+
def quoting
|
1049
|
+
:backslash_escape # single quote strings and convert ' to \' and \ to \\
|
1050
|
+
# or
|
1051
|
+
:single_quote_escape # single quote strings and convert ' to ''
|
1052
|
+
# or
|
1053
|
+
->(value) { ... } # custom method
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
```
|
1057
|
+
|
1012
1058
|
### 2.3
|
1013
1059
|
|
1014
1060
|
To archive queries, create a migration
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/*!
|
2
|
+
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
3
|
+
* Copyright 2011-2019 Twitter, Inc.
|
4
|
+
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
5
|
+
*/
|
6
|
+
@font-face {
|
7
|
+
font-family: "Glyphicons Halflings";
|
8
|
+
src: url('/blazer/glyphicons-halflings-regular.eot');
|
9
|
+
src: url('/blazer/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('/blazer/glyphicons-halflings-regular.woff2') format('woff2'), url('/blazer/glyphicons-halflings-regular.woff') format('woff'), url('/blazer/glyphicons-halflings-regular.ttf') format('truetype'), url('/blazer/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
|
10
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/*!
|
2
|
+
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
3
|
+
* Copyright 2011-2019 Twitter, Inc.
|
4
|
+
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
5
|
+
*/
|
6
|
+
@font-face {
|
7
|
+
font-family: "Glyphicons Halflings";
|
8
|
+
src: url('<%= font_path("blazer/glyphicons-halflings-regular.eot") %>');
|
9
|
+
src: url('<%= font_path("blazer/glyphicons-halflings-regular.eot?#iefix") %>') format('embedded-opentype'), url('<%= font_path("blazer/glyphicons-halflings-regular.woff2") %>') format('woff2'), url('<%= font_path("blazer/glyphicons-halflings-regular.woff") %>') format('woff'), url('<%= font_path("blazer/glyphicons-halflings-regular.ttf") %>') format('truetype'), url('<%= font_path("blazer/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") %>') format('svg');
|
10
|
+
}
|
@@ -263,11 +263,6 @@ th {
|
|
263
263
|
border: 1px solid #ddd !important;
|
264
264
|
}
|
265
265
|
}
|
266
|
-
@font-face {
|
267
|
-
font-family: "Glyphicons Halflings";
|
268
|
-
src: url('<%= font_path("blazer/glyphicons-halflings-regular.eot") %>');
|
269
|
-
src: url('<%= font_path("blazer/glyphicons-halflings-regular.eot?#iefix") %>') format('embedded-opentype'), url('<%= font_path("blazer/glyphicons-halflings-regular.woff2") %>') format('woff2'), url('<%= font_path("blazer/glyphicons-halflings-regular.woff") %>') format('woff'), url('<%= font_path("blazer/glyphicons-halflings-regular.ttf") %>') format('truetype'), url('<%= font_path("blazer/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") %>') format('svg');
|
270
|
-
}
|
271
266
|
.glyphicon {
|
272
267
|
position: relative;
|
273
268
|
top: 1px;
|
@@ -6831,4 +6826,3 @@ button.close {
|
|
6831
6826
|
display: none !important;
|
6832
6827
|
}
|
6833
6828
|
}
|
6834
|
-
/*# sourceMappingURL=bootstrap.css.map */
|
@@ -32,45 +32,34 @@ module Blazer
|
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
-
def process_vars(statement,
|
36
|
-
|
35
|
+
def process_vars(statement, var_params = nil)
|
36
|
+
var_params ||= request.query_parameters
|
37
|
+
(@bind_vars ||= []).concat(statement.variables).uniq!
|
38
|
+
# update in-place so populated in view and consistent across queries on dashboard
|
37
39
|
@bind_vars.each do |var|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
if @success
|
43
|
-
@bind_vars.each do |var|
|
44
|
-
value = params[var].presence
|
45
|
-
if value
|
46
|
-
if ["start_time", "end_time"].include?(var)
|
47
|
-
value = value.to_s.gsub(" ", "+") # fix for Quip bug
|
48
|
-
end
|
49
|
-
|
50
|
-
if var.end_with?("_at")
|
51
|
-
begin
|
52
|
-
value = Blazer.time_zone.parse(value)
|
53
|
-
rescue
|
54
|
-
# do nothing
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
if value =~ /\A\d+\z/
|
59
|
-
value = value.to_i
|
60
|
-
elsif value =~ /\A\d+\.\d+\z/
|
61
|
-
value = value.to_f
|
62
|
-
end
|
63
|
-
end
|
64
|
-
value = Blazer.transform_variable.call(var, value) if Blazer.transform_variable
|
65
|
-
statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
|
40
|
+
if !var_params[var]
|
41
|
+
default = statement.data_source.variable_defaults[var]
|
42
|
+
# only add if default exists
|
43
|
+
var_params[var] = default if default
|
66
44
|
end
|
67
45
|
end
|
46
|
+
runnable = @bind_vars.all? { |v| var_params[v] }
|
47
|
+
statement.add_values(var_params) if runnable
|
48
|
+
runnable
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_query(query)
|
52
|
+
statement = query.statement_object
|
53
|
+
runnable = process_vars(statement)
|
54
|
+
cohort_analysis_statement(statement) if statement.cohort_analysis?
|
55
|
+
statement.clear_cache if runnable
|
68
56
|
end
|
69
57
|
|
70
58
|
def add_cohort_analysis_vars
|
71
59
|
@bind_vars << "cohort_period" unless @bind_vars.include?("cohort_period")
|
72
|
-
@smart_vars["cohort_period"] = ["day", "week", "month"]
|
73
|
-
|
60
|
+
@smart_vars["cohort_period"] = ["day", "week", "month"] if @smart_vars
|
61
|
+
# TODO create var_params method
|
62
|
+
request.query_parameters["cohort_period"] ||= "week"
|
74
63
|
end
|
75
64
|
|
76
65
|
def parse_smart_variables(var, data_source)
|
@@ -94,22 +83,33 @@ module Blazer
|
|
94
83
|
[smart_var, error]
|
95
84
|
end
|
96
85
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
86
|
+
def cohort_analysis_statement(statement)
|
87
|
+
@cohort_period = params["cohort_period"] || "week"
|
88
|
+
@cohort_period = "week" unless ["day", "week", "month"].include?(@cohort_period)
|
89
|
+
|
90
|
+
# for now
|
91
|
+
@conversion_period = @cohort_period
|
92
|
+
@cohort_days =
|
93
|
+
case @cohort_period
|
94
|
+
when "day"
|
95
|
+
1
|
96
|
+
when "week"
|
97
|
+
7
|
98
|
+
when "month"
|
99
|
+
30
|
100
|
+
end
|
101
|
+
|
102
|
+
statement.apply_cohort_analysis(period: @cohort_period, days: @cohort_days)
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO allow all keys
|
106
|
+
# or show error message for disallowed keys
|
107
107
|
UNPERMITTED_KEYS = [:controller, :action, :id, :host, :query, :dashboard, :query_id, :query_ids, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source, :name, :fork_query_id, :blazer, :run_id, :script_name, :original_script_name]
|
108
108
|
|
109
|
-
|
110
|
-
def variable_params(resource)
|
109
|
+
def variable_params(resource, var_params = nil)
|
111
110
|
permitted_keys = resource.variables - UNPERMITTED_KEYS.map(&:to_s)
|
112
|
-
|
111
|
+
var_params ||= request.query_parameters
|
112
|
+
var_params.slice(*permitted_keys)
|
113
113
|
end
|
114
114
|
helper_method :variable_params
|
115
115
|
|
@@ -21,11 +21,8 @@ module Blazer
|
|
21
21
|
|
22
22
|
def show
|
23
23
|
@queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
|
24
|
-
@statements = []
|
25
24
|
@queries.each do |query|
|
26
|
-
|
27
|
-
process_vars(statement, query.data_source)
|
28
|
-
@statements << statement
|
25
|
+
@success = process_vars(query.statement_object)
|
29
26
|
end
|
30
27
|
@bind_vars ||= []
|
31
28
|
|
@@ -48,7 +45,7 @@ module Blazer
|
|
48
45
|
|
49
46
|
def update
|
50
47
|
if update_dashboard(@dashboard)
|
51
|
-
redirect_to dashboard_path(@dashboard, variable_params(@dashboard))
|
48
|
+
redirect_to dashboard_path(@dashboard, params: variable_params(@dashboard))
|
52
49
|
else
|
53
50
|
render_errors @dashboard
|
54
51
|
end
|
@@ -61,13 +58,9 @@ module Blazer
|
|
61
58
|
|
62
59
|
def refresh
|
63
60
|
@dashboard.queries.each do |query|
|
64
|
-
|
65
|
-
statement = query.statement.dup
|
66
|
-
process_vars(statement, query.data_source)
|
67
|
-
Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
|
68
|
-
data_source.clear_cache(statement)
|
61
|
+
refresh_query(query)
|
69
62
|
end
|
70
|
-
redirect_to dashboard_path(@dashboard, variable_params(@dashboard))
|
63
|
+
redirect_to dashboard_path(@dashboard, params: variable_params(@dashboard))
|
71
64
|
end
|
72
65
|
|
73
66
|
private
|
@@ -52,29 +52,26 @@ module Blazer
|
|
52
52
|
@query.status = "active" if @query.respond_to?(:status)
|
53
53
|
|
54
54
|
if @query.save
|
55
|
-
redirect_to query_path(@query, variable_params(@query))
|
55
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
56
56
|
else
|
57
57
|
render_errors @query
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
def show
|
62
|
-
@statement = @query.
|
63
|
-
process_vars(@statement
|
62
|
+
@statement = @query.statement_object
|
63
|
+
@success = process_vars(@statement)
|
64
64
|
|
65
65
|
@smart_vars = {}
|
66
66
|
@sql_errors = []
|
67
|
-
data_source = Blazer.data_sources[@query.data_source]
|
68
67
|
@bind_vars.each do |var|
|
69
|
-
smart_var, error = parse_smart_variables(var, data_source)
|
68
|
+
smart_var, error = parse_smart_variables(var, @statement.data_source)
|
70
69
|
@smart_vars[var] = smart_var if smart_var
|
71
70
|
@sql_errors << error if error
|
72
71
|
end
|
73
72
|
|
74
73
|
@query.update!(status: "active") if @query.respond_to?(:status) && @query.status.in?(["archived", nil])
|
75
74
|
|
76
|
-
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
77
|
-
|
78
75
|
add_cohort_analysis_vars if @query.cohort_analysis?
|
79
76
|
end
|
80
77
|
|
@@ -82,17 +79,24 @@ module Blazer
|
|
82
79
|
end
|
83
80
|
|
84
81
|
def run
|
85
|
-
@statement = params[:statement]
|
86
|
-
# before process_vars
|
87
|
-
@cohort_analysis = Query.new(statement: @statement).cohort_analysis?
|
88
|
-
data_source = params[:data_source]
|
89
|
-
process_vars(@statement, data_source)
|
90
|
-
@only_chart = params[:only_chart]
|
91
|
-
@run_id = blazer_params[:run_id]
|
92
82
|
@query = Query.find_by(id: params[:query_id]) if params[:query_id]
|
83
|
+
|
84
|
+
# use query data source when present
|
85
|
+
# need to update viewable? logic below if this changes
|
93
86
|
data_source = @query.data_source if @query && @query.data_source
|
87
|
+
data_source ||= params[:data_source]
|
94
88
|
@data_source = Blazer.data_sources[data_source]
|
95
89
|
|
90
|
+
@statement = Blazer::Statement.new(params[:statement], @data_source)
|
91
|
+
# before process_vars
|
92
|
+
@cohort_analysis = @statement.cohort_analysis?
|
93
|
+
|
94
|
+
# fallback for now for users with open tabs
|
95
|
+
@var_params = request.request_parameters["variables"] || request.request_parameters
|
96
|
+
@success = process_vars(@statement, @var_params)
|
97
|
+
@only_chart = params[:only_chart]
|
98
|
+
@run_id = blazer_params[:run_id]
|
99
|
+
|
96
100
|
run_cohort_analysis if @cohort_analysis
|
97
101
|
|
98
102
|
# ensure viewable
|
@@ -128,7 +132,7 @@ module Blazer
|
|
128
132
|
|
129
133
|
options = {user: blazer_user, query: @query, refresh_cache: params[:check], run_id: @run_id, async: Blazer.async}
|
130
134
|
if Blazer.async && request.format.symbol != :csv
|
131
|
-
Blazer::RunStatementJob.perform_later(@data_source.id, @statement, options)
|
135
|
+
Blazer::RunStatementJob.perform_later(@data_source.id, @statement.statement, options.merge(values: @statement.values))
|
132
136
|
wait_start = Time.now
|
133
137
|
loop do
|
134
138
|
sleep(0.1)
|
@@ -136,7 +140,7 @@ module Blazer
|
|
136
140
|
break if @result || Time.now - wait_start > 3
|
137
141
|
end
|
138
142
|
else
|
139
|
-
@result = Blazer::RunStatement.new.perform(@
|
143
|
+
@result = Blazer::RunStatement.new.perform(@statement, options)
|
140
144
|
end
|
141
145
|
|
142
146
|
if @result
|
@@ -166,13 +170,8 @@ module Blazer
|
|
166
170
|
end
|
167
171
|
|
168
172
|
def refresh
|
169
|
-
|
170
|
-
@
|
171
|
-
process_vars(@statement, @query.data_source)
|
172
|
-
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
173
|
-
@statement = cohort_analysis_statement(data_source, @statement) if @query.cohort_analysis?
|
174
|
-
data_source.clear_cache(@statement)
|
175
|
-
redirect_to query_path(@query, variable_params(@query))
|
173
|
+
refresh_query(@query)
|
174
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
176
175
|
end
|
177
176
|
|
178
177
|
def update
|
@@ -185,7 +184,7 @@ module Blazer
|
|
185
184
|
@query.errors.add(:base, "Sorry, permission denied")
|
186
185
|
end
|
187
186
|
if @query.errors.empty? && @query.update(query_params)
|
188
|
-
redirect_to query_path(@query, variable_params(@query))
|
187
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
189
188
|
else
|
190
189
|
render_errors @query
|
191
190
|
end
|
@@ -282,6 +281,9 @@ module Blazer
|
|
282
281
|
render layout: false
|
283
282
|
end
|
284
283
|
format.csv do
|
284
|
+
# not ideal, but useful for testing
|
285
|
+
raise Error, @error if @error && Rails.env.test?
|
286
|
+
|
285
287
|
send_data csv_data(@columns, @rows, @data_source), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
|
286
288
|
end
|
287
289
|
end
|
@@ -363,34 +365,12 @@ module Blazer
|
|
363
365
|
end
|
364
366
|
|
365
367
|
def run_cohort_analysis
|
366
|
-
unless @data_source.supports_cohort_analysis?
|
368
|
+
unless @statement.data_source.supports_cohort_analysis?
|
367
369
|
@cohort_error = "This data source does not support cohort analysis"
|
368
370
|
end
|
369
371
|
|
370
372
|
@show_cohort_rows = !params[:query_id] || @cohort_error
|
371
|
-
|
372
|
-
unless @show_cohort_rows
|
373
|
-
@statement = cohort_analysis_statement(@data_source, @statement)
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
def cohort_analysis_statement(data_source, statement)
|
378
|
-
@cohort_period = params["cohort_period"] || "week"
|
379
|
-
@cohort_period = "week" unless ["day", "week", "month"].include?(@cohort_period)
|
380
|
-
|
381
|
-
# for now
|
382
|
-
@conversion_period = @cohort_period
|
383
|
-
@cohort_days =
|
384
|
-
case @cohort_period
|
385
|
-
when "day"
|
386
|
-
1
|
387
|
-
when "week"
|
388
|
-
7
|
389
|
-
when "month"
|
390
|
-
30
|
391
|
-
end
|
392
|
-
|
393
|
-
data_source.cohort_analysis_statement(statement, period: @cohort_period, days: @cohort_days)
|
373
|
+
cohort_analysis_statement(@statement) unless @show_cohort_rows
|
394
374
|
end
|
395
375
|
|
396
376
|
def render_cohort_analysis
|
data/app/models/blazer/query.rb
CHANGED
@@ -35,13 +35,19 @@ module Blazer
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def variables
|
38
|
-
|
38
|
+
# don't require data_source to be loaded
|
39
|
+
variables = Statement.new(statement).variables
|
39
40
|
variables += ["cohort_period"] if cohort_analysis?
|
40
41
|
variables
|
41
42
|
end
|
42
43
|
|
43
44
|
def cohort_analysis?
|
44
|
-
|
45
|
+
# don't require data_source to be loaded
|
46
|
+
Statement.new(statement).cohort_analysis?
|
47
|
+
end
|
48
|
+
|
49
|
+
def statement_object
|
50
|
+
Statement.new(statement, data_source)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<% if @bind_vars.any? %>
|
2
|
+
<% var_params = request.query_parameters %>
|
2
3
|
<script>
|
3
4
|
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
4
5
|
var now = moment.tz(timeZone)
|
@@ -19,14 +20,14 @@
|
|
19
20
|
<% @bind_vars.each_with_index do |var, i| %>
|
20
21
|
<%= label_tag var, var %>
|
21
22
|
<% if (data = @smart_vars[var]) %>
|
22
|
-
<%= select_tag var, options_for_select([[nil, nil]] + data, selected:
|
23
|
+
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: var_params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
|
23
24
|
<script>
|
24
25
|
$("#<%= var %>").selectize({
|
25
26
|
create: true
|
26
27
|
});
|
27
28
|
</script>
|
28
29
|
<% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
|
29
|
-
<%= hidden_field_tag var,
|
30
|
+
<%= hidden_field_tag var, var_params[var] %>
|
30
31
|
|
31
32
|
<div class="selectize-control single" style="width: 200px;">
|
32
33
|
<div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
|
@@ -58,13 +59,13 @@
|
|
58
59
|
})()
|
59
60
|
</script>
|
60
61
|
<% else %>
|
61
|
-
<%= text_field_tag var,
|
62
|
+
<%= text_field_tag var, var_params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !var_params[var], class: "form-control" %>
|
62
63
|
<% end %>
|
63
64
|
<% end %>
|
64
65
|
|
65
66
|
<% if date_vars %>
|
66
67
|
<% date_vars.each do |var| %>
|
67
|
-
<%= hidden_field_tag var,
|
68
|
+
<%= hidden_field_tag var, var_params[var] %>
|
68
69
|
<% end %>
|
69
70
|
|
70
71
|
<%= label_tag nil, date_vars.join(" & ") %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params(@dashboard)) : dashboards_path(variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
|
1
|
+
<%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, params: variable_params(@dashboard)) : dashboards_path(params: variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
|
2
2
|
<% if @dashboard.errors.any? %>
|
3
3
|
<div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
|
4
4
|
<% end %>
|