Commit 6391a97b authored by Matthias Käppler's avatar Matthias Käppler

Merge branch 'add-labels-resolver' into 'master'

Add only_group_labels and include_ancestor_labels to group/project labels graphql endpoint

See merge request gitlab-org/gitlab!53639
parents 6b7a8864 226154b2
......@@ -177,7 +177,7 @@ class LabelsFinder < UnionFinder
end
if group?
@projects = if params[:include_subgroups]
@projects = if params[:include_descendant_groups]
@projects.in_namespace(group.self_and_descendants.select(:id))
else
@projects.in_namespace(group.id)
......
# frozen_string_literal: true
module Resolvers
class GroupLabelsResolver < LabelsResolver
type Types::LabelType.connection_type, null: true
argument :include_descendant_groups, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Include labels from descendant groups.',
default_value: false
argument :only_group_labels, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Include only group level labels.',
default_value: false
end
end
# frozen_string_literal: true
module Resolvers
class LabelsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
authorize :read_label
type Types::LabelType.connection_type, null: true
argument :search_term, GraphQL::STRING_TYPE,
required: false,
description: 'A search term to find labels with.'
argument :include_ancestor_groups, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Include labels from ancestor groups.',
default_value: false
def resolve(**args)
return Label.none if parent.nil?
authorize!(parent)
# LabelsFinder uses `search` param, so we transform `search_term` into `search`
args[:search] = args.delete(:search_term)
LabelsFinder.new(current_user, parent_param.merge(args)).execute
end
private
def parent
object.respond_to?(:sync) ? object.sync : object
end
def parent_param
key = case parent
when Group then :group
when Project then :project
else raise "Unexpected parent type: #{parent.class}"
end
{ "#{key}": parent }
end
end
end
......@@ -107,17 +107,8 @@ module Types
field :labels,
Types::LabelType.connection_type,
null: true,
description: 'Labels available on this group.' do
argument :search_term, GraphQL::STRING_TYPE,
required: false,
description: 'A search term to find labels with.'
end
def labels(search_term: nil)
LabelsFinder
.new(current_user, group: group, search: search_term)
.execute
end
description: 'Labels available on this group.',
resolver: Resolvers::GroupLabelsResolver
def avatar_url
object.avatar_url(only_path: false)
......
......@@ -337,17 +337,8 @@ module Types
field :labels,
Types::LabelType.connection_type,
null: true,
description: 'Labels available on this project.' do
argument :search_term, GraphQL::STRING_TYPE,
required: false,
description: 'A search term to find labels with.'
end
def labels(search_term: nil)
LabelsFinder
.new(current_user, project: project, search: search_term)
.execute
end
description: 'Labels available on this project.',
resolver: Resolvers::LabelsResolver
def avatar_url
object.avatar_url(only_path: false)
......
---
title: Adds only_group_labels and include_ancestor_labels and include_descendant_groups
arguments to the project and group labels resolvers respectively
merge_request: 53639
author:
type: fixed
......@@ -11619,11 +11619,26 @@ type Group {
"""
first: Int
"""
Include labels from ancestor groups.
"""
includeAncestorGroups: Boolean = false
"""
Include labels from descendant groups.
"""
includeDescendantGroups: Boolean = false
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Include only group level labels.
"""
onlyGroupLabels: Boolean = false
"""
A search term to find labels with.
"""
......@@ -19834,6 +19849,11 @@ type Project {
"""
first: Int
"""
Include labels from ancestor groups.
"""
includeAncestorGroups: Boolean = false
"""
Returns the last _n_ elements from the list.
"""
......
......@@ -31572,6 +31572,46 @@
"name": "labels",
"description": "Labels available on this group.",
"args": [
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "includeAncestorGroups",
"description": "Include labels from ancestor groups.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{
"name": "includeDescendantGroups",
"description": "Include labels from descendant groups.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{
"name": "onlyGroupLabels",
"description": "Include only group level labels.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -31611,16 +31651,6 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
......@@ -57717,6 +57747,26 @@
"name": "labels",
"description": "Labels available on this project.",
"args": [
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "includeAncestorGroups",
"description": "Include labels from ancestor groups.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -57756,16 +57806,6 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::GroupLabelsResolver do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
let_it_be(:current_user) { create(:user) }
let_it_be(:group, reload: true) { create(:group, :private) }
let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) }
let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) }
let_it_be(:project, reload: true) { create(:project, :private, group: sub_subgroup) }
let_it_be(:label1) { create(:label, project: project, name: 'project feature') }
let_it_be(:label2) { create(:label, project: project, name: 'new project feature') }
let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') }
let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') }
let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') }
let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') }
let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') }
let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') }
specify do
expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type)
end
describe '#resolve' do
context 'with unauthorized user' do
it 'raises error' do
expect { resolve_labels(subgroup) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'with authorized user' do
it 'does not raise error' do
group.add_guest(current_user)
expect { resolve_labels(subgroup) }.not_to raise_error
end
end
context 'without parent' do
it 'returns no labels' do
expect(resolve_labels(nil)).to eq(Label.none)
end
end
context 'at group level' do
before_all do
group.add_developer(current_user)
end
# because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
# the `nil` value would be equivalent to passing in `false` so just check for `nil` option
where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do
nil | nil | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) }
nil | nil | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) }
nil | true | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) }
nil | true | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
true | nil | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) }
true | nil | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) }
true | true | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) }
true | true | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) }
nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) }
nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2, label2) }
nil | true | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2) }
true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) }
true | nil | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) }
true | true | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2, label2) }
true | true | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2) }
end
with_them do
let(:params) do
{
include_ancestor_groups: include_ancestor_groups,
include_descendant_groups: include_descendant_groups,
only_group_labels: only_group_labels,
search_term: search_term
}
end
subject { resolve_labels(subgroup, params) }
it { self.instance_exec(&test) }
end
end
end
def resolve_labels(parent, args = {}, context = { current_user: current_user })
resolve(described_class, obj: parent, args: args, ctx: context)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::LabelsResolver do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
let_it_be(:current_user) { create(:user) }
let_it_be(:group, reload: true) { create(:group, :private) }
let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) }
let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) }
let_it_be(:project, reload: true) { create(:project, :private, group: subgroup) }
let_it_be(:label1) { create(:label, project: project, name: 'project feature') }
let_it_be(:label2) { create(:label, project: project, name: 'new project feature') }
let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') }
let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') }
let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') }
let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') }
let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') }
let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') }
specify do
expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type)
end
describe '#resolve' do
context 'with unauthorized user' do
it 'returns no labels' do
expect { resolve_labels(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'with authorized user' do
it 'returns no labels' do
group.add_guest(current_user)
expect { resolve_labels(project) }.not_to raise_error
end
end
context 'without parent' do
it 'returns no labels' do
expect(resolve_labels(nil)).to eq(Label.none)
end
end
context 'at project level' do
before_all do
group.add_developer(current_user)
end
# because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
# the `nil` value would be equivalent to passing in `false` so just check for `nil` option
where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do
nil | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
nil | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
nil | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
nil | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
true | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
true | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
true | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
true | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
nil | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
true | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
true | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
true | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
end
with_them do
let(:params) do
{
include_ancestor_groups: include_ancestor_groups,
include_descendant_groups: include_descendant_groups,
only_group_labels: only_group_labels,
search_term: search_term
}
end
subject { resolve_labels(project, params) }
it { self.instance_exec(&test) }
end
end
end
def resolve_labels(parent, args = {}, context = { current_user: current_user })
resolve(described_class, obj: parent, args: args, ctx: context)
end
end
......@@ -38,5 +38,7 @@ RSpec.describe GitlabSchema.types['Group'] do
it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) }
end
it_behaves_like 'a GraphQL type with labels'
it_behaves_like 'a GraphQL type with labels' do
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] }
end
end
......@@ -332,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) }
end
it_behaves_like 'a GraphQL type with labels'
it_behaves_like 'a GraphQL type with labels' do
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups] }
end
describe 'jira_imports' do
subject { resolve_field(:jira_imports, project) }
......
......@@ -18,7 +18,7 @@ RSpec.shared_examples 'a GraphQL type with labels' do
subject { described_class.fields['labels'] }
it { is_expected.to have_graphql_type(Types::LabelType.connection_type) }
it { is_expected.to have_graphql_arguments(:search_term) }
it { is_expected.to have_graphql_arguments(labels_resolver_arguments) }
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