Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Slashes in Feature Names in UI/API #362

Merged
merged 12 commits into from
Jul 31, 2018
11 changes: 7 additions & 4 deletions examples/ui/basic.ru
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ $:.unshift(lib_path)

require "flipper-ui"
require "flipper/adapters/pstore"
require "active_support/notifications"

Flipper.register(:admins) { |actor|
actor.respond_to?(:admin?) && actor.admin?
Expand All @@ -24,10 +25,11 @@ Flipper.register(:early_access) { |actor|
}

# Setup logging of flipper calls.
$logger = Logger.new(STDOUT)
require "active_support/notifications"
require "flipper/instrumentation/log_subscriber"
Flipper::Instrumentation::LogSubscriber.logger = $logger
if ENV["LOG"] == "1"
$logger = Logger.new(STDOUT)
require "flipper/instrumentation/log_subscriber"
Flipper::Instrumentation::LogSubscriber.logger = $logger
end

adapter = Flipper::Adapters::PStore.new
flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
Expand All @@ -42,6 +44,7 @@ flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
# flipper[:secrets].enable_group :early_access
# flipper[:logging].enable_percentage_of_time 5
# flipper[:new_cache].enable_percentage_of_actors 15
# flipper["a/b"].add

run Flipper::UI.app(flipper) { |builder|
builder.use Rack::Session::Cookie, secret: "_super_secret"
Expand Down
27 changes: 21 additions & 6 deletions lib/flipper/api/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
module Flipper
module Api
class Action
module FeatureNameFromRoute
def feature_name
@feature_name ||= begin
match = request.path_info.match(self.class.route_regex)
match ? match[:feature_name] : nil
end
end
private :feature_name
end

extend Forwardable

VALID_REQUEST_METHOD_NAMES = Set.new([
Expand All @@ -21,7 +31,17 @@ class Action
#
# Returns nothing.
def self.route(regex)
@regex = regex
@route_regex = regex
end

# Internal: Does this action's route match the path.
def self.route_match?(path)
path.match(route_regex)
end

# Internal: The regex that matches which routes this action will work for.
def self.route_regex
@route_regex || raise("#{name}.route is not set")
end

# Internal: Initializes and runs an action for a given request.
Expand All @@ -34,11 +54,6 @@ def self.run(flipper, request)
new(flipper, request).run
end

# Internal: The regex that matches which routes this action will work for.
def self.regex
@regex || raise("#{name}.route is not set")
end

# Public: The instance of the Flipper::DSL the middleware was
# initialized with.
attr_reader :flipper
Expand Down
3 changes: 2 additions & 1 deletion lib/flipper/api/action_collection.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Flipper
module Api
# Internal: Used to detect the action that should be used in the middleware.
class ActionCollection
def initialize
@action_classes = []
Expand All @@ -11,7 +12,7 @@ def add(action_class)

def action_for_request(request)
@action_classes.detect do |action_class|
request.path_info =~ action_class.regex
action_class.route_match?(request.path_info)
end
end
end
Expand Down
8 changes: 3 additions & 5 deletions lib/flipper/api/v1/actions/actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module Api
module V1
module Actions
class ActorsGate < Api::Action
route %r{features/[^/]*/actors/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/actors/?\Z}

def post
ensure_valid_params
Expand All @@ -32,10 +34,6 @@ def ensure_valid_params
json_error_response(:flipper_id_invalid) if flipper_id.nil?
end

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
end

def flipper_id
@flipper_id ||= params['flipper_id']
end
Expand Down
6 changes: 3 additions & 3 deletions lib/flipper/api/v1/actions/boolean_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ module Api
module V1
module Actions
class BooleanGate < Api::Action
route %r{features/[^/]*/boolean/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}

def post
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name]
feature.enable
decorated_feature = Decorators::Feature.new(feature)
json_response(decorated_feature.as_json, 200)
end

def delete
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name.to_sym]
feature.disable
decorated_feature = Decorators::Feature.new(feature)
Expand Down
5 changes: 3 additions & 2 deletions lib/flipper/api/v1/actions/clear_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ module Api
module V1
module Actions
class ClearFeature < Api::Action
route %r{features/[^/]*/clear/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/clear/?\Z}

def delete
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name]
feature.clear
json_response({}, 204)
Expand Down
8 changes: 3 additions & 5 deletions lib/flipper/api/v1/actions/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module Api
module V1
module Actions
class Feature < Api::Action
route %r{features/[^/]*/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/?\Z}

def get
return json_error_response(:feature_not_found) unless feature_exists?(feature_name)
Expand All @@ -21,10 +23,6 @@ def delete

private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts.last)
end

def feature_exists?(feature_name)
flipper.features.map(&:key).include?(feature_name)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/api/v1/actions/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Api
module V1
module Actions
class Features < Api::Action
route(/features\Z/)
route %r{\A/features/?\Z}

