Commit 65921bdd authored by Adam Hegyi's avatar Adam Hegyi

Merge branch 'graphql-get-alert-todo' into 'master'

Support getting a todo for an alert in graphql api

See merge request gitlab-org/gitlab!34789
parents 7c94a427 0100d939
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
# action_id: integer # action_id: integer
# author_id: integer # author_id: integer
# project_id; integer # project_id; integer
# target_id; integer
# state: 'pending' (default) or 'done' # state: 'pending' (default) or 'done'
# type: 'Issue' or 'MergeRequest' or ['Issue', 'MergeRequest'] # type: 'Issue' or 'MergeRequest' or ['Issue', 'MergeRequest']
# #
...@@ -23,7 +24,7 @@ class TodosFinder ...@@ -23,7 +24,7 @@ class TodosFinder
NONE = '0' NONE = '0'
TODO_TYPES = Set.new(%w(Issue MergeRequest DesignManagement::Design)).freeze TODO_TYPES = Set.new(%w(Issue MergeRequest DesignManagement::Design AlertManagement::Alert)).freeze
attr_accessor :current_user, :params attr_accessor :current_user, :params
...@@ -47,6 +48,7 @@ class TodosFinder ...@@ -47,6 +48,7 @@ class TodosFinder
items = by_action(items) items = by_action(items)
items = by_author(items) items = by_author(items)
items = by_state(items) items = by_state(items)
items = by_target_id(items)
items = by_types(items) items = by_types(items)
items = by_group(items) items = by_group(items)
# Filtering by project HAS TO be the last because we use # Filtering by project HAS TO be the last because we use
...@@ -198,6 +200,12 @@ class TodosFinder ...@@ -198,6 +200,12 @@ class TodosFinder
items.with_states(params[:state]) items.with_states(params[:state])
end end
def by_target_id(items)
return items if params[:target_id].blank?
items.for_target(params[:target_id])
end
def by_types(items) def by_types(items)
if types.any? if types.any?
items.for_type(types) items.for_type(types)
......
...@@ -4,7 +4,7 @@ module Resolvers ...@@ -4,7 +4,7 @@ module Resolvers
class TodoResolver < BaseResolver class TodoResolver < BaseResolver
type Types::TodoType, null: true type Types::TodoType, null: true
alias_method :user, :object alias_method :target, :object
argument :action, [Types::TodoActionEnum], argument :action, [Types::TodoActionEnum],
required: false, required: false,
...@@ -31,9 +31,10 @@ module Resolvers ...@@ -31,9 +31,10 @@ module Resolvers
description: 'The type of the todo' description: 'The type of the todo'
def resolve(**args) def resolve(**args)
return Todo.none if user != context[:current_user] return Todo.none unless current_user.present? && target.present?
return Todo.none if target.is_a?(User) && target != current_user
TodosFinder.new(user, todo_finder_params(args)).execute TodosFinder.new(current_user, todo_finder_params(args)).execute
end end
private private
...@@ -46,6 +47,15 @@ module Resolvers ...@@ -46,6 +47,15 @@ module Resolvers
author_id: args[:author_id], author_id: args[:author_id],
action_id: args[:action], action_id: args[:action],
project_id: args[:project_id] project_id: args[:project_id]
}.merge(target_params)
end
def target_params
return {} unless TodosFinder::TODO_TYPES.include?(target.class.name)
{
type: target.class.name,
target_id: target.id
} }
end end
end end
......
...@@ -97,6 +97,12 @@ module Types ...@@ -97,6 +97,12 @@ module Types
description: 'URL for metrics embed for the alert', description: 'URL for metrics embed for the alert',
resolve: -> (alert, _args, _context) { alert.present.metrics_dashboard_url } resolve: -> (alert, _args, _context) { alert.present.metrics_dashboard_url }
field :todos,
Types::TodoType.connection_type,
null: true,
description: 'Todos of the current user for the alert',
resolver: Resolvers::TodoResolver
def notes def notes
object.ordered_notes object.ordered_notes
end end
......
...@@ -163,7 +163,8 @@ module TodosHelper ...@@ -163,7 +163,8 @@ module TodosHelper
{ id: '', text: 'Any Type' }, { id: '', text: 'Any Type' },
{ id: 'Issue', text: 'Issue' }, { id: 'Issue', text: 'Issue' },
{ id: 'MergeRequest', text: 'Merge Request' }, { id: 'MergeRequest', text: 'Merge Request' },
{ id: 'DesignManagement::Design', text: 'Design' } { id: 'DesignManagement::Design', text: 'Design' },
{ id: 'AlertManagement::Alert', text: 'Alert' }
] ]
end end
......
---
title: Support getting a todo for an alert in GraphQL API
merge_request: 34789
author:
type: added
...@@ -319,6 +319,61 @@ type AlertManagementAlert implements Noteable { ...@@ -319,6 +319,61 @@ type AlertManagementAlert implements Noteable {
""" """
title: String title: String
"""
Todos of the current user for the alert
"""
todos(
"""
The action to be filtered
"""
action: [TodoActionEnum!]
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
The ID of an author
"""
authorId: [ID!]
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
The ID of a group
"""
groupId: [ID!]
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
The ID of a project
"""
projectId: [ID!]
"""
The state of the todo
"""
state: [TodoStateEnum!]
"""
The type of the todo
"""
type: [TodoTargetEnum!]
): TodoConnection
""" """
Timestamp the alert was last updated Timestamp the alert was last updated
""" """
......
...@@ -871,6 +871,167 @@ ...@@ -871,6 +871,167 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "todos",
"description": "Todos of the current user for the alert",
"args": [
{
"name": "action",
"description": "The action to be filtered",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "TodoActionEnum",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "authorId",
"description": "The ID of an author",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "projectId",
"description": "The ID of a project",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "groupId",
"description": "The ID of a group",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "state",
"description": "The state of the todo",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "TodoStateEnum",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "type",
"description": "The type of the todo",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "TodoTargetEnum",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TodoConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "updatedAt", "name": "updatedAt",
"description": "Timestamp the alert was last updated", "description": "Timestamp the alert was last updated",
...@@ -232,6 +232,26 @@ RSpec.describe TodosFinder do ...@@ -232,6 +232,26 @@ RSpec.describe TodosFinder do
expect(todos).to match_array([todo2, todo1]) expect(todos).to match_array([todo2, todo1])
end end
end end
context 'when filtering by target id' do
it 'returns the expected todos for the target' do
todos = finder.new(user, { target_id: issue.id }).execute
expect(todos).to match_array([todo1])
end
it 'returns the expected todos for multiple target ids' do
todos = finder.new(user, { target_id: [issue.id, merge_request.id] }).execute
expect(todos).to match_array([todo1, todo2])
end
it 'returns the expected todos for empty target id collection' do
todos = finder.new(user, { target_id: [] }).execute
expect(todos).to match_array([todo1, todo2])
end
end
end end
context 'external authorization' do context 'external authorization' do
...@@ -307,9 +327,9 @@ RSpec.describe TodosFinder do ...@@ -307,9 +327,9 @@ RSpec.describe TodosFinder do
it 'returns the expected types' do it 'returns the expected types' do
expected_result = expected_result =
if Gitlab.ee? if Gitlab.ee?
%w[Epic Issue MergeRequest DesignManagement::Design] %w[Epic Issue MergeRequest DesignManagement::Design AlertManagement::Alert]
else else
%w[Issue MergeRequest DesignManagement::Design] %w[Issue MergeRequest DesignManagement::Design AlertManagement::Alert]
end end
expect(described_class.todo_types).to contain_exactly(*expected_result) expect(described_class.todo_types).to contain_exactly(*expected_result)
......
...@@ -99,7 +99,7 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -99,7 +99,7 @@ RSpec.describe Resolvers::TodoResolver do
end end
end end
context 'when no user is provided' do context 'when no target is provided' do
it 'returns no todos' do it 'returns no todos' do
todos = resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user }) todos = resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user })
...@@ -107,7 +107,7 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -107,7 +107,7 @@ RSpec.describe Resolvers::TodoResolver do
end end
end end
context 'when provided user is not current user' do context 'when target user is not the current user' do
it 'returns no todos' do it 'returns no todos' do
other_user = create(:user) other_user = create(:user)
...@@ -116,6 +116,16 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -116,6 +116,16 @@ RSpec.describe Resolvers::TodoResolver do
expect(todos).to be_empty expect(todos).to be_empty
end end
end end
context 'when request is for a todo target' do
it 'returns only the todos for the target' do
target = issue_todo_pending.target
todos = resolve(described_class, obj: target, args: {}, ctx: { current_user: current_user })
expect(todos).to contain_exactly(issue_todo_pending)
end
end
end end
def resolve_todos(args = {}, context = { current_user: current_user }) def resolve_todos(args = {}, context = { current_user: current_user })
......
...@@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do ...@@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
notes notes
discussions discussions
metrics_dashboard_url metrics_dashboard_url
todos
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting Alert Management Alert Assignees' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, project: project) }
let_it_be(:other_alert) { create(:alert_management_alert, project: project) }
let_it_be(:todo) { create(:todo, :pending, target: alert, user: current_user, project: project) }
let_it_be(:other_todo) { create(:todo, :pending, target: other_alert, user: current_user, project: project) }
let(:fields) do
<<~QUERY
nodes {
iid
todos {
nodes {
id
}
}
}
QUERY
end
let(:graphql_query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('alertManagementAlerts', {}, fields)
)
end
let(:gql_alerts) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') }
let(:gql_todos) { gql_alerts.map { |gql_alert| [gql_alert['iid'], gql_alert['todos']['nodes']] }.to_h }
let(:gql_alert_todo) { gql_todos[alert.iid.to_s].first }
let(:gql_other_alert_todo) { gql_todos[other_alert.iid.to_s].first }
before do
project.add_developer(current_user)
end
it 'includes the correct metrics dashboard url' do
post_graphql(graphql_query, current_user: current_user)
expect(gql_alert_todo['id']).to eq(todo.to_global_id.to_s)
expect(gql_other_alert_todo['id']).to eq(other_todo.to_global_id.to_s)
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