Commit f0524a1c authored by Mario Celi's avatar Mario Celi Committed by Etienne Baqué

The work_items feature flag checks 3 actors

work_items FF can be enabled at the project, group
or root_ancestor level
parent 5dad1cf7
...@@ -48,7 +48,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -48,7 +48,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml) push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml) push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml) push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml)
push_frontend_feature_flag(:work_items, project&.group, default_enabled: :yaml) push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
end end
around_action :allow_gitaly_ref_name_caching, only: [:discussions] around_action :allow_gitaly_ref_name_caching, only: [:discussions]
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
class Projects::WorkItemsController < Projects::ApplicationController class Projects::WorkItemsController < Projects::ApplicationController
before_action do before_action do
push_frontend_feature_flag(:work_items, project, default_enabled: :yaml) push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
end end
feature_category :not_owned feature_category :not_owned
def index def index
render_404 unless Feature.enabled?(:work_items, project, default_enabled: :yaml) render_404 unless project&.work_items_feature_flag_enabled?
end end
end end
...@@ -41,7 +41,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -41,7 +41,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml) push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml) push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks) push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
push_frontend_feature_flag(:work_items, @project, default_enabled: :yaml) push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
end end
layout :determine_layout layout :determine_layout
......
...@@ -33,7 +33,7 @@ module Mutations ...@@ -33,7 +33,7 @@ module Mutations
def resolve(project_path:, **attributes) def resolve(project_path:, **attributes)
project = authorized_find!(project_path) project = authorized_find!(project_path)
unless Feature.enabled?(:work_items, project, default_enabled: :yaml) unless project.work_items_feature_flag_enabled?
return { errors: ['`work_items` feature flag disabled for this project'] } return { errors: ['`work_items` feature flag disabled for this project'] }
end end
......
...@@ -31,7 +31,7 @@ module Mutations ...@@ -31,7 +31,7 @@ module Mutations
def resolve(id:, work_item_data:) def resolve(id:, work_item_data:)
work_item = authorized_find!(id: id) work_item = authorized_find!(id: id)
unless Feature.enabled?(:work_items, work_item.project, default_enabled: :yaml) unless work_item.project.work_items_feature_flag_enabled?
return { errors: ['`work_items` feature flag disabled for this project'] } return { errors: ['`work_items` feature flag disabled for this project'] }
end end
......
...@@ -20,7 +20,7 @@ module Mutations ...@@ -20,7 +20,7 @@ module Mutations
def resolve(id:) def resolve(id:)
work_item = authorized_find!(id: id) work_item = authorized_find!(id: id)
unless Feature.enabled?(:work_items, work_item.project, default_enabled: :yaml) unless work_item.project.work_items_feature_flag_enabled?
return { errors: ['`work_items` feature flag disabled for this project'] } return { errors: ['`work_items` feature flag disabled for this project'] }
end end
......
...@@ -28,7 +28,7 @@ module Mutations ...@@ -28,7 +28,7 @@ module Mutations
def resolve(id:, **attributes) def resolve(id:, **attributes)
work_item = authorized_find!(id: id) work_item = authorized_find!(id: id)
unless Feature.enabled?(:work_items, work_item.project, default_enabled: :yaml) unless work_item.project.work_items_feature_flag_enabled?
return { errors: ['`work_items` feature flag disabled for this project'] } return { errors: ['`work_items` feature flag disabled for this project'] }
end end
......
...@@ -12,7 +12,7 @@ module Resolvers ...@@ -12,7 +12,7 @@ module Resolvers
def resolve(id:) def resolve(id:)
work_item = authorized_find!(id: id) work_item = authorized_find!(id: id)
return unless Feature.enabled?(:work_items, work_item.project, default_enabled: :yaml) return unless work_item.project.work_items_feature_flag_enabled?
work_item work_item
end end
......
...@@ -11,7 +11,7 @@ module Resolvers ...@@ -11,7 +11,7 @@ module Resolvers
' Argument is experimental and can be removed in the future without notice.' ' Argument is experimental and can be removed in the future without notice.'
def resolve(taskable: nil) def resolve(taskable: nil)
return unless Feature.enabled?(:work_items, object, default_enabled: :yaml) return unless feature_flag_enabled_for_parent?(object)
# This will require a finder in the future when groups/projects get their work item types # This will require a finder in the future when groups/projects get their work item types
# All groups/projects use the default types for now # All groups/projects use the default types for now
...@@ -20,6 +20,14 @@ module Resolvers ...@@ -20,6 +20,14 @@ module Resolvers
base_scope.order_by_name_asc base_scope.order_by_name_asc
end end
private
def feature_flag_enabled_for_parent?(parent)
return false unless parent.is_a?(::Project) || parent.is_a?(::Group)
parent.work_items_feature_flag_enabled?
end
end end
end end
end end
...@@ -815,6 +815,15 @@ class Group < Namespace ...@@ -815,6 +815,15 @@ class Group < Namespace
].compact.min ].compact.min
end end
def work_items_feature_flag_enabled?
actors = [root_ancestor]
actors << self if root_ancestor != self
actors.any? do |actor|
Feature.enabled?(:work_items, actor, default_enabled: :yaml)
end
end
private private
def max_member_access(user_ids) def max_member_access(user_ids)
......
...@@ -2832,6 +2832,10 @@ class Project < ApplicationRecord ...@@ -2832,6 +2832,10 @@ class Project < ApplicationRecord
pending_delete? || hidden? pending_delete? || hidden?
end end
def work_items_feature_flag_enabled?
group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self, default_enabled: :yaml)
end
private private
# overridden in EE # overridden in EE
......
...@@ -73,6 +73,15 @@ module Gitlab ...@@ -73,6 +73,15 @@ module Gitlab
push_to_gon_attributes(:features, name, enabled) push_to_gon_attributes(:features, name, enabled)
end end
# Exposes the state of a feature flag to the frontend code.
# Can be used for more complex feature flag checks.
#
# name - The name of the feature flag, e.g. `my_feature`.
# enabled - Boolean to be pushed directly to the frontend. Should be fetched by checking a feature flag.
def push_force_frontend_feature_flag(name, enabled)
push_to_gon_attributes(:features, name, !!enabled)
end
def push_to_gon_attributes(key, name, enabled) def push_to_gon_attributes(key, name, enabled)
var_name = name.to_s.camelize(:lower) var_name = name.to_s.camelize(:lower)
# Here the `true` argument signals gon that the value should be merged # Here the `true` argument signals gon that the value should be merged
......
...@@ -53,5 +53,15 @@ RSpec.describe Resolvers::WorkItems::TypesResolver do ...@@ -53,5 +53,15 @@ RSpec.describe Resolvers::WorkItems::TypesResolver do
it_behaves_like 'a work item type resolver' it_behaves_like 'a work item type resolver'
end end
context 'when parent is not a group or project' do
let(:object) { 'not a project/group' }
it 'returns nil because of feature flag check' do
result = resolve(described_class, obj: object, args: {})
expect(result).to be_nil
end
end
end end
end end
...@@ -64,6 +64,34 @@ RSpec.describe Gitlab::GonHelper do ...@@ -64,6 +64,34 @@ RSpec.describe Gitlab::GonHelper do
end end
end end
describe '#push_force_frontend_feature_flag' do
let(:gon) { class_double('Gon') }
before do
skip_feature_flags_yaml_validation
allow(helper)
.to receive(:gon)
.and_return(gon)
end
it 'pushes a feature flag to the frontend with the provided value' do
expect(gon)
.to receive(:push)
.with({ features: { 'myFeatureFlag' => true } }, true)
helper.push_force_frontend_feature_flag(:my_feature_flag, true)
end
it 'pushes a disabled feature flag if provided value is nil' do
expect(gon)
.to receive(:push)
.with({ features: { 'myFeatureFlag' => false } }, true)
helper.push_force_frontend_feature_flag(:my_feature_flag, nil)
end
end
describe '#default_avatar_url' do describe '#default_avatar_url' do
it 'returns an absolute URL' do it 'returns an absolute URL' do
url = helper.default_avatar_url url = helper.default_avatar_url
......
...@@ -3248,4 +3248,46 @@ RSpec.describe Group do ...@@ -3248,4 +3248,46 @@ RSpec.describe Group do
it_behaves_like 'no effective expiration interval' it_behaves_like 'no effective expiration interval'
end end
end end
describe '#work_items_feature_flag_enabled?' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group) { create(:group, parent: root_group) }
let_it_be(:project) { create(:project, group: group) }
subject { group.work_items_feature_flag_enabled? }
context 'when work_items FF is enabled for the root group' do
before do
stub_feature_flags(work_items: root_group)
end
it { is_expected.to be_truthy }
end
context 'when work_items FF is enabled for the group' do
before do
stub_feature_flags(work_items: group)
end
it { is_expected.to be_truthy }
context 'when root_group is the actor' do
it 'is not enabled if the FF is enabled for a child' do
expect(root_group).not_to be_work_items_feature_flag_enabled
end
end
end
context 'when work_items FF is disabled globally' do
before do
stub_feature_flags(work_items: false)
end
it { is_expected.to be_falsey }
end
context 'when work_items FF is enabled globally' do
it { is_expected.to be_truthy }
end
end
end end
...@@ -8011,6 +8011,62 @@ RSpec.describe Project, factory_default: :keep do ...@@ -8011,6 +8011,62 @@ RSpec.describe Project, factory_default: :keep do
end end
end end
describe '#work_items_feature_flag_enabled?' do
shared_examples 'project checking work_items feature flag' do
context 'when work_items FF is disabled globally' do
before do
stub_feature_flags(work_items: false)
end
it { is_expected.to be_falsey }
end
context 'when work_items FF is enabled for the project' do
before do
stub_feature_flags(work_items: project)
end
it { is_expected.to be_truthy }
end
context 'when work_items FF is enabled globally' do
it { is_expected.to be_truthy }
end
end
subject { project.work_items_feature_flag_enabled? }
context 'when a project does not belong to a group' do
let_it_be(:project) { create(:project, namespace: namespace) }
it_behaves_like 'project checking work_items feature flag'
end
context 'when project belongs to a group' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group) { create(:group, parent: root_group) }
let_it_be(:project) { create(:project, group: group) }
it_behaves_like 'project checking work_items feature flag'
context 'when work_items FF is enabled for the root group' do
before do
stub_feature_flags(work_items: root_group)
end
it { is_expected.to be_truthy }
end
context 'when work_items FF is enabled for the group' do
before do
stub_feature_flags(work_items: group)
end
it { is_expected.to be_truthy }
end
end
end
describe 'serialization' do describe 'serialization' do
let(:object) { build(:project) } let(:object) { build(:project) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Work Items' do
let_it_be(:work_item) { create(:work_item) }
let_it_be(:developer) { create(:user) }
before_all do
work_item.project.add_developer(developer)
end
describe 'GET /:namespace/:project/work_items/:id' do
before do
sign_in(developer)
end
context 'when the work_items feature flag is enabled' do
it 'renders index' do
get project_work_items_url(work_item.project, work_items_path: work_item.id)
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
end
it 'returns 404' do
get project_work_items_url(work_item.project, work_items_path: work_item.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment