flipper-ui 0.20.3 → 0.22.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
  SHA256:
3
- metadata.gz: 02c1c4f1cd2496bbf2c3d7c0f2c07d9c284e94d1aad662484a4baf68b1c1f331
4
- data.tar.gz: 3f76ab34733b9c1e4d44ed0a955ac9373dbe7a90ab12861bedb9879c02fa0e22
3
+ metadata.gz: 3be7bbda3ec1a09b1cc1c421766ea520a9a933eeb1628ea27281c35a174725a2
4
+ data.tar.gz: ca0375faf5a0cfd4f2b6c04435c803e9414c2e41337918ad9f8bb9f95f579e46
5
5
  SHA512:
6
- metadata.gz: 996926448609e45c6bd2bc180cd1870606ffb9708ccc9dbc2e318f25b3a4dc7eb2db07d7130bc84caf2cc54f512216bdef44340ac0a163292ada259979bd2e52
7
- data.tar.gz: 66414f02d26662ab28adbebdf09edb26da6b474d45d658b79c41bd5a396179cf2d0d6be7990756be9538a5363b674fecf599d24a5a851eacd2f46cbb37e35f12
6
+ metadata.gz: effdbdd4ab7c0646dcd1a50ebd0d96fd78dc2dabb2a87ae1e524a2a4b32ce43c1b01e6e96046d1e9bc4fe298a805a2c31c713b83ad1dbfb26c31dfb70f714e90
7
+ data.tar.gz: 8d1f5b3f946a0b5d9bb42db7a497484c490cc2a2e9f15562e046ddeb41be0dba1cdda19ef1016bb5f7966ab35c600b8b57418533797b3000cec927671079ff1a
data/docs/ui/README.md CHANGED
@@ -109,7 +109,7 @@ Minimal example for Rack:
109
109
  ```ruby
110
110
  # config.ru
111
111
 
112
- require 'flipper-ui'
112
+ require 'flipper/ui'
113
113
 
114
114
  adapter = Flipper::Adapters::Memory.new
115
115
  flipper = Flipper.new(adapter)
@@ -0,0 +1,46 @@
1
+ #
2
+ # Usage:
3
+ # bundle exec rackup examples/ui/authorization.ru -p 9999
4
+ # bundle exec shotgun examples/ui/authorization.ru -p 9999
5
+ # http://localhost:9999/
6
+ #
7
+ require 'bundler/setup'
8
+ require "flipper/ui"
9
+ require "flipper/adapters/pstore"
10
+
11
+ Flipper.register(:admins) { |actor|
12
+ actor.respond_to?(:admin?) && actor.admin?
13
+ }
14
+
15
+ # Example middleware to allow reading the Flipper UI but nothing else.
16
+ class FlipperReadOnlyMiddleware
17
+ def initialize(app)
18
+ @app = app
19
+ end
20
+
21
+ def call(env)
22
+ request = Rack::Request.new(env)
23
+
24
+ if request.get?
25
+ @app.call(env)
26
+ else
27
+ [401, {}, ["You can only look"]]
28
+ end
29
+ end
30
+ end
31
+
32
+ # You can uncomment these to get some default data:
33
+ # Flipper.enable(:search_performance_another_long_thing)
34
+ # Flipper.disable(:gauges_tracking)
35
+ # Flipper.disable(:unused)
36
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
37
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
38
+ # Flipper.enable_group(:secrets, :admins)
39
+ # Flipper.enable_percentage_of_time(:logging, 5)
40
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
41
+ # Flipper.add("a/b")
42
+
43
+ run Flipper::UI.app { |builder|
44
+ builder.use Rack::Session::Cookie, secret: "_super_secret"
45
+ builder.use FlipperReadOnlyMiddleware
46
+ }
data/examples/ui/basic.ru CHANGED
@@ -1,20 +1,16 @@
1
1
  #
2
2
  # Usage:
3
- # bundle exec rackup examples/ui/basic.ru -p 9999
4
- # bundle exec shotgun examples/ui/basic.ru -p 9999
3
+ # # if you want it to not reload and be really fast
4
+ # bin/rackup examples/ui/basic.ru -p 9999
5
+ #
6
+ # # if you want reloading
7
+ # bin/shotgun examples/ui/basic.ru -p 9999
8
+ #
5
9
  # http://localhost:9999/
6
10
  #
7
- require "pp"
8
- require "logger"
9
- require "pathname"
10
-
11
- root_path = Pathname(__FILE__).dirname.join("..").expand_path
12
- lib_path = root_path.join("lib")
13
- $:.unshift(lib_path)
14
-
15
- require "flipper-ui"
11
+ require 'bundler/setup'
12
+ require "flipper/ui"
16
13
  require "flipper/adapters/pstore"
17
- require "active_support/notifications"
18
14
 
19
15
  Flipper.register(:admins) { |actor|
20
16
  actor.respond_to?(:admin?) && actor.admin?
@@ -24,21 +20,12 @@ Flipper.register(:early_access) { |actor|
24
20
  actor.respond_to?(:early?) && actor.early?
25
21
  }
26
22
 
27
- # Setup logging of flipper calls.
28
- if ENV["LOG"] == "1"
29
- $logger = Logger.new(STDOUT)
30
- require "flipper/instrumentation/log_subscriber"
31
- Flipper::Instrumentation::LogSubscriber.logger = $logger
32
- end
33
-
34
- adapter = Flipper::Adapters::PStore.new
35
- flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
36
-
37
23
  Flipper::UI.configure do |config|
38
24
  # config.banner_text = 'Production Environment'
39
25
  # config.banner_class = 'danger'
40
26
  config.feature_creation_enabled = true
41
27
  config.feature_removal_enabled = true
28
+ config.cloud_recommendation = true
42
29
  # config.show_feature_description_in_list = true
43
30
  config.descriptions_source = lambda do |_keys|
44
31
  {
@@ -55,17 +42,17 @@ Flipper::UI.configure do |config|
55
42
  end
56
43
 
57
44
  # You can uncomment these to get some default data:
58
- # flipper[:search_performance_another_long_thing].enable
59
- # flipper[:gauges_tracking].enable
60
- # flipper[:unused].disable
61
- # flipper[:suits].enable_actor Flipper::Actor.new('1')
62
- # flipper[:suits].enable_actor Flipper::Actor.new('6')
63
- # flipper[:secrets].enable_group :admins
64
- # flipper[:secrets].enable_group :early_access
65
- # flipper[:logging].enable_percentage_of_time 5
66
- # flipper[:new_cache].enable_percentage_of_actors 15
67
- # flipper["a/b"].add
68
-
69
- run Flipper::UI.app(flipper) { |builder|
45
+ # Flipper.enable(:search_performance_another_long_thing)
46
+ # Flipper.disable(:gauges_tracking)
47
+ # Flipper.disable(:unused)
48
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
49
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
50
+ # Flipper.enable_group(:secrets, :admins)
51
+ # Flipper.enable_group(:secrets, :early_access)
52
+ # Flipper.enable_percentage_of_time(:logging, 5)
53
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
54
+ # Flipper.add("a/b")
55
+
56
+ run Flipper::UI.app { |builder|
70
57
  builder.use Rack::Session::Cookie, secret: "_super_secret"
71
58
  }
data/lib/flipper/ui.rb CHANGED
@@ -39,14 +39,14 @@ module Flipper
39
39
  def self.app(flipper = nil, options = {})
40
40
  env_key = options.fetch(:env_key, 'flipper')
41
41
  rack_protection_options = options.fetch(:rack_protection, use: :authenticity_token)
42
+
42
43
  app = ->(_) { [200, { 'Content-Type' => 'text/html' }, ['']] }
43
44
  builder = Rack::Builder.new
44
45
  yield builder if block_given?
45
46
  builder.use Rack::Protection, rack_protection_options
46
47
  builder.use Rack::MethodOverride
47
48
  builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
48
- builder.use Flipper::Middleware::Memoizer, env_key: env_key
49
- builder.use Flipper::UI::Middleware, env_key: env_key
49
+ builder.use Flipper::UI::Middleware, flipper: flipper, env_key: env_key
50
50
  builder.run app
51
51
  klass = self
52
52
  builder.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
@@ -151,7 +151,7 @@ module Flipper
151
151
  # location - The String location to set the Location header to.
152
152
  def redirect_to(location)
153
153
  status 302
154
- header 'Location', "#{script_name}#{location}"
154
+ header 'Location', "#{script_name}#{Rack::Utils.escape_path(location)}"
155
155
  halt [@code, @headers, ['']]
156
156
  end
157
157
 
@@ -27,7 +27,7 @@ module Flipper
27
27
  value = params['value'].to_s.strip
28
28
 
29
29
  if Util.blank?(value)
30
- error = Rack::Utils.escape("#{value.inspect} is not a valid actor value.")
30
+ error = "#{value.inspect} is not a valid actor value."
31
31
  redirect_to("/features/#{feature.key}/actors?error=#{error}")
32
32
  end
33
33
 
@@ -49,14 +49,14 @@ module Flipper
49
49
  value = params['value'].to_s.strip
50
50
 
51
51
  if Util.blank?(value)
52
- error = Rack::Utils.escape("#{value.inspect} is not a valid feature name.")
52
+ error = "#{value.inspect} is not a valid feature name."
53
53
  redirect_to("/features/new?error=#{error}")
54
54
  end
55
55
 
56
56
  feature = flipper[value]
57
57
  feature.add
58
58
 
59
- redirect_to "/features/#{Rack::Utils.escape_path(value)}"
59
+ redirect_to "/features/#{value}"
60
60
  end
61
61
  end
62
62
  end
@@ -35,7 +35,7 @@ module Flipper
35
35
 
36
36
  redirect_to("/features/#{feature.key}")
37
37
  else
38
- error = Rack::Utils.escape("The group named #{value.inspect} has not been registered.")
38
+ error = "The group named #{value.inspect} has not been registered."
39
39
  redirect_to("/features/#{feature.key}/groups?error=#{error}")
40
40
  end
41
41
  end
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_actors params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of actors value: #{exception.message}")
19
+ error = "Invalid percentage of actors value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_time params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of time value: #{exception.message}")
19
+ error = "Invalid percentage of time value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -26,6 +26,10 @@ module Flipper
26
26
  # won't see a videoclip of Taylor Swift when there aren't any features
27
27
  attr_accessor :fun
28
28
 
29
+ # Public: Tired of seeing the awesome message about Cloud? Set this to
30
+ # false and it will go away. Defaults to true.
31
+ attr_accessor :cloud_recommendation
32
+
29
33
  # Public: What should show up in the form to add actors. This can be
30
34
  # different per application since flipper_id's can be whatever an
31
35
  # application needs. Defaults to "a flipper id".
@@ -60,6 +64,7 @@ module Flipper
60
64
  @feature_creation_enabled = true
61
65
  @feature_removal_enabled = true
62
66
  @fun = true
67
+ @cloud_recommendation = true
63
68
  @add_actor_placeholder = "a flipper id"
64
69
  @descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
65
70
  @show_feature_description_in_list = false
@@ -12,6 +12,7 @@ module Flipper
12
12
  def initialize(app, options = {})
13
13
  @app = app
14
14
  @env_key = options.fetch(:env_key, 'flipper')
15
+ @flipper = options.fetch(:flipper) { Flipper }
15
16
 
16
17
  @action_collection = ActionCollection.new
17
18
 
@@ -43,7 +44,7 @@ module Flipper
43
44
  if action_class.nil?
44
45
  @app.call(env)
45
46
  else
46
- flipper = env.fetch(@env_key)
47
+ flipper = env.fetch(@env_key) { Flipper }
47
48
  action_class.run(flipper, request)
48
49
  end
49
50
  end
Binary file
@@ -1,5 +1,5 @@
1
1
  <% if @show_blank_slate %>
2
- <div class="jumbotron text-center">
2
+ <div class="jumbotron text-center m-0">
3
3
  <% if Flipper::UI.configuration.fun %>
4
4
  <h4>But I've got a blank space baby...</h4>
5
5
  <p>And I'll flip your features.</p>
@@ -35,6 +35,21 @@
35
35
  <div>
36
36
  <%== yield %>
37
37
  </div>
38
+
39
+ <% if Flipper::UI.configuration.cloud_recommendation %>
40
+ <div class="d-flex justify-content-center mt-5">
41
+ <div class="row" style="max-width: 350px;">
42
+ <div class="col-auto d-flex align-items-center">
43
+ <a href="https://www.flippercloud.io/?utm_source=oss&utm_medium=ui&utm_campaign=spread_the_love">
44
+ <img src="<%= script_name %>/images/logo.png" width="50" />
45
+ </a>
46
+ </div>
47
+ <div class="col text-muted p-0">
48
+ <small>For support, audit history, finer-grained permissions, multi-environment sync, and all your projects in one place check out <a href="https://www.flippercloud.io/?utm_source=oss&utm_medium=ui&utm_campaign=spread_the_love">Flipper&nbsp;Cloud</a>.</small>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <% end %>
38
53
  </div>
39
54
 
40
55
  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.3'.freeze
2
+ VERSION = '0.22.0'.freeze
3
3
  end
@@ -61,6 +61,23 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
61
61
  expect(last_response.headers['Location']).to eq('/features/search')
62
62
  end
63
63
 
64
+ context "when feature name contains space" do
65
+ before do
66
+ post 'features/sp%20ace/actors',
67
+ { 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
68
+ 'rack.session' => session
69
+ end
70
+
71
+ it 'adds item to members' do
72
+ expect(flipper["sp ace"].actors_value).to include('User;6')
73
+ end
74
+
75
+ it "redirects back to feature" do
76
+ expect(last_response.status).to be(302)
77
+ expect(last_response.headers['Location']).to eq('/features/sp%20ace')
78
+ end
79
+ end
80
+
64
81
  context 'value contains whitespace' do
65
82
  let(:value) { ' User;6 ' }
66
83
 
@@ -75,7 +92,7 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
75
92
 
76
93
  it 'redirects back to feature' do
77
94
  expect(last_response.status).to be(302)
78
- expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
95
+ expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22%20is%20not%20a%20valid%20actor%20value.')
79
96
  end
80
97
  end
81
98
 
@@ -84,7 +101,7 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
84
101
 
85
102
  it 'redirects back to feature' do
86
103
  expect(last_response.status).to be(302)
87
- expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
104
+ expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22%20is%20not%20a%20valid%20actor%20value.')
88
105
  end
89
106
  end
90
107
  end
@@ -31,6 +31,24 @@ RSpec.describe Flipper::UI::Actions::BooleanGate do
31
31
  end
32
32
  end
33
33
 
34
+ context "with space in feature name" do
35
+ before do
36
+ flipper.disable :search
37
+ post 'features/sp%20ace/boolean',
38
+ { 'action' => 'Enable', 'authenticity_token' => token },
39
+ 'rack.session' => session
40
+ end
41
+
42
+ it 'updates feature' do
43
+ expect(flipper.enabled?("sp ace")).to be(true)
44
+ end
45
+
46
+ it 'redirects back to feature' do
47
+ expect(last_response.status).to be(302)
48
+ expect(last_response.headers['Location']).to eq('/features/sp%20ace')
49
+ end
50
+ end
51
+
34
52
  context 'with disable' do
35
53
  before do
36
54
  flipper.enable :search
@@ -29,6 +29,24 @@ RSpec.describe Flipper::UI::Actions::Feature do
29
29
  expect(last_response.headers['Location']).to eq('/features')
30
30
  end
31
31
 
32
+ context "with space in feature name" do
33
+ before do
34
+ flipper.enable "sp ace"
35
+ delete '/features/sp%20ace',
36
+ { 'authenticity_token' => token },
37
+ 'rack.session' => session
38
+ end
39
+
40
+ it 'removes feature' do
41
+ expect(flipper.features.map(&:key)).not_to include('sp ace')
42
+ end
43
+
44
+ it 'redirects to features' do
45
+ expect(last_response.status).to be(302)
46
+ expect(last_response.headers['Location']).to eq('/features')
47
+ end
48
+ end
49
+
32
50
  context 'when feature_removal_enabled is set to false' do
33
51
  around do |example|
34
52
  begin
@@ -95,7 +95,7 @@ RSpec.describe Flipper::UI::Actions::Features do
95
95
  expect(last_response.headers['Location']).to eq('/features/notifications_next')
96
96
  end
97
97
 
98
- context 'feature name contains whitespace' do
98
+ context 'feature name has whitespace at beginning and end' do
99
99
  let(:feature_name) { ' notifications_next ' }
100
100
 
101
101
  it 'adds feature without whitespace' do
@@ -103,6 +103,19 @@ RSpec.describe Flipper::UI::Actions::Features do
103
103
  end
104
104
  end
105
105
 
106
+ context 'feature name contains space' do
107
+ let(:feature_name) { 'notifications next' }
108
+
109
+ it 'adds feature with space' do
110
+ expect(flipper.features.map(&:key)).to include('notifications next')
111
+ end
112
+
113
+ it 'redirects to feature' do
114
+ expect(last_response.status).to be(302)
115
+ expect(last_response.headers['Location']).to eq('/features/notifications%20next')
116
+ end
117
+ end
118
+
106
119
  context 'for an invalid feature name' do
107
120
  context 'empty feature name' do
108
121
  let(:feature_name) { '' }
@@ -113,7 +126,7 @@ RSpec.describe Flipper::UI::Actions::Features do
113
126
 
114
127
  it 'redirects back to feature' do
115
128
  expect(last_response.status).to be(302)
116
- expect(last_response.headers['Location']).to eq('/features/new?error=%22%22+is+not+a+valid+feature+name.')
129
+ expect(last_response.headers['Location']).to eq('/features/new?error=%22%22%20is%20not%20a%20valid%20feature%20name.')
117
130
  end
118
131
  end
119
132
 
@@ -126,7 +139,7 @@ RSpec.describe Flipper::UI::Actions::Features do
126
139
 
127
140
  it 'redirects back to feature' do
128
141
  expect(last_response.status).to be(302)
129
- expect(last_response.headers['Location']).to eq('/features/new?error=%22%22+is+not+a+valid+feature+name.')
142
+ expect(last_response.headers['Location']).to eq('/features/new?error=%22%22%20is%20not%20a%20valid%20feature%20name.')
130
143
  end
131
144
  end
132
145
  end
@@ -60,6 +60,23 @@ RSpec.describe Flipper::UI::Actions::GroupsGate do
60
60
  expect(last_response.headers['Location']).to eq('/features/search')
61
61
  end
62
62
 
63
+ context 'feature name contains space' do
64
+ before do
65
+ post 'features/sp%20ace/groups',
66
+ { 'value' => group_name, 'operation' => 'enable', 'authenticity_token' => token },
67
+ 'rack.session' => session
68
+ end
69
+
70
+ it 'adds item to members' do
71
+ expect(flipper["sp ace"].groups_value).to include('admins')
72
+ end
73
+
74
+ it 'redirects back to feature' do
75
+ expect(last_response.status).to be(302)
76
+ expect(last_response.headers['Location']).to eq('/features/sp%20ace')
77
+ end
78
+ end
79
+
63
80
  context 'group name contains whitespace' do
64
81
  let(:group_name) { ' admins ' }
65
82
 
@@ -74,7 +91,7 @@ RSpec.describe Flipper::UI::Actions::GroupsGate do
74
91
 
75
92
  it 'redirects back to feature' do
76
93
  expect(last_response.status).to be(302)
77
- expect(last_response.headers['Location']).to eq('/features/search/groups?error=The+group+named+%22not_here%22+has+not+been+registered.')
94
+ expect(last_response.headers['Location']).to eq('/features/search/groups?error=The%20group%20named%20%22not_here%22%20has%20not%20been%20registered.')
78
95
  end
79
96
  end
80
97
 
@@ -83,7 +100,7 @@ RSpec.describe Flipper::UI::Actions::GroupsGate do
83
100
 
84
101
  it 'redirects back to feature' do
85
102
  expect(last_response.status).to be(302)
86
- expect(last_response.headers['Location']).to eq('/features/search/groups?error=The+group+named+%22%22+has+not+been+registered.')
103
+ expect(last_response.headers['Location']).to eq('/features/search/groups?error=The%20group%20named%20%22%22%20has%20not%20been%20registered.')
87
104
  end
88
105
  end
89
106
 
@@ -92,7 +109,7 @@ RSpec.describe Flipper::UI::Actions::GroupsGate do
92
109
 
93
110
  it 'redirects back to feature' do
94
111
  expect(last_response.status).to be(302)
95
- expect(last_response.headers['Location']).to eq('/features/search/groups?error=The+group+named+%22%22+has+not+been+registered.')
112
+ expect(last_response.headers['Location']).to eq('/features/search/groups?error=The%20group%20named%20%22%22%20has%20not%20been%20registered.')
96
113
  end
97
114
  end
98
115
  end
@@ -30,6 +30,23 @@ RSpec.describe Flipper::UI::Actions::PercentageOfActorsGate do
30
30
  end
31
31
  end
32
32
 
33
+ context 'with space in feature name' do
34
+ before do
35
+ post 'features/sp%20ace/percentage_of_actors',
36
+ { 'value' => '24', 'authenticity_token' => token },
37
+ 'rack.session' => session
38
+ end
39
+
40
+ it 'enables the feature' do
41
+ expect(flipper["sp ace"].percentage_of_actors_value).to be(24)
42
+ end
43
+
44
+ it 'redirects back to feature' do
45
+ expect(last_response.status).to be(302)
46
+ expect(last_response.headers['Location']).to eq('/features/sp%20ace')
47
+ end
48
+ end
49
+
33
50
  context 'with invalid value' do
34
51
  before do
35
52
  post 'features/search/percentage_of_actors',
@@ -43,7 +60,7 @@ RSpec.describe Flipper::UI::Actions::PercentageOfActorsGate do
43
60
 
44
61
  it 'redirects back to feature' do
45
62
  expect(last_response.status).to be(302)
46
- expect(last_response.headers['Location']).to eq('/features/search?error=Invalid+percentage+of+actors+value%3A+value+must+be+a+positive+number+less+than+or+equal+to+100%2C+but+was+555')
63
+ expect(last_response.headers['Location']).to eq('/features/search?error=Invalid%20percentage%20of%20actors%20value:%20value%20must%20be%20a%20positive%20number%20less%20than%20or%20equal%20to%20100,%20but%20was%20555')
47
64
  end
48
65
  end
49
66
  end
@@ -30,6 +30,23 @@ RSpec.describe Flipper::UI::Actions::PercentageOfTimeGate do
30
30
  end
31
31
  end
32
32
 
33
+ context 'with space in feature name' do
34
+ before do
35
+ post 'features/sp%20ace/percentage_of_time',
36
+ { 'value' => '24', 'authenticity_token' => token },
37
+ 'rack.session' => session
38
+ end
39
+
40
+ it 'enables the feature' do
41
+ expect(flipper["sp ace"].percentage_of_time_value).to be(24)
42
+ end
43
+
44
+ it 'redirects back to feature' do
45
+ expect(last_response.status).to be(302)
46
+ expect(last_response.headers['Location']).to eq('/features/sp%20ace')
47
+ end
48
+ end
49
+
33
50
  context 'with invalid value' do
34
51
  before do
35
52
  post 'features/search/percentage_of_time',
@@ -43,7 +60,7 @@ RSpec.describe Flipper::UI::Actions::PercentageOfTimeGate do
43
60
 
44
61
  it 'redirects back to feature' do
45
62
  expect(last_response.status).to be(302)
46
- expect(last_response.headers['Location']).to eq('/features/search?error=Invalid+percentage+of+time+value%3A+value+must+be+a+positive+number+less+than+or+equal+to+100%2C+but+was+555')
63
+ expect(last_response.headers['Location']).to eq('/features/search?error=Invalid%20percentage%20of%20time%20value:%20value%20must%20be%20a%20positive%20number%20less%20than%20or%20equal%20to%20100,%20but%20was%20555')
47
64
  end
48
65
  end
49
66
  end
@@ -59,6 +59,17 @@ RSpec.describe Flipper::UI::Configuration do
59
59
  end
60
60
  end
61
61
 
62
+ describe "#cloud_recommendation" do
63
+ it "has default value" do
64
+ expect(configuration.cloud_recommendation).to eq(true)
65
+ end
66
+
67
+ it "can be updated" do
68
+ configuration.cloud_recommendation = false
69
+ expect(configuration.cloud_recommendation).to eq(false)
70
+ end
71
+ end
72
+
62
73
  describe "#feature_removal_enabled" do
63
74
  it "has default value" do
64
75
  expect(configuration.feature_removal_enabled).to eq(true)
@@ -24,19 +24,6 @@ RSpec.describe Flipper::UI do
24
24
  end
25
25
  end
26
26
 
27
- describe 'Initializing middleware lazily with a block' do
28
- let(:app) do
29
- build_app(-> { flipper })
30
- end
31
-
32
- it 'works' do
33
- flipper.enable :some_great_feature
34
- get '/features'
35
- expect(last_response.status).to be(200)
36
- expect(last_response.body).to include('some_great_feature')
37
- end
38
- end
39
-
40
27
  describe 'Request method unsupported by action' do
41
28
  it 'raises error' do
42
29
  expect 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.20.3
4
+ version: 0.22.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: 2021-01-10 00:00:00.000000000 Z
11
+ date: 2021-07-08 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.20.3
59
+ version: 0.22.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.20.3
66
+ version: 0.22.0
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: erubi
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +96,7 @@ files:
96
96
  - docs/ui/images/description.png
97
97
  - docs/ui/images/feature.png
98
98
  - docs/ui/images/features.png
99
+ - examples/ui/authorization.ru
99
100
  - examples/ui/basic.ru
100
101
  - flipper-ui.gemspec
101
102
  - lib/flipper-ui.rb