flipper-ui 0.22.1 → 0.24.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: a619370e297b8fc379c57c8394cdfc798120ebfd9d4d091bc1902f3d9a2183be
4
- data.tar.gz: a36c5d0a79836aec62188c04f5954a506d737e110a135028b3fc7d9d889a2579
3
+ metadata.gz: 97b8602defcb221552af235a13c299ecb0c7f5b615332e779251d97797b903b4
4
+ data.tar.gz: 2d884e8940356df6b869e8022f4685f9f2d229af28ae1daae93eddfe13879f6a
5
5
  SHA512:
6
- metadata.gz: b02d0683b248297b59e6013f816a6f09bbde28889e38201e0f41c89d29118f06f16061749d55041a786daf81d2d67da8e88885033c4819233794dbd55df3ea8d
7
- data.tar.gz: 425e485bc419c1ae45554298df69a2d6cfdb4d2169fa2b0dc1b3e5e32009e5bafd27372796ab8a1bf3ac68dbc2d1be6a2474cbd7855b1d245fd61c8940e1cb81
6
+ metadata.gz: 227ee9b40a4f6150abc280924f77500bfd600195679ba36ca70925af1b710ea9f2f6ee3027b2fdd45ecbc2f5be130758eed85865556904023c9935cf05f2a055
7
+ data.tar.gz: 2c32fb3f0b027e2181b4fd550f47bf5d1a6521692d1e73688b53bdea0270eb5708a9536c40dca17fb98c237d6d0e775b17e343ce5182e0f70db6bb2b2b947d35
data/flipper-ui.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |gem|
24
24
  gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.2.0'
25
25
  gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
26
26
  gem.add_dependency 'erubi', '>= 1.0.0', '< 2.0.0'
27
+ gem.add_dependency 'sanitize', '< 7'
27
28
  end
@@ -3,6 +3,7 @@ require 'flipper/ui/configuration'
3
3
  require 'flipper/ui/error'
4
4
  require 'erubi'
5
5
  require 'json'
6
+ require 'sanitize'
6
7
 
7
8
  module Flipper
8
9
  module UI
@@ -28,20 +29,20 @@ module Flipper
28
29
 
29
30
  SOURCES = {
30
31
  bootstrap_css: {
31
- src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css".freeze,
32
- hash: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm".freeze
32
+ src: 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css'.freeze,
33
+ hash: 'sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l'.freeze
33
34
  }.freeze,
34
35
  jquery_js: {
35
- src: "https://code.jquery.com/jquery-3.2.1.slim.min.js".freeze,
36
- hash: "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN".freeze
36
+ src: 'https://code.jquery.com/jquery-3.6.0.slim.js'.freeze,
37
+ hash: 'sha256-HwWONEZrpuoh951cQD1ov2HUK5zA5DwJ1DNUXaM6FsY='.freeze
37
38
  }.freeze,
38
39
  popper_js: {
39
- src: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js".freeze,
40
- hash: "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q".freeze
40
+ src: 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js'.freeze,
41
+ hash: 'sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q'.freeze
41
42
  }.freeze,
42
43
  bootstrap_js: {
43
- src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js".freeze,
44
- hash: "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl".freeze
44
+ src: 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js'.freeze,
45
+ hash: 'sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF'.freeze
45
46
  }.freeze
46
47
  }.freeze
47
48
  SCRIPT_SRCS = SOURCES.values_at(:jquery_js, :popper_js, :bootstrap_js).map { |s| s[:src] }
@@ -238,6 +239,7 @@ module Flipper
238
239
  def view(name)
239
240
  path = views_path.join("#{name}.erb")
240
241
  raise "Template does not exist: #{path}" unless path.exist?
242
+
241
243
  eval(Erubi::Engine.new(path.read, escape: true).src)
242
244
  end
243
245
 
@@ -25,19 +25,22 @@ module Flipper
25
25
  def post
26
26
  feature = flipper[feature_name]
27
27
  value = params['value'].to_s.strip
28
+ values = value.split(UI.configuration.actors_separator).map(&:strip).uniq
28
29
 
29
- if Util.blank?(value)
30
+ if values.empty?
30
31
  error = "#{value.inspect} is not a valid actor value."
31
32
  redirect_to("/features/#{feature.key}/actors?error=#{error}")
32
33
  end
33
34
 
34
- actor = Flipper::Actor.new(value)
35
+ values.each do |value|
36
+ actor = Flipper::Actor.new(value)
35
37
 
