flipper-ui 0.10.2 → 0.11.0.beta1
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/flipper-ui.gemspec +12 -12
- data/lib/flipper/ui.rb +6 -4
- data/lib/flipper/ui/action.rb +22 -21
- data/lib/flipper/ui/action_collection.rb +2 -2
- data/lib/flipper/ui/actions/actors_gate.rb +7 -7
- data/lib/flipper/ui/actions/add_feature.rb +6 -6
- data/lib/flipper/ui/actions/boolean_gate.rb +2 -2
- data/lib/flipper/ui/actions/feature.rb +5 -6
- data/lib/flipper/ui/actions/features.rb +9 -10
- data/lib/flipper/ui/actions/file.rb +0 -1
- data/lib/flipper/ui/actions/gate.rb +5 -2
- data/lib/flipper/ui/actions/groups_gate.rb +16 -14
- data/lib/flipper/ui/actions/home.rb +1 -2
- data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +2 -2
- data/lib/flipper/ui/actions/percentage_of_time_gate.rb +2 -2
- data/lib/flipper/ui/decorators/feature.rb +9 -9
- data/lib/flipper/ui/decorators/gate.rb +7 -6
- data/lib/flipper/ui/middleware.rb +2 -27
- data/lib/flipper/ui/util.rb +2 -2
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/action_spec.rb +22 -22
- data/spec/flipper/ui/actions/actors_gate_spec.rb +49 -45
- data/spec/flipper/ui/actions/add_feature_spec.rb +9 -9
- data/spec/flipper/ui/actions/boolean_gate_spec.rb +22 -22
- data/spec/flipper/ui/actions/feature_spec.rb +34 -34
- data/spec/flipper/ui/actions/features_spec.rb +44 -40
- data/spec/flipper/ui/actions/file_spec.rb +8 -8
- data/spec/flipper/ui/actions/gate_spec.rb +17 -15
- data/spec/flipper/ui/actions/groups_gate_spec.rb +57 -50
- data/spec/flipper/ui/actions/home_spec.rb +4 -4
- data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +24 -22
- data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +24 -22
- data/spec/flipper/ui/decorators/feature_spec.rb +27 -27
- data/spec/flipper/ui/decorators/gate_spec.rb +10 -10
- data/spec/flipper/ui/util_spec.rb +4 -4
- data/spec/flipper/ui_spec.rb +58 -59
- metadata +7 -7
@@ -23,20 +23,20 @@ module Flipper
|
|
23
23
|
'id' => name.to_s,
|
24
24
|
'name' => pretty_name,
|
25
25
|
'state' => state.to_s,
|
26
|
-
'gates' => gates.map
|
26
|
+
'gates' => gates.map do |gate|
|
27
27
|
Decorators::Gate.new(gate, gate_values[gate.key]).as_json
|
28
|
-
|
28
|
+
end,
|
29
29
|
}
|
30
30
|
end
|
31
31
|
|
32
32
|
def color_class
|
33
33
|
case feature.state
|
34
34
|
when :on
|
35
|
-
|
35
|
+
'text-open'
|
36
36
|
when :off
|
37
|
-
|
37
|
+
'text-closed'
|
38
38
|
when :conditional
|
39
|
-
|
39
|
+
'text-pending'
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -45,10 +45,10 @@ module Flipper
|
|
45
45
|
end
|
46
46
|
|
47
47
|
StateSortMap = {
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
51
|
-
}
|
48
|
+
on: 1,
|
49
|
+
conditional: 2,
|
50
|
+
off: 3,
|
51
|
+
}.freeze
|
52
52
|
|
53
53
|
def <=>(other)
|
54
54
|
if state == other.state
|
@@ -17,12 +17,13 @@ module Flipper
|
|
17
17
|
|
18
18
|
# Public: Returns instance as hash that is ready to be json dumped.
|
19
19
|
def as_json
|
20
|
-
value_as_json =
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
value_as_json =
|
21
|
+
case data_type
|
22
|
+
when :set
|
23
|
+
value.to_a # json doesn't like sets
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
26
27
|
|
27
28
|
{
|
28
29
|
'key' => gate.key.to_s,
|
@@ -9,31 +9,9 @@ end
|
|
9
9
|
module Flipper
|
10
10
|
module UI
|
11
11
|
class Middleware
|
12
|
-
|
13
|
-
#
|
14
|
-
# app - The app this middleware is included in.
|
15
|
-
# flipper_or_block - The Flipper::DSL instance or a block that yields a
|
16
|
-
# Flipper::DSL instance to use for all operations.
|
17
|
-
#
|
18
|
-
# Examples
|
19
|
-
#
|
20
|
-
# flipper = Flipper.new(...)
|
21
|
-
#
|
22
|
-
# # using with a normal flipper instance
|
23
|
-
# use Flipper::UI::Middleware, flipper
|
24
|
-
#
|
25
|
-
# # using with a block that yields a flipper instance
|
26
|
-
# use Flipper::UI::Middleware, lambda { Flipper.new(...) }
|
27
|
-
#
|
28
|
-
def initialize(app, flipper_or_block)
|
12
|
+
def initialize(app)
|
29
13
|
@app = app
|
30
14
|
|
31
|
-
if flipper_or_block.respond_to?(:call)
|
32
|
-
@flipper_block = flipper_or_block
|
33
|
-
else
|
34
|
-
@flipper = flipper_or_block
|
35
|
-
end
|
36
|
-
|
37
15
|
@action_collection = ActionCollection.new
|
38
16
|
|
39
17
|
# UI
|
@@ -54,10 +32,6 @@ module Flipper
|
|
54
32
|
@action_collection.add UI::Actions::Home
|
55
33
|
end
|
56
34
|
|
57
|
-
def flipper
|
58
|
-
@flipper ||= @flipper_block.call
|
59
|
-
end
|
60
|
-
|
61
35
|
def call(env)
|
62
36
|
dup.call!(env)
|
63
37
|
end
|
@@ -69,6 +43,7 @@ module Flipper
|
|
69
43
|
if action_class.nil?
|
70
44
|
@app.call(env)
|
71
45
|
else
|
46
|
+
flipper = env.fetch('flipper')
|
72
47
|
action_class.run(flipper, request)
|
73
48
|
end
|
74
49
|
end
|
data/lib/flipper/ui/util.rb
CHANGED
@@ -2,14 +2,14 @@ module Flipper
|
|
2
2
|
module UI
|
3
3
|
module Util
|
4
4
|
# Private: 0x3000: fullwidth whitespace
|
5
|
-
NON_WHITESPACE_REGEXP =
|
5
|
+
NON_WHITESPACE_REGEXP = /[^\s#{[0x3000].pack("U")}]/
|
6
6
|
|
7
7
|
def self.blank?(str)
|
8
8
|
str.to_s !~ NON_WHITESPACE_REGEXP
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.titleize(str)
|
12
|
-
str.to_s.split(
|
12
|
+
str.to_s.split('_').map(&:capitalize).join(' ')
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -1,59 +1,59 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe Flipper::UI::Action do
|
4
|
-
let(:action_subclass)
|
4
|
+
let(:action_subclass) do
|
5
5
|
Class.new(described_class) do
|
6
6
|
def noooope
|
7
|
-
raise
|
7
|
+
raise 'should never run this'
|
8
8
|
end
|
9
9
|
|
10
10
|
def get
|
11
|
-
[200, {},
|
11
|
+
[200, {}, 'get']
|
12
12
|
end
|
13
13
|
|
14
14
|
def post
|
15
|
-
[200, {},
|
15
|
+
[200, {}, 'post']
|
16
16
|
end
|
17
17
|
|
18
18
|
def put
|
19
|
-
[200, {},
|
19
|
+
[200, {}, 'put']
|
20
20
|
end
|
21
21
|
|
22
22
|
def delete
|
23
|
-
[200, {},
|
23
|
+
[200, {}, 'delete']
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
end
|
27
27
|
|
28
28
|
it "won't run method that isn't whitelisted" do
|
29
|
-
fake_request = Struct.new(:request_method, :env, :session).new(
|
29
|
+
fake_request = Struct.new(:request_method, :env, :session).new('NOOOOPE', {}, {})
|
30
30
|
action = action_subclass.new(flipper, fake_request)
|
31
|
-
expect
|
31
|
+
expect do
|
32
32
|
action.run
|
33
|
-
|
33
|
+
end.to raise_error(Flipper::UI::RequestMethodNotSupported)
|
34
34
|
end
|
35
35
|
|
36
|
-
it
|
37
|
-
fake_request = Struct.new(:request_method, :env, :session).new(
|
36
|
+
it 'will run get' do
|
37
|
+
fake_request = Struct.new(:request_method, :env, :session).new('GET', {}, {})
|
38
38
|
action = action_subclass.new(flipper, fake_request)
|
39
|
-
expect(action.run).to eq([200, {},
|
39
|
+
expect(action.run).to eq([200, {}, 'get'])
|
40
40
|
end
|
41
41
|
|
42
|
-
it
|
43
|
-
fake_request = Struct.new(:request_method, :env, :session).new(
|
42
|
+
it 'will run post' do
|
43
|
+
fake_request = Struct.new(:request_method, :env, :session).new('POST', {}, {})
|
44
44
|
action = action_subclass.new(flipper, fake_request)
|
45
|
-
expect(action.run).to eq([200, {},
|
45
|
+
expect(action.run).to eq([200, {}, 'post'])
|
46
46
|
end
|
47
47
|
|
48
|
-
it
|
49
|
-
fake_request = Struct.new(:request_method, :env, :session).new(
|
48
|
+
it 'will run put' do
|
49
|
+
fake_request = Struct.new(:request_method, :env, :session).new('PUT', {}, {})
|
50
50
|
action = action_subclass.new(flipper, fake_request)
|
51
|
-
expect(action.run).to eq([200, {},
|
51
|
+
expect(action.run).to eq([200, {}, 'put'])
|
52
52
|
end
|
53
53
|
|
54
|
-
it
|
55
|
-
fake_request = Struct.new(:request_method, :env, :session).new(
|
54
|
+
it 'will run delete' do
|
55
|
+
fake_request = Struct.new(:request_method, :env, :session).new('DELETE', {}, {})
|
56
56
|
action = action_subclass.new(flipper, fake_request)
|
57
|
-
expect(action.run).to eq([200, {},
|
57
|
+
expect(action.run).to eq([200, {}, 'delete'])
|
58
58
|
end
|
59
59
|
end
|
@@ -1,107 +1,111 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe Flipper::UI::Actions::ActorsGate do
|
4
|
-
let(:token)
|
4
|
+
let(:token) do
|
5
5
|
if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
|
6
6
|
Rack::Protection::AuthenticityToken.random_token
|
7
7
|
else
|
8
|
-
|
8
|
+
'a'
|
9
9
|
end
|
10
|
-
|
11
|
-
let(:session)
|
10
|
+
end
|
11
|
+
let(:session) do
|
12
12
|
if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
|
13
|
-
{:
|
13
|
+
{ csrf: token }
|
14
14
|
else
|
15
|
-
{
|
15
|
+
{ '_csrf_token' => token }
|
16
16
|
end
|
17
|
-
|
17
|
+
end
|
18
18
|
|
19
|
-
describe
|
19
|
+
describe 'GET /features/:feature/actors' do
|
20
20
|
before do
|
21
|
-
get
|
21
|
+
get 'features/search/actors'
|
22
22
|
end
|
23
23
|
|
24
|
-
it
|
24
|
+
it 'responds with success' do
|
25
25
|
expect(last_response.status).to be(200)
|
26
26
|
end
|
27
27
|
|
28
|
-
it
|
28
|
+
it 'renders add new actor form' do
|
29
29
|
expect(last_response.body).to include('<form action="/features/search/actors" method="post">')
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
describe
|
34
|
-
context
|
35
|
-
let(:value) {
|
33
|
+
describe 'POST /features/:feature/actors' do
|
34
|
+
context 'enabling an actor' do
|
35
|
+
let(:value) { 'User:6' }
|
36
36
|
|
37
37
|
before do
|
38
|
-
post
|
39
|
-
|
40
|
-
|
38
|
+
post 'features/search/actors',
|
39
|
+
{ 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
|
40
|
+
'rack.session' => session
|
41
41
|
end
|
42
42
|
|
43
|
-
it
|
44
|
-
expect(flipper[:search].actors_value).to include(
|
43
|
+
it 'adds item to members' do
|
44
|
+
expect(flipper[:search].actors_value).to include('User:6')
|
45
45
|
end
|
46
46
|
|
47
|
-
it
|
47
|
+
it 'redirects back to feature' do
|
48
48
|
expect(last_response.status).to be(302)
|
49
|
-
expect(last_response.headers[
|
49
|
+
expect(last_response.headers['Location']).to eq('/features/search')
|
50
50
|
end
|
51
51
|
|
52
52
|
context 'value contains whitespace' do
|
53
|
-
let(:value) {
|
53
|
+
let(:value) { ' User:6 ' }
|
54
54
|
|
55
|
-
it
|
56
|
-
expect(flipper[:search].actors_value).to include(
|
55
|
+
it 'adds item without whitespace' do
|
56
|
+
expect(flipper[:search].actors_value).to include('User:6')
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
context
|
61
|
-
context
|
62
|
-
let(:value) {
|
60
|
+
context 'for an invalid actor value' do
|
61
|
+
context 'empty value' do
|
62
|
+
let(:value) { '' }
|
63
63
|
|
64
|
-
|
64
|
+
# rubocop:disable Metrics/LineLength
|
65
|
+
it 'redirects back to feature' do
|
65
66
|
expect(last_response.status).to be(302)
|
66
|
-
expect(last_response.headers[
|
67
|
+
expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
|
67
68
|
end
|
69
|
+
# rubocop:enable Metrics/LineLength
|
68
70
|
end
|
69
71
|
|
70
|
-
context
|
72
|
+
context 'nil value' do
|
71
73
|
let(:value) { nil }
|
72
74
|
|
73
|
-
|
75
|
+
# rubocop:disable Metrics/LineLength
|
76
|
+
it 'redirects back to feature' do
|
74
77
|
expect(last_response.status).to be(302)
|
75
|
-
expect(last_response.headers[
|
78
|
+
expect(last_response.headers['Location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
|
76
79
|
end
|
80
|
+
# rubocop:enable Metrics/LineLength
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
80
84
|
|
81
|
-
context
|
82
|
-
let(:value) {
|
85
|
+
context 'disabling an actor' do
|
86
|
+
let(:value) { 'User:6' }
|
83
87
|
|
84
88
|
before do
|
85
|
-
flipper[:search].enable_actor Flipper::UI::Actor.new(
|
86
|
-
post
|
87
|
-
|
88
|
-
|
89
|
+
flipper[:search].enable_actor Flipper::UI::Actor.new('User:6')
|
90
|
+
post 'features/search/actors',
|
91
|
+
{ 'value' => value, 'operation' => 'disable', 'authenticity_token' => token },
|
92
|
+
'rack.session' => session
|
89
93
|
end
|
90
94
|
|
91
|
-
it
|
92
|
-
expect(flipper[:search].actors_value).not_to include(
|
95
|
+
it 'removes item from members' do
|
96
|
+
expect(flipper[:search].actors_value).not_to include('User:6')
|
93
97
|
end
|
94
98
|
|
95
|
-
it
|
99
|
+
it 'redirects back to feature' do
|
96
100
|
expect(last_response.status).to be(302)
|
97
|
-
expect(last_response.headers[
|
101
|
+
expect(last_response.headers['Location']).to eq('/features/search')
|
98
102
|
end
|
99
103
|
|
100
104
|
context 'value contains whitespace' do
|
101
|
-
let(:value) {
|
105
|
+
let(:value) { ' User:6 ' }
|
102
106
|
|
103
|
-
it
|
104
|
-
expect(flipper[:search].actors_value).not_to include(
|
107
|
+
it 'removes item whitout whitespace' do
|
108
|
+
expect(flipper[:search].actors_value).not_to include('User:6')
|
105
109
|
end
|
106
110
|
end
|
107
111
|
end
|
@@ -1,43 +1,43 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe Flipper::UI::Actions::AddFeature do
|
4
|
-
describe
|
4
|
+
describe 'GET /features/new with feature_creation_enabled set to true' do
|
5
5
|
before do
|
6
6
|
@original_feature_creation_enabled = Flipper::UI.feature_creation_enabled
|
7
7
|
Flipper::UI.feature_creation_enabled = true
|
8
|
-
get
|
8
|
+
get '/features/new'
|
9
9
|
end
|
10
10
|
|
11
11
|
after do
|
12
12
|
Flipper::UI.feature_creation_enabled = @original_feature_creation_enabled
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
15
|
+
it 'responds with success' do
|
16
16
|
expect(last_response.status).to be(200)
|
17
17
|
end
|
18
18
|
|
19
|
-
it
|
19
|
+
it 'renders template' do
|
20
20
|
expect(last_response.body).to include('<form action="/features" method="post">')
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
describe
|
24
|
+
describe 'GET /features/new with feature_creation_enabled set to false' do
|
25
25
|
before do
|
26
26
|
@original_feature_creation_enabled = Flipper::UI.feature_creation_enabled
|
27
27
|
Flipper::UI.feature_creation_enabled = false
|
28
|
-
get
|
28
|
+
get '/features/new'
|
29
29
|
end
|
30
30
|
|
31
31
|
after do
|
32
32
|
Flipper::UI.feature_creation_enabled = @original_feature_creation_enabled
|
33
33
|
end
|
34
34
|
|
35
|
-
it
|
35
|
+
it 'returns 403' do
|
36
36
|
expect(last_response.status).to be(403)
|
37
37
|
end
|
38
38
|
|
39
|
-
it
|
40
|
-
expect(last_response.body).to include(
|
39
|
+
it 'renders feature creation disabled template' do
|
40
|
+
expect(last_response.body).to include('Feature creation is disabled.')
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -1,55 +1,55 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe Flipper::UI::Actions::BooleanGate do
|
4
|
-
let(:token)
|
4
|
+
let(:token) do
|
5
5
|
if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
|
6
6
|
Rack::Protection::AuthenticityToken.random_token
|
7
7
|
else
|
8
|
-
|
8
|
+
'a'
|
9
9
|
end
|
10
|
-
|
11
|
-
let(:session)
|
10
|
+
end
|
11
|
+
let(:session) do
|
12
12
|
if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
|
13
|
-
{:
|
13
|
+
{ csrf: token }
|
14
14
|
else
|
15
|
-
{
|
15
|
+
{ '_csrf_token' => token }
|
16
16
|
end
|
17
|
-
|
17
|
+
end
|
18
18
|
|
19
|
-
describe
|
20
|
-
context
|
19
|
+
describe 'POST /features/:feature/boolean' do
|
20
|
+
context 'with enable' do
|
21
21
|
before do
|
22
22
|
flipper.disable :search
|
23
|
-
post
|
24
|
-
|
25
|
-
|
23
|
+
post 'features/search/boolean',
|
24
|
+
{ 'action' => 'Enable', 'authenticity_token' => token },
|
25
|
+
'rack.session' => session
|
26
26
|
end
|
27
27
|
|
28
|
-
it
|
28
|
+
it 'enables the feature' do
|
29
29
|
expect(flipper.enabled?(:search)).to be(true)
|
30
30
|
end
|
31
31
|
|
32
|
-
it
|
32
|
+
it 'redirects back to feature' do
|
33
33
|
expect(last_response.status).to be(302)
|
34
|
-
expect(last_response.headers[
|
34
|
+
expect(last_response.headers['Location']).to eq('/features/search')
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
context
|
38
|
+
context 'with disable' do
|
39
39
|
before do
|
40
40
|
flipper.enable :search
|
41
|
-
post
|
42
|
-
|
43
|
-
|
41
|
+
post 'features/search/boolean',
|
42
|
+
{ 'action' => 'Disable', 'authenticity_token' => token },
|
43
|
+
'rack.session' => session
|
44
44
|
end
|
45
45
|
|
46
|
-
it
|
46
|
+
it 'disables the feature' do
|
47
47
|
expect(flipper.enabled?(:search)).to be(false)
|
48
48
|
end
|
49
49
|
|
50
|
-
it
|
50
|
+
it 'redirects back to feature' do
|
51
51
|
expect(last_response.status).to be(302)
|
52
|
-
expect(last_response.headers[
|
52
|
+
expect(last_response.headers['Location']).to eq('/features/search')
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|