Commit d416cf01 authored by Shinya Maeda's avatar Shinya Maeda

Drop support of legacy feature flag

Legacy feature flags have been deprecated for a long time
and to be dropped in GitLab 14.0.

Changelog: removed
parent 9c4698dc
......@@ -11,6 +11,7 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
before_action :feature_flag, only: [:edit, :update, :destroy]
before_action :ensure_flag_writable!, only: [:update]
before_action :exclude_legacy_flags_check, only: [:edit]
before_action do
push_frontend_feature_flag(:feature_flag_permissions)
......@@ -63,7 +64,6 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
end
def edit
exclude_legacy_flags_check
end
def update
......@@ -108,6 +108,12 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
end
end
def exclude_legacy_flags_check
if feature_flag.legacy_flag?
not_found
end
end
def create_params
params.require(:operations_feature_flag)
.permit(:name, :description, :active, :version,
......@@ -159,12 +165,4 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
render json: { message: messages },
status: status
end
def exclude_legacy_flags_check
if Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml) &&
feature_flag.legacy_flag?
not_found
end
end
end
......@@ -24,11 +24,7 @@ class FeatureFlagsFinder
private
def feature_flags
if exclude_legacy_flags?
project.operations_feature_flags.new_version_only
else
project.operations_feature_flags
end
end
def by_scope(items)
......@@ -41,9 +37,4 @@ class FeatureFlagsFinder
items
end
end
def exclude_legacy_flags?
Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml)
end
end
# 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
# 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
---
name: feature_flag_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18198
rollout_issue_url:
milestone: '12.4'
type: development
group: group::release
default_enabled: false
---
name: remove_legacy_flags
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62484
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332243
milestone: '14.0'
type: development
group: group::release
default_enabled: false
---
name: remove_legacy_flags_override
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62484
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332243
milestone: '14.0'
type: development
group: group::release
default_enabled: false
......@@ -8,293 +8,5 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.5.
WARNING:
This API is deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).
The API for creating, updating, reading and deleting Feature Flag Specs.
Automation engineers benefit from this API by being able to modify Feature Flag Specs without accessing user interface.
To manage the [Feature Flag](../operations/feature_flags.md) resources via public API, please refer to the [Feature Flags API](feature_flags.md) document.
Users with Developer or higher [permissions](../user/permissions.md) can access Feature Flag Specs API.
## List all effective feature flag specs under the specified environment
Get all effective feature flag specs under the specified [environment](../ci/environments/index.md).
For instance, there are two specs, `staging` and `production`, for a feature flag.
When you pass `production` as a parameter to this endpoint, the system returns
the `production` feature flag spec only.
```plaintext
GET /projects/:id/feature_flag_scopes
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `environment` | string | yes | The [environment](../ci/environments/index.md) name |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flag_scopes?environment=production"
```
Example response:
```json
[
{
"id": 88,
"active": true,
"environment_scope": "production",
"strategies": [
{
"name": "userWithId",
"parameters": {
"userIds": "1,2,3"
}
}
],
"created_at": "2019-11-04T08:36:41.327Z",
"updated_at": "2019-11-04T08:36:41.327Z",
"name": "awesome_feature"
},
{
"id": 82,
"active": true,
"environment_scope": "*",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:51.425Z",
"updated_at": "2019-11-04T08:39:45.751Z",
"name": "merge_train"
},
{
"id": 81,
"active": false,
"environment_scope": "production",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.527Z",
"updated_at": "2019-11-04T08:13:10.527Z",
"name": "new_live_trace"
}
]
```
## List all specs of a feature flag
Get all specs of a feature flag.
```plaintext
GET /projects/:id/feature_flags/:name/scopes
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes"
```
Example response:
```json
[
{
"id": 79,
"active": false,
"environment_scope": "*",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.516Z",
"updated_at": "2019-11-04T08:13:10.516Z"
},
{
"id": 80,
"active": true,
"environment_scope": "staging",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.525Z",
"updated_at": "2019-11-04T08:13:10.525Z"
},
{
"id": 81,
"active": false,
"environment_scope": "production",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.527Z",
"updated_at": "2019-11-04T08:13:10.527Z"
}
]
```
## New feature flag spec
Creates a new feature flag spec.
```plaintext
POST /projects/:id/feature_flags/:name/scopes
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
| `active` | boolean | yes | Whether the spec is active. |
| `strategies` | JSON | yes | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes" \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-type: application/json" \
--data @- << EOF
{
"environment_scope": "*",
"active": false,
"strategies": [{ "name": "default", "parameters": {} }]
}
EOF
```
Example response:
```json
{
"id": 81,
"active": false,
"environment_scope": "*",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.527Z",
"updated_at": "2019-11-04T08:13:10.527Z"
}
```
## Single feature flag spec
Gets a single feature flag spec.
```plaintext
GET /projects/:id/feature_flags/:name/scopes/:environment_scope
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The URL-encoded [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/feature_flags/new_live_trace/scopes/production"
```
Example response:
```json
{
"id": 81,
"active": false,
"environment_scope": "production",
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"created_at": "2019-11-04T08:13:10.527Z",
"updated_at": "2019-11-04T08:13:10.527Z"
}
```
## Edit feature flag spec
Updates an existing feature flag spec.
```plaintext
PUT /projects/:id/feature_flags/:name/scopes/:environment_scope
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The URL-encoded [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
| `active` | boolean | yes | Whether the spec is active. |
| `strategies` | JSON | yes | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes/production" \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-type: application/json" \
--data @- << EOF
{
"active": true,
"strategies": [{ "name": "userWithId", "parameters": { "userIds": "1,2,3" } }]
}
EOF
```
Example response:
```json
{
"id": 81,
"active": true,
"environment_scope": "production",
"strategies": [
{
"name": "userWithId",
"parameters": { "userIds": "1,2,3" }
}
],
"created_at": "2019-11-04T08:13:10.527Z",
"updated_at": "2019-11-04T08:13:10.527Z"
}
```
## Delete feature flag spec
Deletes a feature flag spec.
```plaintext
DELETE /projects/:id/feature_flags/:name/scopes/:environment_scope
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The URL-encoded [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" --request DELETE "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes/production"
```
This API was removed in [GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).
Please use [the new API](feature_flags.md) instead.
......@@ -9,315 +9,5 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in GitLab Premium 12.5.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212318) to GitLab Free in 13.5.
WARNING:
This API is deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369). Use [this API](feature_flags.md) instead.
API for accessing resources of [GitLab Feature Flags](../operations/feature_flags.md).
Users with Developer or higher [permissions](../user/permissions.md) can access Feature Flag API.
## Feature Flags pagination
By default, `GET` requests return 20 results at a time because the API results
are [paginated](README.md#pagination).
## List feature flags for a project
Gets all feature flags of the requested project.
```plaintext
GET /projects/:id/feature_flags
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `scope` | string | no | The condition of feature flags, one of: `enabled`, `disabled`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags"
```
Example response:
```json
[
{
"name":"merge_train",
"description":"This feature is about merge train",
"active": true,
"created_at":"2019-11-04T08:13:51.423Z",
"updated_at":"2019-11-04T08:13:51.423Z",
"scopes":[
{
"id":82,
"active":false,
"environment_scope":"*",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:51.425Z",
"updated_at":"2019-11-04T08:13:51.425Z"
},
{
"id":83,
"active":true,
"environment_scope":"review/*",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:51.427Z",
"updated_at":"2019-11-04T08:13:51.427Z"
},
{
"id":84,
"active":false,
"environment_scope":"production",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:51.428Z",
"updated_at":"2019-11-04T08:13:51.428Z"
}
]
},
{
"name":"new_live_trace",
"description":"This is a new live trace feature",
"active": true,
"created_at":"2019-11-04T08:13:10.507Z",
"updated_at":"2019-11-04T08:13:10.507Z",
"scopes":[
{
"id":79,
"active":false,
"environment_scope":"*",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.516Z",
"updated_at":"2019-11-04T08:13:10.516Z"
},
{
"id":80,
"active":true,
"environment_scope":"staging",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.525Z",
"updated_at":"2019-11-04T08:13:10.525Z"
},
{
"id":81,
"active":false,
"environment_scope":"production",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.527Z",
"updated_at":"2019-11-04T08:13:10.527Z"
}
]
}
]
```
## New feature flag
Creates a new feature flag.
```plaintext
POST /projects/:id/feature_flags
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `description` | string | no | The description of the feature flag. |
| `active` | boolean | no | The active state of the flag. Defaults to true. [Supported](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38350) in GitLab 13.3 and later. |
| `scopes` | JSON | no | The feature flag specs of the feature flag. |
| `scopes:environment_scope` | string | no | The environment spec. |
| `scopes:active` | boolean | no | Whether the spec is active. |
| `scopes:strategies` | JSON | no | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags" \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-type: application/json" \
--data @- << EOF
{
"name": "awesome_feature",
"scopes": [{ "environment_scope": "*", "active": false, "strategies": [{ "name": "default", "parameters": {} }] },
{ "environment_scope": "production", "active": true, "strategies": [{ "name": "userWithId", "parameters": { "userIds": "1,2,3" } }] }]
}
EOF
```
Example response:
```json
{
"name":"awesome_feature",
"description":null,
"active": true,
"created_at":"2019-11-04T08:32:27.288Z",
"updated_at":"2019-11-04T08:32:27.288Z",
"scopes":[
{
"id":85,
"active":false,
"environment_scope":"*",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:32:29.324Z",
"updated_at":"2019-11-04T08:32:29.324Z"
},
{
"id":86,
"active":true,
"environment_scope":"production",
"strategies":[
{
"name":"userWithId",
"parameters":{
"userIds":"1,2,3"
}
}
],
"created_at":"2019-11-04T08:32:29.328Z",
"updated_at":"2019-11-04T08:32:29.328Z"
}
]
}
```
## Single feature flag
Gets a single feature flag.
```plaintext
GET /projects/:id/feature_flags/:name
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace"
```
Example response:
```json
{
"name":"new_live_trace",
"description":"This is a new live trace feature",
"active": true,
"created_at":"2019-11-04T08:13:10.507Z",
"updated_at":"2019-11-04T08:13:10.507Z",
"scopes":[
{
"id":79,
"active":false,
"environment_scope":"*",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.516Z",
"updated_at":"2019-11-04T08:13:10.516Z"
},
{
"id":80,
"active":true,
"environment_scope":"staging",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.525Z",
"updated_at":"2019-11-04T08:13:10.525Z"
},
{
"id":81,
"active":false,
"environment_scope":"production",
"strategies":[
{
"name":"default",
"parameters":{
}
}
],
"created_at":"2019-11-04T08:13:10.527Z",
"updated_at":"2019-11-04T08:13:10.527Z"
}
]
}
```
## Delete feature flag
Deletes a feature flag.
```plaintext
DELETE /projects/:id/feature_flags/:name
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" --request DELETE "https://gitlab.example.com/api/v4/projects/1/feature_flags/awesome_feature"
```
This API was removed in [GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).
Please use [the new API](feature_flags.md) instead.
......@@ -168,7 +168,6 @@ module API
mount ::API::ErrorTracking
mount ::API::Events
mount ::API::FeatureFlags
mount ::API::FeatureFlagScopes
mount ::API::FeatureFlagsUserLists
mount ::API::Features
mount ::API::Files
......
# frozen_string_literal: true
module API
class FeatureFlagScopes < ::API::Base
include PaginationParams
ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS = FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS
.merge(environment_scope: API::NO_SLASH_URL_PART_REGEX)
feature_category :feature_flags
before do
authorize_read_feature_flags!
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
resource :feature_flag_scopes do
desc 'Get all effective feature flags under the environment' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::DetailedLegacyScope
end
params do
requires :environment, type: String, desc: 'The environment name'
end
get do
present scopes_for_environment, with: ::API::Entities::FeatureFlag::DetailedLegacyScope
end
end
params do
requires :name, type: String, desc: 'The name of the feature flag'
end
resource 'feature_flags/:name', requirements: FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS do
resource :scopes do
desc 'Get all scopes of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
use :pagination
end
get do
present paginate(feature_flag.scopes), with: ::API::Entities::FeatureFlag::LegacyScope
end
desc 'Create a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
requires :environment_scope, type: String, desc: 'The environment scope of the scope'
requires :active, type: Boolean, desc: 'Whether the scope is active'
requires :strategies, type: JSON, desc: 'The strategies of the scope'
end
post do
authorize_update_feature_flag!
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, scopes_attributes: [declared_params])
.execute(feature_flag)
if result[:status] == :success
present scope, with: ::API::Entities::FeatureFlag::LegacyScope
else
render_api_error!(result[:message], result[:http_status])
end
end
params do
requires :environment_scope, type: String, desc: 'URL-encoded environment scope'
end
resource ':environment_scope', requirements: ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS do
desc 'Get a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
get do
present scope, with: ::API::Entities::FeatureFlag::LegacyScope
end
desc 'Update a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
optional :active, type: Boolean, desc: 'Whether the scope is active'
optional :strategies, type: JSON, desc: 'The strategies of the scope'
end
put do
authorize_update_feature_flag!
scope_attributes = declared_params.merge(id: scope.id)
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, scopes_attributes: [scope_attributes])
.execute(feature_flag)
if result[:status] == :success
updated_scope = result[:feature_flag].scopes
.find { |scope| scope.environment_scope == params[:environment_scope] }
present updated_scope, with: ::API::Entities::FeatureFlag::LegacyScope
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Delete a scope from a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
delete do
authorize_update_feature_flag!
param = { scopes_attributes: [{ id: scope.id, _destroy: true }] }
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, param)
.execute(feature_flag)
if result[:status] == :success
status :no_content
else
render_api_error!(result[:message], result[:http_status])
end
end
end
end
end
end
helpers do
def authorize_read_feature_flags!
authorize! :read_feature_flag, user_project
end
def authorize_update_feature_flag!
authorize! :update_feature_flag, feature_flag
end
def feature_flag
@feature_flag ||= user_project.operations_feature_flags
.find_by_name!(params[:name])
end
def scope
@scope ||= feature_flag.scopes
.find_by_environment_scope!(CGI.unescape(params[:environment_scope]))
end
def scopes_for_environment
Operations::FeatureFlagScope
.for_unleash_client(user_project, params[:environment])
end
end
end
end
......@@ -95,54 +95,6 @@ module API
present_entity(feature_flag)
end
desc 'Enable a strategy for a feature flag on an environment' do
detail 'This feature was introduced in GitLab 12.5'
success ::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
not_found! unless Feature.enabled?(:feature_flag_api, user_project)
exclude_legacy_flags_check!
render_api_error!('Version 2 flags not supported', :unprocessable_entity) if new_version_flag_present?
result = ::FeatureFlags::EnableService
.new(user_project, current_user, params).execute
if result[:status] == :success
status :ok
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Disable a strategy for a feature flag on an environment' do
detail 'This feature is going to be introduced in GitLab 12.5 if `feature_flag_api` feature flag is removed'
success ::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
not_found! unless Feature.enabled?(:feature_flag_api, user_project)
exclude_legacy_flags_check!
render_api_error!('Version 2 flags not supported', :unprocessable_entity) if feature_flag.new_version_flag?
result = ::FeatureFlags::DisableService
.new(user_project, current_user, params).execute
if result[:status] == :success
status :ok
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Update a feature flag' do
detail 'This feature was introduced in GitLab 13.2'
success ::API::Entities::FeatureFlag
......@@ -255,9 +207,7 @@ module API
end
def exclude_legacy_flags_check!
if Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml) &&
feature_flag.legacy_flag?
if feature_flag.legacy_flag?
not_found!
end
end
......
......@@ -69,21 +69,7 @@ module API
def feature_flags
return [] unless unleash_app_name.present?
legacy_flags =
if exclude_legacy_flags?
[]
else
Operations::FeatureFlagScope.for_unleash_client(project, unleash_app_name)
end
new_version_flags = Operations::FeatureFlag.for_unleash_client(project, unleash_app_name)
legacy_flags + new_version_flags
end
def exclude_legacy_flags?
Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml)
Operations::FeatureFlag.for_unleash_client(project, unleash_app_name)
end
end
end
......
......@@ -154,60 +154,6 @@ RSpec.describe Projects::FeatureFlagsController do
end
end
context 'when feature flags have additional scopes' do
let!(:feature_flag_active_scope) do
create(:operations_feature_flag_scope,
feature_flag: feature_flag_active,
environment_scope: 'production',
active: false)
end
let!(:feature_flag_inactive_scope) do
create(:operations_feature_flag_scope,
feature_flag: feature_flag_inactive,
environment_scope: 'staging',
active: false)
end
it 'returns a correct summary' do
subject
expect(json_response['count']['all']).to eq(2)
expect(json_response['count']['enabled']).to eq(1)
expect(json_response['count']['disabled']).to eq(1)
end
it 'recognizes feature flag 1 as active' do
subject
expect(json_response['feature_flags'].first['active']).to be_truthy
end
it 'recognizes feature flag 2 as inactive' do
subject
expect(json_response['feature_flags'].second['active']).to be_falsy
end
it 'has ordered scopes' do
subject
expect(json_response['feature_flags'][0]['scopes'][0]['id'])
.to be < json_response['feature_flags'][0]['scopes'][1]['id']
expect(json_response['feature_flags'][1]['scopes'][0]['id'])
.to be < json_response['feature_flags'][1]['scopes'][1]['id']
end
it 'does not have N+1 problem' do
recorded = ActiveRecord::QueryRecorder.new { subject }
related_count = recorded.log
.count { |query| query.include?('operations_feature_flag') }
expect(related_count).to be_within(5).of(2)
end
end
context 'with version 1 and 2 feature flags' do
let!(:new_version_feature_flag) do
create(:operations_feature_flag, :new_version_flag, project: project, name: 'feature_flag_c')
......@@ -235,7 +181,7 @@ RSpec.describe Projects::FeatureFlagsController do
subject { get(:show, params: params, format: :json) }
let!(:feature_flag) do
create(:operations_feature_flag, project: project)
create(:operations_feature_flag, :legacy_flag, project: project)
end
let(:params) do
......@@ -375,7 +321,7 @@ RSpec.describe Projects::FeatureFlagsController do
subject { get(:edit, params: params) }
context 'with legacy flags' do
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project) }
let(:params) do
{
......@@ -385,29 +331,13 @@ RSpec.describe Projects::FeatureFlagsController do
}
end
context 'removed' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
is_expected.to have_gitlab_http_status(:not_found)
end
end
context 'removed' do
before do
stub_feature_flags(remove_legacy_flags: false)
end
it 'returns ok' do
is_expected.to have_gitlab_http_status(:ok)
end
end
end
context 'with new version flags' do
let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project) }
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let(:params) do
{
......@@ -814,7 +744,7 @@ RSpec.describe Projects::FeatureFlagsController do
describe 'DELETE destroy.json' do
subject { delete(:destroy, params: params, format: :json) }
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project) }
let(:params) do
{
......
......@@ -2,7 +2,7 @@
FactoryBot.define do
factory :operations_feature_flag_scope, class: 'Operations::FeatureFlagScope' do
association :feature_flag, factory: :operations_feature_flag
association :feature_flag, factory: [:operations_feature_flag, :legacy_flag]
active { true }
strategies { [{ name: "default", parameters: {} }] }
sequence(:environment_scope) { |n| "review/patch-#{n}" }
......
......@@ -5,6 +5,7 @@ FactoryBot.define do
sequence(:name) { |n| "feature_flag_#{n}" }
project
active { true }
version { :new_version_flag }
trait :legacy_flag do
version { Operations::FeatureFlag.versions['legacy_flag'] }
......
......@@ -18,65 +18,21 @@ RSpec.describe 'User sees feature flag list', :js do
context 'with legacy feature flags' do
before do
create_flag(project, 'ci_live_trace', false).tap do |feature_flag|
create_flag(project, 'ci_live_trace', false, version: :legacy_flag).tap do |feature_flag|
create_scope(feature_flag, 'review/*', true)
end
create_flag(project, 'drop_legacy_artifacts', false)
create_flag(project, 'mr_train', true).tap do |feature_flag|
create_flag(project, 'drop_legacy_artifacts', false, version: :legacy_flag)
create_flag(project, 'mr_train', true, version: :legacy_flag).tap do |feature_flag|
create_scope(feature_flag, 'production', false)
end
end
it 'user sees the first flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect(page.find('.js-feature-flag-id')).to have_content('^1')
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*')
end
end
end
it 'user sees the second flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(2) do
expect(page.find('.js-feature-flag-id')).to have_content('^2')
expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts')
expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
end
end
end
it 'user sees the third flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(3) do
expect(page.find('.js-feature-flag-id')).to have_content('^3')
expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production')
end
end
end
it 'user sees the status toggle disabled' do
it 'shows empty page' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_disabled
end
expect(page).to have_text 'Get started with feature flags'
expect(page).to have_selector('.btn-confirm', text: 'New feature flag')
expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure')
end
end
......
......@@ -73,16 +73,16 @@ RSpec.describe 'User updates feature flag', :js do
context 'with a legacy feature flag' do
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', true,
description: 'For live trace feature')
description: 'For live trace feature',
version: :legacy_flag)
end
let!(:scope) { create_scope(feature_flag, 'review/*', true) }
it 'the user cannot edit the flag' do
it 'shows not found error' do
visit(edit_project_feature_flag_path(project, feature_flag))
expect(page).to have_text 'This feature flag is read-only, and it will be removed in 14.0.'
expect(page).to have_css('button.js-ff-submit.disabled')
expect(page).to have_text 'Page Not Found'
end
end
end
......@@ -24,10 +24,6 @@ RSpec.describe FeatureFlagsFinder do
let!(:feature_flag_2) { create(:operations_feature_flag, name: 'flag-b', project: project) }
let(:args) { {} }
before do
stub_feature_flags(remove_legacy_flags: false)
end
it 'returns feature flags ordered by name' do
is_expected.to eq([feature_flag_1, feature_flag_2])
end
......@@ -77,21 +73,11 @@ RSpec.describe FeatureFlagsFinder do
end
end
context 'when new version flags are enabled' do
let!(:feature_flag_3) { create(:operations_feature_flag, :new_version_flag, name: 'flag-c', project: project) }
it 'returns new and legacy flags' do
is_expected.to eq([feature_flag_1, feature_flag_2, feature_flag_3])
end
context 'when legacy flags are disabled' do
before do
stub_feature_flags(remove_legacy_flags_override: false, remove_legacy_flags: true)
end
context 'with a legacy flag' do
let!(:feature_flag_3) { create(:operations_feature_flag, :legacy_flag, name: 'flag-c', project: project) }
it 'returns only new flags' do
is_expected.to eq([feature_flag_3])
end
it 'returns new flags' do
is_expected.to eq([feature_flag_1, feature_flag_2])
end
end
end
......
......@@ -29,7 +29,7 @@ RSpec.describe Operations::FeatureFlagScope do
end
context 'when environment scope of a default scope is updated' do
let!(:feature_flag) { create(:operations_feature_flag) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag) }
let!(:scope_default) { feature_flag.default_scope }
it 'keeps default scope intact' do
......@@ -41,7 +41,7 @@ RSpec.describe Operations::FeatureFlagScope do
end
context 'when a default scope is destroyed' do
let!(:feature_flag) { create(:operations_feature_flag) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag) }
let!(:scope_default) { feature_flag.default_scope }
it 'prevents from destroying the default scope' do
......
......@@ -181,7 +181,7 @@ RSpec.describe Operations::FeatureFlag do
end
context 'when the feature flag is active and all scopes are inactive' do
let!(:feature_flag) { create(:operations_feature_flag, active: true) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, active: true) }
it 'returns the flag' do
feature_flag.default_scope.update!(active: false)
......@@ -199,7 +199,7 @@ RSpec.describe Operations::FeatureFlag do
end
context 'when the feature flag is inactive and all scopes are active' do
let!(:feature_flag) { create(:operations_feature_flag, active: false) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, active: false) }
it 'does not return the flag' do
feature_flag.default_scope.update!(active: true)
......@@ -221,7 +221,7 @@ RSpec.describe Operations::FeatureFlag do
end
context 'when the feature flag is active and all scopes are inactive' do
let!(:feature_flag) { create(:operations_feature_flag, active: true) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, active: true) }
it 'does not return the flag' do
feature_flag.default_scope.update!(active: false)
......@@ -239,7 +239,7 @@ RSpec.describe Operations::FeatureFlag do
end
context 'when the feature flag is inactive and all scopes are active' do
let!(:feature_flag) { create(:operations_feature_flag, active: false) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, active: false) }
it 'returns the flag' do
feature_flag.default_scope.update!(active: true)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::FeatureFlagScopes do
include FeatureFlagHelpers
let(:project) { create(:project, :repository) }
let(:developer) { create(:user) }
let(:reporter) { create(:user) }
let(:user) { developer }
before do
project.add_developer(developer)
project.add_reporter(reporter)
end
shared_examples_for 'check user permission' do
context 'when user is reporter' do
let(:user) { reporter }
it 'forbids the request' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
shared_examples_for 'not found' do
it 'returns Not Found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'GET /projects/:id/feature_flag_scopes' do
subject do
get api("/projects/#{project.id}/feature_flag_scopes", user),
params: params
end
let(:feature_flag_1) { create_flag(project, 'flag_1', true) }
let(:feature_flag_2) { create_flag(project, 'flag_2', true) }
before do
create_scope(feature_flag_1, 'staging', false)
create_scope(feature_flag_1, 'production', true)
create_scope(feature_flag_2, 'review/*', false)
end
context 'when environment is production' do
let(:params) { { environment: 'production' } }
it_behaves_like 'check user permission'
it 'returns all effective feature flags under the environment' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_detailed_scopes')
expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => true })
expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => true })
end
end
context 'when environment is staging' do
let(:params) { { environment: 'staging' } }
it 'returns all effective feature flags under the environment' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => false })
expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => true })
end
end
context 'when environment is review/feature X' do
let(:params) { { environment: 'review/feature X' } }
it 'returns all effective feature flags under the environment' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => true })
expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => false })
end
end
end
describe 'GET /projects/:id/feature_flags/:name/scopes' do
subject do
get api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes", user)
end
context 'when there are two scopes' do
let(:feature_flag) { create_flag(project, 'test') }
let!(:additional_scope) { create_scope(feature_flag, 'production', false) }
it_behaves_like 'check user permission'
it 'returns scopes of the feature flag' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scopes')
expect(json_response.count).to eq(2)
expect(json_response.first['environment_scope']).to eq(feature_flag.scopes[0].environment_scope)
expect(json_response.second['environment_scope']).to eq(feature_flag.scopes[1].environment_scope)
end
end
context 'when there are no feature flags' do
let(:feature_flag) { double(:feature_flag, name: 'test') }
it_behaves_like 'not found'
end
end
describe 'POST /projects/:id/feature_flags/:name/scopes' do
subject do
post api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes", user),
params: params
end
let(:params) do
{
environment_scope: 'staging',
active: true,
strategies: [{ name: 'userWithId', parameters: { 'userIds': 'a,b,c' } }].to_json
}
end
context 'when there is a corresponding feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
it_behaves_like 'check user permission'
it 'creates a new scope' do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope')
expect(json_response['environment_scope']).to eq(params[:environment_scope])
expect(json_response['active']).to eq(params[:active])
expect(json_response['strategies']).to eq(Gitlab::Json.parse(params[:strategies]))
end
context 'when the scope already exists' do
before do
create_scope(feature_flag, params[:environment_scope])
end
it 'returns error' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to include('Scopes environment scope (staging) has already been taken')
end
end
end
context 'when feature flag is not found' do
let(:feature_flag) { double(:feature_flag, name: 'test') }
it_behaves_like 'not found'
end
end
describe 'GET /projects/:id/feature_flags/:name/scopes/:environment_scope' do
subject do
get api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}",
user)
end
let(:environment_scope) { scope.environment_scope }
shared_examples_for 'successful response' do
it 'returns a scope' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope')
expect(json_response['id']).to eq(scope.id)
expect(json_response['active']).to eq(scope.active)
expect(json_response['environment_scope']).to eq(scope.environment_scope)
end
end
context 'when there is a feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let(:scope) { feature_flag.default_scope }
it_behaves_like 'check user permission'
it_behaves_like 'successful response'
context 'when environment scope includes slash' do
let!(:scope) { create_scope(feature_flag, 'review/*', false) }
it_behaves_like 'not found'
context 'when URL-encoding the environment scope parameter' do
let(:environment_scope) { CGI.escape(scope.environment_scope) }
it_behaves_like 'successful response'
end
end
end
context 'when there are no feature flags' do
let(:feature_flag) { double(:feature_flag, name: 'test') }
let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') }
it_behaves_like 'not found'
end
end
describe 'PUT /projects/:id/feature_flags/:name/scopes/:environment_scope' do
subject do
put api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}",
user), params: params
end
let(:environment_scope) { scope.environment_scope }
let(:params) do
{
active: true,
strategies: [{ name: 'userWithId', parameters: { 'userIds': 'a,b,c' } }].to_json
}
end
context 'when there is a corresponding feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let(:scope) { create_scope(feature_flag, 'staging', false, [{ name: "default", parameters: {} }]) }
it_behaves_like 'check user permission'
it 'returns the updated scope' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope')
expect(json_response['id']).to eq(scope.id)
expect(json_response['active']).to eq(params[:active])
expect(json_response['strategies']).to eq(Gitlab::Json.parse(params[:strategies]))
end
context 'when there are no corresponding feature flag scopes' do
let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') }
it_behaves_like 'not found'
end
end
context 'when there are no corresponding feature flags' do
let(:feature_flag) { double(:feature_flag, name: 'test') }
let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') }
it_behaves_like 'not found'
end
end
describe 'DELETE /projects/:id/feature_flags/:name/scopes/:environment_scope' do
subject do
delete api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}",
user)
end
let(:environment_scope) { scope.environment_scope }
shared_examples_for 'successful response' do
it 'destroys the scope' do
expect { subject }
.to change { Operations::FeatureFlagScope.exists?(environment_scope: scope.environment_scope) }
.from(true).to(false)
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when there is a feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
context 'when there is a targeted scope' do
let!(:scope) { create_scope(feature_flag, 'production', false) }
it_behaves_like 'check user permission'
it_behaves_like 'successful response'
context 'when environment scope includes slash' do
let!(:scope) { create_scope(feature_flag, 'review/*', false) }
it_behaves_like 'not found'
context 'when URL-encoding the environment scope parameter' do
let(:environment_scope) { CGI.escape(scope.environment_scope) }
it_behaves_like 'successful response'
end
end
end
context 'when there are no targeted scopes' do
let!(:scope) { double(:feature_flag_scope, environment_scope: 'production') }
it_behaves_like 'not found'
end
end
context 'when there are no feature flags' do
let(:feature_flag) { double(:feature_flag, name: 'test') }
let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') }
it_behaves_like 'not found'
end
end
end
......@@ -62,7 +62,7 @@ RSpec.describe API::FeatureFlags do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flags')
expect(json_response.map { |f| f['version'] }).to eq(%w[legacy_flag legacy_flag])
expect(json_response.map { |f| f['version'] }).to eq(%w[new_version_flag new_version_flag])
end
it 'does not have N+1 problem' do
......@@ -145,19 +145,7 @@ RSpec.describe API::FeatureFlags do
expect(response).to match_response_schema('public_api/v4/feature_flag')
expect(json_response['name']).to eq(feature_flag.name)
expect(json_response['description']).to eq(feature_flag.description)
expect(json_response['version']).to eq('legacy_flag')
end
context 'without legacy flags' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
expect(json_response['version']).to eq('new_version_flag')
end
it_behaves_like 'check user permission'
......@@ -465,246 +453,6 @@ RSpec.describe API::FeatureFlags do
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')
expect(feature_flag.name).to eq(params[:name])
expect(scope.strategies).to eq([Gitlab::Json.parse(params[:strategy])])
expect(feature_flag.version).to eq('legacy_flag')
end
it 'returns the flag version and strategies in the json response' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag')
expect(json_response.slice('version', 'strategies')).to eq({
'version' => 'legacy_flag',
'strategies' => []
})
end
it_behaves_like 'check user permission'
context 'without legacy flags' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
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([Gitlab::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, Gitlab::Json.parse(params[:strategy])])
end
context 'when the specified strategy exists already' do
let(:defined_strategy) { Gitlab::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.count { |strategy| strategy['name'] == 'userWithId' }
expect(response).to have_gitlab_http_status(:ok)
expect(strategy_count).to eq(1)
end
end
end
context 'without legacy flags' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with a version 2 flag' do
let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project, name: params[:name]) }
it 'does not change the flag and returns an unprocessable_entity response' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response).to eq({ 'message' => 'Version 2 flags not supported' })
feature_flag.reload
expect(feature_flag.scopes).to eq([])
expect(feature_flag.strategies).to eq([])
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')
expect(scope.strategies)
.to eq([{ name: 'userWithId', parameters: { userIds: 'Project:2' } }.deep_stringify_keys])
end
it 'returns the flag version and strategies in the json response' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag')
expect(json_response.slice('version', 'strategies')).to eq({
'version' => 'legacy_flag',
'strategies' => []
})
end
context 'without legacy flags' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
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
context 'with a version 2 feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project, name: params[:name]) }
it 'does not change the flag and returns an unprocessable_entity response' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response).to eq({ 'message' => 'Version 2 flags not supported' })
feature_flag.reload
expect(feature_flag.scopes).to eq([])
expect(feature_flag.strategies).to eq([])
end
end
end
describe 'PUT /projects/:id/feature_flags/:name' do
context 'with a legacy feature flag' do
let!(:feature_flag) do
......@@ -712,13 +460,13 @@ RSpec.describe API::FeatureFlags do
name: 'feature1', description: 'old description')
end
it 'returns a 422' do
it 'returns a 404' do
params = { description: 'new description' }
put api("/projects/#{project.id}/feature_flags/feature1", user), params: params
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response).to eq({ 'message' => 'PUT operations are not supported for legacy feature flags' })
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response).to eq({ 'message' => '404 Not Found' })
expect(feature_flag.reload.description).to eq('old description')
end
end
......@@ -1024,20 +772,6 @@ RSpec.describe API::FeatureFlags do
expect(feature_flag.reload.strategies.first.scopes.count).to eq(0)
end
end
context 'without legacy flags' do
before do
stub_feature_flags(remove_legacy_flags: true, remove_legacy_flags_override: false)
end
it 'returns not found' do
params = { description: 'new description' }
put api("/projects/#{project.id}/feature_flags/other_flag_name", user), params: params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'DELETE /projects/:id/feature_flags/:name' do
......@@ -1046,7 +780,7 @@ RSpec.describe API::FeatureFlags do
params: params
end
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project) }
let(:params) { {} }
it 'destroys the feature flag' do
......
......@@ -176,34 +176,9 @@ RSpec.describe API::Unleash do
it_behaves_like 'authenticated request'
context 'with version 1 (legacy) feature flags' do
let(:feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true, version: 1) }
let(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project, name: 'feature1', active: true, version: 1) }
it_behaves_like 'support multiple environments'
context 'with a list of feature flags' do
let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } }
let!(:enabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true, version: 1) }
let!(:disabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature2', active: false, version: 1) }
it 'responds with a list of features' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['version']).to eq(1)
expect(json_response['features']).not_to be_empty
expect(json_response['features'].map { |f| f['name'] }.sort).to eq(%w[feature1 feature2])
expect(json_response['features'].sort_by {|f| f['name'] }.map { |f| f['enabled'] }).to eq([true, false])
end
it 'matches json schema' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('unleash/unleash')
end
end
it 'returns a feature flag strategy' do
it 'does not return a legacy feature flag' do
create(:operations_feature_flag_scope,
feature_flag: feature_flag,
environment_scope: 'sandbox',
......@@ -215,81 +190,7 @@ RSpec.describe API::Unleash do
get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(true)
strategies = json_response['features'].first['strategies']
expect(strategies).to eq([{
"name" => "gradualRolloutUserId",
"parameters" => {
"percentage" => "50",
"groupId" => "default"
}
}])
end
it 'returns a default strategy for a scope' do
create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'sandbox', active: true)
headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "sandbox" }
get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(true)
strategies = json_response['features'].first['strategies']
expect(strategies).to eq([{ "name" => "default", "parameters" => {} }])
end
it 'returns multiple strategies for a feature flag' do
create(:operations_feature_flag_scope,
feature_flag: feature_flag,
environment_scope: 'staging',
active: true,
strategies: [{ name: "userWithId", parameters: { userIds: "max,fred" } },
{ name: "gradualRolloutUserId",
parameters: { groupId: "default", percentage: "50" } }])
headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "staging" }
get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(true)
strategies = json_response['features'].first['strategies'].sort_by { |s| s['name'] }
expect(strategies).to eq([{
"name" => "gradualRolloutUserId",
"parameters" => {
"percentage" => "50",
"groupId" => "default"
}
}, {
"name" => "userWithId",
"parameters" => {
"userIds" => "max,fred"
}
}])
end
it 'returns a disabled feature when the flag is disabled' do
flag = create(:operations_feature_flag, project: project, name: 'test_feature', active: false, version: 1)
create(:operations_feature_flag_scope, feature_flag: flag, environment_scope: 'production', active: true)
headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" }
get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(false)
end
context "with an inactive scope" do
let!(:scope) { create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'production', active: false, strategies: [{ name: "default", parameters: {} }]) }
let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } }
it 'returns a disabled feature' do
get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok)
feature_json = json_response['features'].first
expect(feature_json['enabled']).to eq(false)
expect(feature_json['strategies']).to eq([{ 'name' => 'default', 'parameters' => {} }])
end
expect(json_response['features']).to be_empty
end
end
......@@ -534,89 +435,6 @@ RSpec.describe API::Unleash do
}])
end
end
context 'when mixing version 1 and version 2 feature flags' do
it 'returns both types of flags when both match' do
feature_flag_a = create(:operations_feature_flag, project: project,
name: 'feature_a', active: true, version: 2)
strategy = create(:operations_strategy, feature_flag: feature_flag_a,
name: 'userWithId', parameters: { userIds: 'user8' })
create(:operations_scope, strategy: strategy, environment_scope: 'staging')
feature_flag_b = create(:operations_feature_flag, project: project,
name: 'feature_b', active: true, version: 1)
create(:operations_feature_flag_scope, feature_flag: feature_flag_b,
active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging')
get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].sort_by {|f| f['name']}).to eq([{
'name' => 'feature_a',
'enabled' => true,
'strategies' => [{
'name' => 'userWithId',
'parameters' => { 'userIds' => 'user8' }
}]
}, {
'name' => 'feature_b',
'enabled' => true,
'strategies' => [{
'name' => 'default',
'parameters' => {}
}]
}])
end
it 'returns legacy flags when only legacy flags match' do
feature_flag_a = create(:operations_feature_flag, project: project,
name: 'feature_a', active: true, version: 2)
strategy = create(:operations_strategy, feature_flag: feature_flag_a,
name: 'userWithId', parameters: { userIds: 'user8' })
create(:operations_scope, strategy: strategy, environment_scope: 'production')
feature_flag_b = create(:operations_feature_flag, project: project,
name: 'feature_b', active: true, version: 1)
create(:operations_feature_flag_scope, feature_flag: feature_flag_b,
active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging')
get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features']).to eq([{
'name' => 'feature_b',
'enabled' => true,
'strategies' => [{
'name' => 'default',
'parameters' => {}
}]
}])
end
it 'returns new flags when legacy flags are disabled' do
stub_feature_flags(remove_legacy_flags_override: false, remove_legacy_flags: true)
feature_flag_a = create(:operations_feature_flag, :new_version_flag, project: project,
name: 'feature_a', active: true)
strategy = create(:operations_strategy, feature_flag: feature_flag_a,
name: 'userWithId', parameters: { userIds: 'user8' })
create(:operations_scope, strategy: strategy, environment_scope: 'staging')
feature_flag_b = create(:operations_feature_flag, :legacy_flag, project: project,
name: 'feature_b', active: true)
create(:operations_feature_flag_scope, feature_flag: feature_flag_b,
active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging')
get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].sort_by {|f| f['name']}).to eq([{
'name' => 'feature_a',
'enabled' => true,
'strategies' => [{
'name' => 'userWithId',
'parameters' => { 'userIds' => 'user8' }
}]
}])
end
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe FeatureFlags::DisableService do
include FeatureFlagHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:params) { {} }
let(:service) { described_class.new(project, user, params) }
before_all do
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
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe FeatureFlags::EnableService do
include FeatureFlagHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:params) { {} }
let(:service) { described_class.new(project, user, params) }
before_all do
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
......@@ -121,150 +121,5 @@ RSpec.describe FeatureFlags::UpdateService do
subject
end
end
context 'when scope active state is changed' do
let(:params) do
{
scopes_attributes: [{ id: feature_flag.scopes.first.id, active: false }]
}
end
it 'creates audit event about changing active state' do
expect { subject }.to change { AuditEvent.count }.by(1)
expect(audit_event_message).to(
include("Updated rule <strong>*</strong> active state "\
"from <strong>true</strong> to <strong>false</strong>.")
)
end
end
context 'when scope is renamed' do
let(:changed_scope) { feature_flag.scopes.create!(environment_scope: 'review', active: true) }
let(:params) do
{
scopes_attributes: [{ id: changed_scope.id, environment_scope: 'staging' }]
}
end
it 'creates audit event with changed name' do
expect { subject }.to change { AuditEvent.count }.by(1)
expect(audit_event_message).to(
include("Updated rule <strong>staging</strong> environment scope "\
"from <strong>review</strong> to <strong>staging</strong>.")
)
end
context 'when scope can not be updated' do
let(:params) do
{
scopes_attributes: [{ id: changed_scope.id, environment_scope: '' }]
}
end
it 'returns error status' do
expect(subject[:status]).to eq(:error)
end
it 'returns error messages' do
expect(subject[:message]).to include("Scopes environment scope can't be blank")
end
it 'does not create audit event' do
expect { subject }.not_to change { AuditEvent.count }
end
end
end
context 'when scope is deleted' do
let(:deleted_scope) { feature_flag.scopes.create!(environment_scope: 'review', active: true) }
let(:params) do
{
scopes_attributes: [{ id: deleted_scope.id, '_destroy': true }]
}
end
it 'creates audit event with deleted scope' do
expect { subject }.to change { AuditEvent.count }.by(1)
expect(audit_event_message).to include("Deleted rule <strong>review</strong>.")
end
context 'when scope can not be deleted' do
before do
allow(deleted_scope).to receive(:destroy).and_return(false)
end
it 'does not create audit event' do
expect do
subject
end.to not_change { AuditEvent.count }.and raise_error(ActiveRecord::RecordNotDestroyed)
end
end
end
context 'when new scope is being added' do
let(:new_environment_scope) { 'review' }
let(:params) do
{
scopes_attributes: [{ environment_scope: new_environment_scope, active: true }]
}
end
it 'creates audit event with new scope' do
expected = 'Created rule <strong>review</strong> and set it as <strong>active</strong> '\
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>.'
subject
expect(audit_event_message).to include(expected)
end
context 'when scope can not be created' do
let(:new_environment_scope) { '' }
it 'returns error status' do
expect(subject[:status]).to eq(:error)
end
it 'returns error messages' do
expect(subject[:message]).to include("Scopes environment scope can't be blank")
end
it 'does not create audit event' do
expect { subject }.not_to change { AuditEvent.count }
end
end
end
context 'when the strategy is changed' do
let(:scope) do
create(:operations_feature_flag_scope,
feature_flag: feature_flag,
environment_scope: 'sandbox',
strategies: [{ name: "default", parameters: {} }])
end
let(:params) do
{
scopes_attributes: [{
id: scope.id,
environment_scope: 'sandbox',
strategies: [{
name: 'gradualRolloutUserId',
parameters: {
groupId: 'mygroup',
percentage: "40"
}
}]
}]
}
end
it 'creates an audit event' do
expected = %r{Updated rule <strong>sandbox</strong> strategies from <strong>.*</strong> to <strong>.*</strong>.}
expect { subject }.to change { AuditEvent.count }.by(1)
expect(audit_event_message).to match(expected)
end
end
end
end
# frozen_string_literal: true
module FeatureFlagHelpers
def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['legacy_flag'])
def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['new_version_flag'])
create(:operations_feature_flag, name: name, active: active, version: version,
description: description, project: project)
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment