flipper-ui 1.3.6 → 1.3.7.test

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: 42fd7dcb2640b87a4c3a8a841bb1956ee6c2bd8a4a8d1619b2e7ce6d6b61b306
4
- data.tar.gz: 0b2bffb8e6ad0f7f2a6e220956188fcfbd9efc4ddb435be0554672542e1d2e0c
3
+ metadata.gz: e92c4665d60234e6b2942d61dbe7a860e747b573e91a4f4f107975e5c98f27ff
4
+ data.tar.gz: 0d289545c59f59c68b893b090e4192e993b0f1c469d850d3195deaa70cce4864
5
5
  SHA512:
6
- metadata.gz: 11c0782c666370f0e11e174b8c8159773495360d894ec72a6110db00b04cf03d5f5f80c72521a4ae41874f69af60bb208e6bd1cdcc1f9eafb3b9237ac24a4100
7
- data.tar.gz: 0a1b1a91471cb2c9a2561112314dbd2255c72c82eadb6cb499af1b412650f7a44eda39d360fe0d461624b88e933b4daba74cfe523c802aaa1447b91f389ee5ae
6
+ metadata.gz: 6dec25435c957b97c45075942889c435ee97240cf1fac3b6d157674c8a32e3cccf5b4cb620d3b49e6f520e57591c0f30d4efbbaf2b89a4613d8f2ac828289c89
7
+ data.tar.gz: 72c321f75fef4300ea37bc6cd1946f8fd20d597b5f0b24a514dd308c92416629e94714cf5b6f23fdc51e362c3e4515cc87f83d84f6e9991dab91b458720ce628
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.confirm_disable = false
28
29
  config.read_only = false
29
30
  # config.show_feature_description_in_list = true
30
31
  config.descriptions_source = lambda do |_keys|
@@ -7,6 +7,14 @@ require 'sanitize'
7
7
 
8
8
  module Flipper
9
9
  module UI
10
+ # Sanitize config for descriptions in list view. Removes anchor tags to
11
+ # avoid nested links (the feature row is wrapped in an <a> tag).
12
+ # See: https://github.com/flippercloud/flipper/issues/939
13
+ SANITIZE_LIST = Sanitize::Config.merge(
14
+ Sanitize::Config::BASIC,
15
+ elements: Sanitize::Config::BASIC[:elements] - ['a']
16
+ )
17
+
10
18
  class Action
11
19
  module FeatureNameFromRoute
12
20
  def feature_name
@@ -0,0 +1,26 @@
1
+ require 'flipper/ui/action'
2
+ require 'flipper/ui/util'
3
+
4
+ module Flipper
5
+ module UI
6
+ module Actions
7
+ class CloudMigrate < UI::Action
8
+ route %r{\A/settings\/cloud/?\Z}
9
+
10
+ def post
11
+ result = Flipper::Cloud.migrate(flipper)
12
+
13
+ if result.url
14
+ status 302
15
+ header 'location', result.url
16
+ halt [@code, @headers, ['']]
17
+ else
18
+ message = "Migration failed (HTTP #{result.code})"
19
+ message << ": #{result.message}" if result.message
20
+ redirect_to "/settings?error=#{Flipper::UI::Util.escape(message)}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -27,6 +27,7 @@ module Flipper
27
27
  @action_collection.add UI::Actions::Features
28
28
  @action_collection.add UI::Actions::Export
29
29
  @action_collection.add UI::Actions::Import
30
+ @action_collection.add UI::Actions::CloudMigrate
30
31
  @action_collection.add UI::Actions::Settings
31
32
 
32
33
  # Static Assets/Files
