railsblazer 2.0.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 +7 -0
- data/.gitattributes +1 -0
- data/.github/ISSUE_TEMPLATE.md +0 -0
- data/.gitignore +14 -0
- data/CHANGELOG.md +247 -0
- data/CONTRIBUTING.md +42 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +855 -0
- data/Rakefile +1 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/javascripts/blazer/Chart.js +14145 -0
- data/app/assets/javascripts/blazer/Sortable.js +1144 -0
- data/app/assets/javascripts/blazer/ace.js +6 -0
- data/app/assets/javascripts/blazer/ace/ace.js +11 -0
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
- data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
- data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
- data/app/assets/javascripts/blazer/application.js +79 -0
- data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
- data/app/assets/javascripts/blazer/chartkick.js +1693 -0
- data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
- data/app/assets/javascripts/blazer/jquery.js +10308 -0
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
- data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
- data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
- data/app/assets/javascripts/blazer/moment.js +3043 -0
- data/app/assets/javascripts/blazer/queries.js +110 -0
- data/app/assets/javascripts/blazer/routes.js +23 -0
- data/app/assets/javascripts/blazer/selectize.js +3667 -0
- data/app/assets/javascripts/blazer/stupidtable.js +114 -0
- data/app/assets/javascripts/blazer/vue.js +7515 -0
- data/app/assets/stylesheets/blazer/application.css +198 -0
- data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
- data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
- data/app/assets/stylesheets/blazer/github.css +125 -0
- data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
- data/app/controllers/blazer/base_controller.rb +113 -0
- data/app/controllers/blazer/checks_controller.rb +56 -0
- data/app/controllers/blazer/dashboards_controller.rb +105 -0
- data/app/controllers/blazer/queries_controller.rb +337 -0
- data/app/helpers/blazer/base_helper.rb +57 -0
- data/app/mailers/blazer/check_mailer.rb +27 -0
- data/app/mailers/blazer/slack_notifier.rb +76 -0
- data/app/models/blazer/audit.rb +6 -0
- data/app/models/blazer/check.rb +104 -0
- data/app/models/blazer/connection.rb +5 -0
- data/app/models/blazer/dashboard.rb +13 -0
- data/app/models/blazer/dashboard_query.rb +9 -0
- data/app/models/blazer/query.rb +40 -0
- data/app/models/blazer/record.rb +5 -0
- data/app/views/blazer/_nav.html.erb +16 -0
- data/app/views/blazer/_variables.html.erb +102 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
- data/app/views/blazer/checks/_form.html.erb +79 -0
- data/app/views/blazer/checks/edit.html.erb +1 -0
- data/app/views/blazer/checks/index.html.erb +43 -0
- data/app/views/blazer/checks/new.html.erb +1 -0
- data/app/views/blazer/dashboards/_form.html.erb +76 -0
- data/app/views/blazer/dashboards/edit.html.erb +1 -0
- data/app/views/blazer/dashboards/new.html.erb +1 -0
- data/app/views/blazer/dashboards/show.html.erb +47 -0
- data/app/views/blazer/queries/_form.html.erb +240 -0
- data/app/views/blazer/queries/edit.html.erb +2 -0
- data/app/views/blazer/queries/home.html.erb +152 -0
- data/app/views/blazer/queries/new.html.erb +2 -0
- data/app/views/blazer/queries/run.html.erb +165 -0
- data/app/views/blazer/queries/schema.html.erb +20 -0
- data/app/views/blazer/queries/show.html.erb +73 -0
- data/app/views/layouts/blazer/application.html.erb +24 -0
- data/blazer-0.0.1.gem +0 -0
- data/blazer.gemspec +27 -0
- data/config/routes.rb +16 -0
- data/lib/blazer.rb +223 -0
- data/lib/blazer/adapters/athena_adapter.rb +128 -0
- data/lib/blazer/adapters/base_adapter.rb +53 -0
- data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
- data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
- data/lib/blazer/adapters/drill_adapter.rb +28 -0
- data/lib/blazer/adapters/druid_adapter.rb +67 -0
- data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/presto_adapter.rb +45 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
- data/lib/blazer/adapters/sql_adapter.rb +182 -0
- data/lib/blazer/data_source.rb +195 -0
- data/lib/blazer/detect_anomalies.R +19 -0
- data/lib/blazer/engine.rb +30 -0
- data/lib/blazer/result.rb +170 -0
- data/lib/blazer/run_statement.rb +40 -0
- data/lib/blazer/run_statement_job.rb +21 -0
- data/lib/blazer/version.rb +3 -0
- data/lib/generators/blazer/install_generator.rb +39 -0
- data/lib/generators/blazer/templates/config.yml.tt +62 -0
- data/lib/generators/blazer/templates/install.rb.tt +46 -0
- data/lib/tasks/blazer.rake +11 -0
- data/railsblazer-0.0.1.gem +0 -0
- metadata +234 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
module BaseHelper
|
|
3
|
+
def blazer_title(title = nil)
|
|
4
|
+
if title
|
|
5
|
+
content_for(:title) { title }
|
|
6
|
+
else
|
|
7
|
+
content_for?(:title) ? content_for(:title) : nil
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
BLAZER_URL_REGEX = /\Ahttps?:\/\/[\S]+\z/
|
|
12
|
+
BLAZER_IMAGE_EXT = %w[png jpg jpeg gif]
|
|
13
|
+
|
|
14
|
+
def blazer_format_value(key, value)
|
|
15
|
+
if value.is_a?(Integer) && !key.to_s.end_with?("id") && !key.to_s.start_with?("id")
|
|
16
|
+
number_with_delimiter(value)
|
|
17
|
+
elsif value =~ BLAZER_URL_REGEX
|
|
18
|
+
# see if image or link
|
|
19
|
+
if Blazer.images && (key.include?("image") || BLAZER_IMAGE_EXT.include?(value.split(".").last.split("?").first.try(:downcase)))
|
|
20
|
+
link_to value, target: "_blank" do
|
|
21
|
+
image_tag value, referrerpolicy: "no-referrer"
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
link_to value, value, target: "_blank"
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def blazer_maps?
|
|
32
|
+
ENV["MAPBOX_ACCESS_TOKEN"].present?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def blazer_js_var(name, value)
|
|
36
|
+
"var #{name} = #{blazer_json_escape(value.to_json(root: false))};".html_safe
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
|
|
40
|
+
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
|
41
|
+
|
|
42
|
+
# Prior to version 4.1 of rails double quotes were inadventently removed in json_escape.
|
|
43
|
+
# This adds the correct json_escape functionality to rails versions < 4.1
|
|
44
|
+
def blazer_json_escape(s)
|
|
45
|
+
if Rails::VERSION::STRING < "4.1"
|
|
46
|
+
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
|
|
47
|
+
s.html_safe? ? result.html_safe : result
|
|
48
|
+
else
|
|
49
|
+
json_escape(s)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def blazer_series_name(k)
|
|
54
|
+
k.nil? ? "null" : k.to_s
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class CheckMailer < ActionMailer::Base
|
|
3
|
+
include ActionView::Helpers::TextHelper
|
|
4
|
+
|
|
5
|
+
default from: Blazer.from_email if Blazer.from_email
|
|
6
|
+
layout false
|
|
7
|
+
|
|
8
|
+
def state_change(check, state, state_was, rows_count, error, columns, rows, column_types, check_type)
|
|
9
|
+
@check = check
|
|
10
|
+
@state = state
|
|
11
|
+
@state_was = state_was
|
|
12
|
+
@rows_count = rows_count
|
|
13
|
+
@error = error
|
|
14
|
+
@columns = columns
|
|
15
|
+
@rows = rows
|
|
16
|
+
@column_types = column_types
|
|
17
|
+
@check_type = check_type
|
|
18
|
+
mail to: check.emails, reply_to: check.emails, subject: "Check #{state.titleize}: #{check.query.name}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failing_checks(email, checks)
|
|
22
|
+
@checks = checks
|
|
23
|
+
# add reply_to for mailing lists
|
|
24
|
+
mail to: email, reply_to: email, subject: "#{pluralize(checks.size, "Check")} Failing"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
module Blazer
|
|
4
|
+
class SlackNotifier
|
|
5
|
+
def self.state_change(check, state, state_was, rows_count, error, check_type)
|
|
6
|
+
check.split_slack_channels.each do |channel|
|
|
7
|
+
text =
|
|
8
|
+
if error
|
|
9
|
+
error
|
|
10
|
+
elsif rows_count > 0 && check_type == "bad_data"
|
|
11
|
+
pluralize(rows_count, "row")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
payload = {
|
|
15
|
+
channel: channel,
|
|
16
|
+
attachments: [
|
|
17
|
+
{
|
|
18
|
+
title: escape("Check #{state.titleize}: #{check.query.name}"),
|
|
19
|
+
title_link: query_url(check.query_id),
|
|
20
|
+
text: escape(text),
|
|
21
|
+
color: state == "passing" ? "good" : "danger"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
post(Blazer.slack_webhook_url, payload)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.failing_checks(channel, checks)
|
|
31
|
+
text =
|
|
32
|
+
checks.map do |check|
|
|
33
|
+
"<#{query_url(check.query_id)}|#{escape(check.query.name)}> #{escape(check.state)}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
payload = {
|
|
37
|
+
channel: channel,
|
|
38
|
+
attachments: [
|
|
39
|
+
{
|
|
40
|
+
title: escape("#{pluralize(checks.size, "Check")} Failing"),
|
|
41
|
+
text: text.join("\n"),
|
|
42
|
+
color: "warning"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
post(Blazer.slack_webhook_url, payload)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# https://api.slack.com/docs/message-formatting#how_to_escape_characters
|
|
51
|
+
# - Replace the ampersand, &, with &
|
|
52
|
+
# - Replace the less-than sign, < with <
|
|
53
|
+
# - Replace the greater-than sign, > with >
|
|
54
|
+
# That's it. Don't HTML entity-encode the entire message.
|
|
55
|
+
def self.escape(str)
|
|
56
|
+
str.gsub("&", "&").gsub("<", "<").gsub(">", ">") if str
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.pluralize(*args)
|
|
60
|
+
ActionController::Base.helpers.pluralize(*args)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.query_url(id)
|
|
64
|
+
Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.post(url, payload)
|
|
68
|
+
uri = URI.parse(url)
|
|
69
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
70
|
+
http.use_ssl = true
|
|
71
|
+
http.open_timeout = 3
|
|
72
|
+
http.read_timeout = 5
|
|
73
|
+
http.post(uri.request_uri, payload.to_json)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Check < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
belongs_to :query
|
|
5
|
+
|
|
6
|
+
validates :query_id, presence: true
|
|
7
|
+
validate :validate_emails
|
|
8
|
+
validate :validate_variables, if: -> { query_id_changed? }
|
|
9
|
+
|
|
10
|
+
before_validation :set_state
|
|
11
|
+
before_validation :fix_emails
|
|
12
|
+
|
|
13
|
+
def split_emails
|
|
14
|
+
emails.to_s.downcase.split(",").map(&:strip)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def split_slack_channels
|
|
18
|
+
if Blazer.slack?
|
|
19
|
+
slack_channels.to_s.downcase.split(",").map(&:strip)
|
|
20
|
+
else
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def update_state(result)
|
|
26
|
+
check_type =
|
|
27
|
+
if respond_to?(:check_type)
|
|
28
|
+
self.check_type
|
|
29
|
+
elsif respond_to?(:invert)
|
|
30
|
+
invert ? "missing_data" : "bad_data"
|
|
31
|
+
else
|
|
32
|
+
"bad_data"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
message = result.error
|
|
36
|
+
|
|
37
|
+
self.state =
|
|
38
|
+
if result.timed_out?
|
|
39
|
+
"timed out"
|
|
40
|
+
elsif result.error
|
|
41
|
+
"error"
|
|
42
|
+
elsif check_type == "anomaly"
|
|
43
|
+
anomaly, message = result.detect_anomaly
|
|
44
|
+
if anomaly.nil?
|
|
45
|
+
"error"
|
|
46
|
+
elsif anomaly
|
|
47
|
+
"failing"
|
|
48
|
+
else
|
|
49
|
+
"passing"
|
|
50
|
+
end
|
|
51
|
+
elsif result.rows.any?
|
|
52
|
+
check_type == "missing_data" ? "passing" : "failing"
|
|
53
|
+
else
|
|
54
|
+
check_type == "missing_data" ? "failing" : "passing"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
self.last_run_at = Time.now if respond_to?(:last_run_at=)
|
|
58
|
+
self.message = message if respond_to?(:message=)
|
|
59
|
+
|
|
60
|
+
if respond_to?(:timeouts=)
|
|
61
|
+
if result.timed_out?
|
|
62
|
+
self.timeouts += 1
|
|
63
|
+
self.state = "disabled" if timeouts >= 3
|
|
64
|
+
else
|
|
65
|
+
self.timeouts = 0
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# do not notify on creation, except when not passing
|
|
70
|
+
if (state_was != "new" || state != "passing") && state != state_was && emails.present?
|
|
71
|
+
Blazer::CheckMailer.state_change(self, state, state_was, result.rows.size, message, result.columns, result.rows.first(10).as_json, result.column_types, check_type).deliver_now
|
|
72
|
+
Blazer::SlackNotifier.state_change(self, state, state_was, result.rows.size, message, check_type)
|
|
73
|
+
end
|
|
74
|
+
save! if changed?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def set_state
|
|
80
|
+
self.state ||= "new"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def fix_emails
|
|
84
|
+
# some people like doing ; instead of ,
|
|
85
|
+
# but we know what they mean, so let's fix it
|
|
86
|
+
# also, some people like to use whitespace
|
|
87
|
+
if emails.present?
|
|
88
|
+
self.emails = emails.strip.gsub(/[;\s]/, ",").gsub(/,+/, ", ")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_emails
|
|
93
|
+
unless split_emails.all? { |e| e =~ /\A\S+@\S+\.\S+\z/ }
|
|
94
|
+
errors.add(:base, "Invalid emails")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_variables
|
|
99
|
+
if query.variables.any?
|
|
100
|
+
errors.add(:base, "Query can't have variables")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Dashboard < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
has_many :dashboard_queries, dependent: :destroy
|
|
5
|
+
has_many :queries, through: :dashboard_queries
|
|
6
|
+
|
|
7
|
+
validates :name, presence: true
|
|
8
|
+
|
|
9
|
+
def to_param
|
|
10
|
+
[id, name.gsub("'", "").parameterize].join("-")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Query < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
has_many :checks, dependent: :destroy
|
|
5
|
+
has_many :dashboard_queries, dependent: :destroy
|
|
6
|
+
has_many :dashboards, through: :dashboard_queries
|
|
7
|
+
has_many :audits
|
|
8
|
+
|
|
9
|
+
validates :statement, presence: true
|
|
10
|
+
|
|
11
|
+
scope :named, -> { where("blazer_queries.name <> ''") }
|
|
12
|
+
|
|
13
|
+
def to_param
|
|
14
|
+
[id, name].compact.join("-").gsub("'", "").parameterize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def friendly_name
|
|
18
|
+
name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def viewable?(user)
|
|
22
|
+
if Blazer.query_viewable
|
|
23
|
+
Blazer.query_viewable.call(self, user)
|
|
24
|
+
else
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def editable?(user)
|
|
30
|
+
editable = !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
|
|
31
|
+
editable &&= viewable?(user)
|
|
32
|
+
editable &&= Blazer.query_editable.call(self, user) if Blazer.query_editable
|
|
33
|
+
editable
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def variables
|
|
37
|
+
Blazer.extract_vars(statement)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="btn-group" style="vertical-align: top; margin-right: 5px;">
|
|
2
|
+
<%= link_to "Home", root_path, class: "btn btn-primary" %>
|
|
3
|
+
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
4
|
+
<span class="caret"></span>
|
|
5
|
+
<span class="sr-only">Toggle Dropdown</span>
|
|
6
|
+
</button>
|
|
7
|
+
<ul class="dropdown-menu">
|
|
8
|
+
<li><%= link_to "Dashboards", dashboards_path %></li>
|
|
9
|
+
<li><%= link_to "Checks", checks_path %></li>
|
|
10
|
+
<li role="separator" class="divider"></li>
|
|
11
|
+
<li><%= link_to "New Query", new_query_path %></li>
|
|
12
|
+
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
13
|
+
<% check_params = @query ? {query_id: @query.id} : {} %>
|
|
14
|
+
<li><%= link_to "New Check", new_check_path(check_params) %></li>
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<% if @bind_vars.any? %>
|
|
2
|
+
<form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 10px;">
|
|
3
|
+
<% date_vars = ["start_time", "end_time"] %>
|
|
4
|
+
<% if (date_vars - @bind_vars).empty? %>
|
|
5
|
+
<% @bind_vars = @bind_vars - date_vars %>
|
|
6
|
+
<% else %>
|
|
7
|
+
<% date_vars = nil %>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<% @bind_vars.each_with_index do |var, i| %>
|
|
11
|
+
<%= label_tag var, var %>
|
|
12
|
+
<% if (data = @smart_vars[var]) %>
|
|
13
|
+
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
|
|
14
|
+
<script>
|
|
15
|
+
$("#<%= var %>").selectize({
|
|
16
|
+
create: true
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
<% else %>
|
|
20
|
+
<%= text_field_tag var, params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !params[var], class: "form-control" %>
|
|
21
|
+
<% if var.end_with?("_at") %>
|
|
22
|
+
<script>
|
|
23
|
+
$("#<%= var %>").daterangepicker({singleDatePicker: true, locale: {format: "YYYY-MM-DD"}, autoUpdateInput: false});
|
|
24
|
+
// hack to start with empty date
|
|
25
|
+
$("#<%= var %>").on("apply.daterangepicker", function(ev, picker) {
|
|
26
|
+
$(this).val(picker.startDate.format("YYYY-MM-DD"));
|
|
27
|
+
$(this).change();
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<% if date_vars %>
|
|
35
|
+
<% date_vars.each do |var| %>
|
|
36
|
+
<%= hidden_field_tag var, params[var] %>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<%= label_tag nil, date_vars.join(" & ") %>
|
|
40
|
+
<div class="selectize-control single" style="width: 300px;">
|
|
41
|
+
<div id="reportrange" class="selectize-input" style="display: inline-block;">
|
|
42
|
+
<span>Select a time range</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
|
48
|
+
var format = "YYYY-MM-DD"
|
|
49
|
+
var now = moment.tz(timeZone)
|
|
50
|
+
|
|
51
|
+
function dateStr(daysAgo) {
|
|
52
|
+
return now.clone().subtract(daysAgo || 0, "days").format(format)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toDate(time) {
|
|
56
|
+
return moment.tz(time.format(format), timeZone)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setTimeInputs(start, end) {
|
|
60
|
+
$("#start_time").val(toDate(start).utc().format())
|
|
61
|
+
$("#end_time").val(toDate(end).endOf("day").utc().format())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$("#reportrange").daterangepicker(
|
|
65
|
+
{
|
|
66
|
+
ranges: {
|
|
67
|
+
"Today": [dateStr(), dateStr()],
|
|
68
|
+
"Last 7 Days": [dateStr(6), dateStr()],
|
|
69
|
+
"Last 30 Days": [dateStr(29), dateStr()]
|
|
70
|
+
},
|
|
71
|
+
locale: {
|
|
72
|
+
format: format
|
|
73
|
+
},
|
|
74
|
+
startDate: dateStr(29),
|
|
75
|
+
endDate: dateStr(),
|
|
76
|
+
opens: "right"
|
|
77
|
+
},
|
|
78
|
+
function(start, end) {
|
|
79
|
+
setTimeInputs(start, end)
|
|
80
|
+
submitIfCompleted($("#start_time").closest("form"))
|
|
81
|
+
}
|
|
82
|
+
).on("apply.daterangepicker", function(ev, picker) {
|
|
83
|
+
setTimeInputs(picker.startDate, picker.endDate)
|
|
84
|
+
$("#reportrange span").html(toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY"))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if ($("#start_time").val().length > 0) {
|
|
88
|
+
var picker = $("#reportrange").data("daterangepicker")
|
|
89
|
+
picker.setStartDate(moment.tz($("#start_time").val(), timeZone))
|
|
90
|
+
picker.setEndDate(moment.tz($("#end_time").val(), timeZone))
|
|
91
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
|
92
|
+
} else {
|
|
93
|
+
var picker = $("#reportrange").data("daterangepicker")
|
|
94
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
|
95
|
+
submitIfCompleted($("#start_time").closest("form"))
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
<% end %>
|
|
99
|
+
|
|
100
|
+
<input type="submit" class="btn btn-success" value="Run" style="vertical-align: top;" />
|
|
101
|
+
</form>
|
|
102
|
+
<% end %>
|