flipper-ui 0.15.0 → 0.16.0
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 +4 -4
- data/docs/ui/README.md +0 -1
- data/examples/ui/basic.ru +7 -4
- data/flipper-ui.gemspec +2 -0
- data/lib/flipper/ui.rb +0 -2
- data/lib/flipper/ui/action.rb +21 -6
- data/lib/flipper/ui/action_collection.rb +1 -1
- data/lib/flipper/ui/actions/actors_gate.rb +5 -5
- data/lib/flipper/ui/actions/add_feature.rb +1 -1
- data/lib/flipper/ui/actions/boolean_gate.rb +4 -3
- data/lib/flipper/ui/actions/feature.rb +3 -3
- data/lib/flipper/ui/actions/features.rb +1 -1
- data/lib/flipper/ui/actions/groups_gate.rb +5 -5
- data/lib/flipper/ui/actions/home.rb +1 -1
- data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +4 -3
- data/lib/flipper/ui/actions/percentage_of_time_gate.rb +4 -3
- data/lib/flipper/ui/middleware.rb +1 -2
- data/lib/flipper/ui/views/feature.erb +2 -2
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/actions/actors_gate_spec.rb +15 -0
- data/spec/flipper/ui/actions/feature_spec.rb +20 -0
- data/spec/flipper/ui/decorators/feature_spec.rb +0 -1
- data/spec/flipper/ui/decorators/gate_spec.rb +0 -1
- metadata +6 -8
- data/lib/flipper/ui/actions/gate.rb +0 -39
- data/spec/flipper/ui/actions/gate_spec.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25aa2745af2e31218cbd4b27af8193e58737cf9d
|
4
|
+
data.tar.gz: cdda6a59fe7edcbdc5b06be258d2d685a2796636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c834961bc28b80d3890bcb150ef26a9dffc2f7450acb5d0f2dc6e49d5be0f13bbf435c49916a1c08adf6c1944e1100500cf49f00d147b6a28d5f05bd97ed4399
|
7
|
+
data.tar.gz: 505af952e52ec8ccba2b37368c1fb84fcde104308d09a666f5479769677d6453edeea3088f70f74f49c4241a19f52eaa985f02f4c6400124b6b6a38cb5abc2ea
|
data/docs/ui/README.md
CHANGED
data/examples/ui/basic.ru
CHANGED
@@ -14,6 +14,7 @@ $:.unshift(lib_path)
|
|
14
14
|
|
15
15
|
require "flipper-ui"
|
16
16
|
require "flipper/adapters/pstore"
|
17
|
+
require "active_support/notifications"
|
17
18
|
|
18
19
|
Flipper.register(:admins) { |actor|
|
19
20
|
actor.respond_to?(:admin?) && actor.admin?
|
@@ -24,10 +25,11 @@ Flipper.register(:early_access) { |actor|
|
|
24
25
|
}
|
25
26
|
|
26
27
|
# Setup logging of flipper calls.
|
27
|
-
|
28
|
-
|
29
|
-
require "flipper/instrumentation/log_subscriber"
|
30
|
-
Flipper::Instrumentation::LogSubscriber.logger = $logger
|
28
|
+
if ENV["LOG"] == "1"
|
29
|
+
$logger = Logger.new(STDOUT)
|
30
|
+
require "flipper/instrumentation/log_subscriber"
|
31
|
+
Flipper::Instrumentation::LogSubscriber.logger = $logger
|
32
|
+
end
|
31
33
|
|
32
34
|
adapter = Flipper::Adapters::PStore.new
|
33
35
|
flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
@@ -42,6 +44,7 @@ flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
|
42
44
|
# flipper[:secrets].enable_group :early_access
|
43
45
|
# flipper[:logging].enable_percentage_of_time 5
|
44
46
|
# flipper[:new_cache].enable_percentage_of_actors 15
|
47
|
+
# flipper["a/b"].add
|
45
48
|
|
46
49
|
run Flipper::UI.app(flipper) { |builder|
|
47
50
|
builder.use Rack::Session::Cookie, secret: "_super_secret"
|
data/flipper-ui.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require File.expand_path('../lib/flipper/version', __FILE__)
|
3
|
+
require File.expand_path('../lib/flipper/metadata', __FILE__)
|
3
4
|
|
4
5
|
flipper_ui_files = lambda do |file|
|
5
6
|
file =~ %r{(docs|examples|flipper)[\/-]ui}
|
@@ -18,6 +19,7 @@ Gem::Specification.new do |gem|
|
|
18
19
|
gem.name = 'flipper-ui'
|
19
20
|
gem.require_paths = ['lib']
|
20
21
|
gem.version = Flipper::VERSION
|
22
|
+
gem.metadata = Flipper::METADATA
|
21
23
|
|
22
24
|
gem.add_dependency 'rack', '>= 1.4', '< 3'
|
23
25
|
gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.1.0'
|
data/lib/flipper/ui.rb
CHANGED
data/lib/flipper/ui/action.rb
CHANGED
@@ -6,6 +6,16 @@ require 'json'
|
|
6
6
|
module Flipper
|
7
7
|
module UI
|
8
8
|
class Action
|
9
|
+
module FeatureNameFromRoute
|
10
|
+
def feature_name
|
11
|
+
@feature_name ||= begin
|
12
|
+
match = request.path_info.match(self.class.route_regex)
|
13
|
+
match ? match[:feature_name] : nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
private :feature_name
|
17
|
+
end
|
18
|
+
|
9
19
|
extend Forwardable
|
10
20
|
|
11
21
|
VALID_REQUEST_METHOD_NAMES = Set.new([
|
@@ -21,7 +31,17 @@ module Flipper
|
|
21
31
|
#
|
22
32
|
# Returns nothing.
|
23
33
|
def self.route(regex)
|
24
|
-
@
|
34
|
+
@route_regex = regex
|
35
|
+
end
|
36
|
+
|
37
|
+
# Internal: Does this action's route match the path.
|
38
|
+
def self.route_match?(path)
|
39
|
+
path.match(route_regex)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: The regex that matches which routes this action will work for.
|
43
|
+
def self.route_regex
|
44
|
+
@route_regex || raise("#{name}.route is not set")
|
25
45
|
end
|
26
46
|
|
27
47
|
# Internal: Initializes and runs an action for a given request.
|
@@ -34,11 +54,6 @@ module Flipper
|
|
34
54
|
new(flipper, request).run
|
35
55
|
end
|
36
56
|
|
37
|
-
# Internal: The regex that matches which routes this action will work for.
|
38
|
-
def self.regex
|
39
|
-
@regex || raise("#{name}.route is not set")
|
40
|
-
end
|
41
|
-
|
42
57
|
# Private: The path to the views folder.
|
43
58
|
def self.views_path
|
44
59
|
@views_path ||= Flipper::UI.root.join('views')
|
@@ -6,11 +6,12 @@ module Flipper
|
|
6
6
|
module UI
|
7
7
|
module Actions
|
8
8
|
class ActorsGate < UI::Action
|
9
|
-
|
9
|
+
include FeatureNameFromRoute
|
10
|
+
|
11
|
+
route %r{\A/features/(?<feature_name>.*)/actors/?\Z}
|
10
12
|
|
11
13
|
def get
|
12
|
-
|
13
|
-
feature = flipper[feature_name.to_sym]
|
14
|
+
feature = flipper[feature_name]
|
14
15
|
@feature = Decorators::Feature.new(feature)
|
15
16
|
|
16
17
|
breadcrumb 'Home', '/'
|
@@ -22,8 +23,7 @@ module Flipper
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def post
|
25
|
-
|
26
|
-
feature = flipper[feature_name.to_sym]
|
26
|
+
feature = flipper[feature_name]
|
27
27
|
value = params['value'].to_s.strip
|
28
28
|
|
29
29
|
if Util.blank?(value)
|
@@ -5,11 +5,12 @@ module Flipper
|
|
5
5
|
module UI
|
6
6
|
module Actions
|
7
7
|
class BooleanGate < UI::Action
|
8
|
-
|
8
|
+
include FeatureNameFromRoute
|
9
|
+
|
10
|
+
route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}
|
9
11
|
|
10
12
|
def post
|
11
|
-
|
12
|
-
feature = flipper[feature_name.to_sym]
|
13
|
+
feature = flipper[feature_name]
|
13
14
|
@feature = Decorators::Feature.new(feature)
|
14
15
|
|
15
16
|
if params['action'] == 'Enable'
|
@@ -5,10 +5,11 @@ module Flipper
|
|
5
5
|
module UI
|
6
6
|
module Actions
|
7
7
|
class Feature < UI::Action
|
8
|
-
|
8
|
+
include FeatureNameFromRoute
|
9
|
+
|
10
|
+
route %r{\A/features/(?<feature_name>.*)\Z}
|
9
11
|
|
10
12
|
def get
|
11
|
-
feature_name = Rack::Utils.unescape(request.path.split('/').last)
|
12
13
|
@feature = Decorators::Feature.new(flipper[feature_name])
|
13
14
|
@page_title = "#{@feature.key} // Features"
|
14
15
|
@percentages = [0, 1, 5, 10, 15, 25, 50, 75, 100]
|
@@ -30,7 +31,6 @@ module Flipper
|
|
30
31
|
halt view_response(:feature_removal_disabled)
|
31
32
|
end
|
32
33
|
|
33
|
-
feature_name = Rack::Utils.unescape(request.path.split('/').last)
|
34
34
|
feature = flipper[feature_name]
|
35
35
|
feature.remove
|
36
36
|
redirect_to '/features'
|
@@ -5,11 +5,12 @@ module Flipper
|
|
5
5
|
module UI
|
6
6
|
module Actions
|
7
7
|
class GroupsGate < UI::Action
|
8
|
-
|
8
|
+
include FeatureNameFromRoute
|
9
|
+
|
10
|
+
route %r{\A/features/(?<feature_name>.*)/groups/?\Z}
|
9
11
|
|
10
12
|
def get
|
11
|
-
|
12
|
-
feature = flipper[feature_name.to_sym]
|
13
|
+
feature = flipper[feature_name]
|
13
14
|
@feature = Decorators::Feature.new(feature)
|
14
15
|
|
15
16
|
breadcrumb 'Home', '/'
|
@@ -21,8 +22,7 @@ module Flipper
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def post
|
24
|
-
|
25
|
-
feature = flipper[feature_name.to_sym]
|
25
|
+
feature = flipper[feature_name]
|
26
26
|
value = params['value'].to_s.strip
|
27
27
|
|
28
28
|
if Flipper.group_exists?(value)
|
@@ -5,11 +5,12 @@ module Flipper
|
|
5
5
|
module UI
|
6
6
|
module Actions
|
7
7
|
class PercentageOfActorsGate < UI::Action
|
8
|
-
|
8
|
+
include FeatureNameFromRoute
|
9
|
+
|
10
|
+
route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}
|
9
11
|
|
10
12
|
def post
|
11
|
-
|
12
|
-
feature = flipper[feature_name.to_sym]
|
13
|
+
feature = flipper[feature_name]
|
13
14
|
@feature = Decorators::Feature.new(feature)
|
14
15
|
|
15
16
|
begin
|
@@ -5,11 +5,12 @@ module Flipper
|
|
5
5
|
module UI
|
6
6
|
module Actions
|
7
7
|
class PercentageOfTimeGate < UI::Action
|
8
|
-
|
8
|
+
include FeatureNameFromRoute
|
9
|
+
|
10
|
+
route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}
|
9
11
|
|
10
12
|
def post
|
11
|
-
|
12
|
-
feature = flipper[feature_name.to_sym]
|
13
|
+
feature = flipper[feature_name]
|
13
14
|
@feature = Decorators::Feature.new(feature)
|
14
15
|
|
15
16
|
begin
|
@@ -17,13 +17,12 @@ module Flipper
|
|
17
17
|
|
18
18
|
# UI
|
19
19
|
@action_collection.add UI::Actions::AddFeature
|
20
|
-
@action_collection.add UI::Actions::Feature
|
21
20
|
@action_collection.add UI::Actions::ActorsGate
|
22
21
|
@action_collection.add UI::Actions::GroupsGate
|
23
22
|
@action_collection.add UI::Actions::BooleanGate
|
24
23
|
@action_collection.add UI::Actions::PercentageOfTimeGate
|
25
24
|
@action_collection.add UI::Actions::PercentageOfActorsGate
|
26
|
-
@action_collection.add UI::Actions::
|
25
|
+
@action_collection.add UI::Actions::Feature
|
27
26
|
@action_collection.add UI::Actions::Features
|
28
27
|
|
29
28
|
# Static Assets/Files
|
@@ -14,7 +14,7 @@
|
|
14
14
|
</h4>
|
15
15
|
<div class="card-body">
|
16
16
|
<p>
|
17
|
-
<% if @feature.
|
17
|
+
<% if @feature.boolean_value %>
|
18
18
|
The feature is enabled for <strong>everyone</strong>. Disable this feature with one click.
|
19
19
|
<% else %>
|
20
20
|
Enable or disable this feature for <strong>everyone</strong> with one click.
|
@@ -24,7 +24,7 @@
|
|
24
24
|
<form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
|
25
25
|
<%== csrf_input_tag %>
|
26
26
|
|
27
|
-
<% unless @feature.
|
27
|
+
<% unless @feature.boolean_value %>
|
28
28
|
<button type="submit" name="action" value="Enable" class="btn btn-danger" data-toggle="tooltip" title="Enable for everyone">Enable</button>
|
29
29
|
<% end %>
|
30
30
|
|
data/lib/flipper/version.rb
CHANGED
@@ -27,6 +27,21 @@ RSpec.describe Flipper::UI::Actions::ActorsGate do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
describe 'GET /features/:feature/actors with slash in feature name' do
|
31
|
+
before do
|
32
|
+
get 'features/a/b/actors'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'responds with success' do
|
36
|
+
expect(last_response.status).to be(200)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'renders add new actor form' do
|
40
|
+
form = '<form action="/features/a/b/actors" method="post" class="form-inline">'
|
41
|
+
expect(last_response.body).to include(form)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
30
45
|
describe 'POST /features/:feature/actors' do
|
31
46
|
context 'enabling an actor' do
|
32
47
|
let(:value) { 'User:6' }
|
@@ -107,4 +107,24 @@ RSpec.describe Flipper::UI::Actions::Feature do
|
|
107
107
|
expect(last_response.body).to include('Percentage of Actors')
|
108
108
|
end
|
109
109
|
end
|
110
|
+
|
111
|
+
describe 'GET /features/:feature with slash in feature name' do
|
112
|
+
before do
|
113
|
+
get '/features/a/b'
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'responds with success' do
|
117
|
+
expect(last_response.status).to be(200)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'renders template' do
|
121
|
+
expect(last_response.body).to include('a/b')
|
122
|
+
expect(last_response.body).to include('Enable')
|
123
|
+
expect(last_response.body).to include('Disable')
|
124
|
+
expect(last_response.body).to include('Actors')
|
125
|
+
expect(last_response.body).to include('Groups')
|
126
|
+
expect(last_response.body).to include('Percentage of Time')
|
127
|
+
expect(last_response.body).to include('Percentage of Actors')
|
128
|
+
end
|
129
|
+
end
|
110
130
|
end
|
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.16.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: 2018-
|
11
|
+
date: 2018-08-01 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.16.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.16.0
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: erubis
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,7 +103,6 @@ files:
|
|
103
103
|
- lib/flipper/ui/actions/feature.rb
|
104
104
|
- lib/flipper/ui/actions/features.rb
|
105
105
|
- lib/flipper/ui/actions/file.rb
|
106
|
-
- lib/flipper/ui/actions/gate.rb
|
107
106
|
- lib/flipper/ui/actions/groups_gate.rb
|
108
107
|
- lib/flipper/ui/actions/home.rb
|
109
108
|
- lib/flipper/ui/actions/percentage_of_actors_gate.rb
|
@@ -259,7 +258,6 @@ files:
|
|
259
258
|
- spec/flipper/ui/actions/feature_spec.rb
|
260
259
|
- spec/flipper/ui/actions/features_spec.rb
|
261
260
|
- spec/flipper/ui/actions/file_spec.rb
|
262
|
-
- spec/flipper/ui/actions/gate_spec.rb
|
263
261
|
- spec/flipper/ui/actions/groups_gate_spec.rb
|
264
262
|
- spec/flipper/ui/actions/home_spec.rb
|
265
263
|
- spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb
|
@@ -272,7 +270,8 @@ files:
|
|
272
270
|
homepage: https://github.com/jnunemaker/flipper
|
273
271
|
licenses:
|
274
272
|
- MIT
|
275
|
-
metadata:
|
273
|
+
metadata:
|
274
|
+
changelog_uri: https://github.com/jnunemaker/flipper/blob/master/Changelog.md
|
276
275
|
post_install_message:
|
277
276
|
rdoc_options: []
|
278
277
|
require_paths:
|
@@ -301,7 +300,6 @@ test_files:
|
|
301
300
|
- spec/flipper/ui/actions/feature_spec.rb
|
302
301
|
- spec/flipper/ui/actions/features_spec.rb
|
303
302
|
- spec/flipper/ui/actions/file_spec.rb
|
304
|
-
- spec/flipper/ui/actions/gate_spec.rb
|
305
303
|
- spec/flipper/ui/actions/groups_gate_spec.rb
|
306
304
|
- spec/flipper/ui/actions/home_spec.rb
|
307
305
|
- spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb
|
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'flipper/ui/action'
|
2
|
-
require 'flipper/ui/decorators/feature'
|
3
|
-
|
4
|
-
module Flipper
|
5
|
-
module UI
|
6
|
-
module Actions
|
7
|
-
class Gate < UI::Action
|
8
|
-
route %r{features/[^/]*/[^/]*/?\Z}
|
9
|
-
|
10
|
-
def post
|
11
|
-
feature_name, gate_name = request.path.split('/').pop(2)
|
12
|
-
.map(&Rack::Utils.method(:unescape))
|
13
|
-
update_gate_method_name = "update_#{gate_name}"
|
14
|
-
|
15
|
-
feature = flipper[feature_name.to_sym]
|
16
|
-
@feature = Decorators::Feature.new(feature)
|
17
|
-
|
18
|
-
if respond_to?(update_gate_method_name, true)
|
19
|
-
send(update_gate_method_name, feature)
|
20
|
-
else
|
21
|
-
update_gate_method_undefined(gate_name)
|
22
|
-
end
|
23
|
-
|
24
|
-
redirect_to "/features/#{@feature.key}"
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
# Private: Returns error response that gate update method is not defined.
|
30
|
-
def update_gate_method_undefined(gate_name)
|
31
|
-
error = Rack::Utils.escape(
|
32
|
-
"#{gate_name.inspect} gate does not exist therefore it cannot be updated."
|
33
|
-
)
|
34
|
-
redirect_to("/features/#{@feature.key}?error=#{error}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
RSpec.describe Flipper::UI::Actions::Gate 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
|
-
let(:session) do
|
12
|
-
{ :csrf => token, 'csrf' => token, '_csrf_token' => token }
|
13
|
-
end
|
14
|
-
|
15
|
-
describe 'POST /features/:feature/non-existent-gate' do
|
16
|
-
before do
|
17
|
-
post '/features/search/non-existent-gate',
|
18
|
-
{ 'authenticity_token' => token },
|
19
|
-
'rack.session' => session
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'responds with redirect' do
|
23
|
-
expect(last_response.status).to be(302)
|
24
|
-
end
|
25
|
-
|
26
|
-
# rubocop:disable Metrics/LineLength
|
27
|
-
it 'escapes error message' do
|
28
|
-
expect(last_response.headers['Location']).to eq('/features/search?error=%22non-existent-gate%22+gate+does+not+exist+therefore+it+cannot+be+updated.')
|
29
|
-
end
|
30
|
-
# rubocop:enable Metrics/LineLength
|
31
|
-
|
32
|
-
it 'renders error in template' do
|
33
|
-
follow_redirect!
|
34
|
-
expect(last_response.body).to match(/non-existent-gate.*gate does not exist/)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|