@@ -12,8 +12,8 @@ document.addEventListener("DOMContentLoaded", function () {
12
12
 
13
13
  document.querySelectorAll("*[data-confirmation-text]").forEach(function (element) {
14
14
  element.addEventListener("click", function (e) {
15
- var expected = e.target.getAttribute("data-confirmation-text");
16
- var actual = prompt(e.target.getAttribute("data-confirmation-prompt"));
15
+ var expected = element.getAttribute("data-confirmation-text");
16
+ var actual = prompt(element.getAttribute("data-confirmation-prompt"));
17
17
 
18
18
  if (expected !== actual) {
19
19
  e.preventDefault();
@@ -255,14 +255,13 @@
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
- <span class="d-block" data-bs-toggle="tooltip"
260
- <% if Flipper::UI.configuration.confirm_fully_enable %>
261
- 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 %>"
262
- data-confirmation-text="<%= feature_name %>"
263
- <% end %>
264
- title="Enable for everyone"
265
- >
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">
266
265
  Fully Enable
267
266
  </span>
268
267
  </button>
@@ -271,14 +270,13 @@
271
270
 
272
271
  <% unless @feature.off? %>
273
272
  <div class="col d-grid">
274
- <button type="submit" name="action" value="Disable" class="btn btn-outline-danger">
275
- <span class="d-block" data-bs-toggle="tooltip"
276
- <% if Flipper::UI.configuration.confirm_disable %>
277
- data-confirmation-prompt="Are you sure you want to disable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
278
- data-confirmation-text="<%= feature_name %>"
279
- <% end %>
280
- title="Disable for everyone by clearing all percentages, groups and actors."
281
- >
273
+ <button type="submit" name="action" value="Disable" class="btn btn-outline-danger"
274
+ <% if Flipper::UI.configuration.confirm_disable %>
275
+ data-confirmation-prompt="Are you sure you want to disable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
276
+ data-confirmation-text="<%= feature_name %>"
277
+ <% end %>
278
+ >
279
+ <span class="d-block" data-bs-toggle="tooltip" title="Disable for everyone by clearing all percentages, groups and actors.">
282
280
  Disable
283
281
  </span>
284
282
  </button>
@@ -45,7 +45,7 @@
45
45
  <div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
46
46
  <% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
47
47
  <div class="text-muted fw-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
48
- <%== Sanitize.fragment(feature.description, Sanitize::Config::BASIC) %>
48
+ <%== Sanitize.fragment(feature.description, Flipper::UI::SANITIZE_LIST) %>
49
49
  </div>
50
50
  <% end %>
51
51
  <div class="text-muted text-truncate">
@@ -1,3 +1,30 @@
1
+ <% if params.key?("error") %>
2
+ <div class="alert alert-danger"><%= params["error"] %></div>
3
+ <% end %>
4
+
5
+ <div class="card mb-4">
6
+ <div class="card-header">
7
+ <h4 class="m-0">Migrate to Flipper Cloud</h4>
8
+ </div>
9
+ <div class="card-body">
10
+ <p>Flipper Cloud gives you audit history, rollback, finer-grained permissions, and multi-environment sync. We even have a free tier!</p>
11
+
12
+ <ul class="mb-3">
13
+ <li>Audit log of every feature change</li>
14
+ <li>Instant rollback to any previous state</li>
15
+ <li>Team permissions and approval workflows</li>
16
+ <li>Sync flags across environments</li>
17
+ </ul>
18
+
19
+ <p>You have <strong><%= flipper.features.count %></strong> <%= flipper.features.count == 1 ? 'feature' : 'features' %> ready to migrate.</p>
20
+
21
+ <form action="<%= script_name %>/settings/cloud" method="post">
22
+ <%== csrf_input_tag %>
23
+ <input type="submit" value="Migrate to Flipper Cloud" class="btn btn-primary">
24
+ </form>
25
+ </div>
26
+ </div>
27
+
1
28
  <div class="card mb-4 ">
2
29
  <div class="card-header">
3
30
  <h4 class="m-0">Export</h4>
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.3.6'.freeze
2
+ VERSION = '1.3.7.test'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
@@ -0,0 +1,54 @@
1
+ require 'flipper/cloud/migrate'
2
+
3
+ RSpec.describe Flipper::UI::Actions::CloudMigrate do
4
+ let(:token) do
5
+ if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
6
+ Rack::Protection::AuthenticityToken.random_token
7
+ else
8
+ 'a'
9
+ end
10
+ end
11
+
12
+ let(:session) do
13
+ { :csrf => token, 'csrf' => token, '_csrf_token' => token }
14
+ end
15
+
16
+ describe "POST /settings/cloud" do
17
+ context "when migration succeeds" do
18
+ before do
19
+ flipper.enable :search
20
+ allow(Flipper::Cloud).to receive(:migrate).and_return(
21
+ Flipper::Cloud::MigrateResult.new(code: 200, url: "https://www.flippercloud.io/cloud/setup/abc123")
22
+ )
23
+
24
+ post '/settings/cloud',
25
+ {'authenticity_token' => token},
26
+ 'rack.session' => session
27
+ end
28
+
29
+ it 'redirects to the cloud URL' do
30
+ expect(last_response.status).to be(302)
31
+ expect(last_response.headers['location']).to eq('https://www.flippercloud.io/cloud/setup/abc123')
32
+ end
33
+ end
34
+
35
+ context "when migration fails" do
36
+ before do
37
+ flipper.enable :search
38
+ allow(Flipper::Cloud).to receive(:migrate).and_return(
39
+ Flipper::Cloud::MigrateResult.new(code: 500, url: nil)
40
+ )
41
+
42
+ post '/settings/cloud',
43
+ {'authenticity_token' => token},
44
+ 'rack.session' => session
45
+ end
46
+
47
+ it 'redirects back to settings with error' do
48
+ expect(last_response.status).to be(302)
49
+ expect(last_response.headers['location']).to include('/settings')
50
+ expect(last_response.headers['location']).to include('error=')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -211,4 +211,18 @@ RSpec.describe Flipper::UI::Actions::Feature do
211
211
  expect(last_response.body).to include('../../../../blah')
212
212
  end
213
213
  end
214
+
215
+ describe 'GET /features/:feature with question mark in feature name' do
216
+ before do
217
+ get '/features/can_do_stuff%3F'
218
+ end
219
+
220
+ it 'responds with success' do
221
+ expect(last_response.status).to be(200)
222
+ end
223
+
224
+ it 'renders template with question mark in feature name' do
225
+ expect(last_response.body).to include('can_do_stuff?')
226
+ end
227
+ end
214
228
  end
@@ -32,10 +32,12 @@ RSpec.describe Flipper::UI::Actions::Features do
32
32
  flipper.add("../../../../blah")
33
33
  flipper.add("this that")
34
34
  flipper.add("foo/bar")
35
+ flipper.add("can_do_stuff?")
35
36
  get '/features'
36
37
  expect(last_response.body).to include("..%2F..%2F..%2F..%2Fblah")
37
38
  expect(last_response.body).to include("this+that")
38
39
  expect(last_response.body).to include("foo%2Fbar")
40
+ expect(last_response.body).to include("can_do_stuff%3F")
39
41
  end
40
42
 
41
43
  context "when there are no features to list" do
@@ -88,6 +90,25 @@ RSpec.describe Flipper::UI::Actions::Features do
88
90
  expect(last_response.body).not_to include('<a class="btn btn-primary btn-sm" href="/features/new">Add Feature</a>')
89
91
  end
90
92
  end
93
+
94
+ context 'when descriptions have links' do
95
+ before do
96
+ Flipper::UI.configuration.show_feature_description_in_list = true
97
+ Flipper::UI.configuration.descriptions_source = lambda { |_keys|
98
+ { 'test_feature' => 'Check <a href="https://example.com">this link</a> for more info' }
99
+ }
100
+
101
+ flipper[:test_feature].enable
102
+ end
103
+
104
+ it 'strips anchor tags from descriptions to avoid nested links' do
105
+ get '/features'
106
+
107
+ expect(last_response.status).to eq(200)
108
+ expect(last_response.body).to include('Check this link for more info')
109
+ expect(last_response.body).not_to include('<a href="https://example.com">this link</a>')
110
+ end
111
+ end
91
112
  end
92
113
 
93
114
  describe 'POST /features' do
@@ -151,6 +172,19 @@ RSpec.describe Flipper::UI::Actions::Features do
151
172
  end
152
173
  end
153
174
 
175
+ context 'feature name contains question mark' do
176
+ let(:feature_name) { 'can_do_stuff?' }
177
+
178
+ it 'adds feature with question mark' do
179
+ expect(flipper.features.map(&:key)).to include('can_do_stuff?')
180
+ end
181
+
182
+ it 'redirects to feature with encoded question mark' do
183
+ expect(last_response.status).to be(302)
184
+ expect(last_response.headers['location']).to eq('/features/can_do_stuff%3F')
185
+ end
186
+ end
187
+
154
188
  context 'for an invalid feature name' do
155
189
  context 'empty feature name' do
156
190
  let(:feature_name) { '' }
@@ -23,5 +23,13 @@ RSpec.describe Flipper::UI::Actions::Settings do
23
23
  it 'renders template' do
24
24
  expect(last_response.body).to include('Download')
25
25
  end
26
+
27
+ it 'includes migrate to Flipper Cloud card' do
28
+ expect(last_response.body).to include('Migrate to Flipper Cloud')
29
+ end
30
+
31
+ it 'shows feature count' do
32
+ expect(last_response.body).to include('features ready to migrate')
33
+ end
26
34
  end
27
35
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.6
4
+ version: 1.3.7.test
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-02-25 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: rack
@@ -75,14 +76,14 @@ dependencies:
75
76
  requirements:
76
77
  - - "~>"
77
78
  - !ruby/object:Gem::Version
78
- version: 1.3.6
79
+ version: 1.3.7.test
79
80
  type: :runtime
80
81
  prerelease: false
81
82
  version_requirements: !ruby/object:Gem::Requirement
82
83
  requirements:
83
84
  - - "~>"
84
85
  - !ruby/object:Gem::Version
85
- version: 1.3.6
86
+ version: 1.3.7.test
86
87
  - !ruby/object:Gem::Dependency
87
88
  name: erubi
88
89
  requirement: !ruby/object:Gem::Requirement
@@ -117,6 +118,7 @@ dependencies:
117
118
  - - "<"
118
119
  - !ruby/object:Gem::Version
119
120
  version: '8'
121
+ description:
120
122
  email: support@flippercloud.io
121
123
  executables: []
122
124
  extensions: []
@@ -134,6 +136,7 @@ files:
134
136
  - lib/flipper/ui/actions/actors_gate.rb
135
137
  - lib/flipper/ui/actions/add_feature.rb
136
138
  - lib/flipper/ui/actions/boolean_gate.rb
139
+ - lib/flipper/ui/actions/cloud_migrate.rb
137
140
  - lib/flipper/ui/actions/export.rb
138
141
  - lib/flipper/ui/actions/feature.rb
139
142
  - lib/flipper/ui/actions/features.rb
@@ -175,6 +178,7 @@ files:
175
178
  - spec/flipper/ui/actions/actors_gate_spec.rb
176
179
  - spec/flipper/ui/actions/add_feature_spec.rb
177
180
  - spec/flipper/ui/actions/boolean_gate_spec.rb
181
+ - spec/flipper/ui/actions/cloud_migrate_spec.rb
178
182
  - spec/flipper/ui/actions/export_spec.rb
179
183
  - spec/flipper/ui/actions/feature_spec.rb
180
184
  - spec/flipper/ui/actions/features_spec.rb
@@ -198,8 +202,9 @@ metadata:
198
202
  homepage_uri: https://www.flippercloud.io
199
203
  source_code_uri: https://github.com/flippercloud/flipper
200
204
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
201
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.6
205
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.7.test
202
206
  funding_uri: https://github.com/sponsors/flippercloud
207
+ post_install_message:
203
208
  rdoc_options: []
204
209
  require_paths:
205
210
  - lib
@@ -214,26 +219,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
219
  - !ruby/object:Gem::Version
215
220
  version: '0'
216
221
  requirements: []
217
- rubygems_version: 3.6.9
222
+ rubygems_version: 3.5.22
223
+ signing_key:
218
224
  specification_version: 4
219
225
  summary: Feature flag UI for the Flipper gem
220
- test_files:
221
- - spec/flipper/ui/action_spec.rb
222
- - spec/flipper/ui/actions/actors_gate_spec.rb
223
- - spec/flipper/ui/actions/add_feature_spec.rb
224
- - spec/flipper/ui/actions/boolean_gate_spec.rb
225
- - spec/flipper/ui/actions/export_spec.rb
226
- - spec/flipper/ui/actions/feature_spec.rb
227
- - spec/flipper/ui/actions/features_spec.rb
228
- - spec/flipper/ui/actions/file_spec.rb
229
- - spec/flipper/ui/actions/groups_gate_spec.rb
230
- - spec/flipper/ui/actions/home_spec.rb
231
- - spec/flipper/ui/actions/import_spec.rb
232
- - spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb
233
- - spec/flipper/ui/actions/percentage_of_time_gate_spec.rb
234
- - spec/flipper/ui/actions/settings_spec.rb
235
- - spec/flipper/ui/configuration_spec.rb
236
- - spec/flipper/ui/decorators/feature_spec.rb
237
- - spec/flipper/ui/decorators/gate_spec.rb
238
- - spec/flipper/ui/util_spec.rb
239
- - spec/flipper/ui_spec.rb
226
+ test_files: []