Skip to content
代码片段 群组 项目
未验证 提交 fb232b80 编辑于 作者: Heinrich Lee Yu's avatar Heinrich Lee Yu
浏览文件

Update mutation for work item assignees

Allows updating of a work item's assignees
上级 d5314b13
No related branches found
No related tags found
无相关合并请求
......@@ -21,6 +21,9 @@ module UpdateArguments
argument :description_widget, ::Types::WorkItems::Widgets::DescriptionInputType,
required: false,
description: 'Input for description widget.'
argument :assignees_widget, ::Types::WorkItems::Widgets::AssigneesInputType,
required: false,
description: 'Input for assignees widget.'
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyUpdateInputType,
required: false,
description: 'Input for hierarchy widget.'
......
# frozen_string_literal: true
module Types
module WorkItems
module Widgets
class AssigneesInputType < BaseInputObject
graphql_name 'WorkItemWidgetAssigneesInput'
argument :assignee_ids, [::Types::GlobalIDType[::User]],
required: true,
description: 'Global IDs of assignees.',
prepare: ->(ids, _) { ids.map(&:model_id) }
end
end
end
end
......@@ -8,6 +8,7 @@ class WorkItemPolicy < IssuePolicy
rule { can?(:destroy_issue) | is_member_and_author }.enable :delete_work_item
rule { can?(:update_issue) }.enable :update_work_item
rule { can?(:set_issue_metadata) }.enable :set_work_item_metadata
rule { can?(:read_issue) }.enable :read_work_item
# because IssuePolicy delegates to ProjectPolicy and
......
# frozen_string_literal: true
module WorkItems
module Widgets
module AssigneesService
class UpdateService < WorkItems::Widgets::BaseService
def before_update_in_transaction(params:)
return unless params.present? && params.has_key?(:assignee_ids)
return unless has_permission?(:set_work_item_metadata)
assignee_ids = filter_assignees_count(params[:assignee_ids])
assignee_ids = filter_assignee_permissions(assignee_ids)
return if assignee_ids.sort == work_item.assignee_ids.sort
work_item.assignee_ids = assignee_ids
work_item.touch
end
private
def filter_assignees_count(assignee_ids)
return assignee_ids if work_item.allows_multiple_assignees?
assignee_ids.first(1)
end
def filter_assignee_permissions(assignee_ids)
assignees = User.id_in(assignee_ids)
assignees.select { |assignee| assignee.can?(:read_work_item, work_item) }.map(&:id)
end
end
end
end
end
......@@ -5741,6 +5741,7 @@ Input type: `WorkItemUpdateInput`
 
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemupdateassigneeswidget"></a>`assigneesWidget` | [`WorkItemWidgetAssigneesInput`](#workitemwidgetassigneesinput) | Input for assignees widget. |
| <a id="mutationworkitemupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemupdateconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
| <a id="mutationworkitemupdatedescriptionwidget"></a>`descriptionWidget` | [`WorkItemWidgetDescriptionInput`](#workitemwidgetdescriptioninput) | Input for description widget. |
......@@ -22496,6 +22497,7 @@ A time-frame defined as a closed inclusive range of two dates.
 
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemupdatedtaskinputassigneeswidget"></a>`assigneesWidget` | [`WorkItemWidgetAssigneesInput`](#workitemwidgetassigneesinput) | Input for assignees widget. |
| <a id="workitemupdatedtaskinputconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
| <a id="workitemupdatedtaskinputdescriptionwidget"></a>`descriptionWidget` | [`WorkItemWidgetDescriptionInput`](#workitemwidgetdescriptioninput) | Input for description widget. |
| <a id="workitemupdatedtaskinputhierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
......@@ -22504,6 +22506,14 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
| <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the work item. |
 
### `WorkItemWidgetAssigneesInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgetassigneesinputassigneeids"></a>`assigneeIds` | [`[UserID!]!`](#userid) | Global IDs of assignees. |
### `WorkItemWidgetDescriptionInput`
 
#### Arguments
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Types::WorkItems::Widgets::AssigneesInputType do
it { expect(described_class.graphql_name).to eq('WorkItemWidgetAssigneesInput') }
it { expect(described_class.arguments.keys).to match_array(%w[assigneeIds]) }
end
......@@ -181,4 +181,24 @@
end
end
end
describe 'set_work_item_metadata' do
context 'when user is reporter' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:set_work_item_metadata) }
end
context 'when user is guest' do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:set_work_item_metadata) }
context 'when the work item is not persisted yet' do
let(:work_item_subject) { build(:work_item, project: project) }
it { is_expected.to be_allowed(:set_work_item_metadata) }
end
end
end
end
......@@ -427,6 +427,50 @@
end
end
context 'when updating assignees' do
let(:fields) do
<<~FIELDS
workItem {
widgets {
type
... on WorkItemWidgetAssignees {
assignees {
nodes {
id
username
}
}
}
}
}
errors
FIELDS
end
let(:input) do
{ 'assigneesWidget' => { 'assigneeIds' => [developer.to_global_id.to_s] } }
end
it 'updates the work item assignee' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
work_item.reload
end.to change(work_item, :assignee_ids).from([]).to([developer.id])
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['workItem']['widgets']).to include(
{
'type' => 'ASSIGNEES',
'assignees' => {
'nodes' => [
{ 'id' => developer.to_global_id.to_s, 'username' => developer.username }
]
}
}
)
end
end
context 'when unsupported widget input is sent' do
let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') }
let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::AssigneesService::UpdateService, :freeze_time do
let_it_be(:reporter) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:new_assignee) { create(:user) }
let(:work_item) do
create(:work_item, project: project, updated_at: 1.day.ago)
end
let(:widget) { work_item.widgets.find {|widget| widget.is_a?(WorkItems::Widgets::Assignees) } }
let(:current_user) { reporter }
let(:params) { { assignee_ids: [new_assignee.id] } }
before_all do
project.add_reporter(reporter)
project.add_guest(new_assignee)
end
describe '#before_update_in_transaction' do
subject do
described_class.new(widget: widget, current_user: current_user)
.before_update_in_transaction(params: params)
end
it 'updates the assignees and sets updated_at to the current time' do
subject
expect(work_item.assignee_ids).to contain_exactly(new_assignee.id)
expect(work_item.updated_at).to be_like_time(Time.current)
end
context 'when passing an empty array' do
let(:params) { { assignee_ids: [] } }
before do
work_item.assignee_ids = [reporter.id]
end
it 'removes existing assignees' do
subject
expect(work_item.assignee_ids).to be_empty
expect(work_item.updated_at).to be_like_time(Time.current)
end
end
context 'when user does not have access' do
let(:current_user) { create(:user) }
it 'does not update the assignees' do
subject
expect(work_item.assignee_ids).to be_empty
expect(work_item.updated_at).to be_like_time(1.day.ago)
end
end
context 'when multiple assignees are given' do
let(:params) { { assignee_ids: [new_assignee.id, reporter.id] } }
context 'when work item allows multiple assignees' do
before do
allow(work_item).to receive(:allows_multiple_assignees?).and_return(true)
end
it 'sets all the given assignees' do
subject
expect(work_item.assignee_ids).to contain_exactly(new_assignee.id, reporter.id)
expect(work_item.updated_at).to be_like_time(Time.current)
end
end
context 'when work item does not allow multiple assignees' do
before do
allow(work_item).to receive(:allows_multiple_assignees?).and_return(false)
end
it 'only sets the first assignee' do
subject
expect(work_item.assignee_ids).to contain_exactly(new_assignee.id)
expect(work_item.updated_at).to be_like_time(Time.current)
end
end
end
context 'when assignee does not have access to the work item' do
let(:params) { { assignee_ids: [create(:user).id] } }
it 'does not set the assignee' do
subject
expect(work_item.assignee_ids).to be_empty
expect(work_item.updated_at).to be_like_time(1.day.ago)
end
end
context 'when assignee ids are the same as the existing ones' do
before do
work_item.assignee_ids = [new_assignee.id]
end
it 'does not touch updated_at' do
subject
expect(work_item.assignee_ids).to contain_exactly(new_assignee.id)
expect(work_item.updated_at).to be_like_time(1.day.ago)
end
end
end
end
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册