flipper-ui 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd48dae9f3960dac91b3f5f7e37379f645830b1af3b03fb799cd573ab6ada610
4
- data.tar.gz: 5da1c6b37d76ed5c5b7d7a5a971b5c24a61e0f01b814bf63ea08861dee663e4f
3
+ metadata.gz: 2680cc3ed0d05e2a1b100b0f37013e555f44bb3bbc4331a5db58c07a649c27e5
4
+ data.tar.gz: 4ff283072612faf8b9af38da0c30117e85379b241e8da74b652fe218437460f6
5
5
  SHA512:
6
- metadata.gz: 5e021d776e438792829e5c8f54af8911767a00a38853b5b736eb3aef61aba2c9414ab2948d03b4ca95494e3aeaf29b283ddd2e7dd29e2eb653102f8127c9a52b
7
- data.tar.gz: 360bbb64545a87b57462add396a98e629b1ba0a8b969ca631f62903744cf7c8e12d05252ab67a3e3d1dca4c34e103092e263f25ddda72673775ef3c26aeec96a
6
+ metadata.gz: 453c8df0b09edd877b2569b4bdb2b8a1aedb81c6c1822c33125b6676a40fa45b25b1fe6b53f048717215b3950729256bc1f9224486e060b6bd3fa3eec0fbcdf1
7
+ data.tar.gz: 251f22f42d786857ac494d66cf5e75bce1c20236850ae2f07740a9ebd94a7cf281327bdf2789437e6aa1856f6c09e06bfd621fd8ed3e9fc06acdb03a01dd06c3
data/examples/ui/basic.ru CHANGED
@@ -25,6 +25,7 @@ Flipper::UI.configure do |config|
25
25
  config.feature_removal_enabled = true
26
26
  config.cloud_recommendation = true
27
27
  config.confirm_fully_enable = false
28
+ config.read_only = false
28
29
  # config.show_feature_description_in_list = true
29
30
  config.descriptions_source = lambda do |_keys|
30
31
  {
@@ -38,6 +39,13 @@ Flipper::UI.configure do |config|
38
39
  "a/b" => "Why would someone use a slash? I don't know but someone did. Let's make this really long so they regret using slashes. Please don't use slashes.",
39
40
  }
40
41
  end
42
+
43
+ config.actor_names_source = lambda do |_keys|
44
+ {
45
+ '1' => 'John',
46
+ '6' => 'Brandon',
47
+ }
48
+ end
41
49
  end
42
50
 
43
51
  # You can uncomment these to get some default data:
@@ -176,7 +176,7 @@ module Flipper
176
176
  when String
177
177
  object
178
178
  else
179
- JSON.dump(object)
179
+ Typecast.to_json(object)
180
180
  end
181
181
  halt [@code, @headers, [body]]
182
182
  end
@@ -276,7 +276,7 @@ module Flipper
276
276
 
277
277
  # Internal: Method to call when the UI is in read only mode and you want
278
278
  # to inform people of that fact.
279
- def read_only
279
+ def render_read_only
280
280
  status 403
281
281
 
282
282
  breadcrumb 'Home', '/'
@@ -286,6 +286,14 @@ module Flipper
286
286
  halt view_response(:read_only)
287
287
  end
288
288
 
289
+ def read_only?
290
+ Flipper::UI.configuration.read_only || flipper.read_only?
291
+ end
292
+
293
+ def write_allowed?
294
+ !read_only?
295
+ end
296
+
289
297
  def bootstrap_css
290
298
  SOURCES[:bootstrap_css]
291
299
  end
@@ -23,7 +23,7 @@ module Flipper
23
23
  end
24
24
 
25
25
  def post
26
- read_only if Flipper::UI.configuration.read_only
26
+ render_read_only if read_only?
27
27
 
28
28
  feature = flipper[feature_name]
29
29
  value = params['value'].to_s.strip
@@ -8,7 +8,7 @@ module Flipper
8
8
  route %r{\A/features/new/?\Z}
9
9
 
10
10
  def get
11
- read_only if Flipper::UI.configuration.read_only
11
+ render_read_only if read_only?
12
12
 
13
13
  unless Flipper::UI.configuration.feature_creation_enabled
14
14
  status 403
@@ -10,7 +10,7 @@ module Flipper
10
10
  route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}
