Commit 641f3e14 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '31914-graphql-mark-all-todos-as-done-pd' into 'master'

Add GraphQL mutation to mark all todos done for a user

See merge request gitlab-org/gitlab!19482
parents d28e1eb7 60894d9f
...@@ -9,6 +9,12 @@ module Mutations ...@@ -9,6 +9,12 @@ module Mutations
GitlabSchema.object_from_id(id) GitlabSchema.object_from_id(id)
end end
def map_to_global_ids(ids)
return [] if ids.blank?
ids.map { |id| to_global_id(id) }
end
def to_global_id(id) def to_global_id(id)
::URI::GID.build(app: GlobalID.app, model_name: Todo.name, model_id: id, params: nil).to_s ::URI::GID.build(app: GlobalID.app, model_name: Todo.name, model_id: id, params: nil).to_s
end end
......
# frozen_string_literal: true
module Mutations
module Todos
class MarkAllDone < ::Mutations::Todos::Base
graphql_name 'TodosMarkAllDone'
authorize :update_user
field :updated_ids,
[GraphQL::ID_TYPE],
null: false,
description: 'Ids of the updated todos'
def resolve
authorize!(current_user)
updated_ids = mark_all_todos_done
{
updated_ids: map_to_global_ids(updated_ids),
errors: []
}
end
private
def mark_all_todos_done
return [] unless current_user
TodoService.new.mark_all_todos_as_done_by_user(current_user)
end
end
end
end
...@@ -22,6 +22,7 @@ module Types ...@@ -22,6 +22,7 @@ module Types
mount_mutation Mutations::Notes::Destroy mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Todos::MarkDone mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore mount_mutation Mutations::Todos::Restore
mount_mutation Mutations::Todos::MarkAllDone
end end
end end
......
...@@ -174,6 +174,11 @@ class TodoService ...@@ -174,6 +174,11 @@ class TodoService
mark_todos_as_done(todos, current_user) mark_todos_as_done(todos, current_user)
end end
def mark_all_todos_as_done_by_user(current_user)
todos = TodosFinder.new(current_user).execute
mark_todos_as_done(todos, current_user)
end
# When user marks some todos as pending # When user marks some todos as pending
def mark_todos_as_pending(todos, current_user) def mark_todos_as_pending(todos, current_user)
update_todos_state(todos, current_user, :pending) update_todos_state(todos, current_user, :pending)
......
---
title: Add GraphQL mutation to mark all todos done for a user
merge_request: 19482
author:
type: added
...@@ -3520,6 +3520,7 @@ type Mutation { ...@@ -3520,6 +3520,7 @@ type Mutation {
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
todoRestore(input: TodoRestoreInput!): TodoRestorePayload todoRestore(input: TodoRestoreInput!): TodoRestorePayload
todosMarkAllDone(input: TodosMarkAllDoneInput!): TodosMarkAllDonePayload
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
updateNote(input: UpdateNoteInput!): UpdateNotePayload updateNote(input: UpdateNoteInput!): UpdateNotePayload
...@@ -5060,6 +5061,36 @@ enum TodoTargetEnum { ...@@ -5060,6 +5061,36 @@ enum TodoTargetEnum {
MERGEREQUEST MERGEREQUEST
} }
"""
Autogenerated input type of TodosMarkAllDone
"""
input TodosMarkAllDoneInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
}
"""
Autogenerated return type of TodosMarkAllDone
"""
type TodosMarkAllDonePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Reasons why the mutation failed.
"""
errors: [String!]!
"""
Ids of the updated todos
"""
updatedIds: [ID!]!
}
""" """
Autogenerated input type of ToggleAwardEmoji Autogenerated input type of ToggleAwardEmoji
""" """
......
...@@ -14355,6 +14355,33 @@ ...@@ -14355,6 +14355,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "todosMarkAllDone",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TodosMarkAllDonePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "toggleAwardEmoji", "name": "toggleAwardEmoji",
"description": null, "description": null,
...@@ -16825,6 +16852,106 @@ ...@@ -16825,6 +16852,106 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "TodosMarkAllDonePayload",
"description": "Autogenerated return type of TodosMarkAllDone",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Reasons why the mutation failed.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedIds",
"description": "Ids of the updated todos",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
"description": "Autogenerated input type of TodosMarkAllDone",
"fields": null,
"inputFields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "DesignManagementUploadPayload", "name": "DesignManagementUploadPayload",
......
...@@ -785,6 +785,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -785,6 +785,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `errors` | String! => Array | Reasons why the mutation failed. | | `errors` | String! => Array | Reasons why the mutation failed. |
| `todo` | Todo! | The requested todo | | `todo` | Todo! | The requested todo |
### TodosMarkAllDonePayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `updatedIds` | ID! => Array | Ids of the updated todos |
### ToggleAwardEmojiPayload ### ToggleAwardEmojiPayload
| Name | Type | Description | | Name | Type | Description |
......
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::Todos::MarkAllDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let_it_be(:user3) { create(:user) }
describe '#resolve' do
it 'marks all pending todos as done' do
updated_todo_ids = mutation_for(current_user).resolve.dig(:updated_ids)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
end
it 'behaves as expected if there are no todos for the requesting user' do
updated_todo_ids = mutation_for(user3).resolve.dig(:updated_ids)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to be_empty
end
context 'when user is not logged in' do
it 'fails with the expected error' do
expect { mutation_for(nil).resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
def mutation_for(user)
described_class.new(object: nil, context: { current_user: user })
end
end
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Mutations::Todos::MarkDone do describe Mutations::Todos::MarkDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
...@@ -59,8 +61,4 @@ describe Mutations::Todos::MarkDone do ...@@ -59,8 +61,4 @@ describe Mutations::Todos::MarkDone do
def mark_done_mutation(todo) def mark_done_mutation(todo)
mutation.resolve(id: global_id_of(todo)) mutation.resolve(id: global_id_of(todo))
end end
def global_id_of(todo)
todo.to_global_id.to_s
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'Marking all todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:other_user2) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:input) { {} }
let(:mutation) do
graphql_mutation(:todos_mark_all_done, input,
<<-QL.strip_heredoc
clientMutationId
errors
updatedIds
QL
)
end
def mutation_response
graphql_mutation_response(:todos_mark_all_done)
end
it 'marks all pending todos as done' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
updated_todo_ids = mutation_response['updatedIds']
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
end
it 'behaves as expected if there are no todos for the requesting user' do
post_graphql_mutation(mutation, current_user: other_user2)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('pending')
updated_todo_ids = mutation_response['updatedIds']
expect(updated_todo_ids).to be_empty
end
context 'when user is not logged in' do
let(:current_user) { nil }
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
end
end
...@@ -1015,6 +1015,21 @@ describe TodoService do ...@@ -1015,6 +1015,21 @@ describe TodoService do
end end
end end
describe '#mark_all_todos_as_done_by_user' do
it 'marks all todos done' do
todo1 = create(:todo, user: john_doe, state: :pending)
todo2 = create(:todo, user: john_doe, state: :done)
todo3 = create(:todo, user: john_doe, state: :pending)
ids = described_class.new.mark_all_todos_as_done_by_user(john_doe)
expect(ids).to contain_exactly(todo1.id, todo3.id)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
end
end
describe '#mark_todos_as_done_by_ids' do describe '#mark_todos_as_done_by_ids' do
let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
......
...@@ -297,6 +297,10 @@ module GraphqlHelpers ...@@ -297,6 +297,10 @@ module GraphqlHelpers
extract_attribute ? item['node'][extract_attribute] : item['node'] extract_attribute ? item['node'][extract_attribute] : item['node']
end end
end end
def global_id_of(model)
model.to_global_id.to_s
end
end end
# This warms our schema, doing this as part of loading the helpers to avoid # This warms our schema, doing this as part of loading the helpers to avoid
......
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