Commit 4aaabd8d authored by Jan Provaznik's avatar Jan Provaznik Committed by Jarka Košanová

Graphql mutation for issue subscribe

Allows setting of issue's subscribe status in GraphQL API.
parent 4291fd79
# frozen_string_literal: true
module Mutations
module ResolvesSubscription
extend ActiveSupport::Concern
included do
argument :subscribed_state,
GraphQL::BOOLEAN_TYPE,
required: true,
description: 'The desired state of the subscription'
end
def resolve(project_path:, iid:, subscribed_state:)
resource = authorized_find!(project_path: project_path, iid: iid)
project = resource.project
resource.set_subscription(current_user, subscribed_state, project)
{
resource.class.name.underscore.to_sym => resource,
errors: errors_on_object(resource)
}
end
end
end
# frozen_string_literal: true
module Mutations
module Issues
class SetSubscription < Base
graphql_name 'IssueSetSubscription'
include ResolvesSubscription
end
end
end
...@@ -5,22 +5,7 @@ module Mutations ...@@ -5,22 +5,7 @@ module Mutations
class SetSubscription < Base class SetSubscription < Base
graphql_name 'MergeRequestSetSubscription' graphql_name 'MergeRequestSetSubscription'
argument :subscribed_state, include ResolvesSubscription
GraphQL::BOOLEAN_TYPE,
required: true,
description: 'The desired state of the subscription'
def resolve(project_path:, iid:, subscribed_state:)
merge_request = authorized_find!(project_path: project_path, iid: iid)
project = merge_request.project
merge_request.set_subscription(current_user, subscribed_state, project)
{
merge_request: merge_request,
errors: errors_on_object(merge_request)
}
end
end end
end end
end end
...@@ -20,6 +20,7 @@ module Types ...@@ -20,6 +20,7 @@ module Types
mount_mutation Mutations::Issues::SetConfidential mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetLocked
mount_mutation Mutations::Issues::SetDueDate mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::SetSubscription
mount_mutation Mutations::Issues::Update mount_mutation Mutations::Issues::Update
mount_mutation Mutations::MergeRequests::Create mount_mutation Mutations::MergeRequests::Create
mount_mutation Mutations::MergeRequests::Update mount_mutation Mutations::MergeRequests::Update
......
---
title: Allows setting of issue subscribe status in GraphQL API.
merge_request: 38051
author:
type: added
...@@ -6428,6 +6428,51 @@ type IssueSetLockedPayload { ...@@ -6428,6 +6428,51 @@ type IssueSetLockedPayload {
issue: Issue issue: Issue
} }
"""
Autogenerated input type of IssueSetSubscription
"""
input IssueSetSubscriptionInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The IID of the issue to mutate
"""
iid: String!
"""
The project the issue to mutate is in
"""
projectPath: ID!
"""
The desired state of the subscription
"""
subscribedState: Boolean!
}
"""
Autogenerated return type of IssueSetSubscription
"""
type IssueSetSubscriptionPayload {
"""
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 Autogenerated input type of IssueSetWeight
""" """
...@@ -8346,6 +8391,7 @@ type Mutation { ...@@ -8346,6 +8391,7 @@ type Mutation {
issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload
issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload
issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload
issueSetSubscription(input: IssueSetSubscriptionInput!): IssueSetSubscriptionPayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
jiraImportUsers(input: JiraImportUsersInput!): JiraImportUsersPayload jiraImportUsers(input: JiraImportUsersInput!): JiraImportUsersPayload
......
...@@ -17834,6 +17834,136 @@ ...@@ -17834,6 +17834,136 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "IssueSetSubscriptionInput",
"description": "Autogenerated input type of IssueSetSubscription",
"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": "subscribedState",
"description": "The desired state of the subscription",
"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": "IssueSetSubscriptionPayload",
"description": "Autogenerated return type of IssueSetSubscription",
"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", "kind": "INPUT_OBJECT",
"name": "IssueSetWeightInput", "name": "IssueSetWeightInput",
...@@ -24339,6 +24469,33 @@ ...@@ -24339,6 +24469,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "issueSetSubscription",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "IssueSetSubscriptionInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "IssueSetSubscriptionPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "issueSetWeight", "name": "issueSetWeight",
"description": null, "description": null,
...@@ -985,6 +985,16 @@ Autogenerated return type of IssueSetLocked ...@@ -985,6 +985,16 @@ Autogenerated return type of IssueSetLocked
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation | | `issue` | Issue | The issue after mutation |
## IssueSetSubscriptionPayload
Autogenerated return type of IssueSetSubscription
| 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 ## IssueSetWeightPayload
Autogenerated return type of IssueSetWeight Autogenerated return type of IssueSetWeight
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::SetSubscription do
it_behaves_like 'a subscribeable graphql resource' do
let_it_be(:resource) { create(:issue) }
let(:permission_name) { :update_issue }
end
end
...@@ -3,44 +3,8 @@ ...@@ -3,44 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::MergeRequests::SetSubscription do RSpec.describe Mutations::MergeRequests::SetSubscription do
let(:merge_request) { create(:merge_request) } it_behaves_like 'a subscribeable graphql resource' do
let(:project) { merge_request.project } let_it_be(:resource) { create(:merge_request) }
let(:user) { create(:user) } let(:permission_name) { :update_merge_request }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) }
describe '#resolve' do
let(:subscribe) { true }
let(:mutated_merge_request) { subject[:merge_request] }
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, subscribed_state: subscribe) }
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 merge request' do
before do
merge_request.project.add_developer(user)
end
it 'returns the merge request as discussion locked' do
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.subscribed?(user, project)).to eq(true)
expect(subject[:errors]).to be_empty
end
context 'when passing subscribe as false' do
let(:subscribe) { false }
it 'unsubscribes from the discussion' do
merge_request.subscribe(user, project)
expect(mutated_merge_request.subscribed?(user, project)).to eq(false)
end
end
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Setting subscribed status of an issue' do
include GraphqlHelpers
it_behaves_like 'a subscribable resource api' do
let_it_be(:resource) { create(:issue) }
let(:mutation_name) { :issue_set_subscription }
end
end
...@@ -5,59 +5,8 @@ require 'spec_helper' ...@@ -5,59 +5,8 @@ require 'spec_helper'
RSpec.describe 'Setting subscribed status of a merge request' do RSpec.describe 'Setting subscribed status of a merge request' do
include GraphqlHelpers include GraphqlHelpers
let(:current_user) { create(:user) } it_behaves_like 'a subscribable resource api' do
let(:merge_request) { create(:merge_request) } let_it_be(:resource) { create(:merge_request) }
let(:project) { merge_request.project } let(:mutation_name) { :merge_request_set_subscription }
let(:input) { { subscribed_state: true } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: merge_request.iid.to_s
}
graphql_mutation(:merge_request_set_subscription, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
mergeRequest {
id
subscribed
}
QL
)
end
def mutation_response
graphql_mutation_response(:merge_request_set_subscription)['mergeRequest']['subscribed']
end
before do
project.add_developer(current_user)
end
it 'returns an error if the user is not allowed to update the merge request' do
post_graphql_mutation(mutation, current_user: create(:user))
expect(graphql_errors).not_to be_empty
end
it 'marks the merge request as WIP' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response).to eq(true)
end
context 'when passing subscribe false as input' do
let(:input) { { subscribed_state: false } }
it 'unmarks the merge request as subscribed' do
merge_request.subscribe(current_user, project)
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response).to eq(false)
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'a subscribeable graphql resource' do
let(:project) { resource.project }
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(permission_name) }
describe '#resolve' do
let(:subscribe) { true }
let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] }
subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) }
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 resource' do
before do
resource.project.add_developer(user)
end
it 'subscribes to the resource' do
expect(mutated_resource).to eq(resource)
expect(mutated_resource.subscribed?(user, project)).to eq(true)
expect(subject[:errors]).to be_empty
end
context 'when passing subscribe as false' do
let(:subscribe) { false }
it 'unsubscribes from the discussion' do
resource.subscribe(user, project)
expect(mutated_resource.subscribed?(user, project)).to eq(false)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'a subscribable resource api' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let(:project) { resource.project }
let(:input) { { subscribed_state: true } }
let(:resource_ref) { resource.class.name.camelize(:lower) }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: resource.iid.to_s
}
graphql_mutation(
mutation_name,
variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
#{resource_ref} {
id
subscribed
}
QL
)
end
def mutation_response
graphql_mutation_response(mutation_name)[resource_ref]['subscribed']
end
context 'when the user is not authorized' do
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
context 'when user is authorized' do
before do
project.add_developer(current_user)
end
it 'marks the resource as subscribed' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response).to eq(true)
end
context 'when passing subscribe false as input' do
let(:input) { { subscribed_state: false } }
it 'unmarks the resource as subscribed' do
resource.subscribe(current_user, project)
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response).to eq(false)
end
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