11
11
 
12
12
  def post
13
- read_only if Flipper::UI.configuration.read_only
13
+ render_read_only if read_only?
14
14
 
15
15
  feature = flipper[feature_name]
16
16
  @feature = Decorators::Feature.new(feature)
@@ -14,6 +14,7 @@ module Flipper
14
14
  @feature = Decorators::Feature.new(flipper_feature)
15
15
  descriptions = Flipper::UI.configuration.descriptions_source.call([flipper_feature.key])
16
16
  @feature.description = descriptions[@feature.key]
17
+ @feature.actor_names = Flipper::UI.configuration.actor_names_source.call(@feature.actors_value)
17
18
  @page_title = "#{@feature.key} // Features"
18
19
  @percentages = [0, 1, 5, 10, 25, 50, 100]
19
20
 
@@ -25,7 +26,7 @@ module Flipper
25
26
  end
26
27
 
27
28
  def delete
28
- read_only if Flipper::UI.configuration.read_only
29
+ render_read_only if read_only?
29
30
 
30
31
  unless Flipper::UI.configuration.feature_removal_enabled
31
32
  status 403
@@ -36,7 +36,7 @@ module Flipper
36
36
  end
37
37
 
38
38
  def post
39
- read_only if Flipper::UI.configuration.read_only
39
+ render_read_only if read_only?
40
40
 
41
41
  unless Flipper::UI.configuration.feature_creation_enabled
42
42
  status 403
@@ -1,4 +1,8 @@
1
- require 'rack/file'
1
+ if Rack.release >= "2.1"
2
+ require 'rack/files'
3
+ else
4
+ require 'rack/file'
5
+ end
2
6
  require 'flipper/ui/action'
3
7
 
4
8
  module Flipper
@@ -8,7 +12,8 @@ module Flipper
8
12
  route %r{(images|css|js)/.*\Z}
9
13
 
10
14
  def get
11
- Rack::File.new(public_path).call(request.env)
15
+ klass = Rack.release >= "2.1" ? Rack::Files : Rack::File
16
+ klass.new(public_path).call(request.env)
12
17
  end
13
18
  end
14
19
  end
@@ -22,7 +22,7 @@ module Flipper
22
22
  end
23
23
 
24
24
  def post
25
- read_only if Flipper::UI.configuration.read_only
25
+ render_read_only if read_only?
26
26
 
27
27
  feature = flipper[feature_name]
28
28
  value = params['value'].to_s.strip
@@ -10,7 +10,7 @@ module Flipper
10
10
  route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}
11
11
 
12
12
  def post
13
- read_only if Flipper::UI.configuration.read_only
13
+ render_read_only if read_only?
14
14
 
15
15
  feature = flipper[feature_name]
16
16
  @feature = Decorators::Feature.new(feature)
@@ -10,7 +10,7 @@ module Flipper
10
10
  route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}
11
11
 
12
12
  def post
13
- read_only if Flipper::UI.configuration.read_only
13
+ render_read_only if read_only?
14
14
 
15
15
  feature = flipper[feature_name]
16
16
  @feature = Decorators::Feature.new(feature)
@@ -45,6 +45,11 @@ module Flipper
45
45
  # page, and optionally the `features` pages. Defaults to empty block.
46
46
  attr_accessor :descriptions_source
47
47
 
48
+ # Public: If you set this, Flipper::UI will fetch actor names
49
+ # from your external source. Descriptions for `actors` will be shown on `feature`
50
+ # page. Defaults to empty block.
51
+ attr_accessor :actor_names_source
52
+
48
53
  # Public: Should feature descriptions be show on the `features` list page.
49
54
  # Default false. Only works when using descriptions.
50
55
  attr_accessor :show_feature_description_in_list
@@ -70,6 +75,7 @@ module Flipper
70
75
  ).freeze
71
76
 
72
77
  DEFAULT_DESCRIPTIONS_SOURCE = ->(_keys) { {} }
78
+ DEFAULT_ACTOR_NAMES_SOURCE = ->(_keys) { {} }
73
79
 
74
80
  def initialize
75
81
  @delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.")
@@ -81,6 +87,7 @@ module Flipper
81
87
  @cloud_recommendation = true
82
88
  @add_actor_placeholder = "a flipper id"
83
89
  @descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
