flipper-ui 1.0.0 → 1.1.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: 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