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 +4 -4
- data/examples/ui/basic.ru +8 -0
- data/lib/flipper/ui/action.rb +10 -2
- data/lib/flipper/ui/actions/actors_gate.rb +1 -1
- data/lib/flipper/ui/actions/add_feature.rb +1 -1
- data/lib/flipper/ui/actions/boolean_gate.rb +1 -1
- data/lib/flipper/ui/actions/feature.rb +2 -1
- data/lib/flipper/ui/actions/features.rb +1 -1
- data/lib/flipper/ui/actions/file.rb +7 -2
- data/lib/flipper/ui/actions/groups_gate.rb +1 -1
- data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +1 -1
- data/lib/flipper/ui/actions/percentage_of_time_gate.rb +1 -1
- data/lib/flipper/ui/configuration.rb +7 -0
- data/lib/flipper/ui/decorators/feature.rb +4 -0
- data/lib/flipper/ui/views/feature.erb +48 -49
- data/lib/flipper/ui/views/features.erb +3 -3
- data/lib/flipper/ui/views/layout.erb +1 -1
- data/lib/flipper/ui/views/read_only.erb +9 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/actions/actors_gate_spec.rb +17 -0
- data/spec/flipper/ui/actions/add_feature_spec.rb +15 -0
- data/spec/flipper/ui/actions/export_spec.rb +5 -3
- data/spec/flipper/ui/actions/feature_spec.rb +35 -0
- data/spec/flipper/ui/actions/features_spec.rb +14 -0
- data/spec/flipper/ui/configuration_spec.rb +20 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2680cc3ed0d05e2a1b100b0f37013e555f44bb3bbc4331a5db58c07a649c27e5
|
4
|
+
data.tar.gz: 4ff283072612faf8b9af38da0c30117e85379b241e8da74b652fe218437460f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
data/lib/flipper/ui/action.rb
CHANGED
@@ -176,7 +176,7 @@ module Flipper
|
|
176
176
|
when String
|
177
177
|
object
|
178
178
|
else
|
179
|
-
|
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
|
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
|
@@ -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
|
-
|
29
|
+
render_read_only if read_only?
|
29
30
|
|
30
31
|
unless Flipper::UI.configuration.feature_removal_enabled
|
31
32
|
status 403
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
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
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
<%
|
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"
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
<%
|
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
|
-
|
238
|
-
<
|
239
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
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
|
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
|
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
|
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
|
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
|
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 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
|
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>
|
data/lib/flipper/version.rb
CHANGED
@@ -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
|
-
"
|
44
|
-
"
|
45
|
-
"
|
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.
|
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
|
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.
|
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.
|
66
|
+
version: 1.1.0
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: erubi
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|