90
+ @actor_names_source = DEFAULT_ACTOR_NAMES_SOURCE
84
91
  @show_feature_description_in_list = false
85
92
  @actors_separator = ','
86
93
  @confirm_fully_enable = false
@@ -15,6 +15,10 @@ module Flipper
15
15
  # configured for Flipper::UI.
16
16
  attr_accessor :description
17
17
 
18
+ # Internal: Used to preload actor names if actor_names_source is
19
+ # configured for Flipper::UI.
20
+ attr_accessor :actor_names
21
+
18
22
  # Public: Returns name titleized.
19
23
  def pretty_name
20
24
  @pretty_name ||= Util.titleize(name)
@@ -40,7 +40,7 @@
40
40
  </strong>
41
41
  </h6>
42
42
  </div>
43
- <% unless Flipper::UI.configuration.read_only %>
43
+ <% if write_allowed? %>
44
44
  <div class="col col-auto toggle-block-when-off">
45
45
  <button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add an actor</button>
46
46
  </div>
@@ -64,10 +64,16 @@
64
64
  <div class="card-body bg-lightest border-bottom py-3">
65
65
  <div class="row align-items-center">
66
66
  <div class="col col-mr-auto pl-md-5">
67
- <h6 class="m-0"><%= item %></h6>
67
+ <h6 class="m-0">
68
+ <% if Flipper::UI::Util.present?(@feature.actor_names[item]) %>
69
+ <%= "#{@feature.actor_names[item]} (#{item})" %>
70
+ <% else %>
71
+ <%= item %>
72
+ <% end %>
73
+ </h6>
68
74
  </div>
69
75
  <div class="col col-auto">
70
- <% unless Flipper::UI.configuration.read_only %>
76
+ <% if write_allowed? %>
71
77
  <form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post">
72
78
  <%== csrf_input_tag %>
73
79
  <input type="hidden" name="operation" value="disable">
@@ -96,7 +102,7 @@
96
102
  </strong>
97
103
  </h6>
98
104
  </div>
99
- <% unless Flipper::UI.configuration.read_only %>
105
+ <% if write_allowed? %>
100
106
  <div class="col col-auto toggle-block-when-off">
101
107
  <button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add a group</button>
102
108
  </div>
@@ -132,7 +138,7 @@
132
138
  <h6 class="m-0"><%= item %></h6>
133
139
  </div>
134
140
  <div class="col col-auto">
135
- <% unless Flipper::UI.configuration.read_only %>
141
+ <% if write_allowed? %>
136
142
  <form action="<%= script_name %>/features/<%= @feature.key %>/groups" method="post">
137
143
  <%== csrf_input_tag %>
138
144
  <input type="hidden" name="operation" value="disable">
@@ -154,7 +160,7 @@
154
160
  <div class="col col-mr-auto">
155
161
  <h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_actors_value > 0 %>">Enabled for <%= @feature.percentage_of_actors_value %>% of actors</strong></h6>
156
162
  </div>
157
- <% unless Flipper::UI.configuration.read_only %>
163
+ <% if write_allowed? %>
158
164
  <div class="col col-auto toggle-block-when-off">
159
165
  <button class="btn btn-outline-secondary js-toggle-trigger">Edit</button>
160
166
  </div>
@@ -165,7 +171,7 @@
165
171
  </div>
166
172
  </div>
167
173
 
168
- <% unless Flipper::UI.configuration.read_only %>
174
+ <% if write_allowed? %>
169
175
  <div class="card-body border-bottom toggle-block-when-on bg-lightest">
170
176
  <div class="row">
171
177
  <div class="col-12 col-md-6 mb-3 mb-md-0">
@@ -197,7 +203,7 @@
197
203
  <div class="col col-mr-auto">
198
204
  <h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_time_value > 0 %>">Enabled for <%= @feature.percentage_of_time_value %>% of time</strong></h6>
199
205
  </div>
200
- <% unless Flipper::UI.configuration.read_only %>
206
+ <% if write_allowed? %>
201
207
  <div class="col col-auto toggle-block-when-off">
202
208
  <button class="btn btn-outline-secondary js-toggle-trigger">Edit</button>
203
209
  </div>
@@ -208,7 +214,7 @@
208
214
  </div>
209
215
  </div>
210
216
 
