flipper-ui 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6588ef43bcee1f02024cf32085148eae82ffa9f4
4
- data.tar.gz: fd986a24960b953f56510bb5b96a18373276f628
3
+ metadata.gz: 25aa2745af2e31218cbd4b27af8193e58737cf9d
4
+ data.tar.gz: cdda6a59fe7edcbdc5b06be258d2d685a2796636
5
5
  SHA512:
6
- metadata.gz: b4f66474ac658466d3a2a87e67f8c4168935457e1a62d9da3cb8d9891ec50864956cb5a800529bc757a1f879ba943da691c62b5565dffbbd2e32ad1e584a1bf6
7
- data.tar.gz: f0ca4057925cb29dcd2b08116751b71900037cf6319520056a1f5da7a5ef0b559c93e8d84ff36127c0bb8742cd4ee06443f68bea3d47ad52d8bf48de4ca0b2f9
6
+ metadata.gz: c834961bc28b80d3890bcb150ef26a9dffc2f7450acb5d0f2dc6e49d5be0f13bbf435c49916a1c08adf6c1944e1100500cf49f00d147b6a28d5f05bd97ed4399
7
+ data.tar.gz: 505af952e52ec8ccba2b37368c1fb84fcde104308d09a666f5479769677d6453edeea3088f70f74f49c4241a19f52eaa985f02f4c6400124b6b6a38cb5abc2ea
@@ -107,7 +107,6 @@ Minimal example for Rack:
107
107
  # config.ru
108
108
 
109
109
  require 'flipper-ui'
110
- require 'flipper/adapters/memory'
111
110
 
112
111
  adapter = Flipper::Adapters::Memory.new
113
112
  flipper = Flipper.new(adapter)
@@ -14,6 +14,7 @@ $:.unshift(lib_path)
14
14
 
15
15
  require "flipper-ui"
16
16
  require "flipper/adapters/pstore"
17
+ require "active_support/notifications"
17
18
 
