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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +55 -9
  4. data/app/assets/stylesheets/blazer/application.css +1 -0
  5. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  6. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  7. data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +0 -6
  8. data/app/controllers/blazer/base_controller.rb +45 -45
  9. data/app/controllers/blazer/dashboards_controller.rb +4 -11
  10. data/app/controllers/blazer/queries_controller.rb +28 -48
  11. data/app/models/blazer/query.rb +8 -2
  12. data/app/views/blazer/_variables.html.erb +5 -4
  13. data/app/views/blazer/dashboards/_form.html.erb +1 -1
  14. data/app/views/blazer/dashboards/show.html.erb +4 -4
  15. data/app/views/blazer/queries/_caching.html.erb +1 -1
  16. data/app/views/blazer/queries/_form.html.erb +3 -3
  17. data/app/views/blazer/queries/run.html.erb +1 -1
  18. data/app/views/blazer/queries/show.html.erb +12 -7
  19. data/app/views/layouts/blazer/application.html.erb +7 -2
  20. data/lib/blazer/adapters/athena_adapter.rb +51 -15
  21. data/lib/blazer/adapters/base_adapter.rb +16 -1
  22. data/lib/blazer/adapters/bigquery_adapter.rb +13 -2
  23. data/lib/blazer/adapters/cassandra_adapter.rb +15 -4
  24. data/lib/blazer/adapters/drill_adapter.rb +10 -0
  25. data/lib/blazer/adapters/druid_adapter.rb +36 -1
  26. data/lib/blazer/adapters/elasticsearch_adapter.rb +13 -2
  27. data/lib/blazer/adapters/hive_adapter.rb +10 -0
  28. data/lib/blazer/adapters/ignite_adapter.rb +12 -2
  29. data/lib/blazer/adapters/influxdb_adapter.rb +22 -10
  30. data/lib/blazer/adapters/mongodb_adapter.rb +4 -0
  31. data/lib/blazer/adapters/neo4j_adapter.rb +17 -2
  32. data/lib/blazer/adapters/opensearch_adapter.rb +4 -0
  33. data/lib/blazer/adapters/presto_adapter.rb +9 -0
  34. data/lib/blazer/adapters/salesforce_adapter.rb +5 -0
  35. data/lib/blazer/adapters/snowflake_adapter.rb +9 -0
  36. data/lib/blazer/adapters/soda_adapter.rb +9 -0
  37. data/lib/blazer/adapters/spark_adapter.rb +5 -0
  38. data/lib/blazer/adapters/sql_adapter.rb +30 -3
  39. data/lib/blazer/data_source.rb +85 -5
  40. data/lib/blazer/engine.rb +0 -4
  41. data/lib/blazer/run_statement.rb +7 -3
  42. data/lib/blazer/run_statement_job.rb +4 -2
  43. data/lib/blazer/slack_notifier.rb +5 -2
  44. data/lib/blazer/statement.rb +75 -0
  45. data/lib/blazer/version.rb +1 -1
  46. data/lib/blazer.rb +14 -6
  47. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fad6bd2d76ea45a8ec72956177648848a6dede796354a01f27359b4cb7cfeedb
4
- data.tar.gz: a8f81985d77b110c850d421ac86376bbf71cf5f2b318cfea496e8cedb1f1a108
3
+ metadata.gz: 8bdfc1e428f7e01bf9a06461a5f0143a6e940cd1e3d708ba2bd504132bbaa1db
4
+ data.tar.gz: 729e9a408e7f4fa5203ab4c133800e49087fccf634efcf7c9dd6ca80a610a861
5
5
  SHA512:
6
- metadata.gz: 6fd612ec162dc5b6a23ef1f6b3580da0de7f1cec8788520557e8a25ef0bdf4337df775b0cacb4e292003e1a4565460c411a67951a5ea27048a994fb1178463d7
7
- data.tar.gz: 4a5e760ca38fb96fa54df64469f3fd3223d6b23698da0491c43dc8bc3ef92f7f83b14bea028821ba5708adf209bc147975edaf6d4dc76ecb5916d51905e7f2f0
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 (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).
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 only permissions:
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 only permissions:
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 only permissions:
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
@@ -1,4 +1,5 @@
1
1
  /*
2
+ *= require ./bootstrap-sprockets
2
3
  *= require ./bootstrap
3
4
  *= require ./selectize
4
5
  *= require ./github
@@ -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, data_source)
36
- (@bind_vars ||= []).concat(Blazer.extract_vars(statement)).uniq!
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
- params[var] ||= Blazer.data_sources[data_source].variable_defaults[var]
39
- end
40
- @success = @bind_vars.all? { |v| params[v] }
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
- params[:cohort_period] ||= "week"
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
- # don't pass to url helpers
98
- #
99
- # some are dangerous when passed as symbols
100
- # root_url({host: "evilsite.com"})
101
- #
102
- # certain ones (like host) only affect *_url and not *_path
103
- #
104
- # when permitted parameters are passed in Rails 6,
105
- # they appear to be added as GET parameters
106
- # root_url(params.permit(:host))
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
- # remove unpermitted keys from both params and permitted keys for better sleep
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
- params.except(*UNPERMITTED_KEYS).slice(*permitted_keys).permit!
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
- statement = query.statement.dup
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
- data_source = Blazer.data_sources[query.data_source]
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.statement.dup
63
- process_vars(@statement, @query.data_source)
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(@data_source, @statement, options)
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
- data_source = Blazer.data_sources[@query.data_source]
170
- @statement = @query.statement.dup
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
@@ -35,13 +35,19 @@ module Blazer
35
35
  end
36
36
 
37
37
  def variables
38
- variables = Blazer.extract_vars(statement)
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
- /\/\*\s*cohort analysis\s*\*\//i.match?(statement)
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: params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
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, params[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, params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !params[var], class: "form-control" %>
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, params[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 %>