211
- <% unless Flipper::UI.configuration.read_only %>
217
+ <% if write_allowed? %>
212
218
  <div class="card-body border-bottom toggle-block-when-on bg-lightest">
213
219
  <div class="row">
214
220
  <div class="col-12 col-md-6 mb-3 mb-md-0">
@@ -234,52 +240,45 @@
234
240
  </div>
235
241
  <% end %>
236
242
 
237
- <div class="card-body">
238
- <form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
239
- <%== csrf_input_tag %>
243
+ <% if write_allowed? %>
244
+ <div class="card-body">
245
+ <form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
246
+ <%== csrf_input_tag %>
240
247
 
241
- <div class="row">
242
- <% unless @feature.boolean_value %>
243
- <div class="col">
244
- <button type="submit" name="action" value="Enable" <% if Flipper::UI.configuration.confirm_fully_enable %>id="enable_feature__button"<% end %> class="btn btn-outline-success btn-block" <% if Flipper::UI.configuration.read_only %>disabled<% end %>>
245
- <span class="d-block" data-toggle="tooltip"
246
- <% if Flipper::UI.configuration.confirm_fully_enable %>
247
- data-confirmation-text="<%= feature_name %>"
248
- <% end %>
249
- <% if Flipper::UI.configuration.read_only %>
250
- title="Fully enable is not allowed in read only mode."
251
- <% else %>
252
- title="Enable for everyone"
253
- <% end %>
254
- >
255
- Fully Enable
256
- </span>
257
- </button>
258
- </div>
259
- <% end %>
248
+ <div class="row">
249
+ <% unless @feature.boolean_value %>
250
+ <div class="col">
251
+ <button type="submit" name="action" value="Enable" <% if Flipper::UI.configuration.confirm_fully_enable %>id="enable_feature__button"<% end %> class="btn btn-outline-success btn-block">
252
+ <span class="d-block" data-toggle="tooltip"
253
+ <% if Flipper::UI.configuration.confirm_fully_enable %>
254
+ data-confirmation-text="<%= feature_name %>"
255
+ <% end %>
256
+ title="Enable for everyone"
257
+ >
258
+ Fully Enable
259
+ </span>
260
+ </button>
261
+ </div>
262
+ <% end %>
260
263
 
261
- <% unless @feature.off? %>
262
- <div class="col">
263
- <button type="submit" name="action" value="Disable" class="btn btn-outline-danger btn-block" <% if Flipper::UI.configuration.read_only %>disabled<% end %>>
264
- <span class="d-block" data-toggle="tooltip"
265
- <% if Flipper::UI.configuration.read_only %>
266
- title="Disable is not allowed in read only mode.">
267
- <% else %>
268
- title="Disable for everyone by clearing all percentages, groups and actors.">
269
- <% end %>
270
- Disable
271
- </span>
272
- </button>
273
- </div>
274
- <% end %>
275
- </div>
276
- </form>
277
- </div>
264
+ <% unless @feature.off? %>
265
+ <div class="col">
266
+ <button type="submit" name="action" value="Disable" class="btn btn-outline-danger btn-block">
267
+ <span class="d-block" data-toggle="tooltip" title="Disable for everyone by clearing all percentages, groups and actors.">
268
+ Disable
269
+ </span>
270
+ </button>
271
+ </div>
272
+ <% end %>
273
+ </div>
274
+ </form>
275
+ </div>
276
+ <% end %>
278
277
  </div>
279
278
  </div>
280
279
  </div>
281
280
 
282
- <% if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_removal_enabled %>
281
+ <% if write_allowed? && Flipper::UI.configuration.feature_removal_enabled %>
283
282
  <div class="row">
284
283
  <div class="col">
285
284
  <div class="card border">
@@ -3,7 +3,7 @@
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>
6
- <%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
6
+ <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
7
7
  <p>
8
8
  <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
9
9
  </p>
@@ -11,7 +11,7 @@
11
11
  <% else %>
12
12
  <h4>Getting Started</h4>
13
13
  <p class="mb-1">You have not added any features to configure yet.</p>
14
- <%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
14
+ <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
15
15
  <p class="mt-2">
16
16
  <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
17
17
  </p>
@@ -26,7 +26,7 @@
26
26
  <% else %>
27
27
  <div class="card">
28
28
  <div class="card-header">
