Commit 4eaa2abf authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'legacy-read-only-flags-override' into 'master'

Add Override for Legacy Read-Only Feature Flags

See merge request gitlab-org/gitlab!40431
parents 53b97bc9 59a53090
...@@ -85,7 +85,10 @@ export default { ...@@ -85,7 +85,10 @@ export default {
return this.glFeatures.featureFlagsNewVersion; return this.glFeatures.featureFlagsNewVersion;
}, },
hasLegacyReadOnlyFlags() { hasLegacyReadOnlyFlags() {
return this.glFeatures.featureFlagsLegacyReadOnly; return (
this.glFeatures.featureFlagsLegacyReadOnly &&
!this.glFeatures.featureFlagsLegacyReadOnlyOverride
);
}, },
shouldShowNewFlagAlert() { shouldShowNewFlagAlert() {
return !(this.hasNewVersionFlags || this.userDidDismissNewFlagAlert); return !(this.hasNewVersionFlags || this.userDidDismissNewFlagAlert);
...@@ -128,10 +131,11 @@ export default { ...@@ -128,10 +131,11 @@ export default {
<div class="gl-display-flex gl-align-items-center gl-mb-4 gl-mt-4"> <div class="gl-display-flex gl-align-items-center gl-mb-4 gl-mt-4">
<gl-toggle <gl-toggle
:value="active" :value="active"
data-testid="feature-flag-status-toggle"
data-track-event="click_button" data-track-event="click_button"
data-track-label="feature_flag_toggle" data-track-label="feature_flag_toggle"
data-track-context="feature_flag_activity" data-track-context="feature_flag_activity"
class="gl-mr-4 js-feature-flag-status" class="gl-mr-4"
@change="toggleActive" @change="toggleActive"
/> />
<h3 class="page-title gl-m-0">{{ title }}</h3> <h3 class="page-title gl-m-0">{{ title }}</h3>
......
...@@ -45,7 +45,10 @@ export default { ...@@ -45,7 +45,10 @@ export default {
return this.glFeatures.featureFlagsNewVersion; return this.glFeatures.featureFlagsNewVersion;
}, },
isLegacyReadOnlyFlagsEnabled() { isLegacyReadOnlyFlagsEnabled() {
return this.glFeatures.featureFlagsLegacyReadOnly; return (
this.glFeatures.featureFlagsLegacyReadOnly &&
!this.glFeatures.featureFlagsLegacyReadOnlyOverride
);
}, },
modalTitle() { modalTitle() {
return sprintf(s__('FeatureFlags|Delete %{name}?'), { return sprintf(s__('FeatureFlags|Delete %{name}?'), {
...@@ -150,17 +153,22 @@ export default { ...@@ -150,17 +153,22 @@ export default {
</div> </div>
<div class="table-section section-10" role="gridcell"> <div class="table-section section-10" role="gridcell">
<div class="table-mobile-header" role="rowheader">{{ s__('FeatureFlags|Status') }}</div> <div class="table-mobile-header" role="rowheader">{{ s__('FeatureFlags|Status') }}</div>
<div class="table-mobile-content js-feature-flag-status"> <div class="table-mobile-content">
<gl-toggle <gl-toggle
v-if="featureFlag.update_path" v-if="featureFlag.update_path"
:value="featureFlag.active" :value="featureFlag.active"
:disabled="statusToggleDisabled(featureFlag)" :disabled="statusToggleDisabled(featureFlag)"
data-testid="feature-flag-status-toggle"
data-track-event="click_button" data-track-event="click_button"
data-track-label="feature_flag_toggle" data-track-label="feature_flag_toggle"
data-track-context="feature_flag_activity" data-track-context="feature_flag_activity"
@change="toggleFeatureFlag(featureFlag)" @change="toggleFeatureFlag(featureFlag)"
/> />
<gl-badge v-else-if="featureFlag.active" variant="success"> <gl-badge
v-else-if="featureFlag.active"
variant="success"
data-testid="feature-flag-status-badge"
>
{{ s__('FeatureFlags|Active') }} {{ s__('FeatureFlags|Active') }}
</gl-badge> </gl-badge>
<gl-badge v-else variant="danger">{{ s__('FeatureFlags|Inactive') }}</gl-badge> <gl-badge v-else variant="danger">{{ s__('FeatureFlags|Inactive') }}</gl-badge>
......
...@@ -155,6 +155,7 @@ export default { ...@@ -155,6 +155,7 @@ export default {
return ( return (
this.glFeatures.featureFlagsNewVersion && this.glFeatures.featureFlagsNewVersion &&
this.glFeatures.featureFlagsLegacyReadOnly && this.glFeatures.featureFlagsLegacyReadOnly &&
!this.glFeatures.featureFlagsLegacyReadOnlyOverride &&
this.version === LEGACY_FLAG this.version === LEGACY_FLAG
); );
}, },
......
...@@ -16,6 +16,7 @@ class Projects::FeatureFlagsController < Projects::ApplicationController ...@@ -16,6 +16,7 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
push_frontend_feature_flag(:feature_flag_permissions) push_frontend_feature_flag(:feature_flag_permissions)
push_frontend_feature_flag(:feature_flags_new_version, project, default_enabled: true) push_frontend_feature_flag(:feature_flags_new_version, project, default_enabled: true)
push_frontend_feature_flag(:feature_flags_legacy_read_only, project) push_frontend_feature_flag(:feature_flags_legacy_read_only, project)
push_frontend_feature_flag(:feature_flags_legacy_read_only_override, project)
end end
def index def index
...@@ -110,7 +111,9 @@ class Projects::FeatureFlagsController < Projects::ApplicationController ...@@ -110,7 +111,9 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
end end
def ensure_legacy_flags_writable! def ensure_legacy_flags_writable!
if ::Feature.enabled?(:feature_flags_legacy_read_only, project) && feature_flag.legacy_flag? if ::Feature.enabled?(:feature_flags_legacy_read_only, project) &&
::Feature.disabled?(:feature_flags_legacy_read_only_override, project) &&
feature_flag.legacy_flag?
render_error_json(['Legacy feature flags are read-only']) render_error_json(['Legacy feature flags are read-only'])
end end
end end
......
...@@ -915,7 +915,10 @@ RSpec.describe Projects::FeatureFlagsController do ...@@ -915,7 +915,10 @@ RSpec.describe Projects::FeatureFlagsController do
end end
before do before do
stub_feature_flags(feature_flags_legacy_read_only: false) stub_feature_flags(
feature_flags_legacy_read_only: false,
feature_flags_legacy_read_only_override: false
)
end end
subject { put(:update, params: params, format: :json) } subject { put(:update, params: params, format: :json) }
...@@ -1306,6 +1309,18 @@ RSpec.describe Projects::FeatureFlagsController do ...@@ -1306,6 +1309,18 @@ RSpec.describe Projects::FeatureFlagsController do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(["Legacy feature flags are read-only"]) expect(json_response['message']).to eq(["Legacy feature flags are read-only"])
end end
it 'updates the flag if the legacy read-only override is enabled for a particular project' do
stub_feature_flags(
feature_flags_legacy_read_only: true,
feature_flags_legacy_read_only_override: project
)
put_request(feature_flag, name: 'ci_new_live_trace')
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq('ci_new_live_trace')
end
end end
context 'with a version 2 feature flag' do context 'with a version 2 feature flag' do
......
...@@ -84,7 +84,7 @@ RSpec.describe 'User creates feature flag', :js do ...@@ -84,7 +84,7 @@ RSpec.describe 'User creates feature flag', :js do
it 'shows the created feature flag' do it 'shows the created feature flag' do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do 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(1)')).to have_content('*')
...@@ -114,7 +114,7 @@ RSpec.describe 'User creates feature flag', :js do ...@@ -114,7 +114,7 @@ RSpec.describe 'User creates feature flag', :js do
it 'shows the created feature flag' do it 'shows the created feature flag' do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do 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-muted-badge"]:nth-child(1)')).to have_content('*')
...@@ -145,7 +145,7 @@ RSpec.describe 'User creates feature flag', :js do ...@@ -145,7 +145,7 @@ RSpec.describe 'User creates feature flag', :js do
it 'shows the created feature flag' do it 'shows the created feature flag' do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('mr_train') expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do 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(1)')).to have_content('*')
...@@ -175,7 +175,7 @@ RSpec.describe 'User creates feature flag', :js do ...@@ -175,7 +175,7 @@ RSpec.describe 'User creates feature flag', :js do
it 'shows the created feature flag' do it 'shows the created feature flag' do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('mr_train') expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do 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(1)')).to have_content('*')
......
...@@ -26,6 +26,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -26,6 +26,7 @@ RSpec.describe 'User sees feature flag list', :js do
create_flag(project, 'mr_train', true).tap do |feature_flag| create_flag(project, 'mr_train', true).tap do |feature_flag|
create_scope(feature_flag, 'production', false) create_scope(feature_flag, 'production', false)
end end
stub_feature_flags(feature_flags_legacy_read_only_override: false)
end end
it 'user sees the first flag' do it 'user sees the first flag' do
...@@ -34,7 +35,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -34,7 +35,7 @@ RSpec.describe 'User sees feature flag list', :js do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.js-feature-flag-id')).to have_content('^1') expect(page.find('.js-feature-flag-id')).to have_content('^1')
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect(page).to have_css('.js-feature-flag-status button:not(.is-checked)') expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do 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-muted-badge"]:nth-child(1)')).to have_content('*')
...@@ -49,7 +50,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -49,7 +50,7 @@ RSpec.describe 'User sees feature flag list', :js do
within_feature_flag_row(2) do within_feature_flag_row(2) do
expect(page.find('.js-feature-flag-id')).to have_content('^2') expect(page.find('.js-feature-flag-id')).to have_content('^2')
expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts') expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts')
expect(page).to have_css('.js-feature-flag-status button:not(.is-checked)') expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do 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-muted-badge"]:nth-child(1)')).to have_content('*')
...@@ -63,7 +64,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -63,7 +64,7 @@ RSpec.describe 'User sees feature flag list', :js do
within_feature_flag_row(3) do within_feature_flag_row(3) do
expect(page.find('.js-feature-flag-id')).to have_content('^3') expect(page.find('.js-feature-flag-id')).to have_content('^3')
expect(page.find('.feature-flag-name')).to have_content('mr_train') expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do 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(1)')).to have_content('*')
...@@ -76,7 +77,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -76,7 +77,7 @@ RSpec.describe 'User sees feature flag list', :js do
visit(project_feature_flags_path(project)) visit(project_feature_flags_path(project))
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page).to have_css('.js-feature-flag-status button.is-disabled') expect_status_toggle_button_to_be_disabled
end end
end end
...@@ -89,9 +90,34 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -89,9 +90,34 @@ RSpec.describe 'User sees feature flag list', :js do
visit(project_feature_flags_path(project)) visit(project_feature_flags_path(project))
within_feature_flag_row(1) do within_feature_flag_row(1) do
page.find('.js-feature-flag-status button').click status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
visit(project_audit_events_path(project))
expect(page).to(
have_text('Updated feature flag ci_live_trace. Updated active from "false" to "true".')
)
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(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
end end
visit(project_audit_events_path(project)) visit(project_audit_events_path(project))
...@@ -115,7 +141,7 @@ RSpec.describe 'User sees feature flag list', :js do ...@@ -115,7 +141,7 @@ RSpec.describe 'User sees feature flag list', :js do
within_feature_flag_row(1) do within_feature_flag_row(1) do
status_toggle_button.click status_toggle_button.click
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
end end
visit(project_audit_events_path(project)) visit(project_audit_events_path(project))
......
...@@ -14,7 +14,10 @@ RSpec.describe 'User updates feature flag', :js do ...@@ -14,7 +14,10 @@ RSpec.describe 'User updates feature flag', :js do
before do before do
stub_licensed_features(feature_flags: true) stub_licensed_features(feature_flags: true)
stub_feature_flags(feature_flag_permissions: false) stub_feature_flags(
feature_flag_permissions: false,
feature_flags_legacy_read_only_override: false
)
sign_in(user) sign_in(user)
end end
...@@ -97,7 +100,7 @@ RSpec.describe 'User updates feature flag', :js do ...@@ -97,7 +100,7 @@ RSpec.describe 'User updates feature flag', :js do
end end
end end
context 'when user updates a status of a scope' do context 'when user updates the status of a scope' do
before do before do
within_scope_row(2) do within_scope_row(2) do
within_status { find('.project-feature-toggle').click } within_status { find('.project-feature-toggle').click }
...@@ -110,7 +113,7 @@ RSpec.describe 'User updates feature flag', :js do ...@@ -110,7 +113,7 @@ RSpec.describe 'User updates feature flag', :js do
it 'shows the updated feature flag' do it 'shows the updated feature flag' do
within_feature_flag_row(1) do within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do within_feature_flag_scopes do
expect(page.find('.badge:nth-child(1)')).to have_content('*') expect(page.find('.badge:nth-child(1)')).to have_content('*')
...@@ -194,5 +197,20 @@ RSpec.describe 'User updates feature flag', :js do ...@@ -194,5 +197,20 @@ RSpec.describe 'User updates feature flag', :js do
expect(page).to have_css('button.js-ff-submit.disabled') expect(page).to have_css('button.js-ff-submit.disabled')
end end
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
end end
...@@ -80,8 +80,10 @@ describe('Feature flag table', () => { ...@@ -80,8 +80,10 @@ describe('Feature flag table', () => {
}); });
it('Should render a status column', () => { it('Should render a status column', () => {
expect(wrapper.find('.js-feature-flag-status').exists()).toBe(true); const badge = wrapper.find('[data-testid="feature-flag-status-badge"]');
expect(trimText(wrapper.find('.js-feature-flag-status').text())).toEqual('Active');
expect(badge.exists()).toBe(true);
expect(trimText(badge.text())).toEqual('Active');
}); });
it('Should render a feature flag column', () => { it('Should render a feature flag column', () => {
......
...@@ -73,11 +73,19 @@ module FeatureFlagHelpers ...@@ -73,11 +73,19 @@ module FeatureFlagHelpers
end end
def status_toggle_button def status_toggle_button
find('.js-feature-flag-status button') find('[data-testid="feature-flag-status-toggle"] button')
end end
def expect_status_toggle_button_to_be_checked def expect_status_toggle_button_to_be_checked
expect(page).to have_css('.js-feature-flag-status button.is-checked') expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button.is-checked')
end
def expect_status_toggle_button_not_to_be_checked
expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button:not(.is-checked)')
end
def expect_status_toggle_button_to_be_disabled
expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button.is-disabled')
end end
def expect_user_to_see_feature_flags_index_page def expect_user_to_see_feature_flags_index_page
......
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