From 60985a33915da73281a73c06d06618ff2b6a85ba Mon Sep 17 00:00:00 2001 From: Zach Cotter Date: Wed, 21 Oct 2020 15:57:37 -0400 Subject: [PATCH] Show and index endpoints can be used as JSON api --- README.md | 9 ++++ lib/rollout/ui/helpers.rb | 24 ++++++++++ lib/rollout/ui/version.rb | 2 +- lib/rollout/ui/web.rb | 18 +++++-- rollout-ui.gemspec | 5 +- spec/rollout/ui/web_spec.rb | 93 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 8 ++++ 7 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 spec/rollout/ui/web_spec.rb diff --git a/README.md b/README.md index b146615..ca91c7c 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,15 @@ Rails.application.routes.draw do end ``` +## API Endpoints + +The index and show routes can also respond with JSON data instead of HTML when the request's `Accept` header is +`application/json` + +The index route also accepts query parameters to filter by user or group: +`/admin/rollout?user=someone` +`/admin/rollout?group=developers` + ## Logging To get the most out of **rollout-ui**, we recommend you to turn on logging diff --git a/lib/rollout/ui/helpers.rb b/lib/rollout/ui/helpers.rb index 1c0fe57..83ade66 100644 --- a/lib/rollout/ui/helpers.rb +++ b/lib/rollout/ui/helpers.rb @@ -84,5 +84,29 @@ def format_change_value(value) value end end + + def json_request? + request.env['HTTP_ACCEPT'] == 'application/json' + end + + # Filters features by user and group if those params are provided + def filtered_features(rollout, feature_names) + feature_names.select do |feature_name| + feature = rollout.get(feature_name) + user_match = params[:user].nil? || feature.users.member?(params[:user]) + group_match = params[:group].nil? || feature.groups.member?(params[:group].to_sym) + user_match && group_match + end + end + + # Returns a hash of feature data to be rendered as json + def feature_to_hash(feature) + { + data: feature.data, + groups: feature.groups, + name: feature.name, + percentage: feature.percentage + } + end end end diff --git a/lib/rollout/ui/version.rb b/lib/rollout/ui/version.rb index 3080102..ae5dfe3 100644 --- a/lib/rollout/ui/version.rb +++ b/lib/rollout/ui/version.rb @@ -1,5 +1,5 @@ class Rollout module UI - VERSION = "0.2.0" + VERSION = "0.3.0" end end diff --git a/lib/rollout/ui/web.rb b/lib/rollout/ui/web.rb index 39be013..6eb1f61 100644 --- a/lib/rollout/ui/web.rb +++ b/lib/rollout/ui/web.rb @@ -1,4 +1,5 @@ require "sinatra" +require "sinatra/json" require "rollout" require "rollout/ui/version" @@ -15,8 +16,15 @@ class Web < Sinatra::Base get '/' do @rollout = config.get(:instance) @features = @rollout.features.sort_by(&:downcase) - - slim :'features/index' + if json_request? + json( + filtered_features(@rollout, @features).map do |feature| + feature_to_hash(@rollout.get(feature)) + end + ) + else + slim :'features/index' + end end get '/features/new' do @@ -31,7 +39,11 @@ class Web < Sinatra::Base @rollout = config.get(:instance) @feature = @rollout.get(params[:feature_name]) - slim :'features/show' + if json_request? + json(feature_to_hash(@feature)) + else + slim :'features/show' + end end post '/features/:feature_name' do diff --git a/rollout-ui.gemspec b/rollout-ui.gemspec index d598fa8..b72ca3d 100644 --- a/rollout-ui.gemspec +++ b/rollout-ui.gemspec @@ -24,10 +24,13 @@ Gem::Specification.new do |spec| spec.add_dependency 'rollout', '~> 2.5' spec.add_dependency 'sinatra', '~> 2.0' + spec.add_dependency 'sinatra-contrib', '~> 2.1' spec.add_dependency 'slim', '~> 4.0' - spec.add_development_dependency 'bundler', '~> 1.17' + spec.add_development_dependency 'bundler', '>= 1.17' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'rerun', '~> 0.13' + spec.add_development_dependency 'rack-test' + spec.add_development_dependency 'pry' end diff --git a/spec/rollout/ui/web_spec.rb b/spec/rollout/ui/web_spec.rb new file mode 100644 index 0000000..4403ed8 --- /dev/null +++ b/spec/rollout/ui/web_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' +ENV['APP_ENV'] = 'test' +RSpec.describe 'Web UIp' do + include Rack::Test::Methods + + def app + Rollout::UI::Web + end + + it "renders index html" do + get '/' + expect(last_response.body.include?('Rollout UI')) + expect(last_response.status == 200) + end + + it "renders index json" do + ROLLOUT.activate(:fake_test_feature_for_rollout_ui_webspec) + header 'Accept', 'application/json' + get '/' + expect(last_response.status == 200) + expect(last_response.headers['Content-Type'] == 'application/json') + response = JSON.parse(last_response.body) + expected_response = { + "data"=>{}, + "groups"=>[], + "name"=>"fake_test_feature_for_rollout_ui_webspec", + "percentage"=>100.0 + } + expect(response).to(include(expected_response)) + ROLLOUT.delete(:fake_test_feature_for_rollout_ui_webspec) + end + + it "renders index json filtered by user and group" do + ROLLOUT.deactivate(:fake_test_feature_for_rollout_ui_webspec) + ROLLOUT.activate_user(:fake_test_feature_for_rollout_ui_webspec, 'fake_user') + ROLLOUT.activate_group(:fake_test_feature_for_rollout_ui_webspec, :fake_group) + + header 'Accept', 'application/json' + get '/?user=different_user' + expect(last_response.status == 200) + expect(last_response.headers['Content-Type'] == 'application/json') + response = JSON.parse(last_response.body) + expect(response == []) + + expected_feature = { + "data" => {}, + "groups" => ["fake_group"], + "name" => "fake_test_feature_for_rollout_ui_webspec", + "percentage" => 0.0 + } + header 'Accept', 'application/json' + get '/?user=fake_user' + expect(last_response.status == 200) + expect(last_response.headers['Content-Type'] == 'application/json') + response = JSON.parse(last_response.body) + expect(response).to(include(expected_feature)) + + header 'Accept', 'application/json' + get '/?group=fake_group' + expect(last_response.status == 200) + expect(last_response.headers['Content-Type'] == 'application/json') + response = JSON.parse(last_response.body) + expect(response).to(include(expected_feature)) + + ROLLOUT.deactivate_user(:fake_test_feature_for_rollout_ui_webspec, 'fake_user') + ROLLOUT.deactivate_group(:fake_test_feature_for_rollout_ui_webspec, :fake_group) + ROLLOUT.delete(:fake_test_feature_for_rollout_ui_webspec) + end + + it "renders show html" do + get '/features/test' + expect(last_response.body.include?('Rollout UI')) + expect(last_response.body.include?('test')) + expect(last_response.status == 200) + end + + it "renders show json" do + ROLLOUT.activate(:fake_test_feature_for_rollout_ui_webspec) + header 'Accept', 'application/json' + get '/features/fake_test_feature_for_rollout_ui_webspec' + expect(last_response.status == 200) + expect(last_response.headers['Content-Type'] == 'application/json') + response = JSON.parse(last_response.body) + expected_response = { + "data"=>{}, + "groups"=>[], + "name"=>"fake_test_feature_for_rollout_ui_webspec", + "percentage"=>100.0 + } + expect(expected_response == response) + ROLLOUT.delete(:fake_test_feature_for_rollout_ui_webspec) + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9ac3e4a..b70965a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,14 @@ require "bundler/setup" require "rollout/ui" +require 'rack/test' +require 'pry' +require 'redis' +REDIS = Redis.new +ROLLOUT = Rollout.new(REDIS) +Rollout::UI.configure do + instance { ROLLOUT } +end RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status"