29
- <%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
29
+ <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
30
30
  <div class="float-right">
31
31
  <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
32
32
  </div>
@@ -50,7 +50,7 @@
50
50
  </a>
51
51
  </div>
52
52
  <div class="col text-muted p-0">
53
- <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>
53
+ <small>For audit history, rollback, finer-grained permissions, and multi-environment sync check out <a href="https://www.flippercloud.io/?utm_source=oss&utm_medium=ui&utm_campaign=spread_the_love">Flipper&nbsp;Cloud</a>. We even have a free tier!</small>
54
54
  </div>
55
55
  </div>
56
56
  </div>
@@ -1,3 +1,11 @@
1
1
  <div class="alert alert-danger">
2
- The UI is currently in read only mode. To change this, you'll need to set <code>Flipper::UI.configuration.read_only = false</code> wherever flipper is running from.
2
+ <p>The UI is currently in read only mode.</p>
3
+
4
+ <p>
5
+ To change this, you'll need to set <code>Flipper::UI.configuration.read_only = false</code> wherever flipper is running from.
6
+ </p>
7
+
8
+ <p>
9
+ Alternatively, you may be using the <code>Flipper::Adapters::ReadOnly</code> adapter. This will also set Flipper into read only mode.
10
+ </p>
3
11
  </div>
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -129,6 +129,23 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
129
129
  end
130
130
  end
131
131
 
