blazer 2.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of blazer might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +310 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +1041 -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/images/blazer/favicon.png +0 -0
- data/app/assets/javascripts/blazer/Chart.js +14456 -0
- data/app/assets/javascripts/blazer/Sortable.js +1540 -0
- data/app/assets/javascripts/blazer/ace.js +6 -0
- data/app/assets/javascripts/blazer/ace/ace.js +21301 -0
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1993 -0
- data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -0
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -0
- data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -0
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -0
- data/app/assets/javascripts/blazer/application.js +81 -0
- data/app/assets/javascripts/blazer/bootstrap.js +2377 -0
- data/app/assets/javascripts/blazer/chartkick.js +2214 -0
- data/app/assets/javascripts/blazer/daterangepicker.js +1653 -0
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/highlight.min.js +3 -0
- data/app/assets/javascripts/blazer/jquery-ujs.js +555 -0
- data/app/assets/javascripts/blazer/jquery.js +10364 -0
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -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 +26 -0
- data/app/assets/javascripts/blazer/selectize.js +3891 -0
- data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
- data/app/assets/javascripts/blazer/stupidtable.js +281 -0
- data/app/assets/javascripts/blazer/vue.js +10947 -0
- data/app/assets/stylesheets/blazer/application.css +234 -0
- data/app/assets/stylesheets/blazer/bootstrap.css.erb +6756 -0
- data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
- data/app/assets/stylesheets/blazer/github.css +125 -0
- data/app/assets/stylesheets/blazer/selectize.css +403 -0
- data/app/controllers/blazer/base_controller.rb +124 -0
- data/app/controllers/blazer/checks_controller.rb +56 -0
- data/app/controllers/blazer/dashboards_controller.rb +101 -0
- data/app/controllers/blazer/queries_controller.rb +347 -0
- data/app/helpers/blazer/base_helper.rb +43 -0
- data/app/mailers/blazer/check_mailer.rb +27 -0
- data/app/mailers/blazer/slack_notifier.rb +79 -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 +17 -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 +15 -0
- data/app/views/blazer/_variables.html.erb +124 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
- data/app/views/blazer/checks/_form.html.erb +79 -0
- data/app/views/blazer/checks/edit.html.erb +3 -0
- data/app/views/blazer/checks/index.html.erb +69 -0
- data/app/views/blazer/checks/new.html.erb +3 -0
- data/app/views/blazer/dashboards/_form.html.erb +76 -0
- data/app/views/blazer/dashboards/edit.html.erb +3 -0
- data/app/views/blazer/dashboards/new.html.erb +3 -0
- data/app/views/blazer/dashboards/show.html.erb +51 -0
- data/app/views/blazer/queries/_form.html.erb +250 -0
- data/app/views/blazer/queries/docs.html.erb +131 -0
- data/app/views/blazer/queries/edit.html.erb +2 -0
- data/app/views/blazer/queries/home.html.erb +163 -0
- data/app/views/blazer/queries/new.html.erb +2 -0
- data/app/views/blazer/queries/run.html.erb +198 -0
- data/app/views/blazer/queries/schema.html.erb +55 -0
- data/app/views/blazer/queries/show.html.erb +75 -0
- data/app/views/layouts/blazer/application.html.erb +24 -0
- data/config/routes.rb +20 -0
- data/lib/blazer.rb +231 -0
- data/lib/blazer/adapters/athena_adapter.rb +129 -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/influxdb_adapter.rb +45 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/neo4j_adapter.rb +47 -0
- data/lib/blazer/adapters/presto_adapter.rb +45 -0
- data/lib/blazer/adapters/salesforce_adapter.rb +45 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
- data/lib/blazer/adapters/soda_adapter.rb +96 -0
- data/lib/blazer/adapters/sql_adapter.rb +221 -0
- data/lib/blazer/data_source.rb +195 -0
- data/lib/blazer/detect_anomalies.R +19 -0
- data/lib/blazer/engine.rb +43 -0
- data/lib/blazer/result.rb +218 -0
- data/lib/blazer/run_statement.rb +40 -0
- data/lib/blazer/run_statement_job.rb +18 -0
- data/lib/blazer/version.rb +3 -0
- data/lib/generators/blazer/install_generator.rb +22 -0
- data/lib/generators/blazer/templates/config.yml.tt +73 -0
- data/lib/generators/blazer/templates/install.rb.tt +46 -0
- data/lib/tasks/blazer.rake +11 -0
- metadata +231 -0
@@ -0,0 +1,43 @@
|
|
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?(Numeric) && !key.to_s.end_with?("id") && !key.to_s.start_with?("id")
|
16
|
+
number_with_delimiter(value)
|
17
|
+
elsif value.is_a?(String) && 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
|
+
Blazer.mapbox_access_token.present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def blazer_js_var(name, value)
|
36
|
+
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
37
|
+
end
|
38
|
+
|
39
|
+
def blazer_series_name(k)
|
40
|
+
k.nil? ? "null" : k.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
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,79 @@
|
|
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
|
+
# checks shouldn't have variables, but in any case,
|
64
|
+
# avoid passing variable params to url helpers
|
65
|
+
# (known unsafe parameters are removed, but blacklist isn't ideal)
|
66
|
+
def self.query_url(id)
|
67
|
+
Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.post(url, payload)
|
71
|
+
uri = URI.parse(url)
|
72
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
73
|
+
http.use_ssl = true
|
74
|
+
http.open_timeout = 3
|
75
|
+
http.read_timeout = 5
|
76
|
+
http.post(uri.request_uri, payload.to_json)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Blazer
|
2
|
+
class Check < Record
|
3
|
+
belongs_to :creator, optional: true, 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
|
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 if emails.present?
|
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,17 @@
|
|
1
|
+
module Blazer
|
2
|
+
class Dashboard < Record
|
3
|
+
belongs_to :creator, optional: true, 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 variables
|
10
|
+
queries.flat_map { |q| q.variables }.uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_param
|
14
|
+
[id, name.gsub("'", "").parameterize].join("-")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Blazer
|
2
|
+
class Query < Record
|
3
|
+
belongs_to :creator, optional: true, 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,15 @@
|
|
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 "Checks", checks_path %></li>
|
9
|
+
<li role="separator" class="divider"></li>
|
10
|
+
<li><%= link_to "New Query", new_query_path %></li>
|
11
|
+
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
12
|
+
<% check_params = @query ? {query_id: @query.id} : {} %>
|
13
|
+
<li><%= link_to "New Check", new_check_path(check_params) %></li>
|
14
|
+
</ul>
|
15
|
+
</div>
|
@@ -0,0 +1,124 @@
|
|
1
|
+
<% if @bind_vars.any? %>
|
2
|
+
<script>
|
3
|
+
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
4
|
+
var now = moment.tz(timeZone)
|
5
|
+
var format = "YYYY-MM-DD"
|
6
|
+
|
7
|
+
function toDate(time) {
|
8
|
+
return moment.tz(time.format(format), timeZone)
|
9
|
+
}
|
10
|
+
</script>
|
11
|
+
<form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 15px;">
|
12
|
+
<% date_vars = ["start_time", "end_time"] %>
|
13
|
+
<% if (date_vars - @bind_vars).empty? %>
|
14
|
+
<% @bind_vars = @bind_vars - date_vars %>
|
15
|
+
<% else %>
|
16
|
+
<% date_vars = nil %>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<% @bind_vars.each_with_index do |var, i| %>
|
20
|
+
<%= label_tag var, var %>
|
21
|
+
<% 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
|
+
<script>
|
24
|
+
$("#<%= var %>").selectize({
|
25
|
+
create: true
|
26
|
+
});
|
27
|
+
</script>
|
28
|
+
<% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
|
29
|
+
<%= hidden_field_tag var, params[var] %>
|
30
|
+
|
31
|
+
<div class="selectize-control single" style="width: 200px;">
|
32
|
+
<div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
|
33
|
+
<span>Select a date</span>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<script>
|
38
|
+
(function() {
|
39
|
+
var input = $("#<%= var %>")
|
40
|
+
var datePicker = $("#<%= var %>-select")
|
41
|
+
datePicker.daterangepicker({
|
42
|
+
singleDatePicker: true,
|
43
|
+
locale: {format: format},
|
44
|
+
autoUpdateInput: false,
|
45
|
+
startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
|
46
|
+
})
|
47
|
+
// hack to start with empty date
|
48
|
+
datePicker.on("apply.daterangepicker", function(ev, picker) {
|
49
|
+
datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
|
50
|
+
input.val(toDate(picker.startDate).utc().format())
|
51
|
+
submitIfCompleted($("#<%= var %>").closest("form"))
|
52
|
+
})
|
53
|
+
if (input.val().length > 0) {
|
54
|
+
var picker = datePicker.data("daterangepicker")
|
55
|
+
datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
|
56
|
+
}
|
57
|
+
})()
|
58
|
+
</script>
|
59
|
+
<% else %>
|
60
|
+
<%= text_field_tag var, params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !params[var], class: "form-control" %>
|
61
|
+
<% end %>
|
62
|
+
<% end %>
|
63
|
+
|
64
|
+
<% if date_vars %>
|
65
|
+
<% date_vars.each do |var| %>
|
66
|
+
<%= hidden_field_tag var, params[var] %>
|
67
|
+
<% end %>
|
68
|
+
|
69
|
+
<%= label_tag nil, date_vars.join(" & ") %>
|
70
|
+
<div class="selectize-control single" style="width: 300px;">
|
71
|
+
<div id="reportrange" class="selectize-input" style="display: inline-block;">
|
72
|
+
<span>Select a time range</span>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
<script>
|
77
|
+
function dateStr(daysAgo) {
|
78
|
+
return now.clone().subtract(daysAgo || 0, "days").format(format)
|
79
|
+
}
|
80
|
+
|
81
|
+
function setTimeInputs(start, end) {
|
82
|
+
$("#start_time").val(toDate(start).utc().format())
|
83
|
+
$("#end_time").val(toDate(end).endOf("day").utc().format())
|
84
|
+
}
|
85
|
+
|
86
|
+
$("#reportrange").daterangepicker(
|
87
|
+
{
|
88
|
+
ranges: {
|
89
|
+
"Today": [dateStr(), dateStr()],
|
90
|
+
"Last 7 Days": [dateStr(6), dateStr()],
|
91
|
+
"Last 30 Days": [dateStr(29), dateStr()]
|
92
|
+
},
|
93
|
+
locale: {
|
94
|
+
format: format
|
95
|
+
},
|
96
|
+
startDate: dateStr(29),
|
97
|
+
endDate: dateStr(),
|
98
|
+
opens: "right"
|
99
|
+
},
|
100
|
+
function(start, end) {
|
101
|
+
setTimeInputs(start, end)
|
102
|
+
submitIfCompleted($("#start_time").closest("form"))
|
103
|
+
}
|
104
|
+
).on("apply.daterangepicker", function(ev, picker) {
|
105
|
+
setTimeInputs(picker.startDate, picker.endDate)
|
106
|
+
$("#reportrange span").html(toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY"))
|
107
|
+
})
|
108
|
+
|
109
|
+
if ($("#start_time").val().length > 0) {
|
110
|
+
var picker = $("#reportrange").data("daterangepicker")
|
111
|
+
picker.setStartDate(moment.tz($("#start_time").val(), timeZone))
|
112
|
+
picker.setEndDate(moment.tz($("#end_time").val(), timeZone))
|
113
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
114
|
+
} else {
|
115
|
+
var picker = $("#reportrange").data("daterangepicker")
|
116
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
117
|
+
submitIfCompleted($("#start_time").closest("form"))
|
118
|
+
}
|
119
|
+
</script>
|
120
|
+
<% end %>
|
121
|
+
|
122
|
+
<input type="submit" class="btn btn-success" value="Run" style="vertical-align: top;" />
|
123
|
+
</form>
|
124
|
+
<% end %>
|