Commit 37d36eb7 authored by Jason Goodman's avatar Jason Goodman Committed by Shinya Maeda

Move feature flags API and controllers to core

Part of moving feature flags to core
parent c5e00ece
......@@ -154,8 +154,6 @@ Performance/Count:
- 'app/helpers/groups_helper.rb'
- 'app/services/merge_requests/add_context_service.rb'
- 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb'
- 'ee/spec/controllers/projects/feature_flags_controller_spec.rb'
- 'ee/spec/requests/api/feature_flags_spec.rb'
- 'lib/gitlab/sidekiq_status.rb'
- 'spec/lib/gitlab/conflict/file_spec.rb'
- 'spec/lib/gitlab/git/tree_spec.rb'
......@@ -167,7 +165,6 @@ Performance/Count:
Performance/Detect:
Exclude:
- 'ee/spec/controllers/projects/dependencies_controller_spec.rb'
- 'ee/spec/controllers/projects/feature_flags_controller_spec.rb'
- 'spec/lib/gitlab/git/tree_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
- 'spec/models/event_spec.rb'
......
......@@ -9,6 +9,7 @@ module UserCalloutsHelper
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
WEBHOOKS_MOVED = 'webhooks_moved'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
def show_admin_integrations_moved?
!user_dismissed?(ADMIN_INTEGRATIONS_MOVED)
......@@ -50,6 +51,10 @@ module UserCalloutsHelper
customize_homepage && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
def show_feature_flags_new_version?
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
end
private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
......
......@@ -9,7 +9,7 @@
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),
......
......@@ -7,7 +7,7 @@
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),
......
......@@ -13,7 +13,6 @@ module EE
ACCOUNT_RECOVERY_REGULAR_CHECK = 'account_recovery_regular_check'
ACTIVE_USER_COUNT_THRESHOLD = 'active_user_count_threshold'
PERSONAL_ACCESS_TOKEN_EXPIRY = 'personal_access_token_expiry'
FEATURE_FLAGS_NEW_VERISION = 'feature_flags_new_version'
def show_canary_deployment_callout?(project)
!user_dismissed?(CANARY_DEPLOYMENT) &&
......@@ -90,10 +89,6 @@ module EE
!user_dismissed?(PERSONAL_ACCESS_TOKEN_EXPIRY, 1.week.ago)
end
def show_feature_flags_new_version?
!user_dismissed?(FEATURE_FLAGS_NEW_VERISION)
end
private
def hashed_storage_enabled?
......
......@@ -19,9 +19,6 @@ module EE
mount ::API::EpicLinks
mount ::API::Epics
mount ::API::ElasticsearchIndexedNamespaces
mount ::API::FeatureFlags
mount ::API::FeatureFlagsUserLists
mount ::API::FeatureFlagScopes
mount ::API::Geo
mount ::API::GeoReplication
mount ::API::GeoNodes
......
......@@ -14,59 +14,6 @@ RSpec.describe 'User creates feature flag', :js do
sign_in(user)
end
it 'user creates a flag enabled for user ids' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_feature', 'Test feature')
within_strategy_row(1) do
select 'User IDs', from: 'Type'
fill_in 'User IDs', with: 'user1, user2'
environment_plus_button.click
environment_search_input.set('production')
environment_search_results.first.click
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_feature')
end
it 'user creates a flag with default environment scopes' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_flag', 'Test flag')
within_strategy_row(1) do
select 'All users', from: 'Type'
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_flag')
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text('All users')
expect(page).to have_text('All environments')
end
end
it 'removes the correct strategy when a strategy is deleted' do
visit(new_project_feature_flag_path(project))
click_button 'Add strategy'
within_strategy_row(1) do
select 'All users', from: 'Type'
end
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
end
within_strategy_row(1) do
delete_strategy_button.click
end
within_strategy_row(1) do
expect(page).to have_select('Type', selected: 'Percent of users')
end
end
context 'with new version flags disabled' do
before do
stub_feature_flags(feature_flags_new_version: false)
......@@ -80,109 +27,12 @@ RSpec.describe 'User creates feature flag', :js do
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
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('*')
end
end
end
it 'records audit event' do
visit(project_audit_events_path(project))
expect(page).to have_text("Created feature flag ci_live_trace with description \"For live trace\".")
end
end
context 'when creates with disabling the default scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('ci_live_trace', 'For live trace')
within_scope_row(1) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_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
end
context 'when creates with an additional scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set("review/*")
find('.js-create-button').click
end
end
within_scope_row(2) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
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-info-badge"]:nth-child(2)')).to have_content('review/*')
end
end
end
end
context 'when searches an environment name for scope creation' do
let!(:environment) { create(:environment, name: 'production', project: project) }
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set('prod')
click_button 'production'
end
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
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
end
end
private
......@@ -191,16 +41,4 @@ RSpec.describe 'User creates feature flag', :js do
fill_in 'Name', with: name
fill_in 'Description', with: description
end
def environment_plus_button
find('.js-new-environments-dropdown')
end
def environment_search_input
find('.js-new-environments-dropdown input')
end
def environment_search_results
all('.js-new-environments-dropdown button.dropdown-item')
end
end
......@@ -25,10 +25,6 @@ RSpec.describe 'User deletes feature flag', :js do
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'user does not see feature flag' do
expect(page).to have_no_content('ci_live_trace')
end
it 'records audit event' do
visit(project_audit_events_path(project))
......
......@@ -28,58 +28,6 @@ RSpec.describe 'User sees feature flag list', :js do
stub_feature_flags(feature_flags_legacy_read_only_override: false)
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
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_disabled
end
end
context 'when legacy feature flags are not read-only' do
before do
stub_feature_flags(feature_flags_legacy_read_only: false)
......@@ -150,16 +98,4 @@ RSpec.describe 'User sees feature flag list', :js do
)
end
end
context 'when there are no feature flags' do
before do
visit(project_feature_flags_path(project))
end
it 'shows empty page' do
expect(page).to have_text 'Get started with feature flags'
expect(page).to have_selector('.btn-success', text: 'New feature flag')
expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure')
end
end
end
......@@ -20,57 +20,6 @@ RSpec.describe 'User updates feature flag', :js do
sign_in(user)
end
context 'with a new version feature flag' do
let!(:feature_flag) do
create_flag(project, 'test_flag', false, version: Operations::FeatureFlag.versions['new_version_flag'],
description: 'For testing')
end
let!(:strategy) do
create(:operations_strategy, feature_flag: feature_flag,
name: 'default', parameters: {})
end
let!(:scope) do
create(:operations_scope, strategy: strategy, environment_scope: '*')
end
it 'user adds a second strategy' do
visit(edit_project_feature_flag_path(project, feature_flag))
wait_for_requests
click_button 'Add strategy'
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
fill_in 'Percentage', with: '15'
end
click_button 'Save changes'
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text 'All users'
expect(page).to have_text 'All environments'
end
within_strategy_row(2) do
expect(page).to have_text 'Percent of users'
expect(page).to have_field 'Percentage', with: '15'
expect(page).to have_text 'All environments'
end
end
it 'user toggles the flag on' do
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_checked
end
end
end
context 'with a legacy feature flag' do
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', true,
......@@ -86,19 +35,6 @@ RSpec.describe 'User updates feature flag', :js do
visit(edit_project_feature_flag_path(project, feature_flag))
end
it 'user sees persisted default scope' do
within_scope_row(1) do
within_environment_spec do
expect(page).to have_content('* (All Environments)')
end
within_status do
expect(find('.project-feature-toggle')['aria-label'])
.to eq('Toggle Status: ON')
end
end
end
context 'when user updates the status of a scope' do
before do
within_scope_row(2) do
......@@ -109,20 +45,6 @@ RSpec.describe 'User updates feature flag', :js do
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(1)')).to have_content('*')
expect(page.find('.badge:nth-child(1)')['class']).to include('badge-info')
expect(page.find('.badge:nth-child(2)')).to have_content('review/*')
expect(page.find('.badge:nth-child(2)')['class']).to include('badge-muted')
end
end
end
it 'records audit event' do
visit(project_audit_events_path(project))
......@@ -145,15 +67,6 @@ RSpec.describe 'User updates feature flag', :js do
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the newly created scope' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(3)')).to have_content('production')
expect(page.find('.badge:nth-child(3)')['class']).to include('badge-muted')
end
end
end
it 'records audit event' do
visit(project_audit_events_path(project))
......@@ -171,15 +84,6 @@ RSpec.describe 'User updates feature flag', :js do
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page).to have_css('.badge:nth-child(1)')
expect(page).not_to have_css('.badge:nth-child(2)')
end
end
end
it 'records audit event' do
visit(project_audit_events_path(project))
......@@ -187,29 +91,5 @@ RSpec.describe 'User updates feature flag', :js do
end
end
end
context 'when legacy flags are read-only' do
it 'the user cannot edit the flag' 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')
end
end
context 'when legacy flags are read-only, but the override is active for one project' do
it 'the user can edit the flag' do
stub_feature_flags(feature_flags_legacy_read_only_override: project)
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_not_to_be_checked
end
end
end
end
end
......@@ -328,28 +328,6 @@ RSpec.describe EE::UserCalloutsHelper do
end
end
describe '.show_feature_flags_new_version?' do
subject { helper.show_feature_flags_new_version? }
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'when the feature flags new version info has not been dismissed' do
it { is_expected.to be_truthy }
end
context 'when the feature flags new version has been dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::FEATURE_FLAGS_NEW_VERISION)
end
it { is_expected.to be_falsy }
end
end
describe '.show_token_expiry_notification?' do
subject { helper.show_token_expiry_notification? }
......
......@@ -153,6 +153,9 @@ module API
mount ::API::Environments
mount ::API::ErrorTracking
mount ::API::Events
mount ::API::FeatureFlags
mount ::API::FeatureFlagScopes
mount ::API::FeatureFlagsUserLists
mount ::API::Features
mount ::API::Files
mount ::API::FreezePeriods
......
......@@ -90,7 +90,7 @@ RSpec.describe Projects::FeatureFlagsController do
end
it 'matches json schema' do
is_expected.to match_response_schema('feature_flags', dir: 'ee')
is_expected.to match_response_schema('feature_flags')
end
it 'returns false for active when the feature flag is inactive even if it has an active scope' do
......@@ -201,7 +201,7 @@ RSpec.describe Projects::FeatureFlagsController do
recorded = ActiveRecord::QueryRecorder.new { subject }
related_count = recorded.log
.select { |query| query.include?('operations_feature_flag') }.count
.count { |query| query.include?('operations_feature_flag') }
expect(related_count).to be_within(5).of(2)
end
......@@ -263,7 +263,7 @@ RSpec.describe Projects::FeatureFlagsController do
end
it 'matches json schema' do
is_expected.to match_response_schema('feature_flag', dir: 'ee')
is_expected.to match_response_schema('feature_flag')
end
it 'routes based on iid' do
......@@ -439,7 +439,7 @@ RSpec.describe Projects::FeatureFlagsController do
end
it 'matches json schema' do
is_expected.to match_response_schema('feature_flag', dir: 'ee')
is_expected.to match_response_schema('feature_flag')
end
context 'when the same named feature flag has already existed' do
......@@ -473,7 +473,7 @@ RSpec.describe Projects::FeatureFlagsController do
expect { subject }.to change { Operations::FeatureFlag.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('feature_flag', dir: 'ee')
expect(response).to match_response_schema('feature_flag')
expect(json_response['active']).to eq(true)
expect(Operations::FeatureFlag.last.active).to eq(true)
end
......@@ -867,7 +867,7 @@ RSpec.describe Projects::FeatureFlagsController do
end
it 'matches json schema' do
is_expected.to match_response_schema('feature_flag', dir: 'ee')
is_expected.to match_response_schema('feature_flag')
end
context 'when user is reporter' do
......@@ -976,7 +976,7 @@ RSpec.describe Projects::FeatureFlagsController do
end
it 'matches json schema' do
is_expected.to match_response_schema('feature_flag', dir: 'ee')
is_expected.to match_response_schema('feature_flag')
end
context 'when updates active' do
......@@ -1008,7 +1008,7 @@ RSpec.describe Projects::FeatureFlagsController do
put_request(feature_flag, { active: true })
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('feature_flag', dir: 'ee')
expect(response).to match_response_schema('feature_flag')
expect(json_response['active']).to eq(true)
expect(feature_flag.reload.active).to eq(true)
expect(feature_flag.default_scope.reload.active).to eq(false)
......@@ -1167,9 +1167,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "default",
"parameters" => {}
......@@ -1186,9 +1186,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "gradualRolloutUserId",
"parameters" => {
......@@ -1207,9 +1207,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "userWithId",
"parameters" => { "userIds" => "sam,fred" }
......@@ -1226,9 +1226,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "gradualRolloutUserId",
"parameters" => {
......@@ -1247,9 +1247,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([])
end
......@@ -1265,9 +1265,9 @@ RSpec.describe Projects::FeatureFlagsController do
}])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies'].length).to eq(2)
expect(scope_json['strategies']).to include({
"name" => "gradualRolloutUserId",
......@@ -1285,9 +1285,9 @@ RSpec.describe Projects::FeatureFlagsController do
put_request(feature_flag, scopes_attributes: [{ id: scope.id }])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "default",
"parameters" => {}
......@@ -1301,9 +1301,9 @@ RSpec.describe Projects::FeatureFlagsController do
put_request(feature_flag, scopes_attributes: [{ id: scope.id }])
expect(response).to have_gitlab_http_status(:ok)
scope_json = json_response['scopes'].select do |s|
scope_json = json_response['scopes'].find do |s|
s['environment_scope'] == 'production'
end.first
end
expect(scope_json['strategies']).to eq([{
"name" => "gradualRolloutUserId",
"parameters" => { "groupId" => "default", "percentage" => "10" }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User creates feature flag', :js do
include FeatureFlagHelpers
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
end
it 'user creates a flag enabled for user ids' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_feature', 'Test feature')
within_strategy_row(1) do
select 'User IDs', from: 'Type'
fill_in 'User IDs', with: 'user1, user2'
environment_plus_button.click
environment_search_input.set('production')
environment_search_results.first.click
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_feature')
end
it 'user creates a flag with default environment scopes' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_flag', 'Test flag')
within_strategy_row(1) do
select 'All users', from: 'Type'
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_flag')
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text('All users')
expect(page).to have_text('All environments')
end
end
it 'removes the correct strategy when a strategy is deleted' do
visit(new_project_feature_flag_path(project))
click_button 'Add strategy'
within_strategy_row(1) do
select 'All users', from: 'Type'
end
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
end
within_strategy_row(1) do
delete_strategy_button.click
end
within_strategy_row(1) do
expect(page).to have_select('Type', selected: 'Percent of users')
end
end
context 'with new version flags disabled' do
before do
stub_feature_flags(feature_flags_new_version: false)
end
context 'when creates without changing scopes' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('ci_live_trace', 'For live trace')
click_button 'Create feature flag'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
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('*')
end
end
end
end
context 'when creates with disabling the default scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('ci_live_trace', 'For live trace')
within_scope_row(1) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_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
end
context 'when creates with an additional scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set("review/*")
find('.js-create-button').click
end
end
within_scope_row(2) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
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-info-badge"]:nth-child(2)')).to have_content('review/*')
end
end
end
end
context 'when searches an environment name for scope creation' do
let!(:environment) { create(:environment, name: 'production', project: project) }
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set('prod')
click_button 'production'
end
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
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
end
end
private
def set_feature_flag_info(name, description)
fill_in 'Name', with: name
fill_in 'Description', with: description
end
def environment_plus_button
find('.js-new-environments-dropdown')
end
def environment_search_input
find('.js-new-environments-dropdown input')
end
def environment_search_results
all('.js-new-environments-dropdown button.dropdown-item')
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User deletes feature flag', :js do
include FeatureFlagHelpers
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', false,
description: 'For live trace feature')
end
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
visit(project_feature_flags_path(project))
find('.js-feature-flag-delete-button').click
click_button('Delete feature flag')
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'user does not see feature flag' do
expect(page).to have_no_content('ci_live_trace')
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User sees feature flag list', :js do
include FeatureFlagHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
before_all do
project.add_developer(user)
end
before do
sign_in(user)
end
context 'with legacy feature flags' do
before do
create_flag(project, 'ci_live_trace', false).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_scope(feature_flag, 'production', false)
end
stub_feature_flags(feature_flags_legacy_read_only_override: false)
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
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_disabled
end
end
context 'when legacy feature flags are not read-only' do
before do
stub_feature_flags(feature_flags_legacy_read_only: false)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
context 'when legacy feature flags are read-only but the override is active for a project' do
before do
stub_feature_flags(
feature_flags_legacy_read_only: true,
feature_flags_legacy_read_only_override: project
)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
end
context 'with new version flags' do
before do
create(:operations_feature_flag, :new_version_flag, project: project,
name: 'my_flag', active: false)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
context 'when there are no feature flags' do
before do
visit(project_feature_flags_path(project))
end
it 'shows empty page' do
expect(page).to have_text 'Get started with feature flags'
expect(page).to have_selector('.btn-success', text: 'New feature flag')
expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User updates feature flag', :js do
include FeatureFlagHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
before_all do
project.add_developer(user)
end
before do
stub_feature_flags(
feature_flag_permissions: false,
feature_flags_legacy_read_only_override: false
)
sign_in(user)
end
context 'with a new version feature flag' do
let!(:feature_flag) do
create_flag(project, 'test_flag', false, version: Operations::FeatureFlag.versions['new_version_flag'],
description: 'For testing')
end
let!(:strategy) do
create(:operations_strategy, feature_flag: feature_flag,
name: 'default', parameters: {})
end
let!(:scope) do
create(:operations_scope, strategy: strategy, environment_scope: '*')
end
it 'user adds a second strategy' do
visit(edit_project_feature_flag_path(project, feature_flag))
wait_for_requests
click_button 'Add strategy'
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
fill_in 'Percentage', with: '15'
end
click_button 'Save changes'
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text 'All users'
expect(page).to have_text 'All environments'
end
within_strategy_row(2) do
expect(page).to have_text 'Percent of users'
expect(page).to have_field 'Percentage', with: '15'
expect(page).to have_text 'All environments'
end
end
it 'user toggles the flag on' do
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_checked
end
end
end
context 'with a legacy feature flag' do
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', true,
description: 'For live trace feature')
end
let!(:scope) { create_scope(feature_flag, 'review/*', true) }
context 'when legacy flags are editable' do
before do
stub_feature_flags(feature_flags_legacy_read_only: false)
visit(edit_project_feature_flag_path(project, feature_flag))
end
it 'user sees persisted default scope' do
within_scope_row(1) do
within_environment_spec do
expect(page).to have_content('* (All Environments)')
end
within_status do
expect(find('.project-feature-toggle')['aria-label'])
.to eq('Toggle Status: ON')
end
end
end
context 'when user updates the status of a scope' do
before do
within_scope_row(2) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(1)')).to have_content('*')
expect(page.find('.badge:nth-child(1)')['class']).to include('badge-info')
expect(page.find('.badge:nth-child(2)')).to have_content('review/*')
expect(page.find('.badge:nth-child(2)')['class']).to include('badge-muted')
end
end
end
end
context 'when user adds a new scope' do
before do
within_scope_row(3) do
within_environment_spec do
find('.js-env-search > input').set('production')
find('.js-create-button').click
end
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the newly created scope' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(3)')).to have_content('production')
expect(page.find('.badge:nth-child(3)')['class']).to include('badge-muted')
end
end
end
end
context 'when user deletes a scope' do
before do
within_scope_row(2) do
within_delete { find('.js-delete-scope').click }
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page).to have_css('.badge:nth-child(1)')
expect(page).not_to have_css('.badge:nth-child(2)')
end
end
end
end
end
context 'when legacy flags are read-only' do
it 'the user cannot edit the flag' 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')
end
end
context 'when legacy flags are read-only, but the override is active for one project' do
it 'the user can edit the flag' do
stub_feature_flags(feature_flags_legacy_read_only_override: project)
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_not_to_be_checked
end
end
end
end
end
......@@ -139,4 +139,26 @@ RSpec.describe UserCalloutsHelper do
helper.render_flash_user_callout(:warning, 'foo', 'bar')
end
end
describe '.show_feature_flags_new_version?' do
subject { helper.show_feature_flags_new_version? }
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'when the feature flags new version info has not been dismissed' do
it { is_expected.to be_truthy }
end
context 'when the feature flags new version has been dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::FEATURE_FLAGS_NEW_VERSION)
end
it { is_expected.to be_falsy }
end
end
end
......@@ -58,7 +58,7 @@ RSpec.describe API::FeatureFlagScopes do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_detailed_scopes', dir: 'ee')
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
......@@ -104,7 +104,7 @@ RSpec.describe API::FeatureFlagScopes do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scopes', dir: 'ee')
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)
......@@ -141,7 +141,7 @@ RSpec.describe API::FeatureFlagScopes do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope', dir: 'ee')
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]))
......@@ -181,7 +181,7 @@ RSpec.describe API::FeatureFlagScopes do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope', dir: 'ee')
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)
......@@ -241,7 +241,7 @@ RSpec.describe API::FeatureFlagScopes do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/feature_flag_scope', dir: 'ee')
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]))
......
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