Commit d1b9f796 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'ajk-globalid-discussions' into 'master'

[GraphQL] Discussions: use Global-ID scalar

See merge request gitlab-org/gitlab!36101
parents 6fccc84a 4c78cc58
...@@ -3,24 +3,26 @@ ...@@ -3,24 +3,26 @@
module Types module Types
module Notes module Notes
class DiscussionType < BaseObject class DiscussionType < BaseObject
DiscussionID = ::Types::GlobalIDType[::Discussion]
graphql_name 'Discussion' graphql_name 'Discussion'
authorize :read_note authorize :read_note
implements(Types::ResolvableInterface) implements(Types::ResolvableInterface)
field :id, GraphQL::ID_TYPE, null: false, field :id, DiscussionID, null: false,
description: "ID of this discussion" description: "ID of this discussion"
field :reply_id, GraphQL::ID_TYPE, null: false, field :reply_id, DiscussionID, null: false,
description: 'ID used to reply to this discussion' description: 'ID used to reply to this discussion'
field :created_at, Types::TimeType, null: false, field :created_at, Types::TimeType, null: false,
description: "Timestamp of the discussion's creation" description: "Timestamp of the discussion's creation"
field :notes, Types::Notes::NoteType.connection_type, null: false, field :notes, Types::Notes::NoteType.connection_type, null: false,
description: 'All notes in the discussion' description: 'All notes in the discussion'
# The gem we use to generate Global IDs is hard-coded to work with # DiscussionID.coerce_result is suitable here, but will always mark this
# `id` properties. To generate a GID for the `reply_id` property, # as being a 'Discussion'. Using `GlobalId.build` guarantees that we get
# we must use the ::Gitlab::GlobalId module. # the correct class, and that it matches `id`.
def reply_id def reply_id
::Gitlab::GlobalId.build(object, id: object.reply_id) ::Gitlab::GlobalId.build(object, id: object.reply_id)
end end
......
...@@ -11,7 +11,7 @@ module Types ...@@ -11,7 +11,7 @@ module Types
implements(Types::ResolvableInterface) implements(Types::ResolvableInterface)
field :id, GraphQL::ID_TYPE, null: false, field :id, ::Types::GlobalIDType[::Note], null: false,
description: 'ID of the note' description: 'ID of the note'
field :project, Types::ProjectType, field :project, Types::ProjectType,
......
...@@ -7878,7 +7878,7 @@ type Discussion implements ResolvableInterface { ...@@ -7878,7 +7878,7 @@ type Discussion implements ResolvableInterface {
""" """
ID of this discussion ID of this discussion
""" """
id: ID! id: DiscussionID!
""" """
All notes in the discussion All notes in the discussion
...@@ -7908,7 +7908,7 @@ type Discussion implements ResolvableInterface { ...@@ -7908,7 +7908,7 @@ type Discussion implements ResolvableInterface {
""" """
ID used to reply to this discussion ID used to reply to this discussion
""" """
replyId: ID! replyId: DiscussionID!
""" """
Indicates if the object can be resolved Indicates if the object can be resolved
...@@ -16154,7 +16154,7 @@ type Note implements ResolvableInterface { ...@@ -16154,7 +16154,7 @@ type Note implements ResolvableInterface {
""" """
ID of the note ID of the note
""" """
id: ID! id: NoteID!
""" """
The position of this note on a diff The position of this note on a diff
......
...@@ -21766,7 +21766,7 @@ ...@@ -21766,7 +21766,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "ID", "name": "DiscussionID",
"ofType": null "ofType": null
} }
}, },
...@@ -21841,7 +21841,7 @@ ...@@ -21841,7 +21841,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "ID", "name": "DiscussionID",
"ofType": null "ofType": null
} }
}, },
...@@ -47730,7 +47730,7 @@ ...@@ -47730,7 +47730,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "ID", "name": "NoteID",
"ofType": null "ofType": null
} }
}, },
...@@ -1301,9 +1301,9 @@ Aggregated summary of changes. ...@@ -1301,9 +1301,9 @@ Aggregated summary of changes.
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `createdAt` | Time! | Timestamp of the discussion's creation | | `createdAt` | Time! | Timestamp of the discussion's creation |
| `id` | ID! | ID of this discussion | | `id` | DiscussionID! | ID of this discussion |
| `notes` | NoteConnection! | All notes in the discussion | | `notes` | NoteConnection! | All notes in the discussion |
| `replyId` | ID! | ID used to reply to this discussion | | `replyId` | DiscussionID! | ID used to reply to this discussion |
| `resolvable` | Boolean! | Indicates if the object can be resolved | | `resolvable` | Boolean! | Indicates if the object can be resolved |
| `resolved` | Boolean! | Indicates if the object is resolved | | `resolved` | Boolean! | Indicates if the object is resolved |
| `resolvedAt` | Time | Timestamp of when the object was resolved | | `resolvedAt` | Time | Timestamp of when the object was resolved |
...@@ -2429,7 +2429,7 @@ Autogenerated return type of NamespaceIncreaseStorageTemporarily. ...@@ -2429,7 +2429,7 @@ Autogenerated return type of NamespaceIncreaseStorageTemporarily.
| `confidential` | Boolean | Indicates if this note is confidential | | `confidential` | Boolean | Indicates if this note is confidential |
| `createdAt` | Time! | Timestamp of the note creation | | `createdAt` | Time! | Timestamp of the note creation |
| `discussion` | Discussion | The discussion this note is a part of | | `discussion` | Discussion | The discussion this note is a part of |
| `id` | ID! | ID of the note | | `id` | NoteID! | ID of the note |
| `position` | DiffPosition | The position of this note on a diff | | `position` | DiffPosition | The position of this note on a diff |
| `project` | Project | Project associated with the note | | `project` | Project | Project associated with the note |
| `resolvable` | Boolean! | Indicates if the object can be resolved | | `resolvable` | Boolean! | Indicates if the object can be resolved |
......
...@@ -19,8 +19,8 @@ module Gitlab ...@@ -19,8 +19,8 @@ module Gitlab
value value
when URI::GID when URI::GID
GlobalID.new(value) GlobalID.new(value)
when Integer when Integer, String
raise CoerceError, 'Cannot coerce Integer' unless model_name.present? raise CoerceError, "Cannot coerce #{value.class}" unless model_name.present?
GlobalID.new(::Gitlab::GlobalId.build(model_name: model_name, id: value)) GlobalID.new(::Gitlab::GlobalId.build(model_name: model_name, id: value))
else else
......
...@@ -24,6 +24,20 @@ RSpec.describe 'Query.issue(id)' do ...@@ -24,6 +24,20 @@ RSpec.describe 'Query.issue(id)' do
end end
end end
it_behaves_like 'a noteable graphql type we can query' do
let(:noteable) { issue }
let(:project) { issue.project }
let(:path_to_noteable) { [:issue] }
before do
project.add_guest(current_user)
end
def query(fields)
graphql_query_for('issue', issue_params, fields)
end
end
context 'when the user does not have access to the issue' do context 'when the user does not have access to the issue' do
it 'returns nil' do it 'returns nil' do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
......
...@@ -22,6 +22,23 @@ RSpec.describe 'Getting designs related to an issue' do ...@@ -22,6 +22,23 @@ RSpec.describe 'Getting designs related to an issue' do
end end
end end
it_behaves_like 'a noteable graphql type we can query' do
let(:noteable) { design }
let(:note_factory) { :diff_note_on_design }
let(:discussion_factory) { :diff_note_on_design }
let(:path_to_noteable) { [:issue, :design_collection, :designs, :nodes, 0] }
before do
project.add_developer(current_user)
end
def query(fields)
graphql_query_for(:issue, { id: global_id_of(issue) }, <<~FIELDS)
designCollection { designs { nodes { #{fields} } } }
FIELDS
end
end
it 'is not too deep for anonymous users' do it 'is not too deep for anonymous users' do
note_fields = <<~FIELDS note_fields = <<~FIELDS
id id
...@@ -37,7 +54,7 @@ RSpec.describe 'Getting designs related to an issue' do ...@@ -37,7 +54,7 @@ RSpec.describe 'Getting designs related to an issue' do
expect(note_data['id']).to eq(note.to_global_id.to_s) expect(note_data['id']).to eq(note.to_global_id.to_s)
end end
def query(note_fields = all_graphql_fields_for(Note)) def query(note_fields = all_graphql_fields_for(Note, max_depth: 1))
design_node = <<~NODE design_node = <<~NODE
designs { designs {
nodes { nodes {
......
...@@ -510,8 +510,12 @@ module GraphqlHelpers ...@@ -510,8 +510,12 @@ module GraphqlHelpers
end end
end end
def global_id_of(model) def global_id_of(model, id: nil, model_name: nil)
model.to_global_id.to_s if id || model_name
::Gitlab::GlobalId.build(model, id: id, model_name: model_name).to_s
else
model.to_global_id.to_s
end
end end
def missing_required_argument(path, argument) def missing_required_argument(path, argument)
......
# frozen_string_literal: true
# Requires `query(fields)`, `path_to_noteable`, `project`, and `noteable` bindings
RSpec.shared_examples 'a noteable graphql type we can query' do
let(:note_factory) { :note }
let(:discussion_factory) { :discussion_note }
describe '.discussions' do
let(:fields) do
"discussions { nodes { #{all_graphql_fields_for('Discussion')} } }"
end
def expected
noteable.discussions.map do |discussion|
include(
'id' => global_id_of(discussion),
'replyId' => global_id_of(discussion, id: discussion.reply_id),
'createdAt' => discussion.created_at.iso8601,
'notes' => include(
'nodes' => have_attributes(size: discussion.notes.size)
)
)
end
end
it 'can fetch discussions' do
create(discussion_factory, project: project, noteable: noteable)
post_graphql(query(fields), current_user: current_user)
expect(graphql_data_at(*path_to_noteable, :discussions, :nodes))
.to match_array(expected)
end
end
describe '.notes' do
let(:fields) do
"notes { nodes { #{all_graphql_fields_for('Note', max_depth: 2)} } }"
end
def expected
noteable.notes.map do |note|
include(
'id' => global_id_of(note),
'project' => include('id' => global_id_of(project)),
'author' => include('id' => global_id_of(note.author)),
'createdAt' => note.created_at.iso8601,
'body' => eq(note.note)
)
end
end
it 'can fetch notes' do
create(note_factory, project: project, noteable: noteable)
post_graphql(query(fields), current_user: current_user)
expect(graphql_data_at(*path_to_noteable, :notes, :nodes))
.to match_array(expected)
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