Commit 2affb9b4 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch '198439-expose-pending-todo-in-graphql' into 'master'

GraphQL: Expose current user todos for todoables

See merge request gitlab-org/gitlab!40555
parents 539437b6 799a20fe
# frozen_string_literal: true
# Interface to expose todos for the current_user on the `object`
module Types
module CurrentUserTodos
include BaseInterface
field_class Types::BaseField
field :current_user_todos, Types::TodoType.connection_type,
description: 'Todos for the current user',
null: false do
argument :state, Types::TodoStateEnum,
description: 'State of the todos',
required: false
end
def current_user_todos(state: nil)
state ||= %i(done pending) # TodosFinder treats a `nil` state param as `pending`
TodosFinder.new(current_user, state: state, type: object.class.name, target_id: object.id).execute
end
end
end
...@@ -12,6 +12,7 @@ module Types ...@@ -12,6 +12,7 @@ module Types
implements(Types::Notes::NoteableType) implements(Types::Notes::NoteableType)
implements(Types::DesignManagement::DesignFields) implements(Types::DesignManagement::DesignFields)
implements(Types::CurrentUserTodos)
field :versions, field :versions,
Types::DesignManagement::VersionType.connection_type, Types::DesignManagement::VersionType.connection_type,
......
...@@ -7,6 +7,7 @@ module Types ...@@ -7,6 +7,7 @@ module Types
connection_type_class(Types::CountableConnectionType) connection_type_class(Types::CountableConnectionType)
implements(Types::Notes::NoteableType) implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
authorize :read_issue authorize :read_issue
......
...@@ -7,6 +7,7 @@ module Types ...@@ -7,6 +7,7 @@ module Types
connection_type_class(Types::CountableConnectionType) connection_type_class(Types::CountableConnectionType)
implements(Types::Notes::NoteableType) implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
authorize :read_merge_request authorize :read_merge_request
......
...@@ -26,7 +26,7 @@ module Types ...@@ -26,7 +26,7 @@ module Types
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find } resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find }
field :author, Types::UserType, field :author, Types::UserType,
description: 'The owner of this todo', description: 'The author of this todo',
null: false, null: false,
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find } resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find }
......
---
title: Expose the todos of the current user on relevant objects in GraphQL
merge_request: 40555
author:
type: added
...@@ -2944,6 +2944,38 @@ type CreateSnippetPayload { ...@@ -2944,6 +2944,38 @@ type CreateSnippetPayload {
snippet: Snippet snippet: Snippet
} }
interface CurrentUserTodos {
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
}
""" """
Autogenerated input type of DastOnDemandScanCreate Autogenerated input type of DastOnDemandScanCreate
""" """
...@@ -3501,7 +3533,37 @@ type DeleteJobsResponse { ...@@ -3501,7 +3533,37 @@ type DeleteJobsResponse {
""" """
A single design A single design
""" """
type Design implements DesignFields & Noteable { type Design implements CurrentUserTodos & DesignFields & Noteable {
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
""" """
The diff refs for this design The diff refs for this design
""" """
...@@ -4896,7 +4958,7 @@ type EnvironmentEdge { ...@@ -4896,7 +4958,7 @@ type EnvironmentEdge {
""" """
Represents an epic. Represents an epic.
""" """
type Epic implements Noteable { type Epic implements CurrentUserTodos & Noteable {
""" """
Author of the epic Author of the epic
""" """
...@@ -4999,6 +5061,36 @@ type Epic implements Noteable { ...@@ -4999,6 +5061,36 @@ type Epic implements Noteable {
""" """
createdAt: Time createdAt: Time
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
""" """
Number of open and closed descendant epics and issues Number of open and closed descendant epics and issues
""" """
...@@ -5438,7 +5530,7 @@ type EpicHealthStatus { ...@@ -5438,7 +5530,7 @@ type EpicHealthStatus {
""" """
Relationship between an epic and an issue Relationship between an epic and an issue
""" """
type EpicIssue implements Noteable { type EpicIssue implements CurrentUserTodos & Noteable {
""" """
Alert associated to this issue Alert associated to this issue
""" """
...@@ -5494,6 +5586,36 @@ type EpicIssue implements Noteable { ...@@ -5494,6 +5586,36 @@ type EpicIssue implements Noteable {
""" """
createdAt: Time! createdAt: Time!
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
""" """
Description of the issue Description of the issue
""" """
...@@ -7328,7 +7450,7 @@ enum IssuableState { ...@@ -7328,7 +7450,7 @@ enum IssuableState {
opened opened
} }
type Issue implements Noteable { type Issue implements CurrentUserTodos & Noteable {
""" """
Alert associated to this issue Alert associated to this issue
""" """
...@@ -7384,6 +7506,36 @@ type Issue implements Noteable { ...@@ -7384,6 +7506,36 @@ type Issue implements Noteable {
""" """
createdAt: Time! createdAt: Time!
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
""" """
Description of the issue Description of the issue
""" """
...@@ -8964,7 +9116,7 @@ type MemberInterfaceEdge { ...@@ -8964,7 +9116,7 @@ type MemberInterfaceEdge {
node: MemberInterface node: MemberInterface
} }
type MergeRequest implements Noteable { type MergeRequest implements CurrentUserTodos & Noteable {
""" """
Indicates if members of the target project can push to the fork Indicates if members of the target project can push to the fork
""" """
...@@ -9040,6 +9192,36 @@ type MergeRequest implements Noteable { ...@@ -9040,6 +9192,36 @@ type MergeRequest implements Noteable {
""" """
createdAt: Time! createdAt: Time!
"""
Todos for the current user
"""
currentUserTodos(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
State of the todos
"""
state: TodoStateEnum
): TodoConnection!
""" """
Default merge commit message of the merge request Default merge commit message of the merge request
""" """
...@@ -16234,7 +16416,7 @@ type Todo { ...@@ -16234,7 +16416,7 @@ type Todo {
action: TodoActionEnum! action: TodoActionEnum!
""" """
The owner of this todo The author of this todo
""" """
author: User! author: User!
......
...@@ -2374,7 +2374,7 @@ Representing a todo entry ...@@ -2374,7 +2374,7 @@ Representing a todo entry
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `action` | TodoActionEnum! | Action of the todo | | `action` | TodoActionEnum! | Action of the todo |
| `author` | User! | The owner of this todo | | `author` | User! | The author of this todo |
| `body` | String! | Body of the todo | | `body` | String! | Body of the todo |
| `createdAt` | Time! | Timestamp this todo was created | | `createdAt` | Time! | Timestamp this todo was created |
| `group` | Group | Group this todo is associated with | | `group` | Group | Group this todo is associated with |
......
...@@ -14,6 +14,7 @@ module Types ...@@ -14,6 +14,7 @@ module Types
present_using EpicPresenter present_using EpicPresenter
implements(Types::Notes::NoteableType) implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
field :id, GraphQL::ID_TYPE, null: false, field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the epic' description: 'ID of the epic'
......
...@@ -12,9 +12,12 @@ RSpec.describe GitlabSchema.types['Epic'] do ...@@ -12,9 +12,12 @@ RSpec.describe GitlabSchema.types['Epic'] do
web_path web_url relation_path reference issues user_permissions web_path web_url relation_path reference issues user_permissions
notes discussions relative_position subscribed participants notes discussions relative_position subscribed participants
descendant_counts descendant_weight_sum upvotes downvotes health_status descendant_counts descendant_weight_sum upvotes downvotes health_status
current_user_todos
] ]
end end
it { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Epic) } it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Epic) }
it { expect(described_class.graphql_name).to eq('Epic') } it { expect(described_class.graphql_name).to eq('Epic') }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['CurrentUserTodos'] do
specify { expect(described_class.graphql_name).to eq('CurrentUserTodos') }
specify { expect(described_class).to have_graphql_fields(:current_user_todos).only }
end
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['Design'] do RSpec.describe GitlabSchema.types['Design'] do
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it_behaves_like 'a GraphQL type with design fields' do it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[notes discussions versions] } let(:extra_design_fields) { %i[notes current_user_todos discussions versions] }
let_it_be(:design) { create(:design, :with_versions) } let_it_be(:design) { create(:design, :with_versions) }
let(:object_id) { GitlabSchema.id_from_object(design) } let(:object_id) { GitlabSchema.id_from_object(design) }
let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) } let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) }
......
...@@ -11,11 +11,13 @@ RSpec.describe GitlabSchema.types['Issue'] do ...@@ -11,11 +11,13 @@ RSpec.describe GitlabSchema.types['Issue'] do
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) } specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it 'has specific fields' do it 'has specific fields' do
fields = %i[id iid title description state reference author assignees participants labels milestone due_date fields = %i[id iid title description state reference author assignees participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection alert_management_alert severity] designs design_collection alert_management_alert severity current_user_todos]
fields.each do |field_name| fields.each do |field_name|
expect(described_class).to have_graphql_field(field_name) expect(described_class).to have_graphql_field(field_name)
......
...@@ -9,6 +9,8 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do ...@@ -9,6 +9,8 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) } specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it 'has the expected fields' do it 'has the expected fields' do
expected_fields = %w[ expected_fields = %w[
notes discussions user_permissions id iid title title_html description notes discussions user_permissions id iid title title_html description
...@@ -24,7 +26,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do ...@@ -24,7 +26,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
source_branch_exists target_branch_exists source_branch_exists target_branch_exists
upvotes downvotes head_pipeline pipelines task_completion_status upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate milestone assignees participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count total_time_spent reference author merged_at commit_count current_user_todos
] ]
if Gitlab.ee? if Gitlab.ee?
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'A Todoable that implements the CurrentUserTodos interface' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:todoable) { create(:issue, project: project) }
let_it_be(:done_todo) { create(:todo, state: :done, target: todoable, user: current_user) }
let_it_be(:pending_todo) { create(:todo, state: :pending, target: todoable, user: current_user) }
let(:state) { 'null' }
let(:todoable_response) do
graphql_data_at(:project, :issue, :currentUserTodos, :nodes)
end
let(:query) do
<<~GQL
{
project(fullPath: "#{project.full_path}") {
issue(iid: "#{todoable.iid}") {
currentUserTodos(state: #{state}) {
nodes {
#{all_graphql_fields_for('Todo', max_depth: 1)}
}
}
}
}
}
GQL
end
it 'returns todos of the current user' do
post_graphql(query, current_user: current_user)
expect(todoable_response).to contain_exactly(
a_hash_including('id' => global_id_of(done_todo)),
a_hash_including('id' => global_id_of(pending_todo))
)
end
it 'does not return todos of another user', :aggregate_failures do
post_graphql(query, current_user: create(:user))
expect(response).to have_gitlab_http_status(:success)
expect(todoable_response).to be_empty
end
it 'does not error when there is no logged in user', :aggregate_failures do
post_graphql(query)
expect(response).to have_gitlab_http_status(:success)
expect(todoable_response).to be_empty
end
context 'when `state` argument is `pending`' do
let(:state) { 'pending' }
it 'returns just the pending todo' do
post_graphql(query, current_user: current_user)
expect(todoable_response).to contain_exactly(
a_hash_including('id' => global_id_of(pending_todo))
)
end
end
context 'when `state` argument is `done`' do
let(:state) { 'done' }
it 'returns just the done todo' do
post_graphql(query, current_user: current_user)
expect(todoable_response).to contain_exactly(
a_hash_including('id' => global_id_of(done_todo))
)
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