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 +4 -4
- data/flipper-ui.gemspec +1 -0
- data/lib/flipper/ui/action.rb +10 -8
- data/lib/flipper/ui/actions/actors_gate.rb +10 -7
- data/lib/flipper/ui/configuration.rb +6 -0
- data/lib/flipper/ui/views/add_actor.erb +1 -1
- data/lib/flipper/ui/views/feature.erb +3 -3
- data/lib/flipper/ui/views/features.erb +1 -1
- data/lib/flipper/ui/views/layout.erb +1 -1
- data/lib/flipper/ui.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/action_spec.rb +0 -2
- data/spec/flipper/ui/actions/actors_gate_spec.rb +51 -5
- data/spec/flipper/ui/actions/add_feature_spec.rb +0 -2
- data/spec/flipper/ui/actions/boolean_gate_spec.rb +0 -2
- data/spec/flipper/ui/actions/feature_spec.rb +0 -2
- data/spec/flipper/ui/actions/features_spec.rb +0 -2
- data/spec/flipper/ui/actions/file_spec.rb +0 -2
- data/spec/flipper/ui/actions/groups_gate_spec.rb +0 -2
- data/spec/flipper/ui/actions/home_spec.rb +0 -2
- data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +0 -2
- data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +0 -2
- data/spec/flipper/ui/configuration_spec.rb +0 -2
- data/spec/flipper/ui/decorators/feature_spec.rb +0 -2
- data/spec/flipper/ui/decorators/gate_spec.rb +0 -1
- data/spec/flipper/ui/util_spec.rb +0 -1
- data/spec/flipper/ui_spec.rb +0 -2
- metadata +19 -10
- data/docs/ui/README.md +0 -190
- data/docs/ui/images/banner.png +0 -0
- data/docs/ui/images/description.png +0 -0
- data/docs/ui/images/feature.png +0 -0
- data/docs/ui/images/features.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97b8602defcb221552af235a13c299ecb0c7f5b615332e779251d97797b903b4
|
4
|
+
data.tar.gz: 2d884e8940356df6b869e8022f4685f9f2d229af28ae1daae93eddfe13879f6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 227ee9b40a4f6150abc280924f77500bfd600195679ba36ca70925af1b710ea9f2f6ee3027b2fdd45ecbc2f5be130758eed85865556904023c9935cf05f2a055
|
7
|
+
data.tar.gz: 2c32fb3f0b027e2181b4fd550f47bf5d1a6521692d1e73688b53bdea0270eb5708a9536c40dca17fb98c237d6d0e775b17e343ce5182e0f70db6bb2b2b947d35
|
data/flipper-ui.gemspec
CHANGED
data/lib/flipper/ui/action.rb
CHANGED
@@ -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:
|
32
|
-
hash:
|
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:
|
36
|
-
hash:
|
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:
|
40
|
-
hash:
|
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:
|
44
|
-
hash:
|
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
|
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
|
-
|
35
|
+
values.each do |value|
|
36
|
+
actor = Flipper::Actor.new(value)
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
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
|
-
|
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-
|
134
|
-
|
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
|
-
|
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
|
-
|
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
data/lib/flipper/version.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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
|
data/spec/flipper/ui_spec.rb
CHANGED
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.
|
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:
|
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.
|
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.
|
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.
|
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
|
data/docs/ui/images/banner.png
DELETED
Binary file
|
Binary file
|
data/docs/ui/images/feature.png
DELETED
Binary file
|
data/docs/ui/images/features.png
DELETED
Binary file
|