18
19
  Flipper.register(:admins) { |actor|
19
20
  actor.respond_to?(:admin?) && actor.admin?
@@ -24,10 +25,11 @@ Flipper.register(:early_access) { |actor|
24
25
  }
25
26
 
26
27
  # Setup logging of flipper calls.
27
- $logger = Logger.new(STDOUT)
28
- require "active_support/notifications"
29
- require "flipper/instrumentation/log_subscriber"
30
- Flipper::Instrumentation::LogSubscriber.logger = $logger
28
+ if ENV["LOG"] == "1"
29
+ $logger = Logger.new(STDOUT)
30
+ require "flipper/instrumentation/log_subscriber"
31
+ Flipper::Instrumentation::LogSubscriber.logger = $logger
32
+ end
31
33
 
32
34
  adapter = Flipper::Adapters::PStore.new
33
35
  flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
@@ -42,6 +44,7 @@ flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
42
44
  # flipper[:secrets].enable_group :early_access
43
45
  # flipper[:logging].enable_percentage_of_time 5
44
46
  # flipper[:new_cache].enable_percentage_of_actors 15
47
+ # flipper["a/b"].add
45
48
 
46
49
  run Flipper::UI.app(flipper) { |builder|
47
50
  builder.use Rack::Session::Cookie, secret: "_super_secret"
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require File.expand_path('../lib/flipper/version', __FILE__)
3
+ require File.expand_path('../lib/flipper/metadata', __FILE__)
3
4
 
4
5
  flipper_ui_files = lambda do |file|
5
6
  file =~ %r{(docs|examples|flipper)[\/-]ui}
@@ -18,6 +19,7 @@ Gem::Specification.new do |gem|
18
19
  gem.name = 'flipper-ui'
19
20
  gem.require_paths = ['lib']
20
21
  gem.version = Flipper::VERSION
22
+ gem.metadata = Flipper::METADATA
21
23
 
22
24
  gem.add_dependency 'rack', '>= 1.4', '< 3'
23
25
  gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.1.0'
@@ -9,8 +9,6 @@ end
9
9
  require 'rack/protection'
10
10
 
11
11
  require 'flipper'
12
- require 'flipper/middleware/setup_env'
13
- require 'flipper/middleware/memoizer'
14
12
  require 'flipper/ui/middleware'
15
13
  require 'flipper/ui/configuration'
16
14
 
@@ -6,6 +6,16 @@ require 'json'
6
6
  module Flipper
7
7
  module UI
8
8
  class Action
9
+ module FeatureNameFromRoute
10
+ def feature_name
11
+ @feature_name ||= begin
12
+ match = request.path_info.match(self.class.route_regex)
13
+ match ? match[:feature_name] : nil
14
+ end
15
+ end
16
+ private :feature_name
17
+ end
18
+
9
19
  extend Forwardable
10
20
 
11
21
  VALID_REQUEST_METHOD_NAMES = Set.new([
@@ -21,7 +31,17 @@ module Flipper
21
31
  #
22
32
  # Returns nothing.
23
33
  def self.route(regex)
24
- @regex = regex
34
+ @route_regex = regex
35
+ end
36
+
37
+ # Internal: Does this action's route match the path.
38
+ def self.route_match?(path)
39
+ path.match(route_regex)
40
+ end
41
+
42
+ # Internal: The regex that matches which routes this action will work for.
43
+ def self.route_regex
44
+ @route_regex || raise("#{name}.route is not set")
25
45
  end
26
46
 
27
47
  # Internal: Initializes and runs an action for a given request.
@@ -34,11 +54,6 @@ module Flipper
34
54
  new(flipper, request).run
35
55
  end
36
56
 
37
- # Internal: The regex that matches which routes this action will work for.
38
- def self.regex
39
- @regex || raise("#{name}.route is not set")
40
- end
41
-
42
57
  # Private: The path to the views folder.
43
58
  def self.views_path
44
59
  @views_path ||= Flipper::UI.root.join('views')
@@ -12,7 +12,7 @@ module Flipper
12
12
 
13
13
  def action_for_request(request)
14
14
  @action_classes.detect do |action_class|
15
- request.path_info =~ action_class.regex
15
+ action_class.route_match?(request.path_info)
16
16
  end
17
17
  end
18
18
  end
@@ -6,11 +6,12 @@ module Flipper
6
6
  module UI
7
7
  module Actions
8
8
  class ActorsGate < UI::Action
9
- route %r{features/[^/]*/actors/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/actors/?\Z}
10
12
 
11
13
  def get
12
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
13
- feature = flipper[feature_name.to_sym]
14
+ feature = flipper[feature_name]
14
15
  @feature = Decorators::Feature.new(feature)
15
16
 
16
17
  breadcrumb 'Home', '/'
@@ -22,8 +23,7 @@ module Flipper
22
23
  end
23
24
 
24
25
  def post
25
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
26
- feature = flipper[feature_name.to_sym]
26
+ feature = flipper[feature_name]
27
27
  value = params['value'].to_s.strip
28
28
 
29
29
  if Util.blank?(value)
@@ -5,7 +5,7 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class AddFeature < UI::Action
8
- route %r{features/new/?\Z}
8
+ route %r{\A/features/new/?\Z}
9
9
 
10
10
  def get
11
11
  unless Flipper::UI.configuration.feature_creation_enabled
@@ -5,11 +5,12 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class BooleanGate < UI::Action
8
- route %r{features/[^/]*/boolean/?\Z}
8
+ include FeatureNameFromRoute
9
+
10
+ route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}
9
11
 
10
12
  def post
11
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
12
- feature = flipper[feature_name.to_sym]
13
+ feature = flipper[feature_name]
13
14
  @feature = Decorators::Feature.new(feature)
14
15
 
15
16
  if params['action'] == 'Enable'
@@ -5,10 +5,11 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class Feature < UI::Action
8
- route %r{features/[^/]*/?\Z}
8
+ include FeatureNameFromRoute
9
+
10
+ route %r{\A/features/(?<feature_name>.*)\Z}
9
11
 
10
12
  def get
11
- feature_name = Rack::Utils.unescape(request.path.split('/').last)
12
13
  @feature = Decorators::Feature.new(flipper[feature_name])
13
14
  @page_title = "#{@feature.key} // Features"
14
15
  @percentages = [0, 1, 5, 10, 15, 25, 50, 75, 100]
@@ -30,7 +31,6 @@ module Flipper
30
31
  halt view_response(:feature_removal_disabled)
31
32
  end
32
33
 
33
- feature_name = Rack::Utils.unescape(request.path.split('/').last)
34
34
  feature = flipper[feature_name]
35
35
  feature.remove
36
36
  redirect_to '/features'
@@ -6,7 +6,7 @@ module Flipper
6
6
  module UI
7
7
  module Actions
8
8
  class Features < UI::Action
9
- route %r{features/?\Z}
9
+ route %r{\A/features/?\Z}
10
10
 
11
11
  def get
12
12
  @page_title = 'Features'
@@ -5,11 +5,12 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class GroupsGate < UI::Action
8
- route %r{features/[^/]*/groups/?\Z}
8
+ include FeatureNameFromRoute
9
+
10
+ route %r{\A/features/(?<feature_name>.*)/groups/?\Z}
9
11
 
10
12
  def get
11
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
12
- feature = flipper[feature_name.to_sym]
13
+ feature = flipper[feature_name]
13
14
  @feature = Decorators::Feature.new(feature)
14
15
 
15
16
  breadcrumb 'Home', '/'
@@ -21,8 +22,7 @@ module Flipper
21
22
  end
22
23
 
23
24
  def post
24
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
25
- feature = flipper[feature_name.to_sym]
25
+ feature = flipper[feature_name]
26
26
  value = params['value'].to_s.strip
27
27
 
28
28
  if Flipper.group_exists?(value)
@@ -5,7 +5,7 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class Home < UI::Action
8
- route %r{/?\Z}
8
+ route %r{\A/?\Z}
9
9
 
10
10
  def get
11
11
  redirect_to '/features'
@@ -5,11 +5,12 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class PercentageOfActorsGate < UI::Action
8
- route %r{features/[^/]*/percentage_of_actors/?\Z}
8
+ include FeatureNameFromRoute
9
+
10
+ route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}
9
11
 
10
12
  def post
11
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
12
- feature = flipper[feature_name.to_sym]
13
+ feature = flipper[feature_name]
13
14
  @feature = Decorators::Feature.new(feature)
14
15
 
15
16
  begin
@@ -5,11 +5,12 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class PercentageOfTimeGate < UI::Action
8
- route %r{features/[^/]*/percentage_of_time/?\Z}
8
+ include FeatureNameFromRoute
9
+
10
+ route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}
9
11
 
10
12
  def post
11
- feature_name = Rack::Utils.unescape(request.path.split('/')[-2])
12
- feature = flipper[feature_name.to_sym]
13
+ feature = flipper[feature_name]
13
14
  @feature = Decorators::Feature.new(feature)
14
15
 
15
16
  begin
@@ -17,13 +17,12 @@ module Flipper
17
17
 
18
18
  # UI
19
19
  @action_collection.add UI::Actions::AddFeature
20
- @action_collection.add UI::Actions::Feature
21
20
  @action_collection.add UI::Actions::ActorsGate
22
21
  @action_collection.add UI::Actions::GroupsGate
23
22
  @action_collection.add UI::Actions::BooleanGate
24
23
  @action_collection.add UI::Actions::PercentageOfTimeGate
25
24
  @action_collection.add UI::Actions::PercentageOfActorsGate
26
- @action_collection.add UI::Actions::Gate
25
+ @action_collection.add UI::Actions::Feature
27
26
  @action_collection.add UI::Actions::Features
28
27
 
29
28
  # Static Assets/Files
@@ -14,7 +14,7 @@
14
14
  </h4>
15
15
  <div class="card-body">
16
16
  <p>
17
- <% if @feature.on? %>
17
+ <% if @feature.boolean_value %>
18
18
  The feature is enabled for <strong>everyone</strong>. Disable this feature with one click.
19
19
  <% else %>
20
20
  Enable or disable this feature for <strong>everyone</strong> with one click.
@@ -24,7 +24,7 @@
24
24
  <form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
25
25
  <%== csrf_input_tag %>
26
26
 
27
- <% unless @feature.on? %>
27
+ <% unless @feature.boolean_value %>
28
28
  <button type="submit" name="action" value="Enable" class="btn btn-danger" data-toggle="tooltip" title="Enable for everyone">Enable</button>
29
29
  <% end %>
30
30
 
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.15.0'.freeze
2
+ VERSION = '0.16.0'.freeze
3
3
  end
@@ -27,6 +27,21 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
27
27
  end
28
28
  end
29
29
 
30
+ describe 'GET /features/:feature/actors with slash in feature name' do
31
+ before do
32
+ get 'features/a/b/actors'
33
+ end
34
+
35
+ it 'responds with success' do
36
+ expect(last_response.status).to be(200)
37
+ end
38
+
39
+ it 'renders add new actor form' do
40
+ form = '<form action="/features/a/b/actors" method="post" class="form-inline">'
41
+ expect(last_response.body).to include(form)
42
+ end
43
+ end
44
+
30
45
  describe 'POST /features/:feature/actors' do
31
46
  context 'enabling an actor' do
32
47
  let(:value) { 'User:6' }
@@ -107,4 +107,24 @@ RSpec.describe Flipper::UI::Actions::Feature do
107
107
  expect(last_response.body).to include('Percentage of Actors')
108
108
  end
109
109
  end
110
+
111
+ describe 'GET /features/:feature with slash in feature name' do
112
+ before do
113
+ get '/features/a/b'
114
+ end
115
+
116
+ it 'responds with success' do
117
+ expect(last_response.status).to be(200)
118
+ end
119
+
120
+ it 'renders template' do
121
+ expect(last_response.body).to include('a/b')
122
+ expect(last_response.body).to include('Enable')
123
+ expect(last_response.body).to include('Disable')
124
+ expect(last_response.body).to include('Actors')
125
+ expect(last_response.body).to include('Groups')
126
+ expect(last_response.body).to include('Percentage of Time')
127
+ expect(last_response.body).to include('Percentage of Actors')
128
+ end
129
+ end
110
130
  end
@@ -1,5 +1,4 @@
1
1
  require 'helper'
2
- require 'flipper/adapters/memory'
3
2
 
4
3
  RSpec.describe Flipper::UI::Decorators::Feature do
5
4
  let(:source) { {} }
@@ -1,5 +1,4 @@
1
1
  require 'helper'
2
- require 'flipper/adapters/memory'
3
2
  require 'flipper/ui/decorators/gate'
4
3
 
5
4
  RSpec.describe Flipper::UI::Decorators::Gate do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-12 00:00:00.000000000 Z
11
+ date: 2018-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -56,14 +56,14 @@ dependencies:
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: 0.15.0
59
+ version: 0.16.0
60
60
  type: :runtime
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: 0.15.0
66
+ version: 0.16.0
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: erubis
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -103,7 +103,6 @@ files:
103
103
  - lib/flipper/ui/actions/feature.rb
104
104
  - lib/flipper/ui/actions/features.rb
105
105
  - lib/flipper/ui/actions/file.rb
106
- - lib/flipper/ui/actions/gate.rb
107
106
  - lib/flipper/ui/actions/groups_gate.rb
108
107
  - lib/flipper/ui/actions/home.rb
109
108
  - lib/flipper/ui/actions/percentage_of_actors_gate.rb
@@ -259,7 +258,6 @@ files:
259
258
  - spec/flipper/ui/actions/feature_spec.rb
260
259
  - spec/flipper/ui/actions/features_spec.rb
261
260
  - spec/flipper/ui/actions/file_spec.rb
262
- - spec/flipper/ui/actions/gate_spec.rb
263
261
  - spec/flipper/ui/actions/groups_gate_spec.rb
264
262
  - spec/flipper/ui/actions/home_spec.rb
265
263
  - spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb
@@ -272,7 +270,8 @@ files:
272
270
  homepage: https://github.com/jnunemaker/flipper
273
271
  licenses:
274
272
  - MIT
275
- metadata: {}
273
+ metadata:
274
+ changelog_uri: https://github.com/jnunemaker/flipper/blob/master/Changelog.md
276
275
  post_install_message:
277
276
  rdoc_options: []
278
277
  require_paths:
@@ -301,7 +300,6 @@ test_files:
301
300
  - spec/flipper/ui/actions/feature_spec.rb
302
301
  - spec/flipper/ui/actions/features_spec.rb
303
302
  - spec/flipper/ui/actions/file_spec.rb
304
- - spec/flipper/ui/actions/gate_spec.rb
305
303
  - spec/flipper/ui/actions/groups_gate_spec.rb
306
304
  - spec/flipper/ui/actions/home_spec.rb
307
305
  - spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb
@@ -1,39 +0,0 @@
1
- require 'flipper/ui/action'
2
- require 'flipper/ui/decorators/feature'
3
-
4
- module Flipper
5
- module UI
6
- module Actions
7
- class Gate < UI::Action
8
- route %r{features/[^/]*/[^/]*/?\Z}
9
-
10
- def post
11
- feature_name, gate_name = request.path.split('/').pop(2)
12
- .map(&Rack::Utils.method(:unescape))
13
- update_gate_method_name = "update_#{gate_name}"
14
-
15
- feature = flipper[feature_name.to_sym]
16
- @feature = Decorators::Feature.new(feature)
17
-
18
- if respond_to?(update_gate_method_name, true)
19
- send(update_gate_method_name, feature)
20
- else
21
- update_gate_method_undefined(gate_name)
22
- end
23
-
24
- redirect_to "/features/#{@feature.key}"
25
- end
26
-
27
- private
28
-
29
- # Private: Returns error response that gate update method is not defined.
30
- def update_gate_method_undefined(gate_name)
31
- error = Rack::Utils.escape(
32
- "#{gate_name.inspect} gate does not exist therefore it cannot be updated."
33
- )
34
- redirect_to("/features/#{@feature.key}?error=#{error}")
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,37 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.describe Flipper::UI::Actions::Gate do
4
- let(:token) do
5
- if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
6
- Rack::Protection::AuthenticityToken.random_token
7
- else
8
- 'a'
9
- end
10
- end
11
- let(:session) do
12
- { :csrf => token, 'csrf' => token, '_csrf_token' => token }
13
- end
14
-
15
- describe 'POST /features/:feature/non-existent-gate' do
16
- before do
17
- post '/features/search/non-existent-gate',
18
- { 'authenticity_token' => token },
19
- 'rack.session' => session
20
- end
21
-
22
- it 'responds with redirect' do
23
- expect(last_response.status).to be(302)
24
- end
25
-
26
- # rubocop:disable Metrics/LineLength
27
- it 'escapes error message' do
28
- expect(last_response.headers['Location']).to eq('/features/search?error=%22non-existent-gate%22+gate+does+not+exist+therefore+it+cannot+be+updated.')
29
- end
30
- # rubocop:enable Metrics/LineLength
31
-
32
- it 'renders error in template' do
33
- follow_redirect!
34
- expect(last_response.body).to match(/non-existent-gate.*gate does not exist/)
35
- end
36
- end
37
- end