Commit d5fdf995 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'move-ff-api-controllers-to-core' into 'master'

Move Feature Flag Api and Controllers to Core RUN AS-IF-FOSS

See merge request gitlab-org/gitlab!44093
parents c4372c85 37d36eb7
......@@ -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