Commit a2bf9c25 authored by Mario Celi's avatar Mario Celi Committed by Bob Van Landuyt

Add Query.workItem(id) to GraphQL

Necessary for https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78414
Allows to fetch lockVersion
parent 124f7b9d
......@@ -10,6 +10,11 @@ export function createApolloProvider() {
const defaultClient = createDefaultClient(resolvers, {
typeDefs,
cacheConfig: {
possibleTypes: {
LocalWorkItemWidget: ['LocalTitleWidget'],
},
},
});
defaultClient.cache.writeQuery({
......@@ -18,7 +23,7 @@ export function createApolloProvider() {
id: '1',
},
data: {
workItem: {
localWorkItem: {
__typename: 'LocalWorkItem',
id: '1',
type: 'FEATURE',
......
......@@ -22,7 +22,11 @@ export const resolvers = {
},
};
cache.writeQuery({ query: workItemQuery, variables: { id }, data: { workItem } });
cache.writeQuery({
query: workItemQuery,
variables: { id },
data: { localWorkItem: workItem },
});
return {
__typename: 'LocalCreateWorkItemPayload',
......@@ -47,7 +51,11 @@ export const resolvers = {
},
};
cache.writeQuery({ query: workItemQuery, variables: { id: input.id }, data: { workItem } });
cache.writeQuery({
query: workItemQuery,
variables: { id: input.id },
data: { localWorkItem: workItem },
});
return {
__typename: 'LocalUpdateWorkItemPayload',
......
......@@ -51,7 +51,7 @@ type LocalUpdateWorkItemPayload {
}
extend type Query {
workItem(id: ID!): LocalWorkItem!
localWorkItem(id: ID!): LocalWorkItem!
}
extend type Mutation {
......
#import './widget.fragment.graphql'
query WorkItem($id: ID!) {
workItem(id: $id) @client {
localWorkItem(id: $id) @client {
id
type
widgets {
......
......@@ -36,6 +36,9 @@ export default {
id: this.id,
};
},
update(data) {
return data.localWorkItem;
},
},
},
computed: {
......
# frozen_string_literal: true
module Resolvers
class WorkItemResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
authorize :read_issue
type Types::WorkItemType, null: true
argument :id, ::Types::GlobalIDType[::WorkItem], required: true, description: 'Global ID of the work item.'
def resolve(id:)
work_item = authorized_find!(id: id)
return unless Feature.enabled?(:work_items, work_item.project)
work_item
end
private
def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::WorkItem].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
......@@ -87,6 +87,11 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'Global ID of the issue.'
end
field :work_item, Types::WorkItemType,
null: true,
resolver: Resolvers::WorkItemResolver,
description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.'
field :merge_request, Types::MergeRequestType,
null: true,
description: 'Find a merge request.' do
......
......@@ -12,6 +12,8 @@ module Types
description: 'Global ID of the work item.'
field :iid, GraphQL::Types::ID, null: false,
description: 'Internal ID of the work item.'
field :lock_version, GraphQL::Types::Int, null: false,
description: 'Lock version of the work item. Incremented each time the work item is updated.'
field :state, WorkItemStateEnum, null: false,
description: 'State of the work item.'
field :title, GraphQL::Types::String, null: false,
......
......@@ -560,6 +560,18 @@ Returns [`Vulnerability`](#vulnerability).
| ---- | ---- | ----------- |
| <a id="queryvulnerabilityid"></a>`id` | [`VulnerabilityID!`](#vulnerabilityid) | Global ID of the Vulnerability. |
### `Query.workItem`
Find a work item. Returns `null` if `work_items` feature flag is disabled.
Returns [`WorkItem`](#workitem).
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryworkitemid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
## `Mutation` type
The `Mutation` type contains all the mutations you can execute.
......@@ -16726,6 +16738,7 @@ Represents vulnerability letter grades with associated projects.
| <a id="workitemdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="workitemid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
| <a id="workitemiid"></a>`iid` | [`ID!`](#id) | Internal ID of the work item. |
| <a id="workitemlockversion"></a>`lockVersion` | [`Int!`](#int) | Lock version of the work item. Incremented each time the work item is updated. |
| <a id="workitemstate"></a>`state` | [`WorkItemState!`](#workitemstate) | State of the work item. |
| <a id="workitemtitle"></a>`title` | [`String!`](#string) | Title of the work item. |
| <a id="workitemtitlehtml"></a>`titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. |
export const workItemQueryResponse = {
workItem: {
localWorkItem: {
__typename: 'LocalWorkItem',
id: '1',
type: 'FEATURE',
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::WorkItemResolver do
include GraphqlHelpers
describe '#resolve' do
let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } }
let_it_be(:work_item) { create(:work_item, project: project) }
let(:current_user) { developer }
subject(:resolved_work_item) { resolve_work_item('id' => work_item.to_gid.to_s) }
context 'when the user can read the work item' do
it { is_expected.to eq(work_item) }
end
context 'when the user can not read the work item' do
let(:current_user) { create(:user) }
it 'raises a resource not available error' do
expect { resolved_work_item }.to raise_error(::Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
end
it { is_expected.to be_nil }
end
end
private
def resolve_work_item(args = {})
resolve(described_class, args: args, ctx: { current_user: current_user })
end
end
......@@ -5,8 +5,10 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['WorkItem'] do
specify { expect(described_class.graphql_name).to eq('WorkItem') }
specify { expect(described_class).to require_graphql_authorizations(:read_issue) }
it 'has specific fields' do
fields = %i[description description_html id iid state title title_html work_item_type]
fields = %i[description description_html id iid lock_version state title title_html work_item_type]
fields.each do |field_name|
expect(described_class).to have_graphql_fields(*fields)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.work_item(id)' do
include GraphqlHelpers
let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } }
let_it_be(:work_item) { create(:work_item, project: project) }
let(:current_user) { developer }
let(:work_item_data) { graphql_data['workItem'] }
let(:work_item_fields) { all_graphql_fields_for('WorkItem') }
let(:query) do
graphql_query_for('workItem', { 'id' => work_item.to_gid.to_s }, work_item_fields)
end
context 'when the user can read the work item' do
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns all fields' do
expect(work_item_data).to include(
'description' => work_item.description,
'id' => work_item.to_gid.to_s,
'iid' => work_item.iid.to_s,
'lockVersion' => work_item.lock_version,
'state' => "OPEN",
'title' => work_item.title,
'workItemType' => hash_including('id' => work_item.work_item_type.to_gid.to_s)
)
end
end
context 'when the user can not read the work item' do
let(:current_user) { create(:user) }
before do
post_graphql(query)
end
it 'returns an access error' do
expect(work_item_data).to be_nil
expect(graphql_errors).to contain_exactly(
hash_including('message' => ::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
)
end
end
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
end
it 'returns nil' do
post_graphql(query)
expect(work_item_data).to be_nil
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