diff --git a/lib/resque_bus/server.rb b/lib/resque_bus/server.rb index d26eb62..9d01bad 100644 --- a/lib/resque_bus/server.rb +++ b/lib/resque_bus/server.rb @@ -22,6 +22,125 @@ def self.included(base) } end + + class Helpers + class << self + def parse_query(query_string) + query_string = query_string.to_s.strip + has_open_brace = query_string.include?("{") + has_close_brace = query_string.include?("}") + has_multiple_lines = query_string.include?("\n") + has_colon = query_string.include?(":") + has_comma = query_string.include?(",") + has_quote = query_string.include?("\"") + + exception = nil + + # first let's see if it parses + begin + query_attributes = JSON.parse(query_string) + raise "Not a JSON Object" unless query_attributes.is_a?(Hash) + rescue StandardError => e + exception = e + end + return query_attributes unless exception + + if query_attributes + # it parsed but it's something else + if query_attributes.is_a?(Array) && query_attributes.length == 1 + # maybe it's pasted from the inputs in the web UI like queues/bus_incoming + # this is an array (of job arguments) and the first one is a JSON string + json_string = query_attributes.first + fixed = JSON.parse(json_string) rescue nil + return fixed if fixed + end + + # something else? + raise exception + end + + if !has_open_brace && !has_close_brace + # maybe they just forgot the braces + fixed = JSON.parse("{ #{query_string} }") rescue nil + return fixed if fixed + end + + if !has_open_brace + # maybe they just forgot the braces + fixed = JSON.parse("{ #{query_string}") rescue nil + return fixed if fixed + end + + if !has_close_brace + # maybe they just forgot the braces + fixed = JSON.parse("#{query_string} }") rescue nil + return fixed if fixed + end + + if !has_multiple_lines && !has_colon && !has_open_brace && !has_close_brace + # we say they just put a bus_event type here, so help them out + return {"bus_event_type" => query_string, "more_here" => true} + end + + if has_colon && !has_quote + # maybe it's some search syntax like this: field: value other: true, etc + # maybe use something like this later: https://github.com/dxwcyber/search-query-parser + + # quote all the strings, (simply) tries to avoid integers + test_query = query_string.gsub(/([a-zA-z]\w*)/,'"\0"') + if !has_comma + test_query.gsub!("\n", ",\n") + end + if !has_open_brace && !has_close_brace + test_query = "{ #{test_query} }" + end + + fixed = JSON.parse(test_query) rescue nil + return fixed if fixed + end + + if has_open_brace && has_close_brace + # maybe the whole thing is a hash output from a hash.inspect log + ruby_hash_text = query_string.clone + # https://stackoverflow.com/questions/1667630/how-do-i-convert-a-string-object-into-a-hash-object + # Transform object string symbols to quoted strings + ruby_hash_text.gsub!(/([{,]\s*):([^>\s]+)\s*=>/, '\1"\2"=>') + # Transform object string numbers to quoted strings + ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '\1"\2"=>') + # Transform object value symbols to quotes strings + ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '\1\2=>"\3"') + # Transform array value symbols to quotes strings + ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '\1"\2"') + # fix up nil situation + ruby_hash_text.gsub!(/=>nil/, '=>null') + # Transform object string object value delimiter to colon delimiter + ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, '\1\2:') + fixed = JSON.parse(ruby_hash_text) rescue nil + return fixed if fixed + end + + raise exception + end + + def sort_query(query_attributes) + query_attributes.each do |key, value| + if value.is_a?(Hash) + query_attributes[key] = sort_query(value) + end + end + query_attributes.sort_by { |key| key }.to_h + end + + def query_subscriptions(app, query_attributes) + # TODO: all of this can move to method in queue-bus to replace event_display_tuples + if query_attributes + subscriptions = app.subscription_matches(query_attributes) + else + subscriptions = app.send(:subscriptions).all + end + end + end + end end end diff --git a/lib/resque_bus/server/views/bus.erb b/lib/resque_bus/server/views/bus.erb index 312b691..aba5b9b 100644 --- a/lib/resque_bus/server/views/bus.erb +++ b/lib/resque_bus/server/views/bus.erb @@ -9,6 +9,13 @@ if (agree) else return false ; } + +function setSample() { + var text = document.getElementById("query_attributes").textContent; + var textArea = document.getElementById('querytext'); + textArea.value = text; + return false; +} // --> @@ -16,27 +23,50 @@ else app_hash = {} class_hash = {} event_hash = {} + query_string = params[:query].to_s.strip + + query_attributes = nil + query_error = nil + if query_string.length > 0 + begin + query_attributes = ::ResqueBus::Server::Helpers.parse_query(query_string) + raise "Not a JSON Object" unless query_attributes.is_a?(Hash) + rescue Exception => e + query_attributes = nil + query_error = e.message + end + + if query_attributes + # sort keys for display + query_attributes = ::ResqueBus::Server::Helpers.sort_query(query_attributes) + end + end # collect each differently ::QueueBus::Application.all.each do |app| app_key = app.app_key - app_hash[app_key] ||= [] - app.event_display_tuples.each do |tuple| - class_name, queue, filters = tuple + subscriptions = ::ResqueBus::Server::Helpers.query_subscriptions(app, query_attributes) + subscriptions.each do |sub| + class_name = sub.class_name + queue = sub.queue_name + filters = sub.matcher.filters + sub_key = sub.key + if filters["bus_event_type"] event = filters["bus_event_type"] else event = "see filter" end - app_hash[app_key] << [event, class_name, queue, filters] + app_hash[app_key] ||= [] + app_hash[app_key] << [sub_key, event, class_name, queue, filters] class_hash[class_name] ||= [] - class_hash[class_name] << [app_key, event, queue, filters] + class_hash[class_name] << [app_key, sub_key, event, queue, filters] event_hash[event] ||= [] - event_hash[event] << [app_key, class_name, queue, filters] + event_hash[event] << [app_key, sub_key, class_name, queue, filters] end end @@ -53,14 +83,14 @@ else form = "" if button text, url = button - form = "
" + form = "
" end if !val out = "  " else - one, two, queue, filters = val - out = "#{h(one)}#{h(two)}#{h(queue)}" + one, two, three, queue, filters = val + out = "#{h(one)}#{h(two)}#{h(three)}#{h(queue)}" out << "#{h(::QueueBus::Util.encode(filters).gsub(/\"bus_special_value_(\w+)\"/){ "(#{$1})" }).gsub(" ", " ").gsub('","', '", "')}" end @@ -85,19 +115,48 @@ else end %> +

