flipper-ui 1.4.0 → 1.4.1

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: 00b09ef9134ecfe7b076edae579e9ff91827964ada4053fa8d5b5167b2965992
4
- data.tar.gz: 22b200ae581fd91ced63c0936bfa03ce9ee306779bf09c7ed40809ec97e9d4c6
3
+ metadata.gz: '019dbf0664d70ba33fd6425f7ba20bff41a1be13ff42c97b184ea37c2c500fd5'
4
+ data.tar.gz: d1f2131f1d38cc5aa1ac4b0b5cc1298c914e8d4fd6e6f8693f218f6802977e8b
5
5
  SHA512:
6
- metadata.gz: d85919950864694f4156846ffd1cf20c758451821409c6ae7fb566b11759740842368af8393669617c5d8c5764ac5a9d11c7f91207641c51892dec0169cd09ad
7
- data.tar.gz: 62b4831c0a38c4321ab627480e79ab5043140d4b57869c80a3792288d9b2d21421133f701ba1a8df72b6d5d707854df65714cce7009cb383aee039c04177718a
6
+ metadata.gz: 9cee5defa8de58857b30dabf3db81047e61b1138bfaa090817d2b9b0732bc662bf7b1eaa4e5973cb59ae1ded71ccda44c178901450ccd20c7b2ebec5b79f292b
7
+ data.tar.gz: 011ba0f8d3db3ef0efaab5e684d79beb9fee04c28dc660ec394b1cdfab37ac1c0af3c83ab3f1731a623cdd92a159d87c8f157f269b0d1eac269603603eba18be
@@ -16,6 +16,10 @@ module Flipper
16
16
  @feature = Decorators::Feature.new(feature)
17
17
 
18
18
  if params['action'] == 'Enable'
19
+ if Flipper::UI.configuration.disable_fully_enable
20
+ status 403
21
+ halt view_response(:disable_fully_enable)
22
+ end
19
23
  feature.enable
20
24
  else
21
25
  feature.disable
@@ -77,6 +77,20 @@ module Flipper
77
77
  # Default is false.
78
78
  attr_accessor :confirm_disable
79
79
 
