flipper-ui 0.16.2 → 0.22.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/docs/ui/README.md +35 -25
- 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
- data/examples/ui/authorization.ru +46 -0
- data/examples/ui/basic.ru +39 -32
- data/flipper-ui.gemspec +2 -3
- data/lib/flipper/ui/action.rb +51 -7
- data/lib/flipper/ui/actions/actors_gate.rb +11 -8
- data/lib/flipper/ui/actions/feature.rb +5 -2
- data/lib/flipper/ui/actions/features.rb +16 -3
- data/lib/flipper/ui/actions/file.rb +1 -1
- 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/assets/javascripts/application.coffee +5 -3
- data/lib/flipper/ui/configuration.rb +45 -10
- data/lib/flipper/ui/decorators/feature.rb +42 -16
- data/lib/flipper/ui/middleware.rb +2 -1
- data/lib/flipper/ui/public/css/application.css +26 -6492
- data/lib/flipper/ui/public/images/logo.png +0 -0
- data/lib/flipper/ui/public/js/application.js +5 -5
- data/lib/flipper/ui/util.rb +40 -0
- data/lib/flipper/ui/views/add_actor.erb +3 -3
- data/lib/flipper/ui/views/add_feature.erb +2 -2
- data/lib/flipper/ui/views/add_group.erb +1 -1
- data/lib/flipper/ui/views/feature.erb +199 -180
- data/lib/flipper/ui/views/features.erb +56 -37
- data/lib/flipper/ui/views/layout.erb +22 -18
- data/lib/flipper/ui.rb +5 -8
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/actions/actors_gate_spec.rb +76 -15
- data/spec/flipper/ui/actions/boolean_gate_spec.rb +18 -0
- data/spec/flipper/ui/actions/feature_spec.rb +32 -16
- data/spec/flipper/ui/actions/features_spec.rb +65 -17
- data/spec/flipper/ui/actions/file_spec.rb +0 -20
- data/spec/flipper/ui/actions/groups_gate_spec.rb +20 -9
- data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +18 -3
- data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +18 -3
- data/spec/flipper/ui/configuration_spec.rb +80 -34
- data/spec/flipper/ui/decorators/feature_spec.rb +2 -32
- data/spec/flipper/ui_spec.rb +1 -14
- metadata +22 -141
- data/docs/ui/images/configured-ui.png +0 -0
- data/docs/ui/images/environment-banner.png +0 -0
- data/lib/flipper/ui/assets/stylesheets/.DS_Store +0 -0
- data/lib/flipper/ui/assets/stylesheets/application.scss +0 -19
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_alert.scss +0 -51
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_badge.scss +0 -47
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_breadcrumb.scss +0 -38
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_button-group.scss +0 -166
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_buttons.scss +0 -143
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_card.scss +0 -270
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_carousel.scss +0 -191
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_close.scss +0 -34
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_code.scss +0 -56
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_custom-forms.scss +0 -297
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_dropdown.scss +0 -131
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_forms.scss +0 -333
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_functions.scss +0 -86
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_grid.scss +0 -52
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_images.scss +0 -42
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_input-group.scss +0 -159
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_jumbotron.scss +0 -16
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_list-group.scss +0 -115
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_media.scss +0 -8
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_mixins.scss +0 -42
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_modal.scss +0 -168
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_nav.scss +0 -118
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_navbar.scss +0 -311
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_pagination.scss +0 -77
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_popover.scss +0 -183
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_print.scss +0 -124
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_progress.scss +0 -33
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_reboot.scss +0 -482
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_root.scss +0 -19
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_tables.scss +0 -180
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_tooltip.scss +0 -115
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_transitions.scss +0 -36
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_type.scss +0 -125
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_utilities.scss +0 -14
- data/lib/flipper/ui/assets/stylesheets/bootstrap/_variables.scss +0 -894
- data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap-grid.scss +0 -32
- data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap-reboot.scss +0 -12
- data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap.scss +0 -42
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_alert.scss +0 -13
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_background-variant.scss +0 -21
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_badge.scss +0 -12
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_border-radius.scss +0 -35
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_box-shadow.scss +0 -5
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_breakpoints.scss +0 -123
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_buttons.scss +0 -109
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_caret.scss +0 -65
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_clearfix.scss +0 -7
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_float.scss +0 -11
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_forms.scss +0 -137
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_gradients.scss +0 -45
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_grid-framework.scss +0 -67
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_grid.scss +0 -52
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_hover.scss +0 -39
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_image.scss +0 -36
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_list-group.scss +0 -21
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_lists.scss +0 -7
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_nav-divider.scss +0 -10
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_navbar-align.scss +0 -10
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_pagination.scss +0 -22
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_reset-text.scss +0 -17
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_resize.scss +0 -6
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_screen-reader.scss +0 -35
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_size.scss +0 -6
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_table-row.scss +0 -30
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-emphasis.scss +0 -14
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-hide.scss +0 -9
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-truncate.scss +0 -8
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_transition.scss +0 -9
- data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_visibility.scss +0 -7
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_align.scss +0 -8
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_background.scss +0 -19
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_borders.scss +0 -59
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_clearfix.scss +0 -3
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_display.scss +0 -38
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_embed.scss +0 -52
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_flex.scss +0 -46
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_float.scss +0 -9
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_position.scss +0 -36
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_screenreaders.scss +0 -11
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_sizing.scss +0 -12
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_spacing.scss +0 -51
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_text.scss +0 -52
- data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_visibility.scss +0 -11
- data/lib/flipper/ui/assets/stylesheets/primer/.scss-lint.yml +0 -446
- data/lib/flipper/ui/assets/stylesheets/primer/_alerts.scss +0 -106
- data/lib/flipper/ui/assets/stylesheets/primer/_avatars.scss +0 -36
- data/lib/flipper/ui/assets/stylesheets/primer/_base.scss +0 -40
- data/lib/flipper/ui/assets/stylesheets/primer/_blankslate.scss +0 -96
- data/lib/flipper/ui/assets/stylesheets/primer/_buttons.scss +0 -404
- data/lib/flipper/ui/assets/stylesheets/primer/_counter.scss +0 -10
- data/lib/flipper/ui/assets/stylesheets/primer/_filter-list.scss +0 -68
- data/lib/flipper/ui/assets/stylesheets/primer/_flex-table.scss +0 -20
- data/lib/flipper/ui/assets/stylesheets/primer/_forms.scss +0 -756
- data/lib/flipper/ui/assets/stylesheets/primer/_layout.scss +0 -69
- data/lib/flipper/ui/assets/stylesheets/primer/_menu.scss +0 -113
- data/lib/flipper/ui/assets/stylesheets/primer/_mixins.scss +0 -53
- data/lib/flipper/ui/assets/stylesheets/primer/_normalize.scss +0 -425
- data/lib/flipper/ui/assets/stylesheets/primer/_states.scss +0 -32
- data/lib/flipper/ui/assets/stylesheets/primer/_tabnav.scss +0 -65
- data/lib/flipper/ui/assets/stylesheets/primer/_tooltips.scss +0 -255
- data/lib/flipper/ui/assets/stylesheets/primer/_truncate.scss +0 -27
- data/lib/flipper/ui/assets/stylesheets/primer/_type.scss +0 -92
- data/lib/flipper/ui/assets/stylesheets/primer/_utility.scss +0 -73
- data/lib/flipper/ui/assets/stylesheets/primer/_variables.scss +0 -34
- data/lib/flipper/ui/assets/stylesheets/primer/primer.scss +0 -39
- data/lib/flipper/ui/eruby.rb +0 -11
- data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
- data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.svg +0 -288
- data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
- data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
- data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
- data/lib/flipper/ui/public/images/remove.png +0 -0
- data/lib/flipper/ui/public/octicons/LICENSE.txt +0 -9
- data/lib/flipper/ui/public/octicons/README.md +0 -1
- data/lib/flipper/ui/public/octicons/octicons-local.ttf +0 -0
- data/lib/flipper/ui/public/octicons/octicons.css +0 -236
- data/lib/flipper/ui/public/octicons/octicons.eot +0 -0
- data/lib/flipper/ui/public/octicons/octicons.less +0 -235
- data/lib/flipper/ui/public/octicons/octicons.svg +0 -200
- data/lib/flipper/ui/public/octicons/octicons.ttf +0 -0
- data/lib/flipper/ui/public/octicons/octicons.woff +0 -0
- data/lib/flipper/ui/public/octicons/sprockets-octicons.scss +0 -232
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b17af1c2cd05d9026a457941c46e7c497460e4289bd8446b90d2f0342e53f34c
|
4
|
+
data.tar.gz: 4adae31beca41aebaae69c9c3a0efe51bb024d0acc54eadf3982ddfff4a16748
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8e0d113cb89d77c9e8cd0ec83d8deb7a26f3f6e00452aa81ab6a308843dfe45e069b0e1303374ece24e4a5bd79718958e160249db9195ea22249bab2df66a5d
|
7
|
+
data.tar.gz: cc5f6567f690c2bd48b762083046b19e868f4885384885667f20d808cd09f0533df5d3df5239e4a21cc649babbe231e494af984b5c1261b4ef9358b68581784a
|
data/docs/ui/README.md
CHANGED
@@ -5,9 +5,11 @@ UI for the [Flipper](https://github.com/jnunemaker/flipper) gem.
|
|
5
5
|
## Screenshots
|
6
6
|
|
7
7
|
Viewing list of features:
|
8
|
+
|
8
9
|
![features](images/features.png)
|
9
10
|
|
10
11
|
Viewing an individual feature:
|
12
|
+
|
11
13
|
![feature](images/feature.png)
|
12
14
|
|
13
15
|
## Installation
|
@@ -37,14 +39,15 @@ YourRailsApp::Application.routes.draw do
|
|
37
39
|
end
|
38
40
|
```
|
39
41
|
|
40
|
-
If you'd like to lazy load flipper, you can pass a block
|
42
|
+
If you'd like to lazy load flipper, you can instead pass a block to initialize it:
|
41
43
|
|
42
44
|
```ruby
|
43
45
|
# config/routes.rb
|
44
46
|
YourRailsApp::Application.routes.draw do
|
45
47
|
flipper_block = lambda {
|
46
48
|
# some flipper initialization here, for example:
|
47
|
-
|
49
|
+
adapter = Flipper::Adapters::Memory.new
|
50
|
+
Flipper.new(adapter)
|
48
51
|
}
|
49
52
|
mount Flipper::UI.app(flipper_block) => '/flipper'
|
50
53
|
end
|
@@ -95,7 +98,7 @@ end
|
|
95
98
|
# config/routes.rb
|
96
99
|
|
97
100
|
constraints CanAccessFlipperUI do
|
98
|
-
mount Flipper::UI.app(
|
101
|
+
mount Flipper::UI.app(Flipper) => '/flipper'
|
99
102
|
end
|
100
103
|
```
|
101
104
|
|
@@ -106,7 +109,7 @@ Minimal example for Rack:
|
|
106
109
|
```ruby
|
107
110
|
# config.ru
|
108
111
|
|
109
|
-
require 'flipper
|
112
|
+
require 'flipper/ui'
|
110
113
|
|
111
114
|
adapter = Flipper::Adapters::Memory.new
|
112
115
|
flipper = Flipper.new(adapter)
|
@@ -126,32 +129,30 @@ See [examples/ui/basic.ru](https://github.com/jnunemaker/flipper/blob/master/exa
|
|
126
129
|
|
127
130
|
### Configuration
|
128
131
|
|
129
|
-
Flipper UI can be customized via `configure`, which yields a configuration instance
|
132
|
+
Flipper UI can be customized via `configure`, which yields a configuration instance.
|
130
133
|
|
131
|
-
|
132
|
-
* `config.groups`
|
133
|
-
* `config.percentage_of_actors`
|
134
|
-
* `config.percentage_of_time`
|
135
|
-
* `config.delete`
|
134
|
+
#### Description
|
136
135
|
|
137
|
-
|
136
|
+
We can associate a `description` for each `feature` by providing a descriptions source:
|
138
137
|
|
139
|
-
*e.g. customzing the percentage_of_actors and delete sections' titles and descriptions*
|
140
138
|
```ruby
|
141
139
|
Flipper::UI.configure do |config|
|
142
|
-
config.
|
143
|
-
|
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
144
|
|
145
|
-
|
146
|
-
|
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
|
147
148
|
end
|
148
149
|
```
|
149
150
|
|
150
|
-
|
151
|
+
Descriptions show up in the UI like so:
|
151
152
|
|
152
|
-
![
|
153
|
+
![description](images/description.png)
|
153
154
|
|
154
|
-
|
155
|
+
#### Banner
|
155
156
|
|
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.
|
157
158
|
|
@@ -166,15 +167,24 @@ By default the `environment` is set to an empty string so no banner will show. I
|
|
166
167
|
|
167
168
|
The above configuration results in:
|
168
169
|
|
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
|
+
```
|
170
181
|
|
171
182
|
## Contributing
|
172
183
|
|
173
184
|
1. Fork it
|
174
185
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
175
186
|
3. **Fire up the app** (`script/server`)
|
176
|
-
4.
|
177
|
-
5.
|
178
|
-
6.
|
179
|
-
7.
|
180
|
-
8. Create new Pull Request
|
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
|
data/docs/ui/images/feature.png
CHANGED
Binary file
|
data/docs/ui/images/features.png
CHANGED
Binary file
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# Usage:
|
3
|
+
# bundle exec rackup examples/ui/authorization.ru -p 9999
|
4
|
+
# bundle exec shotgun examples/ui/authorization.ru -p 9999
|
5
|
+
# http://localhost:9999/
|
6
|
+
#
|
7
|
+
require 'bundler/setup'
|
8
|
+
require "flipper/ui"
|
9
|
+
require "flipper/adapters/pstore"
|
10
|
+
|
11
|
+
Flipper.register(:admins) { |actor|
|
12
|
+
actor.respond_to?(:admin?) && actor.admin?
|
13
|
+
}
|
14
|
+
|
15
|
+
# Example middleware to allow reading the Flipper UI but nothing else.
|
16
|
+
class FlipperReadOnlyMiddleware
|
17
|
+
def initialize(app)
|
18
|
+
@app = app
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
request = Rack::Request.new(env)
|
23
|
+
|
24
|
+
if request.get?
|
25
|
+
@app.call(env)
|
26
|
+
else
|
27
|
+
[401, {}, ["You can only look"]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# You can uncomment these to get some default data:
|
33
|
+
# Flipper.enable(:search_performance_another_long_thing)
|
34
|
+
# Flipper.disable(:gauges_tracking)
|
35
|
+
# Flipper.disable(:unused)
|
36
|
+
# Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
|
37
|
+
# Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
|
38
|
+
# Flipper.enable_group(:secrets, :admins)
|
39
|
+
# Flipper.enable_percentage_of_time(:logging, 5)
|
40
|
+
# Flipper.enable_percentage_of_actors(:new_cache, 15)
|
41
|
+
# Flipper.add("a/b")
|
42
|
+
|
43
|
+
run Flipper::UI.app { |builder|
|
44
|
+
builder.use Rack::Session::Cookie, secret: "_super_secret"
|
45
|
+
builder.use FlipperReadOnlyMiddleware
|
46
|
+
}
|
data/examples/ui/basic.ru
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
#
|
2
2
|
# Usage:
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# # if you want it to not reload and be really fast
|
4
|
+
# bin/rackup examples/ui/basic.ru -p 9999
|
5
|
+
#
|
6
|
+
# # if you want reloading
|
7
|
+
# bin/shotgun examples/ui/basic.ru -p 9999
|
8
|
+
#
|
5
9
|
# http://localhost:9999/
|
6
10
|
#
|
7
|
-
require
|
8
|
-
require "
|
9
|
-
require "pathname"
|
10
|
-
|
11
|
-
root_path = Pathname(__FILE__).dirname.join("..").expand_path
|
12
|
-
lib_path = root_path.join("lib")
|
13
|
-
$:.unshift(lib_path)
|
14
|
-
|
15
|
-
require "flipper-ui"
|
11
|
+
require 'bundler/setup'
|
12
|
+
require "flipper/ui"
|
16
13
|
require "flipper/adapters/pstore"
|
17
|
-
require "active_support/notifications"
|
18
14
|
|
19
15
|
Flipper.register(:admins) { |actor|
|
20
16
|
actor.respond_to?(:admin?) && actor.admin?
|
@@ -24,28 +20,39 @@ Flipper.register(:early_access) { |actor|
|
|
24
20
|
actor.respond_to?(:early?) && actor.early?
|
25
21
|
}
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
Flipper::UI.configure do |config|
|
24
|
+
# config.banner_text = 'Production Environment'
|
25
|
+
# config.banner_class = 'danger'
|
26
|
+
config.feature_creation_enabled = true
|
27
|
+
config.feature_removal_enabled = true
|
28
|
+
config.cloud_recommendation = true
|
29
|
+
# config.show_feature_description_in_list = true
|
30
|
+
config.descriptions_source = lambda do |_keys|
|
31
|
+
{
|
32
|
+
"search_performance_another_long_thing" => "Just to test feature name length.",
|
33
|
+
"gauges_tracking" => "Should we track page views with gaug.es.",
|
34
|
+
"unused" => "Not used.",
|
35
|
+
"suits" => "Are suits necessary in business?",
|
36
|
+
"secrets" => "Secrets are lies.",
|
37
|
+
"logging" => "Log all the things.",
|
38
|
+
"new_cache" => "Like the old cache but newer.",
|
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.",
|
40
|
+
}
|
41
|
+
end
|
32
42
|
end
|
33
43
|
|
34
|
-
adapter = Flipper::Adapters::PStore.new
|
35
|
-
flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
36
|
-
|
37
44
|
# You can uncomment these to get some default data:
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
|
49
|
-
run Flipper::UI.app
|
45
|
+
# Flipper.enable(:search_performance_another_long_thing)
|
46
|
+
# Flipper.disable(:gauges_tracking)
|
47
|
+
# Flipper.disable(:unused)
|
48
|
+
# Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
|
49
|
+
# Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
|
50
|
+
# Flipper.enable_group(:secrets, :admins)
|
51
|
+
# Flipper.enable_group(:secrets, :early_access)
|
52
|
+
# Flipper.enable_percentage_of_time(:logging, 5)
|
53
|
+
# Flipper.enable_percentage_of_actors(:new_cache, 15)
|
54
|
+
# Flipper.add("a/b")
|
55
|
+
|
56
|
+
run Flipper::UI.app { |builder|
|
50
57
|
builder.use Rack::Session::Cookie, secret: "_super_secret"
|
51
58
|
}
|
data/flipper-ui.gemspec
CHANGED
@@ -10,7 +10,6 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.authors = ['John Nunemaker']
|
11
11
|
gem.email = ['nunemaker@gmail.com']
|
12
12
|
gem.summary = 'UI for the Flipper gem'
|
13
|
-
gem.description = 'Rack middleware that provides a fully featured web interface for the flipper gem.'
|
14
13
|
gem.license = 'MIT'
|
15
14
|
gem.homepage = 'https://github.com/jnunemaker/flipper'
|
16
15
|
|
@@ -22,7 +21,7 @@ Gem::Specification.new do |gem|
|
|
22
21
|
gem.metadata = Flipper::METADATA
|
23
22
|
|
24
23
|
gem.add_dependency 'rack', '>= 1.4', '< 3'
|
25
|
-
gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.
|
24
|
+
gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.2.0'
|
26
25
|
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
|
27
|
-
gem.add_dependency '
|
26
|
+
gem.add_dependency 'erubi', '>= 1.0.0', '< 2.0.0'
|
28
27
|
end
|
data/lib/flipper/ui/action.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
require 'flipper/ui/configuration'
|
2
3
|
require 'flipper/ui/error'
|
3
|
-
require '
|
4
|
+
require 'erubi'
|
4
5
|
require 'json'
|
5
6
|
|
6
7
|
module Flipper
|
@@ -25,6 +26,36 @@ module Flipper
|
|
25
26
|
'delete'.freeze,
|
26
27
|
]).freeze
|
27
28
|
|
29
|
+
SOURCES = {
|
30
|
+
bootstrap_css: {
|
31
|
+
src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css".freeze,
|
32
|
+
hash: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm".freeze
|
33
|
+
}.freeze,
|
34
|
+
jquery_js: {
|
35
|
+
src: "https://code.jquery.com/jquery-3.2.1.slim.min.js".freeze,
|
36
|
+
hash: "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN".freeze
|
37
|
+
}.freeze,
|
38
|
+
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
|
41
|
+
}.freeze,
|
42
|
+
bootstrap_js: {
|
43
|
+
src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js".freeze,
|
44
|
+
hash: "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl".freeze
|
45
|
+
}.freeze
|
46
|
+
}.freeze
|
47
|
+
SCRIPT_SRCS = SOURCES.values_at(:jquery_js, :popper_js, :bootstrap_js).map { |s| s[:src] }
|
48
|
+
STYLE_SRCS = SOURCES.values_at(:bootstrap_css).map { |s| s[:src] }
|
49
|
+
CONTENT_SECURITY_POLICY = <<-CSP.delete("\n")
|
50
|
+
default-src 'none';
|
51
|
+
img-src 'self';
|
52
|
+
font-src 'self';
|
53
|
+
script-src 'report-sample' 'self' #{SCRIPT_SRCS.join(' ')};
|
54
|
+
style-src 'self' 'unsafe-inline' #{STYLE_SRCS.join(' ')};
|
55
|
+
style-src-attr 'unsafe-inline' ;
|
56
|
+
style-src-elem 'self' #{STYLE_SRCS.join(' ')};
|
57
|
+
CSP
|
58
|
+
|
28
59
|
# Public: Call this in subclasses so the action knows its route.
|
29
60
|
#
|
30
61
|
# regex - The Regexp that this action should run for.
|
@@ -129,6 +160,7 @@ module Flipper
|
|
129
160
|
# Returns a response.
|
130
161
|
def view_response(name)
|
131
162
|
header 'Content-Type', 'text/html'
|
163
|
+
header 'Content-Security-Policy', CONTENT_SECURITY_POLICY
|
132
164
|
body = view_with_layout { view_without_layout name }
|
133
165
|
halt [@code, @headers, [body]]
|
134
166
|
end
|
@@ -150,7 +182,7 @@ module Flipper
|
|
150
182
|
# location - The String location to set the Location header to.
|
151
183
|
def redirect_to(location)
|
152
184
|
status 302
|
153
|
-
header 'Location', "#{script_name}#{location}"
|
185
|
+
header 'Location', "#{script_name}#{Rack::Utils.escape_path(location)}"
|
154
186
|
halt [@code, @headers, ['']]
|
155
187
|
end
|
156
188
|
|
@@ -205,12 +237,8 @@ module Flipper
|
|
205
237
|
# Private
|
206
238
|
def view(name)
|
207
239
|
path = views_path.join("#{name}.erb")
|
208
|
-
|
209
240
|
raise "Template does not exist: #{path}" unless path.exist?
|
210
|
-
|
211
|
-
contents = path.read
|
212
|
-
compiled = Eruby.new(contents)
|
213
|
-
compiled.result proc {}.binding
|
241
|
+
eval(Erubi::Engine.new(path.read, escape: true).src)
|
214
242
|
end
|
215
243
|
|
216
244
|
# Internal: The path the app is mounted at.
|
@@ -240,6 +268,22 @@ module Flipper
|
|
240
268
|
def valid_request_method?
|
241
269
|
VALID_REQUEST_METHOD_NAMES.include?(request_method_name)
|
242
270
|
end
|
271
|
+
|
272
|
+
def bootstrap_css
|
273
|
+
SOURCES[:bootstrap_css]
|
274
|
+
end
|
275
|
+
|
276
|
+
def bootstrap_js
|
277
|
+
SOURCES[:bootstrap_js]
|
278
|
+
end
|
279
|
+
|
280
|
+
def popper_js
|
281
|
+
SOURCES[:popper_js]
|
282
|
+
end
|
283
|
+
|
284
|
+
def jquery_js
|
285
|
+
SOURCES[:jquery_js]
|
286
|
+
end
|
243
287
|
end
|
244
288
|
end
|
245
289
|
end
|
@@ -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
|
-
error =
|
30
|
+
if values.empty?
|
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}")
|
@@ -10,9 +10,12 @@ module Flipper
|
|
10
10
|
route %r{\A/features/(?<feature_name>.*)\Z}
|
11
11
|
|
12
12
|
def get
|
13
|
-
|
13
|
+
flipper_feature = flipper[feature_name]
|
14
|
+
@feature = Decorators::Feature.new(flipper_feature)
|
15
|
+
descriptions = Flipper::UI.configuration.descriptions_source.call([flipper_feature.key])
|
16
|
+
@feature.description = descriptions[@feature.key]
|
14
17
|
@page_title = "#{@feature.key} // Features"
|
15
|
-
@percentages = [0, 1, 5, 10,
|
18
|
+
@percentages = [0, 1, 5, 10, 25, 50, 100]
|
16
19
|
|
17
20
|
breadcrumb 'Home', '/'
|
18
21
|
breadcrumb 'Features', '/features'
|
@@ -10,8 +10,21 @@ module Flipper
|
|
10
10
|
|
11
11
|
def get
|
12
12
|
@page_title = 'Features'
|
13
|
+
keys = flipper.features.map(&:key)
|
14
|
+
descriptions = if Flipper::UI.configuration.show_feature_description_in_list?
|
15
|
+
Flipper::UI.configuration.descriptions_source.call(keys)
|
16
|
+
else
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
|
13
20
|
@features = flipper.features.map do |feature|
|
14
|
-
Decorators::Feature.new(feature)
|
21
|
+
decorated_feature = Decorators::Feature.new(feature)
|
22
|
+
|
23
|
+
if Flipper::UI.configuration.show_feature_description_in_list?
|
24
|
+
decorated_feature.description = descriptions[feature.key]
|
25
|
+
end
|
26
|
+
|
27
|
+
decorated_feature
|
15
28
|
end.sort
|
16
29
|
|
17
30
|
@show_blank_slate = @features.empty?
|
@@ -36,14 +49,14 @@ module Flipper
|
|
36
49
|
value = params['value'].to_s.strip
|
37
50
|
|
38
51
|
if Util.blank?(value)
|
39
|
-
error =
|
52
|
+
error = "#{value.inspect} is not a valid feature name."
|
40
53
|
redirect_to("/features/new?error=#{error}")
|
41
54
|
end
|
42
55
|
|
43
56
|
feature = flipper[value]
|
44
57
|
feature.add
|
45
58
|
|
46
|
-
redirect_to "/features/#{
|
59
|
+
redirect_to "/features/#{value}"
|
47
60
|
end
|
48
61
|
end
|
49
62
|
end
|
@@ -35,7 +35,7 @@ module Flipper
|
|
35
35
|
|
36
36
|
redirect_to("/features/#{feature.key}")
|
37
37
|
else
|
38
|
-
error =
|
38
|
+
error = "The group named #{value.inspect} has not been registered."
|
39
39
|
redirect_to("/features/#{feature.key}/groups?error=#{error}")
|
40
40
|
end
|
41
41
|
end
|
@@ -16,7 +16,7 @@ module Flipper
|
|
16
16
|
begin
|
17
17
|
feature.enable_percentage_of_actors params['value']
|
18
18
|
rescue ArgumentError => exception
|
19
|
-
error =
|
19
|
+
error = "Invalid percentage of actors value: #{exception.message}"
|
20
20
|
redirect_to("/features/#{@feature.key}?error=#{error}")
|
21
21
|
end
|
22
22
|
|
@@ -16,7 +16,7 @@ module Flipper
|
|
16
16
|
begin
|
17
17
|
feature.enable_percentage_of_time params['value']
|
18
18
|
rescue ArgumentError => exception
|
19
|
-
error =
|
19
|
+
error = "Invalid percentage of time value: #{exception.message}"
|
20
20
|
redirect_to("/features/#{@feature.key}?error=#{error}")
|
21
21
|
end
|
22
22
|
|
@@ -3,11 +3,7 @@ require 'flipper/ui/configuration/option'
|
|
3
3
|
module Flipper
|
4
4
|
module UI
|
5
5
|
class Configuration
|
6
|
-
attr_reader :
|
7
|
-
:delete,
|
8
|
-
:groups,
|
9
|
-
:percentage_of_actors,
|
10
|
-
:percentage_of_time
|
6
|
+
attr_reader :delete
|
11
7
|
|
12
8
|
attr_accessor :banner_text,
|
13
9
|
:banner_class
|
@@ -26,6 +22,33 @@ module Flipper
|
|
26
22
|
# set to false, users won't be able to delete features from the UI.
|
27
23
|
attr_accessor :feature_removal_enabled
|
28
24
|
|
25
|
+
# Public: Are you feeling lucky? Defaults to true. If set to false, users
|
26
|
+
# won't see a videoclip of Taylor Swift when there aren't any features
|
27
|
+
attr_accessor :fun
|
28
|
+
|
29
|
+
# Public: Tired of seeing the awesome message about Cloud? Set this to
|
30
|
+
# false and it will go away. Defaults to true.
|
31
|
+
attr_accessor :cloud_recommendation
|
32
|
+
|
33
|
+
# Public: What should show up in the form to add actors. This can be
|
34
|
+
# different per application since flipper_id's can be whatever an
|
35
|
+
# application needs. Defaults to "a flipper id".
|
36
|
+
attr_accessor :add_actor_placeholder
|
37
|
+
|
38
|
+
# Public: If you set this, Flipper::UI will fetch descriptions
|
39
|
+
# from your external source. Descriptions for `features` will be shown on `feature`
|
40
|
+
# page, and optionally the `features` pages. Defaults to empty block.
|
41
|
+
attr_accessor :descriptions_source
|
42
|
+
|
43
|
+
# Public: Should feature descriptions be show on the `features` list page.
|
44
|
+
# Default false. Only works when using descriptions.
|
45
|
+
attr_accessor :show_feature_description_in_list
|
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
|
+
|
29
52
|
VALID_BANNER_CLASS_VALUES = %w(
|
30
53
|
danger
|
31
54
|
dark
|
@@ -37,16 +60,28 @@ module Flipper
|
|
37
60
|
warning
|
38
61
|
).freeze
|
39
62
|
|
63
|
+
DEFAULT_DESCRIPTIONS_SOURCE = ->(_keys) { {} }
|
64
|
+
|
40
65
|
def initialize
|
41
|
-
@
|
42
|
-
@groups = Option.new("Groups", "Enable groups using the form above.")
|
43
|
-
@percentage_of_actors = Option.new("Percentage of Actors", "Percentage of actors functions independently of percentage of time. If you enable 50% of Actors and 25% of Time then the feature will always be enabled for 50% of users and occasionally enabled 25% of the time for everyone.") # rubocop:disable Metrics/LineLength
|
44
|
-
@percentage_of_time = Option.new("Percentage of Time", "Percentage of actors functions independently of percentage of time. If you enable 50% of Actors and 25% of Time then the feature will always be enabled for 50% of users and occasionally enabled 25% of the time for everyone.") # rubocop:disable Metrics/LineLength
|
45
|
-
@delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.") # rubocop:disable Metrics/LineLength
|
66
|
+
@delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.")
|
46
67
|
@banner_text = nil
|
47
68
|
@banner_class = 'danger'
|
48
69
|
@feature_creation_enabled = true
|
49
70
|
@feature_removal_enabled = true
|
71
|
+
@fun = true
|
72
|
+
@cloud_recommendation = true
|
73
|
+
@add_actor_placeholder = "a flipper id"
|
74
|
+
@descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
|
75
|
+
@show_feature_description_in_list = false
|
76
|
+
@actors_separator = ','
|
77
|
+
end
|
78
|
+
|
79
|
+
def using_descriptions?
|
80
|
+
@descriptions_source != DEFAULT_DESCRIPTIONS_SOURCE
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_feature_description_in_list?
|
84
|
+
using_descriptions? && @show_feature_description_in_list
|
50
85
|
end
|
51
86
|
|
52
87
|
def banner_class=(value)
|