Commit aaceef09 authored by Luke Duncalfe's avatar Luke Duncalfe

Merge branch 'Add-GraphQL-Todoable-interface-for-to-do-targets' into 'master'

Add GraphQL Todoable interface for to-do targets

See merge request gitlab-org/gitlab!73603
parents d58c9b96 4539cd90
......@@ -9,6 +9,7 @@ module Types
present_using ::AlertManagement::AlertPresenter
implements(Types::Notes::NoteableInterface)
implements(Types::TodoableInterface)
authorize :read_alert_management_alert
......@@ -127,6 +128,12 @@ module Types
null: true,
description: 'Alert condition for Prometheus.'
field :web_url,
GraphQL::Types::String,
method: :details_url,
null: false,
description: 'URL of the alert.'
def notes
object.ordered_notes
end
......
......@@ -8,6 +8,8 @@ module Types
present_using CommitPresenter
implements(Types::TodoableInterface)
field :id, type: GraphQL::Types::ID, null: false,
description: 'ID (global ID) of the commit.'
......
......@@ -13,6 +13,12 @@ module Types
implements(Types::Notes::NoteableInterface)
implements(Types::DesignManagement::DesignFields)
implements(Types::CurrentUserTodos)
implements(Types::TodoableInterface)
field :web_url,
GraphQL::Types::String,
null: false,
description: 'URL of the design.'
field :versions,
Types::DesignManagement::VersionType.connection_type,
......@@ -40,6 +46,10 @@ module Types
def request_cache_base_key
self.class.name
end
def web_url
Gitlab::UrlBuilder.build(object)
end
end
end
end
......@@ -8,6 +8,7 @@ module Types
implements(Types::Notes::NoteableInterface)
implements(Types::CurrentUserTodos)
implements(Types::TodoableInterface)
authorize :read_issue
......
......@@ -8,6 +8,7 @@ module Types
implements(Types::Notes::NoteableInterface)
implements(Types::CurrentUserTodos)
implements(Types::TodoableInterface)
authorize :read_merge_request
......
......@@ -31,6 +31,11 @@ module Types
description: 'Action of the to-do item.',
null: false
field :target, Types::TodoableInterface,
description: 'Target of the to-do item.',
calls_gitaly: true,
null: false
field :target_type, Types::TodoTargetEnum,
description: 'Target type of the to-do item.',
null: false
......@@ -59,5 +64,28 @@ module Types
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
def target
if object.for_commit?
Gitlab::Graphql::Loaders::BatchCommitLoader.new(
container_class: Project,
container_id: object.project_id,
oid: object.commit_id
).find
else
Gitlab::Graphql::Loaders::BatchModelLoader.new(target_type_class, object.target_id).find
end
end
private
def target_type_class
klass = object.target_type.safe_constantize
raise "Invalid target type \"#{object.target_type}\"" unless klass < Todoable
klass
end
end
end
Types::TodoType.prepend_mod
# frozen_string_literal: true
module Types
module TodoableInterface
include Types::BaseInterface
graphql_name 'Todoable'
field :web_url, GraphQL::Types::String, null: true, description: 'URL of this object.'
def self.resolve_type(object, context)
case object
when Issue
Types::IssueType
when MergeRequest
Types::MergeRequestType
when ::DesignManagement::Design
Types::DesignManagement::DesignType
when ::AlertManagement::Alert
Types::AlertManagement::AlertType
when Commit
Types::CommitType
else
raise "Unknown GraphQL type for #{object}"
end
end
end
end
Types::TodoableInterface.prepend_mod
......@@ -8595,6 +8595,7 @@ Describes an alert from the project's Alert Management.
| <a id="alertmanagementalertstatus"></a>`status` | [`AlertManagementStatus`](#alertmanagementstatus) | Status of the alert. |
| <a id="alertmanagementalerttitle"></a>`title` | [`String`](#string) | Title of the alert. |
| <a id="alertmanagementalertupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp the alert was last updated. |
| <a id="alertmanagementalertweburl"></a>`webUrl` | [`String!`](#string) | URL of the alert. |
#### Fields with arguments
......@@ -9991,6 +9992,7 @@ A single design.
| <a id="designnotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
| <a id="designnotescount"></a>`notesCount` | [`Int!`](#int) | Total count of user-created notes for this design. |
| <a id="designproject"></a>`project` | [`Project!`](#project) | Project the design belongs to. |
| <a id="designweburl"></a>`webUrl` | [`String!`](#string) | URL of the design. |
#### Fields with arguments
......@@ -15901,6 +15903,7 @@ Representing a to-do entry.
| <a id="todoid"></a>`id` | [`ID!`](#id) | ID of the to-do item. |
| <a id="todoproject"></a>`project` | [`Project`](#project) | Project this to-do item is associated with. |
| <a id="todostate"></a>`state` | [`TodoStateEnum!`](#todostateenum) | State of the to-do item. |
| <a id="todotarget"></a>`target` | [`Todoable!`](#todoable) | Target of the to-do item. |
| <a id="todotargettype"></a>`targetType` | [`TodoTargetEnum!`](#todotargetenum) | Target type of the to-do item. |
### `Topic`
......@@ -19414,6 +19417,25 @@ Returns [`TimeboxReport`](#timeboxreport).
| ---- | ---- | ----------- |
| <a id="timeboxreportinterfacereportfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project or group used as a scope for report. For example, `gitlab-org` or `gitlab-org/gitlab`. |
#### `Todoable`
Implementations:
- [`AlertManagementAlert`](#alertmanagementalert)
- [`BoardEpic`](#boardepic)
- [`Commit`](#commit)
- [`Design`](#design)
- [`Epic`](#epic)
- [`EpicIssue`](#epicissue)
- [`Issue`](#issue)
- [`MergeRequest`](#mergerequest)
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="todoableweburl"></a>`webUrl` | [`String`](#string) | URL of this object. |
#### `User`
Representation of a GitLab user.
# frozen_string_literal: true
module EE
module Types
module TodoableInterface
extend ActiveSupport::Concern
class_methods do
extend ::Gitlab::Utils::Override
override :resolve_type
def resolve_type(object, *)
return ::Types::EpicType if Epic === object
super
end
end
end
end
end
......@@ -17,6 +17,7 @@ module Types
implements(Types::Notes::NoteableInterface)
implements(Types::CurrentUserTodos)
implements(Types::EventableType)
implements(Types::TodoableInterface)
field :confidential, GraphQL::Types::Boolean, null: true,
description: 'Indicates if the epic is confidential.'
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::TodoableInterface do
let(:extended_class) { Types::TodoableInterface }
describe ".resolve_type" do
it 'knows the correct type for EE-only objects' do
expect(extended_class.resolve_type(build(:epic), {})).to eq(Types::EpicType)
end
end
end
......@@ -20,6 +20,8 @@ RSpec.describe GitlabSchema.types['Epic'] do
it { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it { expect(described_class.interfaces).to include(Types::TodoableInterface) }
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Epic) }
it { expect(described_class.graphql_name).to eq('Epic') }
......
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class BatchCommitLoader
def initialize(container_class:, container_id:, oid:)
@container_class = container_class
@container_id = container_id
@oid = oid
end
def find
Gitlab::Graphql::Lazy.with_value(find_containers) do |container|
BatchLoader::GraphQL.for(oid).batch(key: container) do |oids, loader, args|
container = args[:key]
container.repository.commits_by(oids: oids).each do |commit|
loader.call(commit.id, commit) if commit
end
end
end
end
private
def find_containers
BatchLoader::GraphQL.for(container_id.to_i).batch(key: container_class) do |ids, loader, args|
model = args[:key]
results = model.includes(:route).id_in(ids) # rubocop: disable CodeReuse/ActiveRecord
results.each { |record| loader.call(record.id, record) }
end
end
attr_reader :container_class, :container_id, :oid
end
end
end
end
......@@ -7,6 +7,8 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
specify { expect(described_class).to require_graphql_authorizations(:read_alert_management_alert) }
specify { expect(described_class.interfaces).to include(Types::TodoableInterface) }
it 'exposes the expected fields' do
expected_fields = %i[
iid
......@@ -34,6 +36,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
details_url
prometheus_alert
environment
web_url
]
expect(described_class).to have_graphql_fields(*expected_fields)
......
......@@ -7,6 +7,8 @@ RSpec.describe GitlabSchema.types['Commit'] do
specify { expect(described_class).to require_graphql_authorizations(:download_code) }
specify { expect(described_class).to include(Types::TodoableInterface) }
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
:id, :sha, :short_id, :title, :full_title, :full_title_html, :description, :description_html, :message, :title_html, :authored_date,
......
......@@ -5,8 +5,10 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Design'] do
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
specify { expect(described_class.interfaces).to include(Types::TodoableInterface) }
it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[notes current_user_todos discussions versions] }
let(:extra_design_fields) { %i[notes current_user_todos discussions versions web_url] }
let_it_be(:design) { create(:design, :with_versions) }
let(:object_id) { GitlabSchema.id_from_object(design) }
let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) }
......
......@@ -13,6 +13,8 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
specify { expect(described_class.interfaces).to include(Types::TodoableInterface) }
it 'has the expected fields' do
expected_fields = %w[
notes discussions user_permissions id iid title title_html description
......
......@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Todo'] do
it 'has the correct fields' do
expected_fields = [:id, :project, :group, :author, :action, :target_type, :body, :state, :created_at]
expected_fields = [:id, :project, :group, :author, :action, :target, :target_type, :body, :state, :created_at]
expect(described_class).to have_graphql_fields(*expected_fields)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::TodoableInterface do
it 'exposes the expected fields' do
expected_fields = %i[
web_url
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
describe ".resolve_type" do
it 'knows the correct type for objects' do
expect(described_class.resolve_type(build(:issue), {})).to eq(Types::IssueType)
expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType)
expect(described_class.resolve_type(build(:design), {})).to eq(Types::DesignManagement::DesignType)
expect(described_class.resolve_type(build(:alert_management_alert), {})).to eq(Types::AlertManagement::AlertType)
expect(described_class.resolve_type(build(:commit), {})).to eq(Types::CommitType)
end
it 'raises an error for an unknown type' do
project = build(:project)
expect { described_class.resolve_type(project, {}) }.to raise_error("Unknown GraphQL type for #{project}")
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Loaders::BatchCommitLoader do
include RepoHelpers
describe '#find' do
let_it_be(:first_project) { create(:project, :repository) }
let_it_be(:second_project) { create(:project, :repository) }
let_it_be(:first_commit) { first_project.commit(sample_commit.id) }
let_it_be(:second_commit) { first_project.commit(another_sample_commit.id) }
let_it_be(:third_commit) { second_project.commit(sample_big_commit.id) }
it 'finds a commit by id' do
result = described_class.new(
container_class: Project,
container_id: first_project.id,
oid: first_commit.id
).find
expect(result.force).to eq(first_commit)
end
before do
ActiveSupport::Notifications.subscribe('sql.active_record') do |event|
raise "no!" if event.payload[:sql].include?('SELECT "namespaces".')
end
end
it 'only queries once' do
expect do
[
described_class.new(
container_class: Project,
container_id: first_project.id,
oid: first_commit.id
).find,
described_class.new(
container_class: Project,
container_id: first_project.id,
oid: second_commit.id
).find,
described_class.new(
container_class: Project,
container_id: second_project.id,
oid: third_commit.id
).find
].map(&:force)
end.not_to exceed_query_limit(2)
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