Sample Event

+

Enter JSON of an event to see applicable subscriptions.

+
+
" style="float:none;padding:0;margin:0;"> + +
+ + +
+
+<% if query_error %> +
<%= 
+  h(query_error.strip)
+  %>
+<% end %> +<% if query_attributes %> +
<%= 
+  h(JSON.pretty_generate(query_attributes).strip)
+  %>
+
+ +
+<% end %> + +

Applications

The apps below have registered the given classes and queues.

+ - <%= output_hash(app_hash, ["Unsubscribe", "bus/unsubscribe"]) %> + + <%= output_hash(app_hash, query_attributes ? false : ["Unsubscribe", "bus/unsubscribe"]) %>
App KeySubscription Key Event Type Class Name Queue Filters

 

@@ -108,6 +167,7 @@ else Event Type App Key + Subscription Key Class Name Queue Filters @@ -125,6 +185,7 @@ else Class Name App Key + Subscription Key Event Type Queue Filters diff --git a/spec/server_helper_spec.rb b/spec/server_helper_spec.rb new file mode 100644 index 0000000..0ee8d8e --- /dev/null +++ b/spec/server_helper_spec.rb @@ -0,0 +1,114 @@ +require 'spec_helper' +require_relative '../lib/resque_bus/server' + +describe "Web Server Helper" do + describe ".parse_query" do + it "should pass through valid json" do + input = %Q{ + { "name": "here", "number": 1, "bool": true } + } + output = {"name"=>"here", "number"=>1, "bool"=>true} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should handle multi-line json" do + input = %Q{ + { + "name": "here", + "number": 1, + "bool": true + } + } + output = {"name"=>"here", "number"=>1, "bool"=>true} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should interpret simple string as bus_event_type" do + input = %Q{ user_created } + output = {"bus_event_type" => "user_created", "more_here" => true} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should raise error on valid json that's not an Object" do + input = '[{ "name": "here" }]' + lambda { + ::ResqueBus::Server::Helpers.parse_query(input) + }.should raise_error("Not a JSON Object") + end + + it "should allow array from resque server panel with encoded json string arg array" do + input = '["{\"name\":\"here\"}"]' + output = {"name" => "here"} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should take in a arg list and make it json" do + input = %Q{ + bus_event_type: my_event + user_updated: true + } + output = {"bus_event_type" => "my_event", "user_updated" => "true"} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should take in an arg list with quoted json and commas" do + input = %Q{ + "bus_event_type": "my_event", + "user_updated": true + } + output = {"bus_event_type" => "my_event", "user_updated" => true} + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should parse logged output from event.inspect" do + input = %Q{ + {"bus_published_at"=>1563793250, "bus_event_type"=>"user_created", :user_id=>42, :name=>"Brian" } + } + output = { + "bus_published_at" => 1563793250, + "bus_event_type" => "user_created", + "user_id" => 42, + "name" => "Brian" + } + check = ::ResqueBus::Server::Helpers.parse_query(input) + check.should == output + end + + it "should throw json parse error when it can't be handled" do + input = '{ "name": "here" q }' + lambda { + ::ResqueBus::Server::Helpers.parse_query(input) + }.should raise_error(/unexpected token/) + end + end + + describe ".sort_query" do + it "should alphabetize a query hash" do + input = {"cat" => true, "apple" => true, "dog" => true, "bear" => true} + output = {"apple" => true, "bear" => true, "cat" => true, "dog" => true } + check = ::ResqueBus::Server::Helpers.sort_query(input) + check.should == output + end + + it "should alphabetize a query sub-hashes but not arrays" do + input = {"cat" => true, "apple" => [ + "jackal", "kangaroo", "iguana" + ], "dog" => { + "frog" => 11, "elephant" => 12, "hare" => 16, "goat" => 14 + }, "bear" => true} + output = {"apple" => [ + "jackal", "kangaroo", "iguana" + ], "bear" => true, "cat" => true, "dog" => { + "elephant" => 12, "frog" => 11, "goat" => 14, "hare" => 16 + }} + check = ::ResqueBus::Server::Helpers.sort_query(input) + check.should == output + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6b6c896..54b2685 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -60,7 +60,7 @@ def test_list(*args) end config.after(:each) do begin - QueueBus.redis { |redis| redis.flushall } + QueueBus.redis { |redis| redis.flushdb } rescue end QueueBus.send(:reset)