Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
530d6316
Commit
530d6316
authored
Oct 10, 2019
by
Shinya Maeda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduce Feature Flag enable/disable API
This commit introduces enable/disable endpoints for Feature Flag API.
parent
845a43c3
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
595 additions
and
0 deletions
+595
-0
changelogs/unreleased/introduce-feature-flag-api-enable-disable.yml
.../unreleased/introduce-feature-flag-api-enable-disable.yml
+5
-0
ee/app/services/feature_flags/base_service.rb
ee/app/services/feature_flags/base_service.rb
+14
-0
ee/app/services/feature_flags/disable_service.rb
ee/app/services/feature_flags/disable_service.rb
+46
-0
ee/app/services/feature_flags/enable_service.rb
ee/app/services/feature_flags/enable_service.rb
+93
-0
ee/lib/api/feature_flags.rb
ee/lib/api/feature_flags.rb
+38
-0
ee/spec/requests/api/feature_flags_spec.rb
ee/spec/requests/api/feature_flags_spec.rb
+153
-0
ee/spec/services/feature_flags/disable_service_spec.rb
ee/spec/services/feature_flags/disable_service_spec.rb
+92
-0
ee/spec/services/feature_flags/enable_service_spec.rb
ee/spec/services/feature_flags/enable_service_spec.rb
+154
-0
No files found.
changelogs/unreleased/introduce-feature-flag-api-enable-disable.yml
0 → 100644
View file @
530d6316
---
title
:
Support Enable/Disable operations in Feature Flag API
merge_request
:
18368
author
:
type
:
added
ee/app/services/feature_flags/base_service.rb
View file @
530d6316
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
module
FeatureFlags
module
FeatureFlags
class
BaseService
<
::
BaseService
class
BaseService
<
::
BaseService
include
Gitlab
::
Utils
::
StrongMemoize
AUDITABLE_ATTRIBUTES
=
%w(name description)
.
freeze
AUDITABLE_ATTRIBUTES
=
%w(name description)
.
freeze
protected
protected
...
@@ -37,5 +39,17 @@ module FeatureFlags
...
@@ -37,5 +39,17 @@ module FeatureFlags
"and set it as <strong>
#{
scope
.
active
?
"active"
:
"inactive"
}
</strong> "
\
"and set it as <strong>
#{
scope
.
active
?
"active"
:
"inactive"
}
</strong> "
\
"with strategies <strong>
#{
scope
.
strategies
}
</strong>."
"with strategies <strong>
#{
scope
.
strategies
}
</strong>."
end
end
def
feature_flag_by_name
strong_memoize
(
:feature_flag_by_name
)
do
project
.
operations_feature_flags
.
find_by_name
(
params
[
:name
])
end
end
def
feature_flag_scope_by_environment_scope
strong_memoize
(
:feature_flag_scope_by_environment_scope
)
do
feature_flag_by_name
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
end
end
end
end
end
end
ee/app/services/feature_flags/disable_service.rb
0 → 100644
View file @
530d6316
# frozen_string_literal: true
module
FeatureFlags
class
DisableService
<
BaseService
def
execute
return
error
(
'Feature Flag not found'
,
404
)
unless
feature_flag_by_name
return
error
(
'Feature Flag Scope not found'
,
404
)
unless
feature_flag_scope_by_environment_scope
return
error
(
'Strategy not found'
,
404
)
unless
strategy_exist_in_persisted_data?
::
FeatureFlags
::
UpdateService
.
new
(
project
,
current_user
,
update_params
)
.
execute
(
feature_flag_by_name
)
end
private
def
update_params
if
remaining_strategies
.
empty?
params_to_destroy_scope
else
params_to_update_scope
end
end
def
remaining_strategies
strong_memoize
(
:remaining_strategies
)
do
feature_flag_scope_by_environment_scope
.
strategies
.
reject
do
|
strategy
|
strategy
[
'name'
]
==
params
[
:strategy
][
'name'
]
&&
strategy
[
'parameters'
]
==
params
[
:strategy
][
'parameters'
]
end
end
end
def
strategy_exist_in_persisted_data?
feature_flag_scope_by_environment_scope
.
strategies
!=
remaining_strategies
end
def
params_to_destroy_scope
{
scopes_attributes:
[{
id:
feature_flag_scope_by_environment_scope
.
id
,
_destroy:
true
}]
}
end
def
params_to_update_scope
{
scopes_attributes:
[{
id:
feature_flag_scope_by_environment_scope
.
id
,
strategies:
remaining_strategies
}]
}
end
end
end
ee/app/services/feature_flags/enable_service.rb
0 → 100644
View file @
530d6316
# frozen_string_literal: true
module
FeatureFlags
class
EnableService
<
BaseService
def
execute
if
feature_flag_by_name
update_feature_flag
else
create_feature_flag
end
end
private
def
create_feature_flag
::
FeatureFlags
::
CreateService
.
new
(
project
,
current_user
,
create_params
)
.
execute
end
def
update_feature_flag
::
FeatureFlags
::
UpdateService
.
new
(
project
,
current_user
,
update_params
)
.
execute
(
feature_flag_by_name
)
end
def
create_params
if
params
[
:environment_scope
]
==
'*'
params_to_create_flag_with_default_scope
else
params_to_create_flag_with_additional_scope
end
end
def
update_params
if
feature_flag_scope_by_environment_scope
params_to_update_scope
else
params_to_create_scope
end
end
def
params_to_create_flag_with_default_scope
{
name:
params
[
:name
],
scopes_attributes:
[
{
active:
true
,
environment_scope:
'*'
,
strategies:
[
params
[
:strategy
]]
}
]
}
end
def
params_to_create_flag_with_additional_scope
{
name:
params
[
:name
],
scopes_attributes:
[
{
active:
false
,
environment_scope:
'*'
},
{
active:
true
,
environment_scope:
params
[
:environment_scope
],
strategies:
[
params
[
:strategy
]]
}
]
}
end
def
params_to_create_scope
{
scopes_attributes:
[{
active:
true
,
environment_scope:
params
[
:environment_scope
],
strategies:
[
params
[
:strategy
]]
}]
}
end
def
params_to_update_scope
{
scopes_attributes:
[{
id:
feature_flag_scope_by_environment_scope
.
id
,
active:
true
,
strategies:
feature_flag_scope_by_environment_scope
.
strategies
|
[
params
[
:strategy
]]
}]
}
end
end
end
ee/lib/api/feature_flags.rb
View file @
530d6316
...
@@ -76,6 +76,44 @@ module API
...
@@ -76,6 +76,44 @@ module API
present
feature_flag
,
with:
EE
::
API
::
Entities
::
FeatureFlag
present
feature_flag
,
with:
EE
::
API
::
Entities
::
FeatureFlag
end
end
desc
'Enable a strategy for a feature flag on an environment'
do
success
EE
::
API
::
Entities
::
FeatureFlag
end
params
do
requires
:environment_scope
,
type:
String
,
desc:
'The environment scope of the feature flag'
requires
:strategy
,
type:
JSON
,
desc:
'The strategy to be enabled on the scope'
end
post
:enable
do
result
=
::
FeatureFlags
::
EnableService
.
new
(
user_project
,
current_user
,
params
).
execute
if
result
[
:status
]
==
:success
status
:ok
present
result
[
:feature_flag
],
with:
EE
::
API
::
Entities
::
FeatureFlag
else
render_api_error!
(
result
[
:message
],
result
[
:http_status
])
end
end
desc
'Disable a strategy for a feature flag on an environment'
do
success
EE
::
API
::
Entities
::
FeatureFlag
end
params
do
requires
:environment_scope
,
type:
String
,
desc:
'The environment scope of the feature flag'
requires
:strategy
,
type:
JSON
,
desc:
'The strategy to be disabled on the scope'
end
post
:disable
do
result
=
::
FeatureFlags
::
DisableService
.
new
(
user_project
,
current_user
,
params
).
execute
if
result
[
:status
]
==
:success
status
:ok
present
result
[
:feature_flag
],
with:
EE
::
API
::
Entities
::
FeatureFlag
else
render_api_error!
(
result
[
:message
],
result
[
:http_status
])
end
end
desc
'Delete a feature flag'
do
desc
'Delete a feature flag'
do
success
EE
::
API
::
Entities
::
FeatureFlag
success
EE
::
API
::
Entities
::
FeatureFlag
end
end
...
...
ee/spec/requests/api/feature_flags_spec.rb
View file @
530d6316
...
@@ -186,6 +186,159 @@ describe API::FeatureFlags do
...
@@ -186,6 +186,159 @@ describe API::FeatureFlags do
end
end
end
end
describe
'POST /projects/:id/feature_flags/:name/enable'
do
subject
do
post
api
(
"/projects/
#{
project
.
id
}
/feature_flags/
#{
params
[
:name
]
}
/enable"
,
user
),
params:
params
end
let
(
:params
)
do
{
name:
'awesome-feature'
,
environment_scope:
'production'
,
strategy:
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:1'
}
}.
to_json
}
end
context
'when feature flag does not exist yet'
do
it
'creates a new feature flag with the specified scope and strategy'
do
subject
feature_flag
=
project
.
operations_feature_flags
.
last
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
match_response_schema
(
'public_api/v4/feature_flag'
,
dir:
'ee'
)
expect
(
feature_flag
.
name
).
to
eq
(
params
[
:name
])
expect
(
scope
.
strategies
).
to
eq
([
JSON
.
parse
(
params
[
:strategy
])])
end
it_behaves_like
'check user permission'
end
context
'when feature flag exists already'
do
let!
(
:feature_flag
)
{
create_flag
(
project
,
params
[
:name
])
}
context
'when feature flag scope does not exist yet'
do
it
'creates a new scope with the specified strategy'
do
subject
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
scope
.
strategies
).
to
eq
([
JSON
.
parse
(
params
[
:strategy
])])
end
it_behaves_like
'check user permission'
end
context
'when feature flag scope exists already'
do
let
(
:defined_strategy
)
{
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:2'
}
}
}
before
do
create_scope
(
feature_flag
,
params
[
:environment_scope
],
true
,
[
defined_strategy
])
end
it
'adds an additional strategy to the scope'
do
subject
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
scope
.
strategies
).
to
eq
([
defined_strategy
.
deep_stringify_keys
,
JSON
.
parse
(
params
[
:strategy
])])
end
context
'when the specified strategy exists already'
do
let
(
:defined_strategy
)
{
JSON
.
parse
(
params
[
:strategy
])
}
it
'does not add a duplicate strategy'
do
subject
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
strategy_count
=
scope
.
strategies
.
select
{
|
strategy
|
strategy
[
'name'
]
==
'userWithId'
}.
count
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
strategy_count
).
to
eq
(
1
)
end
end
end
end
end
describe
'POST /projects/:id/feature_flags/:name/disable'
do
subject
do
post
api
(
"/projects/
#{
project
.
id
}
/feature_flags/
#{
params
[
:name
]
}
/disable"
,
user
),
params:
params
end
let
(
:params
)
do
{
name:
'awesome-feature'
,
environment_scope:
'production'
,
strategy:
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:1'
}
}.
to_json
}
end
context
'when feature flag does not exist yet'
do
it_behaves_like
'not found'
end
context
'when feature flag exists already'
do
let!
(
:feature_flag
)
{
create_flag
(
project
,
params
[
:name
])
}
context
'when feature flag scope does not exist yet'
do
it_behaves_like
'not found'
end
context
'when feature flag scope exists already and has the specified strategy'
do
let
(
:defined_strategies
)
do
[
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:1'
}
},
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:2'
}
}
]
end
before
do
create_scope
(
feature_flag
,
params
[
:environment_scope
],
true
,
defined_strategies
)
end
it
'removes the strategy from the scope'
do
subject
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
match_response_schema
(
'public_api/v4/feature_flag'
,
dir:
'ee'
)
expect
(
scope
.
strategies
)
.
to
eq
([{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:2'
}
}.
deep_stringify_keys
])
end
it_behaves_like
'check user permission'
context
'when strategies become empty array after the removal'
do
let
(
:defined_strategies
)
do
[{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:1'
}
}]
end
it
'destroys the scope'
do
subject
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
scope
).
to
be_nil
end
it_behaves_like
'check user permission'
end
end
context
'when scope exists already but cannot find the corresponding strategy'
do
let
(
:defined_strategy
)
{
{
name:
'userWithId'
,
parameters:
{
userIds:
'Project:2'
}
}
}
before
do
create_scope
(
feature_flag
,
params
[
:environment_scope
],
true
,
[
defined_strategy
])
end
it_behaves_like
'not found'
end
end
end
describe
'DELETE /projects/:id/feature_flags/:name'
do
describe
'DELETE /projects/:id/feature_flags/:name'
do
subject
do
subject
do
delete
api
(
"/projects/
#{
project
.
id
}
/feature_flags/
#{
feature_flag
.
name
}
"
,
user
),
delete
api
(
"/projects/
#{
project
.
id
}
/feature_flags/
#{
feature_flag
.
name
}
"
,
user
),
...
...
ee/spec/services/feature_flags/disable_service_spec.rb
0 → 100644
View file @
530d6316
# frozen_string_literal: true
require
'spec_helper'
describe
FeatureFlags
::
DisableService
do
include
FeatureFlagHelpers
let_it_be
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
let
(
:params
)
{
{}
}
before
do
stub_licensed_features
(
feature_flags:
true
)
project
.
add_developer
(
user
)
end
describe
'#execute'
do
subject
{
service
.
execute
}
context
'with params to disable default strategy on prd scope'
do
let
(
:params
)
do
{
name:
'awesome'
,
environment_scope:
'prd'
,
strategy:
{
name:
'userWithId'
,
parameters:
{
'userIds'
:
'User:1'
}
}.
deep_stringify_keys
}
end
context
'when there is a persisted feature flag'
do
let!
(
:feature_flag
)
{
create_flag
(
project
,
params
[
:name
])
}
context
'when there is a persisted scope'
do
let!
(
:scope
)
do
create_scope
(
feature_flag
,
params
[
:environment_scope
],
true
,
strategies
)
end
context
'when there is a persisted strategy'
do
let
(
:strategies
)
do
[
{
name:
'userWithId'
,
parameters:
{
'userIds'
:
'User:1'
}
}.
deep_stringify_keys
,
{
name:
'userWithId'
,
parameters:
{
'userIds'
:
'User:2'
}
}.
deep_stringify_keys
]
end
it
'deletes the specified strategy'
do
subject
scope
.
reload
expect
(
scope
.
strategies
.
count
).
to
eq
(
1
)
expect
(
scope
.
strategies
).
not_to
include
(
params
[
:strategy
])
end
context
'when strategies will be empty'
do
let
(
:strategies
)
{
[
params
[
:strategy
]]
}
it
'deletes the persisted scope'
do
subject
expect
(
feature_flag
.
scopes
.
exists?
(
environment_scope:
params
[
:environment_scope
]))
.
to
eq
(
false
)
end
end
end
context
'when there is no persisted strategy'
do
let
(
:strategies
)
{
[{
name:
'default'
,
parameters:
{}
}]
}
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
'Strategy not found'
)
end
end
end
context
'when there is no persisted scope'
do
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
'Feature Flag Scope not found'
)
end
end
end
context
'when there is no persisted feature flag'
do
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
'Feature Flag not found'
)
end
end
end
end
end
ee/spec/services/feature_flags/enable_service_spec.rb
0 → 100644
View file @
530d6316
# frozen_string_literal: true
require
'spec_helper'
describe
FeatureFlags
::
EnableService
do
include
FeatureFlagHelpers
let_it_be
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
let
(
:params
)
{
{}
}
before
do
stub_licensed_features
(
feature_flags:
true
)
project
.
add_developer
(
user
)
end
describe
'#execute'
do
subject
{
service
.
execute
}
context
'with params to enable default strategy on prd scope'
do
let
(
:params
)
do
{
name:
'awesome'
,
environment_scope:
'prd'
,
strategy:
{
name:
'default'
,
parameters:
{}
}.
stringify_keys
}
end
context
'when there is no persisted feature flag'
do
it
'creates a new feature flag with scope'
do
feature_flag
=
subject
[
:feature_flag
]
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
subject
[
:status
]).
to
eq
(
:success
)
expect
(
feature_flag
.
name
).
to
eq
(
params
[
:name
])
expect
(
feature_flag
.
default_scope
).
not_to
be_active
expect
(
scope
).
to
be_active
expect
(
scope
.
strategies
).
to
include
(
params
[
:strategy
])
end
context
'when params include default scope'
do
let
(
:params
)
do
{
name:
'awesome'
,
environment_scope:
'*'
,
strategy:
{
name:
'userWithId'
,
parameters:
{
'userIds'
:
'abc'
}
}.
deep_stringify_keys
}
end
it
'create a new feature flag with an active default scope with the specified strategy'
do
feature_flag
=
subject
[
:feature_flag
]
expect
(
subject
[
:status
]).
to
eq
(
:success
)
expect
(
feature_flag
.
default_scope
).
to
be_active
expect
(
feature_flag
.
default_scope
.
strategies
).
to
include
(
params
[
:strategy
])
end
end
end
context
'when there is a persisted feature flag'
do
let!
(
:feature_flag
)
{
create_flag
(
project
,
params
[
:name
])
}
context
'when there is no persisted scope'
do
it
'creates a new scope for the persisted feature flag'
do
feature_flag
=
subject
[
:feature_flag
]
scope
=
feature_flag
.
scopes
.
find_by_environment_scope
(
params
[
:environment_scope
])
expect
(
subject
[
:status
]).
to
eq
(
:success
)
expect
(
feature_flag
.
name
).
to
eq
(
params
[
:name
])
expect
(
scope
).
to
be_active
expect
(
scope
.
strategies
).
to
include
(
params
[
:strategy
])
end
end
context
'when there is a persisted scope'
do
let!
(
:feature_flag_scope
)
do
create_scope
(
feature_flag
,
params
[
:environment_scope
],
active
,
strategies
)
end
let
(
:active
)
{
true
}
context
'when the persisted scope does not have the specified strategy yet'
do
let
(
:strategies
)
{
[{
name:
'userWithId'
,
parameters:
{
'userIds'
:
'abc'
}
}]
}
it
'adds the specified strategy to the scope'
do
subject
feature_flag_scope
.
reload
expect
(
feature_flag_scope
.
strategies
).
to
include
(
params
[
:strategy
])
end
context
'when the persisted scope is inactive'
do
let
(
:active
)
{
false
}
it
'reactivates the scope'
do
expect
{
subject
}
.
to
change
{
feature_flag_scope
.
reload
.
active
}.
from
(
false
).
to
(
true
)
end
end
end
context
'when the persisted scope has the specified strategy already'
do
let
(
:strategies
)
{
[
params
[
:strategy
]]
}
it
'does not add a duplicated strategy to the scope'
do
expect
{
subject
}
.
not_to
change
{
feature_flag_scope
.
reload
.
strategies
.
count
}
end
end
end
end
end
context
'when strategy is not specified in params'
do
let
(
:params
)
do
{
name:
'awesome'
,
environment_scope:
'prd'
}
end
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
'Scopes strategies must be an array of strategy hashes'
)
end
end
context
'when environment scope is not specified in params'
do
let
(
:params
)
do
{
name:
'awesome'
,
strategy:
{
name:
'default'
,
parameters:
{}
}.
stringify_keys
}
end
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
"Scopes environment scope can't be blank"
)
end
end
context
'when name is not specified in params'
do
let
(
:params
)
do
{
environment_scope:
'prd'
,
strategy:
{
name:
'default'
,
parameters:
{}
}.
stringify_keys
}
end
it
'returns error'
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
include
(
"Name can't be blank"
)
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment