Commit dfd5e40a authored by Ethan Urie's avatar Ethan Urie

Merge branch '353485-work-item-global-id-type' into 'master'

Allow Types::GlobalIDType[::WorkItem] to receive IssueID as input

See merge request gitlab-org/gitlab!81490
parents 44b4dfbf b904038c
......@@ -49,7 +49,11 @@ module Types
# Construct a restricted type, that can only be inhabited by an ID of
# a given model class.
def self.[](model_class)
@id_types ||= {}
@id_types ||= {
# WorkItem has a special class as we want to allow IssueID
# on WorkItemID while we transition into work items
::WorkItem => ::Types::WorkItemIdType
}
@id_types[model_class] ||= Class.new(self) do
model_name = model_class.name
......
# frozen_string_literal: true
module Types
# rubocop:disable Graphql/AuthorizeTypes
# TODO: This type should be removed when Work Items become generally available.
# This mechanism is introduced temporarily to make the client implementation easier during this transition.
class WorkItemIdType < GlobalIDType
graphql_name 'WorkItemID'
description <<~DESC
A `WorkItemID` is a global ID. It is encoded as a string.
An example `WorkItemID` is: `"gid://gitlab/WorkItem/1"`.
While we transition from Issues into Work Items this type will temporarily support
`IssueID` like: `"gid://gitlab/Issue/1"`. This behavior will be removed without notice in the future.
DESC
class << self
def coerce_result(gid, ctx)
global_id = ::Gitlab::GlobalId.as_global_id(gid, model_name: 'WorkItem')
raise GraphQL::CoercionError, "Expected a WorkItem ID, got #{global_id}" unless suitable?(global_id)
# Always return a WorkItemID even if an Issue is returned by a resolver
work_item_gid(global_id).to_s
end
def coerce_input(string, ctx)
gid = super
# Always return a WorkItemID even if an Issue Global ID is provided as input
return work_item_gid(gid) if suitable?(gid)
raise GraphQL::CoercionError, "#{string.inspect} does not represent an instance of WorkItem"
end
def suitable?(gid)
return false if gid&.model_name&.safe_constantize.blank?
[::WorkItem, ::Issue].any? { |model_class| gid.model_class == model_class }
end
private
def work_item_gid(gid)
GlobalID.new(::Gitlab::GlobalId.build(model_name: 'WorkItem', id: gid.model_id))
end
end
end
# rubocop:enable Graphql/AuthorizeTypes
end
......@@ -19145,6 +19145,9 @@ A `WorkItemID` is a global ID. It is encoded as a string.
An example `WorkItemID` is: `"gid://gitlab/WorkItem/1"`.
While we transition from Issues into Work Items this type will temporarily support
`IssueID` like: `"gid://gitlab/Issue/1"`. This behavior will be removed without notice in the future.
### `WorkItemsTypeID`
A `WorkItemsTypeID` is a global ID. It is encoded as a string.
......@@ -376,4 +376,10 @@ RSpec.describe Types::GlobalIDType do
expect(described_class.model_name_to_graphql_name('DesignManagement::Design')).to eq('DesignManagementDesignID')
end
end
describe '.[]' do
it 'returns a custom class for work items' do
expect(described_class[::WorkItem]).to eq(::Types::WorkItemIdType)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::WorkItemIdType do
let_it_be(:project) { create(:project) }
let_it_be(:work_item) { create(:work_item, project: project) }
let_it_be(:issue) { create(:issue, project: project) }
let(:work_item_gid) { work_item.to_gid }
let(:issue_gid) { issue.to_gid }
let(:ctx) { {} }
describe '.coerce_input' do
it 'can coerce valid issue input' do
coerced = described_class.coerce_input(issue_gid.to_s, ctx)
expect(coerced).to eq(WorkItem.find(issue.id).to_gid)
end
it 'can coerce valid work item input' do
coerced = described_class.coerce_input(work_item_gid.to_s, ctx)
expect(coerced).to eq(work_item_gid)
end
it 'fails for other input types' do
project_gid = project.to_gid
expect { described_class.coerce_input(project_gid.to_s, ctx) }
.to raise_error(GraphQL::CoercionError, "#{project_gid.to_s.inspect} does not represent an instance of WorkItem")
end
end
describe '.coerce_result' do
it 'can coerce issue results and return a WorkItem global ID' do
expect(described_class.coerce_result(issue_gid, ctx)).to eq(WorkItem.find(issue.id).to_gid.to_s)
end
it 'can coerce work item results' do
expect(described_class.coerce_result(work_item_gid, ctx)).to eq(work_item_gid.to_s)
end
it 'fails for other input types' do
project_gid = project.to_gid
expect { described_class.coerce_result(project_gid, ctx) }
.to raise_error(GraphQL::CoercionError, "Expected a WorkItem ID, got #{project_gid}")
end
end
end
......@@ -12,9 +12,10 @@ RSpec.describe 'Query.work_item(id)' do
let(:current_user) { developer }
let(:work_item_data) { graphql_data['workItem'] }
let(:work_item_fields) { all_graphql_fields_for('WorkItem') }
let(:global_id) { work_item.to_gid.to_s }
let(:query) do
graphql_query_for('workItem', { 'id' => work_item.to_gid.to_s }, work_item_fields)
graphql_query_for('workItem', { 'id' => global_id }, work_item_fields)
end
context 'when the user can read the work item' do
......@@ -35,6 +36,14 @@ RSpec.describe 'Query.work_item(id)' do
'workItemType' => hash_including('id' => work_item.work_item_type.to_gid.to_s)
)
end
context 'when an Issue Global ID is provided' do
let(:global_id) { Issue.find(work_item.id).to_gid.to_s }
it 'allows an Issue GID as input' do
expect(work_item_data).to include('id' => work_item.to_gid.to_s)
end
end
end
context 'when the user can not read the work item' do
......
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