flipper-api 1.0.0 → 1.1.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/lib/flipper/api/action.rb +7 -2
- data/lib/flipper/api/error_response.rb +1 -0
- data/lib/flipper/api/json_params.rb +2 -1
- data/lib/flipper/api/middleware.rb +1 -0
- data/lib/flipper/api/v1/actions/expression_gate.rb +43 -0
- data/lib/flipper/api/v1/decorators/gate.rb +4 -1
- data/lib/flipper/api.rb +1 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/api/action_spec.rb +10 -0
- data/spec/flipper/api/v1/actions/expression_gate_spec.rb +157 -0
- data/spec/flipper/api/v1/actions/feature_spec.rb +20 -0
- data/spec/flipper/api/v1/actions/features_spec.rb +11 -0
- data/spec/flipper/api/v1/actions/import_spec.rb +5 -1
- data/spec/flipper/api_spec.rb +14 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b25c0b97a02c78eb7c67cf2f4a2471f0c1ca3d6ac07fb1199f4e9b00fce15f82
|
4
|
+
data.tar.gz: 2c00025cbdb9d13b9b38e99b8ddc79786bc2c6f540d100d7ab5c8b3bfdc68234
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2a0ab2da9e51ce266b5b25e782163891b3b3be0428642391cb1b32d81b707097c9a869583c630d737633a004b4d23fdc9e5a616202d1507bb0c1c47f3f95917
|
7
|
+
data.tar.gz: 57f01b47c7e7ca94a70222a35062e6ff58b1e0ebcc1b03f344bb8092431140feb0b7a4e106626be516f5538894b74b852ed85407f0263160a0d9bbe59aa2cc4e
|
data/lib/flipper/api/action.rb
CHANGED
@@ -19,6 +19,7 @@ module Flipper
|
|
19
19
|
extend Forwardable
|
20
20
|
|
21
21
|
VALID_REQUEST_METHOD_NAMES = Set.new([
|
22
|
+
'head'.freeze,
|
22
23
|
'get'.freeze,
|
23
24
|
'post'.freeze,
|
24
25
|
'put'.freeze,
|
@@ -115,7 +116,7 @@ module Flipper
|
|
115
116
|
def json_response(object, status = 200)
|
116
117
|
header 'content-type', Api::CONTENT_TYPE
|
117
118
|
status(status)
|
118
|
-
body =
|
119
|
+
body = Typecast.to_json(object)
|
119
120
|
halt [@code, @headers, [body]]
|
120
121
|
end
|
121
122
|
|
@@ -147,8 +148,12 @@ module Flipper
|
|
147
148
|
private
|
148
149
|
|
149
150
|
# Private: Returns the request method converted to an action method.
|
151
|
+
# Converts head to get.
|
150
152
|
def request_method_name
|
151
|
-
@request_method_name ||=
|
153
|
+
@request_method_name ||= begin
|
154
|
+
name = @request.request_method.downcase
|
155
|
+
name == "head" ? "get" : name
|
156
|
+
end
|
152
157
|
end
|
153
158
|
|
154
159
|
# Private: split request path by "/"
|
@@ -28,6 +28,7 @@ module Flipper
|
|
28
28
|
flipper_id_invalid: Error.new(4, 'Required parameter flipper_id is missing.', 422),
|
29
29
|
name_invalid: Error.new(5, 'Required parameter name is missing.', 422),
|
30
30
|
import_invalid: Error.new(6, 'Import invalid.', 422),
|
31
|
+
expression_invalid: Error.new(7, 'The provided expression was not valid.', 422),
|
31
32
|
}.freeze
|
32
33
|
end
|
33
34
|
end
|
@@ -34,7 +34,8 @@ module Flipper
|
|
34
34
|
# This method accomplishes similar functionality
|
35
35
|
def update_params(env, data)
|
36
36
|
return if data.empty?
|
37
|
-
parsed_request_body =
|
37
|
+
parsed_request_body = Typecast.from_json(data)
|
38
|
+
env["parsed_request_body".freeze] = parsed_request_body
|
38
39
|
parsed_query_string = parse_query(env[QUERY_STRING])
|
39
40
|
parsed_query_string.merge!(parsed_request_body)
|
40
41
|
parameters = build_query(parsed_query_string)
|
@@ -14,6 +14,7 @@ module Flipper
|
|
14
14
|
@env_key = options.fetch(:env_key, 'flipper')
|
15
15
|
|
16
16
|
@action_collection = ActionCollection.new
|
17
|
+
@action_collection.add Api::V1::Actions::ExpressionGate
|
17
18
|
@action_collection.add Api::V1::Actions::PercentageOfTimeGate
|
18
19
|
@action_collection.add Api::V1::Actions::PercentageOfActorsGate
|
19
20
|
@action_collection.add Api::V1::Actions::ActorsGate
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'flipper/api/action'
|
2
|
+
require 'flipper/api/v1/decorators/feature'
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Api
|
6
|
+
module V1
|
7
|
+
module Actions
|
8
|
+
class ExpressionGate < Api::Action
|
9
|
+
include FeatureNameFromRoute
|
10
|
+
|
11
|
+
route %r{\A/features/(?<feature_name>.*)/expression/?\Z}
|
12
|
+
|
13
|
+
def post
|
14
|
+
feature = flipper[feature_name]
|
15
|
+
|
16
|
+
begin
|
17
|
+
expression = Flipper::Expression.build(expression_hash)
|
18
|
+
feature.enable_expression expression
|
19
|
+
decorated_feature = Decorators::Feature.new(feature)
|
20
|
+
json_response(decorated_feature.as_json, 200)
|
21
|
+
rescue NameError, ArgumentError => exception
|
22
|
+
json_error_response(:expression_invalid)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete
|
27
|
+
feature = flipper[feature_name]
|
28
|
+
feature.disable_expression
|
29
|
+
|
30
|
+
decorated_feature = Decorators::Feature.new(feature)
|
31
|
+
json_response(decorated_feature.as_json, 200)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def expression_hash
|
37
|
+
@expression_hash ||= request.env["parsed_request_body".freeze] || {}.freeze
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -25,9 +25,12 @@ module Flipper
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
+
# Set of types that should be represented as Array in JSON.
|
29
|
+
JSON_ARRAY_TYPES = Set[:set].freeze
|
30
|
+
|
28
31
|
# json doesn't like sets
|
29
32
|
def value_as_json
|
30
|
-
data_type
|
33
|
+
JSON_ARRAY_TYPES.include?(data_type) ? value.to_a : value
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|
data/lib/flipper/api.rb
CHANGED
@@ -12,6 +12,7 @@ module Flipper
|
|
12
12
|
app = ->(_) { [404, { 'content-type'.freeze => CONTENT_TYPE }, ['{}'.freeze]] }
|
13
13
|
builder = Rack::Builder.new
|
14
14
|
yield builder if block_given?
|
15
|
+
builder.use Rack::Head
|
15
16
|
builder.use Flipper::Api::JsonParams
|
16
17
|
builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
|
17
18
|
builder.use Flipper::Api::Middleware, env_key: env_key
|
data/lib/flipper/version.rb
CHANGED
@@ -9,6 +9,10 @@ RSpec.describe Flipper::Api::Action do
|
|
9
9
|
[200, {}, 'get']
|
10
10
|
end
|
11
11
|
|
12
|
+
def head
|
13
|
+
[200, {}, 'head']
|
14
|
+
end
|
15
|
+
|
12
16
|
def post
|
13
17
|
[200, {}, 'post']
|
14
18
|
end
|
@@ -38,6 +42,12 @@ RSpec.describe Flipper::Api::Action do
|
|
38
42
|
expect(action.run).to eq([200, {}, 'get'])
|
39
43
|
end
|
40
44
|
|
45
|
+
it 'will run head' do
|
46
|
+
fake_request = Struct.new(:request_method, :env, :session).new('HEAD', {}, {})
|
47
|
+
action = action_subclass.new(flipper, fake_request)
|
48
|
+
expect(action.run).to eq([200, {}, 'get'])
|
49
|
+
end
|
50
|
+
|
41
51
|
it 'will run post' do
|
42
52
|
fake_request = Struct.new(:request_method, :env, :session).new('POST', {}, {})
|
43
53
|
action = action_subclass.new(flipper, fake_request)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
RSpec.describe Flipper::Api::V1::Actions::ExpressionGate do
|
2
|
+
let(:app) { build_api(flipper) }
|
3
|
+
let(:actor) {
|
4
|
+
Flipper::Actor.new('1', {
|
5
|
+
"plan" => "basic",
|
6
|
+
"age" => 21,
|
7
|
+
})
|
8
|
+
}
|
9
|
+
let(:expression) { Flipper.property(:plan).eq("basic") }
|
10
|
+
|
11
|
+
describe 'enable' do
|
12
|
+
before do
|
13
|
+
flipper[:my_feature].disable_expression
|
14
|
+
post '/features/my_feature/expression', JSON.dump(expression.value),
|
15
|
+
"CONTENT_TYPE" => "application/json"
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'enables feature for expression' do
|
19
|
+
expect(last_response.status).to eq(200)
|
20
|
+
expect(flipper[:my_feature].enabled?(actor)).to be_truthy
|
21
|
+
expect(flipper[:my_feature].enabled_gate_names).to eq([:expression])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns decorated feature with expression enabled' do
|
25
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
26
|
+
expect(gate['value']).to eq(expression.value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'disable' do
|
31
|
+
before do
|
32
|
+
flipper[:my_feature].enable_expression(expression)
|
33
|
+
delete '/features/my_feature/expression', JSON.dump({}),
|
34
|
+
"CONTENT_TYPE" => "application/json"
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'disables expression for feature' do
|
38
|
+
expect(last_response.status).to eq(200)
|
39
|
+
expect(flipper[:my_feature].enabled?(actor)).to be_falsy
|
40
|
+
expect(flipper[:my_feature].enabled_gate_names).to be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns decorated feature with expression gate disabled' do
|
44
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
45
|
+
expect(gate['value']).to be(nil)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'enable feature with slash in name' do
|
50
|
+
before do
|
51
|
+
flipper["my/feature"].disable_expression
|
52
|
+
post '/features/my/feature/expression', JSON.dump(expression.value),
|
53
|
+
"CONTENT_TYPE" => "application/json"
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'enables feature for expression' do
|
57
|
+
expect(last_response.status).to eq(200)
|
58
|
+
expect(flipper["my/feature"].enabled?(actor)).to be_truthy
|
59
|
+
expect(flipper["my/feature"].enabled_gate_names).to eq([:expression])
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns decorated feature with expression enabled' do
|
63
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
64
|
+
expect(gate['value']).to eq(expression.value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'enable feature with space in name' do
|
69
|
+
before do
|
70
|
+
flipper["sp ace"].disable_expression
|
71
|
+
post '/features/sp%20ace/expression', JSON.dump(expression.value),
|
72
|
+
"CONTENT_TYPE" => "application/json"
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'enables feature for expression' do
|
76
|
+
expect(last_response.status).to eq(200)
|
77
|
+
expect(flipper["sp ace"].enabled?(actor)).to be_truthy
|
78
|
+
expect(flipper["sp ace"].enabled_gate_names).to eq([:expression])
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns decorated feature with expression enabled' do
|
82
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
83
|
+
expect(gate['value']).to eq(expression.value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'enable with no data' do
|
88
|
+
before do
|
89
|
+
post '/features/my_feature/expression', "CONTENT_TYPE" => "application/json"
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns correct error response' do
|
93
|
+
expect(last_response.status).to eq(422)
|
94
|
+
expect(json_response).to eq(api_expression_invalid_response)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'enable with empty object' do
|
99
|
+
before do
|
100
|
+
data = {}
|
101
|
+
post '/features/my_feature/expression', JSON.dump(data),
|
102
|
+
"CONTENT_TYPE" => "application/json"
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns correct error response' do
|
106
|
+
expect(last_response.status).to eq(422)
|
107
|
+
expect(json_response).to eq(api_expression_invalid_response)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'enable with invalid data' do
|
112
|
+
before do
|
113
|
+
data = {"blah" => "blah"}
|
114
|
+
post '/features/my_feature/expression', JSON.dump(data),
|
115
|
+
"CONTENT_TYPE" => "application/json"
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'returns correct error response' do
|
119
|
+
expect(last_response.status).to eq(422)
|
120
|
+
expect(json_response).to eq(api_expression_invalid_response)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'enable missing feature' do
|
125
|
+
before do
|
126
|
+
post '/features/my_feature/expression', JSON.dump(expression.value), "CONTENT_TYPE" => "application/json"
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'enables expression for feature' do
|
130
|
+
expect(last_response.status).to eq(200)
|
131
|
+
expect(flipper[:my_feature].enabled?(actor)).to be_truthy
|
132
|
+
expect(flipper[:my_feature].enabled_gate_names).to eq([:expression])
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'returns decorated feature with expression enabled' do
|
136
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
137
|
+
expect(gate['value']).to eq(expression.value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe 'disable missing feature' do
|
142
|
+
before do
|
143
|
+
delete '/features/my_feature/expression', "CONTENT_TYPE" => "application/json"
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'disables expression for feature' do
|
147
|
+
expect(last_response.status).to eq(200)
|
148
|
+
expect(flipper[:my_feature].enabled?(actor)).to be_falsy
|
149
|
+
expect(flipper[:my_feature].enabled_gate_names).to be_empty
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns decorated feature with expression gate disabled' do
|
153
|
+
gate = json_response['gates'].find { |gate| gate['key'] == 'expression' }
|
154
|
+
expect(gate['value']).to be(nil)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -20,6 +20,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
|
|
20
20
|
'name' => 'boolean',
|
21
21
|
'value' => 'true',
|
22
22
|
},
|
23
|
+
{
|
24
|
+
'key' => 'expression',
|
25
|
+
'name' => 'expression',
|
26
|
+
'value' => nil,
|
27
|
+
},
|
23
28
|
{
|
24
29
|
'key' => 'actors',
|
25
30
|
'name' => 'actor',
|
@@ -64,6 +69,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
|
|
64
69
|
'name' => 'boolean',
|
65
70
|
'value' => nil,
|
66
71
|
},
|
72
|
+
{
|
73
|
+
'key' => 'expression',
|
74
|
+
'name' => 'expression',
|
75
|
+
'value' => nil,
|
76
|
+
},
|
67
77
|
{
|
68
78
|
'key' => 'actors',
|
69
79
|
'name' => 'actor',
|
@@ -124,6 +134,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
|
|
124
134
|
'name' => 'boolean',
|
125
135
|
'value' => 'true',
|
126
136
|
},
|
137
|
+
{
|
138
|
+
'key' => 'expression',
|
139
|
+
'name' => 'expression',
|
140
|
+
'value' => nil,
|
141
|
+
},
|
127
142
|
{
|
128
143
|
'key' => 'actors',
|
129
144
|
'name' => 'actor',
|
@@ -168,6 +183,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
|
|
168
183
|
'name' => 'boolean',
|
169
184
|
'value' => 'true',
|
170
185
|
},
|
186
|
+
{
|
187
|
+
'key' => 'expression',
|
188
|
+
'name' => 'expression',
|
189
|
+
'value' => nil,
|
190
|
+
},
|
171
191
|
{
|
172
192
|
'key' => 'actors',
|
173
193
|
'name' => 'actor',
|
@@ -24,6 +24,11 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
|
|
24
24
|
'name' => 'boolean',
|
25
25
|
'value' => 'true',
|
26
26
|
},
|
27
|
+
{
|
28
|
+
'key' => 'expression',
|
29
|
+
'name' => 'expression',
|
30
|
+
'value' => nil,
|
31
|
+
},
|
27
32
|
{
|
28
33
|
'key' => 'actors',
|
29
34
|
'name' => 'actor',
|
@@ -60,6 +65,7 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
|
|
60
65
|
'state' => 'on',
|
61
66
|
'gates' => [
|
62
67
|
{ 'key' => 'boolean', 'value' => 'true'},
|
68
|
+
{"key" => "expression", "value" => nil},
|
63
69
|
{ 'key' => 'actors', 'value' => ['10']},
|
64
70
|
{'key' => 'percentage_of_actors', 'value' => nil},
|
65
71
|
{ 'key' => 'percentage_of_time', 'value' => nil},
|
@@ -140,6 +146,11 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
|
|
140
146
|
'name' => 'boolean',
|
141
147
|
'value' => nil,
|
142
148
|
},
|
149
|
+
{
|
150
|
+
'key' => 'expression',
|
151
|
+
'name' => 'expression',
|
152
|
+
'value' => nil,
|
153
|
+
},
|
143
154
|
{
|
144
155
|
'key' => 'actors',
|
145
156
|
'name' => 'actor',
|
@@ -2,6 +2,8 @@ RSpec.describe Flipper::Api::V1::Actions::Import do
|
|
2
2
|
let(:app) { build_api(flipper) }
|
3
3
|
|
4
4
|
describe 'post' do
|
5
|
+
let(:expression) { Flipper.property(:plan).eq("basic") }
|
6
|
+
|
5
7
|
context 'succesful request' do
|
6
8
|
before do
|
7
9
|
flipper.enable(:search)
|
@@ -10,6 +12,7 @@ RSpec.describe Flipper::Api::V1::Actions::Import do
|
|
10
12
|
source_flipper = build_flipper
|
11
13
|
source_flipper.disable(:search)
|
12
14
|
source_flipper.enable_actor(:google_analytics, Flipper::Actor.new("User;1"))
|
15
|
+
source_flipper.enable(:analytics, expression)
|
13
16
|
|
14
17
|
export = source_flipper.export
|
15
18
|
|
@@ -23,7 +26,8 @@ RSpec.describe Flipper::Api::V1::Actions::Import do
|
|
23
26
|
it 'imports features' do
|
24
27
|
expect(flipper[:search].boolean_value).to be(false)
|
25
28
|
expect(flipper[:google_analytics].actors_value).to eq(Set["User;1"])
|
26
|
-
expect(flipper.
|
29
|
+
expect(flipper[:analytics].expression_value).to eq(expression.value)
|
30
|
+
expect(flipper.features.map(&:key)).to eq(["search", "google_analytics", "analytics"])
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
data/spec/flipper/api_spec.rb
CHANGED
@@ -14,6 +14,20 @@ RSpec.describe Flipper::Api do
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
describe "Head requests" do
|
18
|
+
let(:app) { build_api(flipper) }
|
19
|
+
|
20
|
+
it "are allowed but perform get and return no body" do
|
21
|
+
flipper.enable :a
|
22
|
+
flipper.disable :b
|
23
|
+
|
24
|
+
head '/features'
|
25
|
+
|
26
|
+
expect(last_response.status).to be(200)
|
27
|
+
expect(last_response.body).to be_empty
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
17
31
|
describe 'Initializing middleware lazily with a block' do
|
18
32
|
let(:app) { build_api(-> { flipper }) }
|
19
33
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.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: 2023-08
|
11
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 1.
|
39
|
+
version: 1.1.0
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.
|
46
|
+
version: 1.1.0
|
47
47
|
description:
|
48
48
|
email: support@flippercloud.io
|
49
49
|
executables: []
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- lib/flipper/api/v1/actions/actors_gate.rb
|
64
64
|
- lib/flipper/api/v1/actions/boolean_gate.rb
|
65
65
|
- lib/flipper/api/v1/actions/clear_feature.rb
|
66
|
+
- lib/flipper/api/v1/actions/expression_gate.rb
|
66
67
|
- lib/flipper/api/v1/actions/feature.rb
|
67
68
|
- lib/flipper/api/v1/actions/features.rb
|
68
69
|
- lib/flipper/api/v1/actions/groups_gate.rb
|
@@ -79,6 +80,7 @@ files:
|
|
79
80
|
- spec/flipper/api/v1/actions/actors_spec.rb
|
80
81
|
- spec/flipper/api/v1/actions/boolean_gate_spec.rb
|
81
82
|
- spec/flipper/api/v1/actions/clear_feature_spec.rb
|
83
|
+
- spec/flipper/api/v1/actions/expression_gate_spec.rb
|
82
84
|
- spec/flipper/api/v1/actions/feature_spec.rb
|
83
85
|
- spec/flipper/api/v1/actions/features_spec.rb
|
84
86
|
- spec/flipper/api/v1/actions/groups_gate_spec.rb
|
@@ -122,6 +124,7 @@ test_files:
|
|
122
124
|
- spec/flipper/api/v1/actions/actors_spec.rb
|
123
125
|
- spec/flipper/api/v1/actions/boolean_gate_spec.rb
|
124
126
|
- spec/flipper/api/v1/actions/clear_feature_spec.rb
|
127
|
+
- spec/flipper/api/v1/actions/expression_gate_spec.rb
|
125
128
|
- spec/flipper/api/v1/actions/feature_spec.rb
|
126
129
|
- spec/flipper/api/v1/actions/features_spec.rb
|
127
130
|
- spec/flipper/api/v1/actions/groups_gate_spec.rb
|