36
- case params['operation']
37
- when 'enable'
38
- feature.enable_actor actor
39
- when 'disable'
40
- feature.disable_actor actor
38
+ case params['operation']
39
+ when 'enable'
40
+ feature.enable_actor actor
41
+ when 'disable'
42
+ feature.disable_actor actor
43
+ end
41
44
  end
42
45
 
43
46
  redirect_to("/features/#{feature.key}")
@@ -44,6 +44,11 @@ module Flipper
44
44
  # Default false. Only works when using descriptions.
45
45
  attr_accessor :show_feature_description_in_list
46
46
 
47
+ # Public: What should be used to denote you are trying to add multiple
48
+ # actors at once (instead of just a single actor).
49
+ # Default is comma ",".
50
+ attr_accessor :actors_separator
51
+
47
52
  VALID_BANNER_CLASS_VALUES = %w(
48
53
  danger
49
54
  dark
@@ -68,6 +73,7 @@ module Flipper
68
73
  @add_actor_placeholder = "a flipper id"
69
74
  @descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
70
75
  @show_feature_description_in_list = false
76
+ @actors_separator = ','
71
77
  end
72
78
 
73
79
  def using_descriptions?
@@ -8,7 +8,7 @@
8
8
  <h4 class="card-header">Enable Actor for <%= @feature.key %></h4>
9
9
  <div class="card-body">
10
10
  <p>
11
- Turn on this feature for an individual actor.
11
+ Turn on this feature for actors.
12
12
  </p>
13
13
  <form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post" class="form-inline">
14
14
  <%== csrf_input_tag %>
@@ -9,7 +9,7 @@
9
9
  <div class="col">
10
10
  <div class="card">
11
11
  <div class="card-body">
12
- <%= @feature.description %>
12
+ <%== Sanitize.fragment(@feature.description, Sanitize::Config::BASIC) %>
13
13
  </div>
14
14
  </div>
15
15
  </div>
@@ -130,8 +130,8 @@
130
130
  <%== csrf_input_tag %>
131
131
  <input type="hidden" name="operation" value="disable">
132
132
  <input type="hidden" name="value" value="<%= item %>">
133
- <button type="submit" value="Disable" class="btn btn-link btn-sm text-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
134
- <span class="octicon octicon-trashcan"></span>
133
+ <button type="submit" value="Disable" class="btn btn-outline-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
134
+ Remove
135
135
  </button>
136
136
  </form>
137
137
  </div>
@@ -47,7 +47,7 @@
47
47
  <div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
48
48
  <% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
49
49
  <div class="text-muted font-weight-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
50
- <%= feature.description %>
50
+ <%== Sanitize.fragment(feature.description, Sanitize::Config::BASIC) %>
51
51
  </div>
52
52
  <% end %>
53
53
  <div class="text-muted text-truncate">
@@ -13,7 +13,7 @@
13
13
  <div class="container mw-600">
14
14
  <%- unless Flipper::UI.configuration.banner_text.nil? -%>
15
15
  <div class="alert alert-<%= Flipper::UI.configuration.banner_class %> text-center font-weight-bold">
16
- <%= Flipper::UI.configuration.banner_text %>
16
+ <%== Sanitize.fragment(Flipper::UI.configuration.banner_text, Sanitize::Config::BASIC) %>
17
17
  </div>
18
18
  <%- end -%>
19
19
 
data/lib/flipper/ui.rb CHANGED
@@ -50,7 +50,7 @@ module Flipper
50
50
  builder.run app
51
51
  klass = self
52
52
  builder.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
53
- builder
53
+ builder.to_app
54
54
  end
55
55
 
56
56
  # Public: yields configuration instance for customizing UI text
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.22.1'.freeze
2
+ VERSION = '0.24.0'.freeze
3
3
  end
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Action do
4
2
  describe 'request methods' do
5
3
  let(:action_subclass) do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::ActorsGate do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -45,6 +43,7 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
45
43
  describe 'POST /features/:feature/actors' do
46
44
  context 'enabling an actor' do
47
45
  let(:value) { 'User;6' }
46
+ let(:multi_value) { 'User;5, User;7, User;9, User;12' }
48
47
 
49
48
  before do
50
49
  post 'features/search/actors',
@@ -53,7 +52,18 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
53
52
  end
54
53
 
55
54
  it 'adds item to members' do
56
- expect(flipper[:search].actors_value).to include('User;6')
55
+ expect(flipper[:search].actors_value).to include(value)
56
+ end
57
+
58
+ it 'adds item to multiple members' do
59
+ post 'features/search/actors',
60
+ { 'value' => multi_value, 'operation' => 'enable', 'authenticity_token' => token },
61
+ 'rack.session' => session
62
+
63
+ expect(flipper[:search].actors_value).to include('User;5')
64
+ expect(flipper[:search].actors_value).to include('User;7')
65
+ expect(flipper[:search].actors_value).to include('User;9')
66
+ expect(flipper[:search].actors_value).to include('User;12')
57
67
  end
58
68
 
59
69
  it 'redirects back to feature' do
@@ -80,10 +90,22 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
80
90
 
81
91
  context 'value contains whitespace' do
82
92
  let(:value) { ' User;6 ' }
93
+ let(:multi_value) { ' User;5 , User;7 , User;9 , User;12 ' }
83
94
 
84
95
  it 'adds item without whitespace' do
85
96
  expect(flipper[:search].actors_value).to include('User;6')
86
97
  end
98
+
99
+ it 'adds item to multi members without whitespace' do
100
+ post 'features/search/actors',
101
+ { 'value' => multi_value, 'operation' => 'enable', 'authenticity_token' => token },
102
+ 'rack.session' => session
103
+
104
+ expect(flipper[:search].actors_value).to include('User;5')
105
+ expect(flipper[:search].actors_value).to include('User;7')
106
+ expect(flipper[:search].actors_value).to include('User;9')
107
+ expect(flipper[:search].actors_value).to include('User;12')
108
+ end
87
109
  end
88
110
 
89
111
  context 'for an invalid actor value' do
@@ -109,16 +131,29 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
109
131
 
110
132
  context 'disabling an actor' do
111
133
  let(:value) { 'User;6' }
134
+ let(:multi_value) { 'User;5, User;7, User;9, User;12' }
112
135
 
113
136
  before do
114
- flipper[:search].enable_actor Flipper::Actor.new('User;6')
137
+ flipper[:search].enable_actor Flipper::Actor.new(value)
115
138
  post 'features/search/actors',
116
139
  { 'value' => value, 'operation' => 'disable', 'authenticity_token' => token },
117
140
  'rack.session' => session
118
141
  end
119
142
 
120
143
  it 'removes item from members' do
121
- expect(flipper[:search].actors_value).not_to include('User;6')
144
+ expect(flipper[:search].actors_value).not_to include(value)
145
+ end
146
+
147
+ it 'removes item from multi members' do
148
+ multi_value.split(',').map(&:strip).each do |value|
149
+ flipper[:search].enable_actor Flipper::Actor.new(value)
150
+ end
151
+
152
+ post 'features/search/actors',
153
+ { 'value' => multi_value, 'operation' => 'disable', 'authenticity_token' => token },
154
+ 'rack.session' => session
155
+
156
+ expect(flipper[:search].actors_value).not_to eq(Set.new(multi_value.split(',').map(&:strip)))
122
157
  end
123
158
 
124
159
  it 'redirects back to feature' do
@@ -128,10 +163,21 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
128
163
 
129
164
  context 'value contains whitespace' do
130
165
  let(:value) { ' User;6 ' }
166
+ let(:multi_value) { ' User;5 , User;7 , User;9 , User;12 ' }
131
167
 
132
168
  it 'removes item without whitespace' do
133
169
  expect(flipper[:search].actors_value).not_to include('User;6')
134
170
  end
171
+
172
+ it 'removes item without whitespace' do
173
+ multi_value.split(',').map(&:strip).each do |value|
174
+ flipper[:search].enable_actor Flipper::Actor.new(value)
175
+ end
176
+ post 'features/search/actors',
177
+ { 'value' => multi_value, 'operation' => 'disable', 'authenticity_token' => token },
178
+ 'rack.session' => session
179
+ expect(flipper[:search].actors_value).not_to eq(Set.new(multi_value.split(',').map(&:strip)))
180
+ end
135
181
  end
136
182
  end
137
183
  end
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::AddFeature do
4
2
  describe 'GET /features/new with feature_creation_enabled set to true' do
5
3
  before do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::BooleanGate do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::Feature do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::Features do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::File do
4
2
  describe 'GET /images/logo.png' do
5
3
  before do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::GroupsGate do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::Home do
4
2
  describe 'GET /' do
5
3
  before do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::PercentageOfActorsGate do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Actions::PercentageOfTimeGate do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Configuration do
4
2
  let(:configuration) { described_class.new }
5
3
 
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Decorators::Feature do
4
2
  let(:source) { {} }
5
3
  let(:adapter) { Flipper::Adapters::Memory.new(source) }
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/ui/decorators/gate'
3
2
 
4
3
  RSpec.describe Flipper::UI::Decorators::Gate do
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/ui/util'
3
2
 
4
3
  RSpec.describe Flipper::UI::Util do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI do
4
2
  let(:token) do
5
3
  if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
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: 0.22.1
4
+ version: 0.24.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: 2021-08-23 00:00:00.000000000 Z
11
+ date: 2022-02-17 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: 0.22.1
59
+ version: 0.24.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: 0.22.1
66
+ version: 0.24.0
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: erubi
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -84,6 +84,20 @@ dependencies:
84
84
  - - "<"
85
85
  - !ruby/object:Gem::Version
86
86
  version: 2.0.0
87
+ - !ruby/object:Gem::Dependency
88
+ name: sanitize
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "<"
92
+ - !ruby/object:Gem::Version
93
+ version: '7'
94
+ type: :runtime
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "<"
99
+ - !ruby/object:Gem::Version
100
+ version: '7'
87
101
  description:
88
102
  email:
89
103
  - nunemaker@gmail.com
@@ -91,11 +105,6 @@ executables: []
91
105
  extensions: []
92
106
  extra_rdoc_files: []
93
107
  files:
94
- - docs/ui/README.md
95
- - docs/ui/images/banner.png
96
- - docs/ui/images/description.png
97
- - docs/ui/images/feature.png
98
- - docs/ui/images/features.png
99
108
  - examples/ui/authorization.ru
100
109
  - examples/ui/basic.ru
101
110
  - flipper-ui.gemspec
@@ -169,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
178
  - !ruby/object:Gem::Version
170
179
  version: '0'
171
180
  requirements: []
172
- rubygems_version: 3.0.3
181
+ rubygems_version: 3.1.2
173
182
  signing_key:
174
183
  specification_version: 4
175
184
  summary: UI for the Flipper gem
data/docs/ui/README.md DELETED
@@ -1,190 +0,0 @@
1
- # Flipper::UI
2
-
3
- UI for the [Flipper](https://github.com/jnunemaker/flipper) gem.
4
-
5
- ## Screenshots
6
-
7
- Viewing list of features:
8
-
9
- ![features](images/features.png)
10
-
11
- Viewing an individual feature:
12
-
13
- ![feature](images/feature.png)
14
-
15
- ## Installation
16
-
17
- Add this line to your application's Gemfile:
18
-
19
- gem 'flipper-ui'
20
-
21
- And then execute:
22
-
23
- $ bundle
24
-
25
- Or install it yourself as:
26
-
27
- $ gem install flipper-ui
28
-
29
- ## Usage
30
-
31
- ### Rails
32
-
33
- Given that you've already initialized `Flipper` as per the [flipper](https://github.com/jnunemaker/flipper) readme, you can mount `Flipper::UI` to a route of your choice:
34
-
35
- ```ruby
36
- # config/routes.rb
37
- YourRailsApp::Application.routes.draw do
38
- mount Flipper::UI.app(Flipper) => '/flipper'
39
- end
40
- ```
41
-
42
- If you'd like to lazy load flipper, you can instead pass a block to initialize it:
43
-
44
- ```ruby
45
- # config/routes.rb
46
- YourRailsApp::Application.routes.draw do
47
- flipper_block = lambda {
48
- # some flipper initialization here, for example:
49
- adapter = Flipper::Adapters::Memory.new
50
- Flipper.new(adapter)
51
- }
52
- mount Flipper::UI.app(flipper_block) => '/flipper'
53
- end
54
- ```
55
-
56
- #### Security
57
-
58
- You almost certainly want to limit access when using Flipper::UI in production.
59
-
60
- ##### Basic Authentication via Rack
61
- The `Flipper::UI.app` method yields a builder instance prior to any predefined middleware. You can insert the `Rack::Auth::Basic` middleware, that'll prompt for a username and password when visiting the defined (i.e., `/flipper`) route.
62
-
63
- ```ruby
64
- # config/routes.rb
65
-
66
- flipper_app = Flipper::UI.app(Flipper.instance) do |builder|
67
- builder.use Rack::Auth::Basic do |username, password|
68
- # Verify credentials
69
- end
70
- end
71
- mount flipper_app, at: '/flipper'
72
- ```
73
-
74
- ##### Route Constraints
75
- It is possible to use [routes constraints](http://guides.rubyonrails.org/routing.html#request-based-constraints) to limit access to routes:
76
-
77
- ```ruby
78
- # config/routes.rb
79
-
80
- flipper_constraint = lambda { |request| request.remote_ip == '127.0.0.1' }
81
- constraints flipper_constraint do
82
- mount Flipper::UI.app(flipper) => '/flipper'
83
- end
84
- ```
85
-
86
- Another example is to use the `current_user` when using a gem-based authentication system (i.e., [warden](https://github.com/hassox/warden) or [devise](https://github.com/plataformatec/devise)):
87
-
88
- ```ruby
89
- # initializers/admin_access.rb
90
-
91
- class CanAccessFlipperUI
92
- def self.matches?(request)
93
- current_user = request.env['warden'].user
94
- current_user.present? && current_user.respond_to?(:admin?) && current_user.admin?
95
- end
96
- end
97
-
98
- # config/routes.rb
99
-
100
- constraints CanAccessFlipperUI do
101
- mount Flipper::UI.app(Flipper) => '/flipper'
102
- end
103
- ```
104
-
105
- ### Standalone
106
-
107
- Minimal example for Rack:
108
-
109
- ```ruby
110
- # config.ru
111
-
112
- require 'flipper/ui'
113
-
114
- adapter = Flipper::Adapters::Memory.new
115
- flipper = Flipper.new(adapter)
116
-
117
- run Flipper::UI.app(flipper) { |builder|
118
- builder.use Rack::Session::Cookie, secret: "something long and random"
119
- }
120
- ```
121
-
122
- The key is that you need to have sessions setup. Rails does this for you, so this step isn't necessary, but for standalone rack, you'll need it. Without sessions setup, you will receive a Runtime error like:
123
-
124
- ```
125
- RuntimeError: you need to set up a session middleware *before* Rack::Protection::RemoteToken.
126
- ```
127
-
128
- See [examples/ui/basic.ru](https://github.com/jnunemaker/flipper/blob/master/examples/ui/basic.ru) for a more full example
129
-
130
- ### Configuration
131
-
132
- Flipper UI can be customized via `configure`, which yields a configuration instance.
133
-
134
- #### Description
135
-
136
- We can associate a `description` for each `feature` by providing a descriptions source:
137
-
138
- ```ruby
139
- Flipper::UI.configure do |config|
140
- config.descriptions_source = ->(keys) do
141
- # descriptions loaded from YAML file or database (postgres, mysql, etc)
142
- # return has to be hash of {String key => String description}
143
- end
144
-
145
- # Defaults to false. Set to true to show feature descriptions on the list
146
- # page as well as the view page.
147
- # config.show_feature_description_in_list = true
148
- end
149
- ```
150
-
151
- Descriptions show up in the UI like so:
152
-
153
- ![description](images/description.png)
154
-
155
- #### Banner
156
-
157
- Flipper UI can display a banner across the top of the page. The `banner_text` and `banner_class` can be configured by using the `Flipper::UI.configure` block as seen below.
158
-
159
- ```ruby
160
- Flipper::UI.configure do |config|
161
- config.banner_text = 'Production Environment'
162
- config.banner_class = 'danger'
163
- end
164
- ```
165
-
166
- By default the `environment` is set to an empty string so no banner will show. If you wish to customize the look of the banner, you can set `banner_class` to one of the bootstrap color classes: `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, or `dark`. The default `banner_class` is `danger`.
167
-
168
- The above configuration results in:
169
-
170
- ![banner](images/banner.png)
171
-
172
- #### Fun mode
173
-
174
- By default, Flipper UI displays a videoclip when there are no flags. The `fun` mode can be configured by using the `Flipper::UI.configure` block as seen below.
175
-
176
- ```ruby
177
- Flipper::UI.configure do |config|
178
- config.fun = false
179
- end
180
- ```
181
-
182
- ## Contributing
183
-
184
- 1. Fork it
185
- 2. Create your feature branch (`git checkout -b my-new-feature`)
186
- 3. **Fire up the app** (`script/server`)
187
- 4. Run the tests `bundle exec rake`
188
- 5. Commit your changes (`git commit -am 'Added some feature'`)
189
- 6. Push to the branch (`git push origin my-new-feature`)
190
- 7. Create new Pull Request
Binary file
Binary file
Binary file
Binary file