Commit 9d6e0610 authored by John Hope's avatar John Hope Committed by Bob Van Landuyt

Resolve "GraphQL: Mutation for changing locked status of an issue"

parent 7ce7a8a3
# frozen_string_literal: true
module Mutations
module Issues
class SetLocked < Base
graphql_name 'IssueSetLocked'
argument :locked,
GraphQL::BOOLEAN_TYPE,
required: true,
description: 'Whether or not to lock discussion on the issue'
def resolve(project_path:, iid:, locked:)
issue = authorized_find!(project_path: project_path, iid: iid)
::Issues::UpdateService.new(issue.project, current_user, discussion_locked: locked)
.execute(issue)
{
issue: issue,
errors: errors_on_object(issue)
}
end
end
end
end
......@@ -17,6 +17,7 @@ module Types
mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetLocked
mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::MergeRequests::Create
......
---
title: 'GraphQL mutation for changing locked status of an issue'
merge_request: 36866
author:
type: added
......@@ -6178,6 +6178,51 @@ type IssueSetIterationPayload {
issue: Issue
}
"""
Autogenerated input type of IssueSetLocked
"""
input IssueSetLockedInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The iid of the issue to mutate
"""
iid: String!
"""
Whether or not to lock discussion on the issue
"""
locked: Boolean!
"""
The project the issue to mutate is in
"""
projectPath: ID!
}
"""
Autogenerated return type of IssueSetLocked
"""
type IssueSetLockedPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue after mutation
"""
issue: Issue
}
"""
Autogenerated input type of IssueSetWeight
"""
......@@ -8087,6 +8132,7 @@ type Mutation {
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload
issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload
issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
jiraImportUsers(input: JiraImportUsersInput!): JiraImportUsersPayload
......
......@@ -17078,6 +17078,136 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IssueSetLockedInput",
"description": "Autogenerated input type of IssueSetLocked",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project the issue to mutate is in",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "locked",
"description": "Whether or not to lock discussion on the issue",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"defaultValue": null
},
{
"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",
"name": "IssueSetLockedPayload",
"description": "Autogenerated return type of IssueSetLocked",
"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": "Errors encountered during execution of the mutation.",
"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": "issue",
"description": "The issue after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IssueSetWeightInput",
......@@ -23457,6 +23587,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueSetLocked",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "IssueSetLockedInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "IssueSetLockedPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueSetWeight",
"description": null,
......@@ -928,6 +928,16 @@ Autogenerated return type of IssueSetIteration
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## IssueSetLockedPayload
Autogenerated return type of IssueSetLocked
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## IssueSetWeightPayload
Autogenerated return type of IssueSetWeight
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::SetLocked do
let_it_be(:issue) { create(:issue) }
let_it_be(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
describe '#resolve' do
let(:locked) { true }
subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, locked: locked) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the issue' do
let(:mutated_issue) { subject[:issue] }
before do
issue.project.add_developer(user)
end
it 'returns the issue as discussion locked' do
expect(mutated_issue).to eq(issue)
expect(mutated_issue).to be_discussion_locked
expect(subject[:errors]).to be_empty
end
context 'when passing locked as false' do
let(:locked) { false }
it 'unlocks the discussion' do
issue.update!(discussion_locked: true)
expect(mutated_issue).not_to be_discussion_locked
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Setting an issue as locked' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:issue) { create(:issue) }
let_it_be(:project) { issue.project }
let(:input) { { locked: true } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: issue.iid.to_s
}
graphql_mutation(:issue_set_locked, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
issue {
iid
discussionLocked
}
QL
)
end
def mutation_response
graphql_mutation_response(:issue_set_locked)
end
context 'when the user is not allowed to update the issue' do
it 'returns an error' do
error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
post_graphql_mutation(mutation, current_user: current_user)
expect(graphql_errors).to include(a_hash_including('message' => error))
end
end
context 'when user is allowed to update the issue' do
before do
project.add_developer(current_user)
end
it 'updates the issue locked status' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['issue']['discussionLocked']).to be_truthy
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