/
app.rb
168 lines (137 loc) · 7.13 KB
/
app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
class App < Jsonatra::Base
ds = SQL["UPDATE stats SET num=num + ?::int
WHERE group_id=?::text AND client_id=?::text AND date=?::timestamp AND key=?::text AND value=?::text", :$number, :$group, :$client, :$date, :$key, :$value]
ds.prepare(:update, :update_stat)
ds = SQL["UPDATE stats SET num=num + ?::int
WHERE group_id=?::text AND client_id=?::text AND date=?::timestamp AND hour=?::int AND key=?::text AND value=?::text", :$number, :$group, :$client, :$date, :$hour, :$key, :$value]
ds.prepare(:update, :update_stat_hour)
ds = SQL["INSERT INTO stats (group_id, client_id, date, key, value, num)
SELECT ?::text, ?::text, ?::timestamp, ?::text, ?::text, ?::int
WHERE NOT EXISTS (SELECT 1 FROM stats
WHERE group_id=?::text AND client_id=?::text AND date=?::timestamp AND key=?::text AND value=?::text)", :$group, :$client, :$date, :$key, :$value, :$number, :$group, :$client, :$date, :$key, :$value]
ds.prepare(:insert, :insert_stat)
ds = SQL["INSERT INTO stats (group_id, client_id, date, hour, key, value, num)
SELECT ?::text, ?::text, ?::timestamp, ?::int, ?::text, ?::text, ?::int
WHERE NOT EXISTS (SELECT 1 FROM stats
WHERE group_id=?::text AND client_id=?::text AND date=?::timestamp AND hour=?::int AND key=?::text AND value=?::text)", :$group, :$client, :$date, :$hour, :$key, :$value, :$number, :$group, :$client, :$date, :$hour, :$key, :$value]
ds.prepare(:insert, :insert_stat_hour)
configure do
set :arrayified_params, [:keys]
end
get '/' do
{
hello: 'world'
}
end
post '/report' do
param_error :date, 'missing', 'date parameter required' if params[:date].blank?
# group_id is allowed to be null
param_error :client_id, 'missing', 'client_id parameter required' if params[:client_id].blank?
param_error :key, 'missing', 'key parameter required' if params[:key].blank?
param_error :value, 'missing', 'value parameter required' if params[:value].blank?
param_error :number, 'missing', 'number parameter required' if params[:number].blank?
param_error :number, 'invalid', 'number must be an integer' unless params[:number] && params[:number].to_s.match(/^[0-9]+$/)
params[:precision] = 'day' if params[:precision].blank?
param_error :precision, 'invalid', 'precision must be day or hour' unless ['day','hour'].include? params[:precision]
# If precision is "hour" then the date must be a full timestamp
if params[:precision] == 'day'
param_error :date, 'invalid', 'date parameter should look like YYYY-MM-DD for day precision' unless params[:date] && params[:date].to_s.match(/^\d{4}-\d{2}-\d{2}$/)
else
param_error :date, 'invalid', 'date parameter should look like "YYYY-MM-DD HH:mm:ss" for hour precision' unless params[:date] && params[:date].to_s.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)
end
halt if response.error?
params[:value] = params[:value].to_s.scrub
if params[:precision] == 'day'
SQL.call :update_stat, :number => params[:number], :group => (params[:group_id] || ''), :client => params[:client_id], :date => params[:date], :key => params[:key], :value => params[:value]
SQL.call :insert_stat, :number => params[:number], :group => (params[:group_id] || ''), :client => params[:client_id], :date => params[:date], :key => params[:key], :value => params[:value]
else
date = params[:date].match(/^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}$/)[1]
hour = params[:date].match(/^\d{4}-\d{2}-\d{2} (\d{2}):\d{2}:\d{2}$/)[1]
SQL.call :update_stat_hour, :number => params[:number], :group => (params[:group_id] || ''), :client => params[:client_id], :date => date, :hour => hour, :key => params[:key], :value => params[:value]
SQL.call :insert_stat_hour, :number => params[:number], :group => (params[:group_id] || ''), :client => params[:client_id], :date => date, :hour => hour, :key => params[:key], :value => params[:value]
end
{
result: "ok"
}
end
get '/query' do
param_error :keys, 'missing', 'keys parameter required' if params[:keys] == nil || params[:keys].size == 0
param_error :value, 'invalid', 'value parameter cannot be specified when requesting multiple keys' if (params[:value] || !params[:value].blank?) and params[:keys].size > 1
param_error :from, 'invalid', 'date parameter should look like YYYY-MM-DD' if params[:from] && !params[:from].to_s.match(/^\d{4}-\d{2}-\d{2}$/)
param_error :to, 'invalid', 'date parameter should look like YYYY-MM-DD' if params[:to] && !params[:to].to_s.match(/^\d{4}-\d{2}-\d{2}$/)
# jsonatra doesn't split query string parameters into an array right now
# https://github.com/esripdx/jsonatra/issues/3
if String === params[:value]
params[:value] = params[:value].split ','
end
if params[:format] == 'panic'
param_error :keys, 'invalid', 'only one key can be specified when format=panic' if params[:keys].size > 1
end
halt if response.error?
stats = STATS.select_group(:date, :key, :value)
stats.select_append!{sum(num).as(num)}
stats.filter!(key: params[:keys])
# filter by optional arguments
stats.filter!(group_id: params[:group_id]) if params[:group_id]
stats.filter!(client_id: params[:client_id]) if params[:client_id]
stats.filter!(value: params[:value]) if params[:value]
stats.filter!("date >= ?", params[:from]) if params[:from]
stats.filter!("date <= ?", params[:to]) if params[:to]
stats.order_by!(:date, :key, :value)
if params[:format] == 'panic'
# JSON format for Panic StatusBoard
# Collect all the dates
dates = stats.map{|s| s[:date]}.uniq.sort
# Set the values for each date for each key to 0, to ensure each key has values for each date
datapoints = {}
dates.each do |d|
datapoints[d.strftime('%b %-d')] = {
:title => d.strftime('%b %-d'),
:value => 0
}
end
results = {}
stats.each do |s|
value = s[:value]
# Set up the initial data
if results[value].nil?
results[value] = {
:title => value,
:datapoints => datapoints.clone
}
end
results[value][:datapoints][s[:date].strftime('%b %-d')] = {
:title => s[:date].strftime('%b %-d'),
:value => s[:num]
}
end
sequences = results.values
# Remove the date key from the datapoints objects
sequences.each do |s|
s[:datapoints] = s[:datapoints].values
end
{
graph: {
title: (params[:title] || params[:keys][0].split('_').map{|w| w.capitalize}.join(' ')),
refreshEveryNSeconds: 120,
type: "bar",
datasequences: sequences
}
}
else
# Standard JSON format
results = {}
stats.each{ |s|
key = s[:key]
date = s[:date]
results[date] = results[date] || {}
results[date][key] = results[date][key] || {}
results[date][key][s[:value]] = s[:num]
results[date][:date] = date.strftime '%Y-%m-%d'
}
{
data: results.values
}
end
end
end