80
+ # Public: Set to disable the Fully Enable button in the UI, preventing
81
+ # users from fully enabling features via the web interface. Set to true
82
+ # for a default message, or a string for a custom message. Defaults to nil.
83
+ #
84
+ # Note: This only affects the UI. If flipper-api is mounted, full enable
85
+ # is still possible via the API.
86
+ #
87
+ # Examples:
88
+ #
89
+ # config.disable_fully_enable = true
90
+ # config.disable_fully_enable = "Use deploy pipeline instead."
91
+ #
92
+ attr_accessor :disable_fully_enable
93
+
80
94
  VALID_BANNER_CLASS_VALUES = %w(
81
95
  danger
82
96
  dark
@@ -90,6 +104,7 @@ module Flipper
90
104
 
91
105
  DEFAULT_DESCRIPTIONS_SOURCE = ->(_keys) { {} }
92
106
  DEFAULT_ACTOR_NAMES_SOURCE = ->(_keys) { {} }
107
+ DEFAULT_DISABLE_FULLY_ENABLE_MESSAGE = "Fully enabling features via the UI is disabled."
93
108
 
94
109
  def initialize
95
110
  @delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.")
@@ -106,6 +121,7 @@ module Flipper
106
121
  @actors_separator = ','
107
122
  @confirm_fully_enable = false
108
123
  @confirm_disable = true
124
+ @disable_fully_enable = nil
109
125
  @read_only = false
110
126
  @nav_items = [
111
127
  { title: "Features", href: "features" },
@@ -121,6 +137,14 @@ module Flipper
121
137
  using_descriptions? && @show_feature_description_in_list
122
138
  end
123
139
 
140
+ def disable_fully_enable_message
141
+ if @disable_fully_enable.is_a?(String)
142
+ @disable_fully_enable
143
+ else
144
+ DEFAULT_DISABLE_FULLY_ENABLE_MESSAGE
145
+ end
146
+ end
147
+
124
148
  def banner_class=(value)
125
149
  unless VALID_BANNER_CLASS_VALUES.include?(value)
126
150
  raise InvalidConfigurationValue, "The banner_class provided '#{value}' is " \
@@ -0,0 +1,3 @@
1
+ <div class="alert alert-danger">
2
+ <%= Flipper::UI.configuration.disable_fully_enable_message %>
3
+ </div>
@@ -255,16 +255,24 @@
255
255
  <div class="row">
256
256
  <% unless @feature.boolean_value %>
257
257
  <div class="col d-grid">
258
- <button type="submit" name="action" value="Enable" class="btn btn-outline-success"
259
- <% if Flipper::UI.configuration.confirm_fully_enable %>
260
- data-confirmation-prompt="Are you sure you want to fully enable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
261
- data-confirmation-text="<%= feature_name %>"
262
- <% end %>
263
- >
264
- <span class="d-block" data-bs-toggle="tooltip" title="Enable for everyone">
265
- Fully Enable
258
+ <% if Flipper::UI.configuration.disable_fully_enable %>
259
+ <span class="d-inline-block" tabindex="0" data-bs-toggle="tooltip" title="<%= Flipper::UI.configuration.disable_fully_enable_message %>">
260
+ <button type="submit" name="action" value="Enable" class="btn btn-outline-success w-100" disabled style="pointer-events: none;">
261
+ Fully Enable
262
+ </button>
266
263
  </span>
267
- </button>
264
+ <% else %>
265
+ <button type="submit" name="action" value="Enable" class="btn btn-outline-success"
266
+ <% if Flipper::UI.configuration.confirm_fully_enable %>
267
+ data-confirmation-prompt="Are you sure you want to fully enable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
268
+ data-confirmation-text="<%= feature_name %>"
269
+ <% end %>
270
+ >
271
+ <span class="d-block" data-bs-toggle="tooltip" title="Enable for everyone">
272
+ Fully Enable
273
+ </span>
274
+ </button>
275
+ <% end %>
268
276
  </div>
269
277
  <% end %>
270
278
 
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '1.4.1'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
@@ -64,5 +64,76 @@ RSpec.describe Flipper::UI::Actions::BooleanGate do
64
64
  expect(last_response.headers['location']).to eq('/features/search')
65
65
  end
66
66
  end
67
+
68
+ context 'when disable_fully_enable is false' do
69
+ before { Flipper::UI.configuration.disable_fully_enable = false }
70
+ after { Flipper::UI.configuration.disable_fully_enable = nil }
71
+
72
+ it 'allows enabling the feature' do
73
+ flipper.disable :search
74
+ post 'features/search/boolean',
75
+ { 'action' => 'Enable', 'authenticity_token' => token },
76
+ 'rack.session' => session
77
+ expect(flipper.enabled?(:search)).to be(true)
78
+ end
79
+ end
80
+
81
+ context 'when disable_fully_enable is true' do
82
+ before { Flipper::UI.configuration.disable_fully_enable = true }
83
+ after { Flipper::UI.configuration.disable_fully_enable = nil }
84
+
85
+ context 'with enable' do
86
+ before do
87
+ flipper.disable :search
88
+ post 'features/search/boolean',
89
+ { 'action' => 'Enable', 'authenticity_token' => token },
90
+ 'rack.session' => session
91
+ end
92
+
93
+ it 'does not enable the feature' do
94
+ expect(flipper.enabled?(:search)).to be(false)
95
+ end
96
+
97
+ it 'returns 403 status' do
98
+ expect(last_response.status).to be(403)
99
+ end
100
+
101
+ it 'renders the default disabled message' do
102
+ expect(last_response.body).to include('Fully enabling features via the UI is disabled.')
103
+ end
104
+ end
105
+
106
+ context 'with disable' do
107
+ before do
108
+ flipper.enable :search
109
+ post 'features/search/boolean',
110
+ { 'action' => 'Disable', 'authenticity_token' => token },
111
+ 'rack.session' => session
112
+ end
113
+
114
+ it 'still allows disabling the feature' do
115
+ expect(flipper.enabled?(:search)).to be(false)
116
+ end
117
+
118
+ it 'redirects back to feature' do
119
+ expect(last_response.status).to be(302)
120
+ expect(last_response.headers['location']).to eq('/features/search')
121
+ end
122
+ end
123
+ end
124
+
125
+ context 'when disable_fully_enable is a custom message' do
126
+ before { Flipper::UI.configuration.disable_fully_enable = "Use deploy pipeline instead." }
127
+ after { Flipper::UI.configuration.disable_fully_enable = nil }
128
+
129
+ it 'renders the custom message on 403' do
130
+ flipper.disable :search
131
+ post 'features/search/boolean',
132
+ { 'action' => 'Enable', 'authenticity_token' => token },
133
+ 'rack.session' => session
134
+ expect(last_response.status).to be(403)
135
+ expect(last_response.body).to include('Use deploy pipeline instead.')
136
+ end
137
+ end
67
138
  end
68
139
  end
@@ -125,6 +125,32 @@ RSpec.describe Flipper::UI::Actions::Feature do
125
125
  end
126
126
  end
127
127
 
128
+ context "when disable_fully_enable is true" do
129
+ before { Flipper::UI.configuration.disable_fully_enable = true }
130
+ after { Flipper::UI.configuration.disable_fully_enable = nil }
131
+
132
+ it 'renders the Fully Enable button as disabled' do
133
+ get '/features/search'
134
+ expect(last_response.body).to include('Fully Enable')
135
+ expect(last_response.body).to match(/disabled\s*>/)
136
+ end
137
+
138
+ it 'shows the default disabled tooltip' do
139
+ get '/features/search'
140
+ expect(last_response.body).to include('Fully enabling features via the UI is disabled.')
141
+ end
142
+ end
143
+
144
+ context "when disable_fully_enable is a custom message" do
145
+ before { Flipper::UI.configuration.disable_fully_enable = "Use deploy pipeline instead." }
146
+ after { Flipper::UI.configuration.disable_fully_enable = nil }
147
+
148
+ it 'shows custom disabled tooltip' do
149
+ get '/features/search'
150
+ expect(last_response.body).to include('Use deploy pipeline instead.')
151
+ end
152
+ end
153
+
128
154
  context 'custom actor names' do
129
155
  before do
130
156
  actor = Flipper::Actor.new('some_actor_name')
@@ -146,6 +146,39 @@ RSpec.describe Flipper::UI::Configuration do
146
146
  end
147
147
  end
148
148
 
149
+ describe "#disable_fully_enable" do
150
+ it "defaults to nil" do
151
+ expect(configuration.disable_fully_enable).to be_nil
152
+ end
153
+
154
+ it "can be set to true" do
155
+ configuration.disable_fully_enable = true
156
+ expect(configuration.disable_fully_enable).to eq(true)
157
+ end
158
+
159
+ it "can be set to false" do
160
+ configuration.disable_fully_enable = false
161
+ expect(configuration.disable_fully_enable).to eq(false)
162
+ end
163
+
164
+ it "can be set to a custom message" do
165
+ configuration.disable_fully_enable = "Use deploy pipeline instead."
166
+ expect(configuration.disable_fully_enable).to eq("Use deploy pipeline instead.")
167
+ end
168
+ end
169
+
170
+ describe "#disable_fully_enable_message" do
171
+ it "returns default message when set to true" do
172
+ configuration.disable_fully_enable = true
173
+ expect(configuration.disable_fully_enable_message).to eq("Fully enabling features via the UI is disabled.")
174
+ end
175
+
176
+ it "returns custom message when set to a string" do
177
+ configuration.disable_fully_enable = "Use deploy pipeline instead."
178
+ expect(configuration.disable_fully_enable_message).to eq("Use deploy pipeline instead.")
179
+ end
180
+ end
181
+
149
182
  describe "#show_feature_description_in_list" do
150
183
  it "has default value" do
151
184
  expect(configuration.show_feature_description_in_list).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.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-26 00:00:00.000000000 Z
11
+ date: 2026-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -76,14 +76,14 @@ dependencies:
76
76
  requirements:
77
77
  - - "~>"
78
78
  - !ruby/object:Gem::Version
79
- version: 1.4.0
79
+ version: 1.4.1
80
80
  type: :runtime
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - "~>"
85
85
  - !ruby/object:Gem::Version
86
- version: 1.4.0
86
+ version: 1.4.1
87
87
  - !ruby/object:Gem::Dependency
88
88
  name: erubi
89
89
  requirement: !ruby/object:Gem::Requirement
@@ -166,6 +166,7 @@ files:
166
166
  - lib/flipper/ui/views/add_actor.erb
167
167
  - lib/flipper/ui/views/add_feature.erb
168
168
  - lib/flipper/ui/views/add_group.erb
169
+ - lib/flipper/ui/views/disable_fully_enable.erb
169
170
  - lib/flipper/ui/views/feature.erb
170
171
  - lib/flipper/ui/views/feature_creation_disabled.erb
171
172
  - lib/flipper/ui/views/feature_removal_disabled.erb
@@ -202,7 +203,7 @@ metadata:
202
203
  homepage_uri: https://www.flippercloud.io
203
204
  source_code_uri: https://github.com/flippercloud/flipper
204
205
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
205
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.4.0
206
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.4.1
206
207
  funding_uri: https://github.com/sponsors/flippercloud
207
208
  post_install_message:
208
209
  rdoc_options: []