Merge branch 'bw-add-graphql-groups' into 'master'
Add basic GraphQL for a Group Closes #60786 See merge request gitlab-org/gitlab-ce!27492
This commit is contained in:
commit
1911521043
13 changed files with 222 additions and 5 deletions
|
@ -7,14 +7,14 @@ module Resolvers
|
|||
prepended do
|
||||
argument :full_path, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: 'The full path of the project or namespace, e.g., "gitlab-org/gitlab-ce"'
|
||||
description: 'The full path of the project, group or namespace, e.g., "gitlab-org/gitlab-ce"'
|
||||
end
|
||||
|
||||
def model_by_full_path(model, full_path)
|
||||
BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args|
|
||||
# `with_route` avoids an N+1 calculating full_path
|
||||
args[:key].where_full_path_in(full_paths).with_route.each do |project|
|
||||
loader.call(project.full_path, project)
|
||||
args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
|
||||
loader.call(model_instance.full_path, model_instance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
13
app/graphql/resolvers/group_resolver.rb
Normal file
13
app/graphql/resolvers/group_resolver.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class GroupResolver < BaseResolver
|
||||
prepend FullPathResolver
|
||||
|
||||
type Types::GroupType, null: true
|
||||
|
||||
def resolve(full_path:)
|
||||
model_by_full_path(Group, full_path)
|
||||
end
|
||||
end
|
||||
end
|
21
app/graphql/types/group_type.rb
Normal file
21
app/graphql/types/group_type.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class GroupType < NamespaceType
|
||||
graphql_name 'Group'
|
||||
|
||||
authorize :read_group
|
||||
|
||||
expose_permissions Types::PermissionTypes::Group
|
||||
|
||||
field :web_url, GraphQL::STRING_TYPE, null: true
|
||||
|
||||
field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do
|
||||
group.avatar_url(only_path: false)
|
||||
end
|
||||
|
||||
if ::Group.supports_nested_objects?
|
||||
field :parent, GroupType, null: true
|
||||
end
|
||||
end
|
||||
end
|
19
app/graphql/types/namespace_type.rb
Normal file
19
app/graphql/types/namespace_type.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class NamespaceType < BaseObject
|
||||
graphql_name 'Namespace'
|
||||
|
||||
field :id, GraphQL::ID_TYPE, null: false
|
||||
|
||||
field :name, GraphQL::STRING_TYPE, null: false
|
||||
field :path, GraphQL::STRING_TYPE, null: false
|
||||
field :full_name, GraphQL::STRING_TYPE, null: false
|
||||
field :full_path, GraphQL::ID_TYPE, null: false
|
||||
|
||||
field :description, GraphQL::STRING_TYPE, null: true
|
||||
field :visibility, GraphQL::STRING_TYPE, null: true
|
||||
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?
|
||||
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
|
||||
end
|
||||
end
|
11
app/graphql/types/permission_types/group.rb
Normal file
11
app/graphql/types/permission_types/group.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module PermissionTypes
|
||||
class Group < BasePermissionType
|
||||
graphql_name 'GroupPermissions'
|
||||
|
||||
abilities :read_group
|
||||
end
|
||||
end
|
||||
end
|
|
@ -66,6 +66,9 @@ module Types
|
|||
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true
|
||||
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true
|
||||
|
||||
field :namespace, Types::NamespaceType, null: false
|
||||
field :group, Types::GroupType, null: true
|
||||
|
||||
field :merge_requests,
|
||||
Types::MergeRequestType.connection_type,
|
||||
null: true,
|
||||
|
|
|
@ -9,6 +9,11 @@ module Types
|
|||
resolver: Resolvers::ProjectResolver,
|
||||
description: "Find a project"
|
||||
|
||||
field :group, Types::GroupType,
|
||||
null: true,
|
||||
resolver: Resolvers::GroupResolver,
|
||||
description: "Find a group"
|
||||
|
||||
field :metadata, Types::MetadataType,
|
||||
null: true,
|
||||
resolver: Resolvers::MetadataResolver,
|
||||
|
|
5
changelogs/unreleased/bw-add-graphql-groups.yml
Normal file
5
changelogs/unreleased/bw-add-graphql-groups.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add initial GraphQL query for Groups
|
||||
merge_request: 27492
|
||||
author:
|
||||
type: added
|
|
@ -29,7 +29,11 @@ curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gi
|
|||
|
||||
## Available queries
|
||||
|
||||
A first iteration of a GraphQL API includes a query for: `project`. Within a project it is also possible to fetch a `mergeRequest` by IID.
|
||||
A first iteration of a GraphQL API includes the following queries
|
||||
|
||||
1. `project` : Within a project it is also possible to fetch a `mergeRequest` by IID.
|
||||
|
||||
1. `group` : Only basic group information is currently supported.
|
||||
|
||||
## GraphiQL
|
||||
|
||||
|
|
11
spec/graphql/types/group_type_spec.rb
Normal file
11
spec/graphql/types/group_type_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GitlabSchema.types['Group'] do
|
||||
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
|
||||
|
||||
it { expect(described_class.graphql_name).to eq('Group') }
|
||||
|
||||
it { expect(described_class).to require_graphql_authorizations(:read_group) }
|
||||
end
|
7
spec/graphql/types/namespace_type.rb
Normal file
7
spec/graphql/types/namespace_type.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GitlabSchema.types['Namespace'] do
|
||||
it { expect(described_class.graphql_name).to eq('Namespace') }
|
||||
end
|
|
@ -5,7 +5,7 @@ describe GitlabSchema.types['Query'] do
|
|||
expect(described_class.graphql_name).to eq('Query')
|
||||
end
|
||||
|
||||
it { is_expected.to have_graphql_fields(:project, :echo, :metadata) }
|
||||
it { is_expected.to have_graphql_fields(:project, :group, :echo, :metadata) }
|
||||
|
||||
describe 'project field' do
|
||||
subject { described_class.fields['project'] }
|
||||
|
|
118
spec/requests/api/graphql/group_query_spec.rb
Normal file
118
spec/requests/api/graphql/group_query_spec.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
# Based on spec/requests/api/groups_spec.rb
|
||||
# Should follow closely in order to ensure all situations are covered
|
||||
describe 'getting group information' do
|
||||
include GraphqlHelpers
|
||||
include UploadHelpers
|
||||
|
||||
let(:user1) { create(:user, can_create_group: false) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:public_group) { create(:group, :public) }
|
||||
let(:private_group) { create(:group, :private) }
|
||||
|
||||
# similar to the API "GET /groups/:id"
|
||||
describe "Query group(fullPath)" do
|
||||
def group_query(group)
|
||||
graphql_query_for('group', 'fullPath' => group.full_path)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query' do
|
||||
before do
|
||||
post_graphql(group_query(public_group))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns nil for a private group' do
|
||||
post_graphql(group_query(private_group))
|
||||
|
||||
expect(graphql_data['group']).to be_nil
|
||||
end
|
||||
|
||||
it 'returns a public group' do
|
||||
post_graphql(group_query(public_group))
|
||||
|
||||
expect(graphql_data['group']).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated as user" do
|
||||
let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
|
||||
let!(:group2) { create(:group, :private) }
|
||||
|
||||
before do
|
||||
group1.add_owner(user1)
|
||||
group2.add_owner(user2)
|
||||
end
|
||||
|
||||
it "returns one of user1's groups" do
|
||||
project = create(:project, namespace: group2, path: 'Foo')
|
||||
create(:project_group_link, project: project, group: group1)
|
||||
|
||||
post_graphql(group_query(group1), current_user: user1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(graphql_data['group']['id']).to eq(group1.id.to_s)
|
||||
expect(graphql_data['group']['name']).to eq(group1.name)
|
||||
expect(graphql_data['group']['path']).to eq(group1.path)
|
||||
expect(graphql_data['group']['description']).to eq(group1.description)
|
||||
expect(graphql_data['group']['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level))
|
||||
expect(graphql_data['group']['avatarUrl']).to eq(group1.avatar_url(only_path: false))
|
||||
expect(graphql_data['group']['webUrl']).to eq(group1.web_url)
|
||||
expect(graphql_data['group']['requestAccessEnabled']).to eq(group1.request_access_enabled)
|
||||
expect(graphql_data['group']['fullName']).to eq(group1.full_name)
|
||||
expect(graphql_data['group']['fullPath']).to eq(group1.full_path)
|
||||
expect(graphql_data['group']['parentId']).to eq(group1.parent_id)
|
||||
end
|
||||
|
||||
it "does not return a non existing group" do
|
||||
query = graphql_query_for('group', 'fullPath' => '1328')
|
||||
|
||||
post_graphql(query, current_user: user1)
|
||||
|
||||
expect(graphql_data['group']).to be_nil
|
||||
end
|
||||
|
||||
it "does not return a group not attached to user1" do
|
||||
private_group.add_owner(user2)
|
||||
|
||||
post_graphql(group_query(private_group), current_user: user1)
|
||||
|
||||
expect(graphql_data['group']).to be_nil
|
||||
end
|
||||
|
||||
it 'avoids N+1 queries' do
|
||||
post_graphql(group_query(group1), current_user: admin)
|
||||
|
||||
control_count = ActiveRecord::QueryRecorder.new do
|
||||
post_graphql(group_query(group1), current_user: admin)
|
||||
end.count
|
||||
|
||||
create(:project, namespace: group1)
|
||||
|
||||
expect do
|
||||
post_graphql(group_query(group1), current_user: admin)
|
||||
end.not_to exceed_query_limit(control_count)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated as admin" do
|
||||
it "returns any existing group" do
|
||||
post_graphql(group_query(private_group), current_user: admin)
|
||||
|
||||
expect(graphql_data['group']['name']).to eq(private_group.name)
|
||||
end
|
||||
|
||||
it "does not return a non existing group" do
|
||||
query = graphql_query_for('group', 'fullPath' => '1328')
|
||||
post_graphql(query, current_user: admin)
|
||||
|
||||
expect(graphql_data['group']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue