Commit f04c0ba7 authored by Jason Goodman's avatar Jason Goodman Committed by Jan Provaznik

Enable list of target users across all feature flag environment scopes

Return enabled userWithId strategy to unleash client
parent d13ca399
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Operations module Operations
class FeatureFlagScope < ApplicationRecord class FeatureFlagScope < ApplicationRecord
prepend HasEnvironmentScope prepend HasEnvironmentScope
include Gitlab::Utils::StrongMemoize
self.table_name = 'operations_feature_flag_scopes' self.table_name = 'operations_feature_flag_scopes'
...@@ -25,6 +26,12 @@ module Operations ...@@ -25,6 +26,12 @@ module Operations
scope :enabled, -> { where(active: true) } scope :enabled, -> { where(active: true) }
scope :disabled, -> { where(active: false) } scope :disabled, -> { where(active: false) }
def userwithid_strategy
strong_memoize(:userwithid_strategy) do
strategies.select { |s| s['name'] == FeatureFlagStrategiesValidator::STRATEGY_USERWITHID }
end
end
def self.with_name_and_description def self.with_name_and_description
joins(:feature_flag) joins(:feature_flag)
.select(FeatureFlag.arel_table[:name], FeatureFlag.arel_table[:description]) .select(FeatureFlag.arel_table[:name], FeatureFlag.arel_table[:description])
......
---
title: Enable target users across all feature flag environment scopes
merge_request: 15500
author:
type: fixed
...@@ -705,8 +705,22 @@ module EE ...@@ -705,8 +705,22 @@ module EE
class UnleashFeature < Grape::Entity class UnleashFeature < Grape::Entity
expose :name expose :name
expose :description, unless: ->(feature) { feature.description.nil? } expose :description, unless: ->(feature) { feature.description.nil? }
expose :active, as: :enabled # The UI has a single field for user ids for whom the feature flag should be enabled across all scopes.
expose :strategies # Each scope is given a userWithId strategy with the list of user ids.
# However, the user can also directly toggle the active field of a scope.
# So if the user has entered user ids, and disabled the scope, we need to send an enabled scope with
# the list of user ids.
# See: https://gitlab.com/gitlab-org/gitlab-ee/issues/14011
expose :active, as: :enabled do |feature|
feature.active || feature.userwithid_strategy.present?
end
expose :strategies do |feature|
if !feature.active && feature.userwithid_strategy.present?
feature.userwithid_strategy
else
feature.strategies
end
end
end end
class GitlabSubscription < Grape::Entity class GitlabSubscription < Grape::Entity
......
...@@ -150,6 +150,8 @@ describe API::Unleash do ...@@ -150,6 +150,8 @@ describe API::Unleash do
%w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint| %w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint|
describe "GET #{features_endpoint}" do describe "GET #{features_endpoint}" do
let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) } let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) }
let(:client) { create(:operations_feature_flags_client, project: project) }
let(:feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true) }
subject { get api(features_url), params: params, headers: headers } subject { get api(features_url), params: params, headers: headers }
...@@ -157,7 +159,6 @@ describe API::Unleash do ...@@ -157,7 +159,6 @@ describe API::Unleash do
it_behaves_like 'support multiple environments' it_behaves_like 'support multiple environments'
context 'with a list of feature flag' do context 'with a list of feature flag' do
let(:client) { create(:operations_feature_flags_client, project: project) }
let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" }} let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" }}
let!(:enable_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true) } let!(:enable_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true) }
let!(:disabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature2', active: false) } let!(:disabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature2', active: false) }
...@@ -169,6 +170,7 @@ describe API::Unleash do ...@@ -169,6 +170,7 @@ describe API::Unleash do
expect(json_response['version']).to eq(1) expect(json_response['version']).to eq(1)
expect(json_response['features']).not_to be_empty 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'].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 end
it 'matches json schema' do it 'matches json schema' do
...@@ -180,8 +182,6 @@ describe API::Unleash do ...@@ -180,8 +182,6 @@ describe API::Unleash do
end end
it 'returns a feature flag strategy' do it 'returns a feature flag strategy' do
client = create(:operations_feature_flags_client, project: project)
feature_flag = create(:operations_feature_flag, project: project, name: 'feature1', active: true)
create(:operations_feature_flag_scope, create(:operations_feature_flag_scope,
feature_flag: feature_flag, feature_flag: feature_flag,
environment_scope: 'sandbox', environment_scope: 'sandbox',
...@@ -193,6 +193,7 @@ describe API::Unleash do ...@@ -193,6 +193,7 @@ describe API::Unleash do
get api(features_url), headers: headers get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(true)
strategies = json_response['features'].first['strategies'] strategies = json_response['features'].first['strategies']
expect(strategies).to eq([{ expect(strategies).to eq([{
"name" => "gradualRolloutUserId", "name" => "gradualRolloutUserId",
...@@ -204,21 +205,18 @@ describe API::Unleash do ...@@ -204,21 +205,18 @@ describe API::Unleash do
end end
it 'returns a default strategy for a scope' do it 'returns a default strategy for a scope' do
client = create(:operations_feature_flags_client, project: project)
feature_flag = create(:operations_feature_flag, project: project, name: 'feature1', active: true)
create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'sandbox', active: true) create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'sandbox', active: true)
headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "sandbox" } headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "sandbox" }
get api(features_url), headers: headers get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].first['enabled']).to eq(true)
strategies = json_response['features'].first['strategies'] strategies = json_response['features'].first['strategies']
expect(strategies).to eq([{ "name" => "default", "parameters" => {} }]) expect(strategies).to eq([{ "name" => "default", "parameters" => {} }])
end end
it 'returns multiple strategies for a feature flag' do it 'returns multiple strategies for a feature flag' do
client = create(:operations_feature_flags_client, project: project)
feature_flag = create(:operations_feature_flag, project: project, name: 'feature1', active: true)
create(:operations_feature_flag_scope, create(:operations_feature_flag_scope,
feature_flag: feature_flag, feature_flag: feature_flag,
environment_scope: 'staging', environment_scope: 'staging',
...@@ -231,6 +229,7 @@ describe API::Unleash do ...@@ -231,6 +229,7 @@ describe API::Unleash do
get api(features_url), headers: headers get api(features_url), headers: headers
expect(response).to have_gitlab_http_status(:ok) 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'] } strategies = json_response['features'].first['strategies'].sort_by { |s| s['name'] }
expect(strategies).to eq([{ expect(strategies).to eq([{
"name" => "gradualRolloutUserId", "name" => "gradualRolloutUserId",
...@@ -245,6 +244,42 @@ describe API::Unleash do ...@@ -245,6 +244,42 @@ describe API::Unleash do
} }
}]) }])
end end
context "with an inactive scope" do
let!(:scope) { create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'production', active: false, strategies: strategies) }
let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } }
context 'with userWithId strategy' do
let(:strategies) { [{ name: "default", parameters: {} }, { name: "userWithId", parameters: { userIds: "fred" } }] }
it 'returns an enabled feature with only the userWithId strategy' 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(true)
expect(feature_json['strategies']).to eq([{
'name' => 'userWithId',
'parameters' => {
'userIds' => 'fred'
}
}])
end
end
context 'with default strategy' do
let(:strategies) { [{ name: "default", parameters: {} }] }
it 'returns a disabled feature that does not contain a userWithId strategy' 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
end
end
end end
end 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