Commit f0722cd8 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '241990-show-inherited-labels' into 'master'

Show inherited labels in a subgroup and project labels page

Closes #241990

See merge request gitlab-org/gitlab!42960
parents f5dd96b3 1a1873d5
# frozen_string_literal: true
module ShowInheritedLabelsChecker
extend ActiveSupport::Concern
private
def show_inherited_labels?(include_ancestor_groups)
Feature.enabled?(:show_inherited_labels, @project || @group) || include_ancestor_groups # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Groups::LabelsController < Groups::ApplicationController class Groups::LabelsController < Groups::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include ShowInheritedLabelsChecker
before_action :label, only: [:edit, :update, :destroy] before_action :label, only: [:edit, :update, :destroy]
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy]
...@@ -12,8 +13,9 @@ class Groups::LabelsController < Groups::ApplicationController ...@@ -12,8 +13,9 @@ class Groups::LabelsController < Groups::ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html do format.html do
@labels = GroupLabelsFinder # at group level we do not want to list project labels,
.new(current_user, @group, params.merge(sort: sort)).execute # we only want `only_group_labels = false` when pulling labels for label filter dropdowns, fetched through json
@labels = available_labels(params.merge(only_group_labels: true)).page(params[:page])
end end
format.json do format.json do
render json: LabelSerializer.new.represent_appearance(available_labels) render json: LabelSerializer.new.represent_appearance(available_labels)
...@@ -74,7 +76,7 @@ class Groups::LabelsController < Groups::ApplicationController ...@@ -74,7 +76,7 @@ class Groups::LabelsController < Groups::ApplicationController
end end
def label def label
@label ||= @group.labels.find(params[:id]) @label ||= available_labels(params.merge(only_group_labels: true)).find(params[:id])
end end
alias_method :subscribable_resource, :label alias_method :subscribable_resource, :label
...@@ -102,15 +104,17 @@ class Groups::LabelsController < Groups::ApplicationController ...@@ -102,15 +104,17 @@ class Groups::LabelsController < Groups::ApplicationController
session[:previous_labels_path] = URI(request.referer || '').path session[:previous_labels_path] = URI(request.referer || '').path
end end
def available_labels def available_labels(options = params)
@available_labels ||= @available_labels ||=
LabelsFinder.new( LabelsFinder.new(
current_user, current_user,
group_id: @group.id, group_id: @group.id,
only_group_labels: params[:only_group_labels], only_group_labels: options[:only_group_labels],
include_ancestor_groups: params[:include_ancestor_groups], include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]),
include_descendant_groups: params[:include_descendant_groups], sort: sort,
search: params[:search]).execute subscribed: options[:subscribed],
include_descendant_groups: options[:include_descendant_groups],
search: options[:search]).execute
end end
def sort def sort
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include ShowInheritedLabelsChecker
before_action :check_issuables_available! before_action :check_issuables_available!
before_action :label, only: [:edit, :update, :destroy, :promote] before_action :label, only: [:edit, :update, :destroy, :promote]
...@@ -161,7 +162,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -161,7 +162,7 @@ class Projects::LabelsController < Projects::ApplicationController
@available_labels ||= @available_labels ||=
LabelsFinder.new(current_user, LabelsFinder.new(current_user,
project_id: @project.id, project_id: @project.id,
include_ancestor_groups: params[:include_ancestor_groups], include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]),
search: params[:search], search: params[:search],
subscribed: params[:subscribed], subscribed: params[:subscribed],
sort: sort).execute sort: sort).execute
......
# frozen_string_literal: true
class GroupLabelsFinder
attr_reader :current_user, :group, :params
def initialize(current_user, group, params = {})
@current_user = current_user
@group = group
@params = params
end
def execute
group.labels
.optionally_subscribed_by(subscriber_id)
.optionally_search(params[:search])
.order_by(params[:sort])
.page(params[:page])
end
private
def subscriber_id
current_user&.id if subscribed?
end
def subscribed?
params[:subscribed] == 'true'
end
end
---
name: show_inherited_labels
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42960
rollout_issue_url:
group: group::project management
type: development
default_enabled: false
...@@ -9,6 +9,8 @@ RSpec.describe Groups::LabelsController do ...@@ -9,6 +9,8 @@ RSpec.describe Groups::LabelsController do
before do before do
group.add_owner(user) group.add_owner(user)
# by default FFs are enabled in specs so we turn it off
stub_feature_flags(show_inherited_labels: false)
sign_in(user) sign_in(user)
end end
...@@ -32,14 +34,44 @@ RSpec.describe Groups::LabelsController do ...@@ -32,14 +34,44 @@ RSpec.describe Groups::LabelsController do
subgroup.add_owner(user) subgroup.add_owner(user)
end end
RSpec.shared_examples 'returns ancestor group labels' do
it 'returns ancestor group labels' do it 'returns ancestor group labels' do
get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json get :index, params: params, format: :json
label_ids = json_response.map {|label| label['title']} label_ids = json_response.map {|label| label['title']}
expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title])
end end
end end
context 'when include_ancestor_groups true' do
let(:params) { { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true } }
it_behaves_like 'returns ancestor group labels'
end
context 'when include_ancestor_groups false' do
let(:params) { { group_id: subgroup, only_group_labels: true } }
it 'does not return ancestor group labels', :aggregate_failures do
get :index, params: params, format: :json
label_ids = json_response.map {|label| label['title']}
expect(label_ids).to match_array([subgroup_label_1.title])
expect(label_ids).not_to include([group_label_1.title])
end
end
context 'when show_inherited_labels enabled' do
let(:params) { { group_id: subgroup } }
before do
stub_feature_flags(show_inherited_labels: true)
end
it_behaves_like 'returns ancestor group labels'
end
end
context 'external authorization' do context 'external authorization' do
subject { get :index, params: { group_id: group.to_param } } subject { get :index, params: { group_id: group.to_param } }
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::LabelsController do RSpec.describe Projects::LabelsController do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:project) { create(:project, namespace: group) } let_it_be(:project, reload: true) { create(:project, namespace: group) }
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -14,16 +14,21 @@ RSpec.describe Projects::LabelsController do ...@@ -14,16 +14,21 @@ RSpec.describe Projects::LabelsController do
end end
describe 'GET #index' do describe 'GET #index' do
let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } let_it_be(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') }
let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } let_it_be(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') }
let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } let_it_be(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') }
let!(:label_4) { create(:label, project: project, title: 'Label 4') } let_it_be(:label_4) { create(:label, project: project, title: 'Label 4') }
let!(:label_5) { create(:label, project: project, title: 'Label 5') } let_it_be(:label_5) { create(:label, project: project, title: 'Label 5') }
let!(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') } let_it_be(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') }
let!(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') } let_it_be(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') }
let!(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') } let_it_be(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') }
let!(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') } let_it_be(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') }
let_it_be(:group_labels) { [group_label_3, group_label_4]}
let_it_be(:project_labels) { [label_4, label_5]}
let_it_be(:group_priority_labels) { [group_label_1, group_label_2]}
let_it_be(:project_priority_labels) { [label_1, label_2, label_3]}
before do before do
create(:label_priority, project: project, label: group_label_1, priority: 3) create(:label_priority, project: project, label: group_label_1, priority: 3)
...@@ -68,6 +73,60 @@ RSpec.describe Projects::LabelsController do ...@@ -68,6 +73,60 @@ RSpec.describe Projects::LabelsController do
end end
end end
context 'with subgroups' do
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') }
let_it_be(:subgroup_label_2) { create(:group_label, group: subgroup, title: 'subgroup_label_2') }
before do
project.update!(namespace: subgroup)
subgroup.add_owner(user)
create(:label_priority, project: project, label: subgroup_label_2, priority: 1)
end
RSpec.shared_examples 'returns ancestor group labels' do
it 'returns ancestor group labels', :aggregate_failures do
get :index, params: params
expect(assigns(:labels)).to match_array([subgroup_label_1] + group_labels + project_labels)
expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + group_priority_labels + project_priority_labels)
end
end
context 'when show_inherited_labels disabled' do
before do
stub_feature_flags(show_inherited_labels: false)
end
context 'when include_ancestor_groups false' do
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
it 'does not return ancestor group labels', :aggregate_failures do
get :index, params: params
expect(assigns(:labels)).to match_array([subgroup_label_1] + project_labels)
expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + project_priority_labels)
end
end
context 'when include_ancestor_groups true' do
let(:params) { { namespace_id: project.namespace.to_param, project_id: project, include_ancestor_groups: true } }
it_behaves_like 'returns ancestor group labels'
end
end
context 'when show_inherited_labels enabled' do
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
before do
stub_feature_flags(show_inherited_labels: true)
end
it_behaves_like 'returns ancestor group labels'
end
end
def list_labels def list_labels
get :index, params: { namespace_id: project.namespace.to_param, project_id: project } get :index, params: { namespace_id: project.namespace.to_param, project_id: project }
end end
...@@ -75,7 +134,7 @@ RSpec.describe Projects::LabelsController do ...@@ -75,7 +134,7 @@ RSpec.describe Projects::LabelsController do
describe 'POST #generate' do describe 'POST #generate' do
context 'personal project' do context 'personal project' do
let(:personal_project) { create(:project, namespace: user.namespace) } let_it_be(:personal_project) { create(:project, namespace: user.namespace) }
it 'creates labels' do it 'creates labels' do
post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project } post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project }
...@@ -116,8 +175,8 @@ RSpec.describe Projects::LabelsController do ...@@ -116,8 +175,8 @@ RSpec.describe Projects::LabelsController do
end end
describe 'POST #promote' do describe 'POST #promote' do
let!(:promoted_label_name) { "Promoted Label" } let_it_be(:promoted_label_name) { "Promoted Label" }
let!(:label_1) { create(:label, title: promoted_label_name, project: project) } let_it_be(:label_1) { create(:label, title: promoted_label_name, project: project) }
context 'not group reporters' do context 'not group reporters' do
it 'denies access' do it 'denies access' do
...@@ -196,7 +255,7 @@ RSpec.describe Projects::LabelsController do ...@@ -196,7 +255,7 @@ RSpec.describe Projects::LabelsController do
end end
context 'when requesting a redirected path' do context 'when requesting a redirected path' do
let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'redirects to the canonical path' do it 'redirects to the canonical path' do
get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
...@@ -242,7 +301,7 @@ RSpec.describe Projects::LabelsController do ...@@ -242,7 +301,7 @@ RSpec.describe Projects::LabelsController do
end end
context 'when requesting a redirected path' do context 'when requesting a redirected path' do
let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'returns not found' do it 'returns not found' do
post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupLabelsFinder, '#execute' do
let!(:group) { create(:group) }
let!(:user) { create(:user) }
let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) }
let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) }
it 'returns all group labels sorted by name if no params' do
result = described_class.new(user, group).execute
expect(result.to_a).to match_array([label2, label1])
end
it 'returns all group labels sorted by name desc' do
result = described_class.new(user, group, sort: 'name_desc').execute
expect(result.to_a).to match_array([label2, label1])
end
it 'returns group labels that match search' do
result = described_class.new(user, group, search: 'Foo').execute
expect(result.to_a).to match_array([label1])
end
it 'returns group labels user subscribed to' do
label2.subscribe(user)
result = described_class.new(user, group, subscribed: 'true').execute
expect(result.to_a).to match_array([label2])
end
it 'returns second page of labels' do
result = described_class.new(user, group, page: '2').execute
expect(result.to_a).to match_array([])
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