Commit 226154b2 authored by Alexandru Croitor's avatar Alexandru Croitor

Add labels resolver

Adding separate labels resolved to fetch labels for
groups and projects with corresponding set of parameters.
parent 30741c7a
...@@ -177,7 +177,7 @@ class LabelsFinder < UnionFinder ...@@ -177,7 +177,7 @@ class LabelsFinder < UnionFinder
end end
if group? if group?
@projects = if params[:include_subgroups] @projects = if params[:include_descendant_groups]
@projects.in_namespace(group.self_and_descendants.select(:id)) @projects.in_namespace(group.self_and_descendants.select(:id))
else else
@projects.in_namespace(group.id) @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 ...@@ -107,17 +107,8 @@ module Types
field :labels, field :labels,
Types::LabelType.connection_type, Types::LabelType.connection_type,
null: true, null: true,
description: 'Labels available on this group.' do description: 'Labels available on this group.',
argument :search_term, GraphQL::STRING_TYPE, resolver: Resolvers::GroupLabelsResolver
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
def avatar_url def avatar_url
object.avatar_url(only_path: false) object.avatar_url(only_path: false)
......
...@@ -337,17 +337,8 @@ module Types ...@@ -337,17 +337,8 @@ module Types
field :labels, field :labels,
Types::LabelType.connection_type, Types::LabelType.connection_type,
null: true, null: true,
description: 'Labels available on this project.' do description: 'Labels available on this project.',
argument :search_term, GraphQL::STRING_TYPE, resolver: Resolvers::LabelsResolver
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
def avatar_url def avatar_url
object.avatar_url(only_path: false) 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 { ...@@ -11619,11 +11619,26 @@ type Group {
""" """
first: Int 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. Returns the last _n_ elements from the list.
""" """
last: Int last: Int
"""
Include only group level labels.
"""
onlyGroupLabels: Boolean = false
""" """
A search term to find labels with. A search term to find labels with.
""" """
...@@ -19834,6 +19849,11 @@ type Project { ...@@ -19834,6 +19849,11 @@ type Project {
""" """
first: Int first: Int
"""
Include labels from ancestor groups.
"""
includeAncestorGroups: Boolean = false
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
......
...@@ -31572,6 +31572,46 @@ ...@@ -31572,6 +31572,46 @@
"name": "labels", "name": "labels",
"description": "Labels available on this group.", "description": "Labels available on this group.",
"args": [ "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", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -31611,16 +31651,6 @@ ...@@ -31611,16 +31651,6 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
} }
], ],
"type": { "type": {
...@@ -57717,6 +57747,26 @@ ...@@ -57717,6 +57747,26 @@
"name": "labels", "name": "labels",
"description": "Labels available on this project.", "description": "Labels available on this project.",
"args": [ "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", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -57756,16 +57806,6 @@ ...@@ -57756,16 +57806,6 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "searchTerm",
"description": "A search term to find labels with.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
} }
], ],
"type": { "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 ...@@ -38,5 +38,7 @@ RSpec.describe GitlabSchema.types['Group'] do
it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) } it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) }
end 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 end
...@@ -332,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -332,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) } it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) }
end 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 describe 'jira_imports' do
subject { resolve_field(:jira_imports, project) } subject { resolve_field(:jira_imports, project) }
......
...@@ -18,7 +18,7 @@ RSpec.shared_examples 'a GraphQL type with labels' do ...@@ -18,7 +18,7 @@ RSpec.shared_examples 'a GraphQL type with labels' do
subject { described_class.fields['labels'] } subject { described_class.fields['labels'] }
it { is_expected.to have_graphql_type(Types::LabelType.connection_type) } 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
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