132
+ context "when a readonly adapter is configured" do
133
+ let(:value) { 'User;6' }
134
+
135
+ before do
136
+ allow(flipper).to receive(:read_only?) { true }
137
+ end
138
+
139
+ it "does not allow an actor to be added" do
140
+ post 'features/search/actors',
141
+ { 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
142
+ 'rack.session' => session
143
+
144
+ expect(flipper[:search].actors_value).not_to include('User;6')
145
+ expect(last_response.body).to include("The UI is currently in read only mode.")
146
+ end
147
+ end
148
+
132
149
  context 'disabling an actor' do
133
150
  let(:value) { 'User;6' }
134
151
  let(:multi_value) { 'User;5, User;7, User;9, User;12' }
@@ -39,4 +39,19 @@ RSpec.describe Flipper::UI::Actions::AddFeature do
39
39
  expect(last_response.body).to include('Feature creation is disabled.')
40
40
  end
41
41
  end
42
+
43
+ describe 'GET /features/new when an adpter is set to readonly' do
44
+ before do
45
+ allow(flipper).to receive(:read_only?) { true }
46
+ get '/features/new'
47
+ end
48
+
49
+ it 'returns 403' do
50
+ expect(last_response.status).to be(403)
51
+ end
52
+
53
+ it 'shows that the UI is currently read-only mode' do
54
+ expect(last_response.body).to include('The UI is currently in read only mode.')
55
+ end
56
+ end
42
57
  end
@@ -21,6 +21,7 @@ RSpec.describe Flipper::UI::Actions::Features do
21
21
  flipper.enable_group :search, :employees
22
22
  flipper.enable :plausible
23
23
  flipper.disable :google_analytics
24
+ flipper.enable :analytics, Flipper.property(:plan).eq("basic")
24
25
 
25
26
  post '/settings/export',
26
27
  {'authenticity_token' => token},
@@ -40,9 +41,10 @@ RSpec.describe Flipper::UI::Actions::Features do
40
41
  expect(last_response.headers['Content-Type']).to eq('application/json')
41
42
  expect(data['version']).to eq(1)
42
43
  expect(data['features']).to eq({
43
- "search"=> {"boolean"=>nil, "groups"=>["admins", "employees"], "actors"=>["User;1", "User;100"], "percentage_of_actors"=>"10", "percentage_of_time"=>"15"},
44
- "plausible"=> {"boolean"=>"true", "groups"=>[], "actors"=>[], "percentage_of_actors"=>nil, "percentage_of_time"=>nil},
45
- "google_analytics"=> {"boolean"=>nil, "groups"=>[], "actors"=>[], "percentage_of_actors"=>nil, "percentage_of_time"=>nil},
44
+ "analytics" => {"boolean"=>nil, "expression"=>{"Equal"=>[{"Property"=>["plan"]}, "basic"]}, "groups"=>[], "actors"=>[], "percentage_of_actors"=>nil, "percentage_of_time"=>nil},
45
+ "search"=> {"boolean"=>nil, "expression"=>nil, "groups"=>["admins", "employees"], "actors"=>["User;1", "User;100"], "percentage_of_actors"=>"10", "percentage_of_time"=>"15"},
46
+ "plausible"=> {"boolean"=>"true", "expression"=>nil, "groups"=>[], "actors"=>[], "percentage_of_actors"=>nil, "percentage_of_time"=>nil},
47
+ "google_analytics"=> {"boolean"=>nil, "expression"=>nil, "groups"=>[], "actors"=>[], "percentage_of_actors"=>nil, "percentage_of_time"=>nil},
46
48
  })
47
49
  end
48
50
  end
@@ -112,6 +112,41 @@ RSpec.describe Flipper::UI::Actions::Feature do
112
112
  expect(last_response.body).to include('Enabled for 0% of actors')
113
113
  expect(last_response.body).to include('Most in-depth search')
114
114
  end
115
+
116
+ context "when in read-only mode" do
117
+ before do
118
+ allow(flipper).to receive(:read_only?) { true }
119
+ end
120
+
121
+ before { get '/features' }
122
+
123
+ it 'renders template with no buttons or ways to modify a feature' do
124
+ expect(last_response.body).not_to include("Fully Enable")
125
+ end
126
+ end
127
+
128
+ context 'custom actor names' do
129
+ before do
130
+ actor = Flipper::Actor.new('some_actor_name')
131
+ flipper['search'].enable_actor(actor)
132
+
133
+ Flipper::UI.configure do |config|
134
+ config.actor_names_source = lambda { |_keys|
135
+ {
136
+ "some_actor_name" => "Some Actor Name",
137
+ "some_other_actor_name" => "Some Other Actor Name",
138
+ }
139
+ }
140
+ end
141
+
142
+ get '/features/search'
143
+ end
144
+
145
+ it 'renders template with custom actor names' do
146
+ expect(last_response.body).to include('Some Actor Name (some_actor_name)')
147
+ expect(last_response.body).not_to include('Some Other Actor Name')
148
+ end
149
+ end
115
150
  end
116
151
 
117
152
  describe 'GET /features/:feature with _features in feature name' do
@@ -64,6 +64,20 @@ RSpec.describe Flipper::UI::Actions::Features do
64
64
  end
65
65
  end
66
66
  end
67
+
68
+ context "when in read-only mode" do
69
+ before { allow(flipper).to receive(:read_only?) { true } }
70
+
71
+ before { get '/features' }
72
+
73
+ it 'responds with success' do
74
+ expect(last_response.status).to be(200)
75
+ end
76
+
77
+ it 'renders template with no button' do
78
+ expect(last_response.body).not_to include('<a class="btn btn-primary btn-sm" href="/features/new">Add Feature</a>')
79
+ end
80
+ end
67
81
  end
68
82
 
69
83
  describe 'POST /features' do
@@ -110,6 +110,26 @@ RSpec.describe Flipper::UI::Configuration do
110
110
  end
111
111
  end
112
112
 
113
+ describe "#actor_names_source" do
114
+ it "has default value" do
115
+ expect(configuration.actor_names_source.call(%w[foo bar])).to eq({})
116
+ end
117
+
118
+ context "actor names source is provided" do
119
+ it "can be updated" do
120
+ configuration.actor_names_source = lambda do |_keys|
121
+ YAML.load_file(FlipperRoot.join('spec/support/actor_names.yml'))
122
+ end
123
+ keys = %w[actor_1 foo]
124
+ result = configuration.actor_names_source.call(keys)
125
+ expected = {
126
+ "actor_name_1" => "Actor #1",
127
+ }
128
+ expect(result).to eq(expected)
129
+ end
130
+ end
131
+ end
132
+
113
133
  describe "#confirm_fully_enable" do
114
134
  it "has default value" do
115
135
  expect(configuration.confirm_fully_enable).to eq(false)
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: 1.0.0
4
+ version: 1.1.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: 2023-08-23 00:00:00.000000000 Z
11
+ date: 2023-12-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: 1.0.0
59
+ version: 1.1.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: 1.0.0
66
+ version: 1.1.0
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: erubi
69
69
  requirement: !ruby/object:Gem::Requirement