Commit b336d667 authored by Kerri Miller's avatar Kerri Miller

Merge branch '346132-delete-work-item-api' into 'master'

Add workItemDelete mutation

See merge request gitlab-org/gitlab!79371
parents 6a2948e1 043a36ab
# frozen_string_literal: true
module Mutations
module WorkItems
class Delete < BaseMutation
description "Deletes a work item." \
" Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice."
graphql_name 'WorkItemDelete'
authorize :delete_work_item
argument :id, ::Types::GlobalIDType[::WorkItem],
required: true,
description: 'Global ID of the work item.'
field :project, Types::ProjectType,
null: true,
description: 'Project the deleted work item belonged to.'
def resolve(id:)
work_item = authorized_find!(id: id)
unless Feature.enabled?(:work_items, work_item.project)
return { errors: ['`work_items` feature flag disabled for this project'] }
end
result = ::WorkItems::DeleteService.new(
project: work_item.project,
current_user: current_user
).execute(work_item)
{
project: result.success? ? work_item.project : nil,
errors: result.errors
}
end
private
def find_object(id:)
# TODO: Remove coercion when working on https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::WorkItem].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
......@@ -126,6 +126,7 @@ module Types
mount_mutation Mutations::Packages::DestroyFile
mount_mutation Mutations::Echo
mount_mutation Mutations::WorkItems::Create, feature_flag: :work_items
mount_mutation Mutations::WorkItems::Delete
mount_mutation Mutations::WorkItems::Update
end
end
......
......@@ -2,4 +2,11 @@
class WorkItemPolicy < BasePolicy
delegate { @subject.project }
desc 'User is author of the work item'
condition(:author) do
@user && @user == @subject.author
end
rule { can?(:owner_access) | author }.enable :delete_work_item
end
# frozen_string_literal: true
module WorkItems
class DeleteService < Issuable::DestroyService
def execute(work_item)
unless current_user.can?(:delete_work_item, work_item)
return ::ServiceResponse.error(message: 'User not authorized to delete work item')
end
if super
::ServiceResponse.success
else
::ServiceResponse.error(message: work_item.errors.full_messages)
end
end
end
end
......@@ -5145,6 +5145,27 @@ Input type: `WorkItemCreateInput`
| <a id="mutationworkitemcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationworkitemcreateworkitem"></a>`workItem` | [`WorkItem`](#workitem) | Created work item. |
### `Mutation.workItemDelete`
Deletes a work item. Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice.
Input type: `WorkItemDeleteInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemdeleteid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationworkitemdeleteproject"></a>`project` | [`Project`](#project) | Project the deleted work item belonged to. |
### `Mutation.workItemUpdate`
Updates a work item by Global ID. Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Delete a work item' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
let(:current_user) { developer }
let(:mutation) { graphql_mutation(:workItemDelete, { 'id' => work_item.to_global_id.to_s }) }
let(:mutation_response) { graphql_mutation_response(:work_item_delete) }
context 'when the user is not allowed to delete a work item' do
let(:work_item) { create(:work_item, project: project) }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to delete a work item' do
let_it_be(:authored_work_item, refind: true) { create(:work_item, project: project, author: developer, assignees: [developer]) }
let(:work_item) { authored_work_item }
it 'deletes the work item' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.to change(WorkItem, :count).by(-1)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['project']).to include('id' => work_item.project.to_global_id.to_s)
end
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
end
it 'does not delete the work item' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.to not_change(WorkItem, :count)
expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::DeleteService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:guest) { create(:user) }
let_it_be(:work_item, refind: true) { create(:work_item, project: project, author: guest) }
let(:user) { guest }
before_all do
project.add_guest(guest)
# note necessary to test note removal as part of work item deletion
create(:note, project: project, noteable: work_item)
end
describe '#execute' do
subject(:result) { described_class.new(project: project, current_user: user).execute(work_item) }
context 'when user can delete the work item' do
it { is_expected.to be_success }
# currently we don't expect destroy to fail. Mocking here for coverage and keeping
# the service's return type consistent
context 'when there are errors preventing to delete the work item' do
before do
allow(work_item).to receive(:destroy).and_return(false)
work_item.errors.add(:title)
end
it { is_expected.to be_error }
it 'returns error messages' do
expect(result.errors).to contain_exactly('Title is invalid')
end
end
end
context 'when user cannot delete the work item' do
let(:user) { create(:user) }
it { is_expected.to be_error }
it 'returns error messages' do
expect(result.errors).to contain_exactly('User not authorized to delete work item')
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