def get
keys = params['keys']
Expand Down
8 changes: 3 additions & 5 deletions lib/flipper/api/v1/actions/groups_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module Api
module V1
module Actions
class GroupsGate < Api::Action
route %r{features/[^/]*/groups/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/groups/?\Z}

def post
ensure_valid_params
Expand Down Expand Up @@ -46,10 +48,6 @@ def disallow_unregistered_groups?
!allow_unregistered_groups?
end

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
end

def group_name
@group_name ||= params['name']
end
Expand Down
8 changes: 3 additions & 5 deletions lib/flipper/api/v1/actions/percentage_of_actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module Api
module V1
module Actions
class PercentageOfActorsGate < Api::Action
route %r{features/[^/]*/percentage_of_actors/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}

def post
if percentage < 0 || percentage > 100
Expand All @@ -28,10 +30,6 @@ def delete

private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
end

def percentage
@percentage ||= begin
Integer(params['percentage'])
Expand Down
8 changes: 3 additions & 5 deletions lib/flipper/api/v1/actions/percentage_of_time_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module Api
module V1
module Actions
class PercentageOfTimeGate < Api::Action
route %r{features/[^/]*/percentage_of_time/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}

def post
if percentage < 0 || percentage > 100
Expand All @@ -29,10 +31,6 @@ def delete

private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
end

def percentage
@percentage ||= begin
Integer(params['percentage'])
Expand Down
27 changes: 21 additions & 6 deletions lib/flipper/ui/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
module Flipper
module UI
class Action
module FeatureNameFromRoute
def feature_name
@feature_name ||= begin
match = request.path_info.match(self.class.route_regex)
match ? match[:feature_name] : nil
end
end
private :feature_name
end

extend Forwardable

VALID_REQUEST_METHOD_NAMES = Set.new([
Expand All @@ -21,7 +31,17 @@ class Action
#
# Returns nothing.
def self.route(regex)
@regex = regex
@route_regex = regex
end

# Internal: Does this action's route match the path.
def self.route_match?(path)
path.match(route_regex)
end

# Internal: The regex that matches which routes this action will work for.
def self.route_regex
@route_regex || raise("#{name}.route is not set")
end

# Internal: Initializes and runs an action for a given request.
Expand All @@ -34,11 +54,6 @@ def self.run(flipper, request)
new(flipper, request).run
end

# Internal: The regex that matches which routes this action will work for.
def self.regex
@regex || raise("#{name}.route is not set")
end

# Private: The path to the views folder.
def self.views_path
@views_path ||= Flipper::UI.root.join('views')
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/action_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def add(action_class)

def action_for_request(request)
@action_classes.detect do |action_class|
request.path_info =~ action_class.regex
action_class.route_match?(request.path_info)
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/flipper/ui/actions/actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ module Flipper
module UI
module Actions
class ActorsGate < UI::Action
route %r{features/[^/]*/actors/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/actors/?\Z}

def get
feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
feature = flipper[feature_name.to_sym]
feature = flipper[feature_name]
@feature = Decorators::Feature.new(feature)

breadcrumb 'Home', '/'
Expand All @@ -22,8 +23,7 @@ def get
end

def post
feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
feature = flipper[feature_name.to_sym]
feature = flipper[feature_name]
value = params['value'].to_s.strip

if Util.blank?(value)
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/add_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Flipper
module UI
module Actions
class AddFeature < UI::Action
route %r{features/new/?\Z}
route %r{\A/features/new/?\Z}

def get
unless Flipper::UI.configuration.feature_creation_enabled
Expand Down
7 changes: 4 additions & 3 deletions lib/flipper/ui/actions/boolean_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ module Flipper
module UI
module Actions
class BooleanGate < UI::Action
route %r{features/[^/]*/boolean/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}

def post
feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
feature = flipper[feature_name.to_sym]
feature = flipper[feature_name]
@feature = Decorators::Feature.new(feature)

if params['action'] == 'Enable'
Expand Down
6 changes: 3 additions & 3 deletions lib/flipper/ui/actions/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ module Flipper
module UI
module Actions
class Feature < UI::Action
route %r{features/[^/]*/?\Z}
include FeatureNameFromRoute

route %r{\A/features/(?<feature_name>.*)\Z}

def get
feature_name = Rack::Utils.unescape(request.path.split('/').last)
@feature = Decorators::Feature.new(flipper[feature_name])
@page_title = "#{@feature.key} // Features"
@percentages = [0, 1, 5, 10, 15, 25, 50, 75, 100]
Expand All @@ -30,7 +31,6 @@ def delete
halt view_response(:feature_removal_disabled)
end

feature_name = Rack::Utils.unescape(request.path.split('/').last)
feature = flipper[feature_name]
feature.remove
redirect_to '/features'
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Flipper
module UI
module Actions
class Features < UI::Action
route %r{features/?\Z}
route %r{\A/features/?\Z}

def get
@page_title = 'Features'
Expand Down
Loading