Commit dc587e38 authored by Luke Duncalfe's avatar Luke Duncalfe

Expose smaller design image in GraphQL

As the smaller design image is not generated immediately, and in some
cases will never be generated (i.e., the image is an SVG at time of
writing), the GraphQL property can emit an `null`.

https://gitlab.com/gitlab-org/gitlab/-/issues/12577
https://gitlab.com/gitlab-org/gitlab/-/issues/13815
parent c3935123
......@@ -776,10 +776,15 @@ type Design implements DesignFields & Noteable {
id: ID!
"""
The URL of the image
The URL of the full-sized image
"""
image: String!
"""
The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated
"""
imageV432x230: String
"""
The issue the design belongs to
"""
......@@ -891,10 +896,15 @@ type DesignAtVersion implements DesignFields {
id: ID!
"""
The URL of the image
The URL of the full-sized image
"""
image: String!
"""
The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated
"""
imageV432x230: String
"""
The issue the design belongs to
"""
......@@ -1144,10 +1154,15 @@ interface DesignFields {
id: ID!
"""
The URL of the image
The URL of the full-sized image
"""
image: String!
"""
The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated
"""
imageV432x230: String
"""
The issue the design belongs to
"""
......
......@@ -2247,7 +2247,7 @@
},
{
"name": "image",
"description": "The URL of the image",
"description": "The URL of the full-sized image",
"args": [
],
......@@ -2263,6 +2263,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "imageV432x230",
"description": "The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue the design belongs to",
......@@ -2583,7 +2597,7 @@
},
{
"name": "image",
"description": "The URL of the image",
"description": "The URL of the full-sized image",
"args": [
],
......@@ -2599,6 +2613,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "imageV432x230",
"description": "The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue the design belongs to",
......@@ -3326,7 +3354,7 @@
},
{
"name": "image",
"description": "The URL of the image",
"description": "The URL of the full-sized image",
"args": [
],
......@@ -3342,6 +3370,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "imageV432x230",
"description": "The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue the design belongs to",
......
......@@ -170,7 +170,8 @@ A single design
| `filename` | String! | The filename of the design |
| `fullPath` | String! | The full path to the design file |
| `id` | ID! | The ID of this design |
| `image` | String! | The URL of the image |
| `image` | String! | The URL of the full-sized image |
| `imageV432x230` | String | The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated |
| `issue` | Issue! | The issue the design belongs to |
| `notesCount` | Int! | The total count of user-created notes for this design |
| `project` | Project! | The project the design belongs to |
......@@ -187,7 +188,8 @@ A design pinned to a specific version. The image field reflects the design as of
| `filename` | String! | The filename of the design |
| `fullPath` | String! | The full path to the design file |
| `id` | ID! | The ID of this design |
| `image` | String! | The URL of the image |
| `image` | String! | The URL of the full-sized image |
| `imageV432x230` | String | The URL of the design resized to fit within the bounds of 432x230. This will be `null` if the image has not been generated |
| `issue` | Issue! | The issue the design belongs to |
| `notesCount` | Int! | The total count of user-created notes for this design |
| `project` | Project! | The project the design belongs to |
......
......@@ -12,7 +12,10 @@ module Types
field :issue, Types::IssueType, null: false, description: 'The issue the design belongs to'
field :filename, GraphQL::STRING_TYPE, null: false, description: 'The filename of the design'
field :full_path, GraphQL::STRING_TYPE, null: false, description: 'The full path to the design file'
field :image, GraphQL::STRING_TYPE, null: false, extras: [:parent], description: 'The URL of the image'
field :image, GraphQL::STRING_TYPE, null: false, extras: [:parent], description: 'The URL of the full-sized image'
field :image_v432x230, GraphQL::STRING_TYPE, null: true, extras: [:parent],
description: 'The URL of the design resized to fit within the bounds of 432x230. ' \
'This will be `null` if the image has not been generated'
field :diff_refs, Types::DiffRefsType,
null: false,
calls_gitaly: true,
......@@ -39,6 +42,16 @@ module Types
Gitlab::UrlBuilder.build(design, ref: sha)
end
def image_v432x230(parent:)
version = cached_stateful_version(parent)
action = design.actions.up_to_version(version).most_recent.first
# A `nil` return value indicates that the image has not been processed
return unless action.image_v432x230.file
Gitlab::UrlBuilder.build(design, ref: version.sha, size: :v432x230)
end
def event(parent:)
version = cached_stateful_version(parent)
......
---
title: Expose smaller sized Design Management design images in GraphQL
merge_request: 26947
author:
type: added
......@@ -6,24 +6,33 @@ describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
let_it_be(:design) { create(:design, :with_smaller_image_versions, versions_count: 1) }
let_it_be(:current_user) { design.project.owner }
let(:design_query) do
<<~NODE
designs {
edges {
node {
id
filename
fullPath
event
image
imageV432x230
}
}
}
NODE
end
let(:issue) { design.issue }
let(:project) { issue.project }
let(:query) { make_query }
let(:design_collection) do
graphql_data_at(:project, :issue, :design_collection)
end
let(:design_response) do
design_collection.dig('designs', 'edges').first['node']
end
def make_query(dq = design_query)
designs_field = query_graphql_field(:design_collection, {}, dq)
......@@ -32,12 +41,8 @@ describe 'Getting designs related to an issue' do
graphql_query_for(:project, { fullPath: project.full_path }, issue_field)
end
let(:design_collection) do
graphql_data_at(:project, :issue, :design_collection)
end
let(:design_response) do
design_collection.dig('designs', 'edges').first['node']
def design_image_url(design, ref: nil, size: nil)
Gitlab::UrlBuilder.build(design, ref: ref, size: size)
end
context 'when the feature is not available' do
......@@ -64,10 +69,33 @@ describe 'Getting designs related to an issue' do
enable_design_management
end
it 'returns the design filename' do
it 'returns the design properties correctly' do
version_sha = design.versions.first.sha
post_graphql(query, current_user: current_user)
expect(design_response).to eq(
'id' => design.to_global_id.to_s,
'event' => 'CREATION',
'fullPath' => design.full_path,
'filename' => design.filename,
'image' => design_image_url(design, ref: version_sha),
'imageV432x230' => design_image_url(design, ref: version_sha, size: :v432x230)
)
end
context 'when the v432x230-sized design image has not been processed' do
before do
allow_next_instance_of(DesignManagement::DesignV432x230Uploader) do |uploader|
allow(uploader).to receive(:file).and_return(nil)
end
end
it 'returns nil for the v432x230-sized design image' do
post_graphql(query, current_user: current_user)
expect(design_response['filename']).to eq(design.filename)
expect(design_response['imageV432x230']).to be_nil
end
end
describe 'pagination' do
......@@ -152,7 +180,7 @@ describe 'Getting designs related to an issue' do
describe 'viewing a design board at a particular version' do
let_it_be(:issue) { design.issue }
let_it_be(:second_design) { create(:design, :with_file, issue: issue, versions_count: 1) }
let_it_be(:second_design, reload: true) { create(:design, :with_smaller_image_versions, issue: issue, versions_count: 1) }
let_it_be(:deleted_design) { create(:design, :with_versions, issue: issue, deleted: true, versions_count: 1) }
let(:all_versions) { issue.design_versions.ordered.reverse }
let(:design_query) do
......@@ -162,6 +190,7 @@ describe 'Getting designs related to an issue' do
node {
id
image
imageV432x230
event
versions {
edges {
......@@ -179,10 +208,6 @@ describe 'Getting designs related to an issue' do
design_collection['designs']['edges']
end
def image_url(design, sha = nil)
Gitlab::UrlBuilder.build(design, ref: sha)
end
def global_id(object)
object.to_global_id.to_s
end
......@@ -214,9 +239,15 @@ describe 'Getting designs related to an issue' do
)
end
it 'returns the correct version of the design image' do
it 'returns the correct full-sized design image' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => design_image_url(design, ref: version.sha))
)
end
it 'returns the correct v432x230-sized design image' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => image_url(design, version.sha))
a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230))
)
end
......@@ -247,10 +278,17 @@ describe 'Getting designs related to an issue' do
)
end
it 'returns the correct versions of the design images' do
it 'returns the correct full-sized design images' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => design_image_url(design, ref: version.sha)),
a_hash_including('image' => design_image_url(second_design, ref: version.sha))
)
end
it 'returns the correct v432x230-sized design images' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => image_url(design, version.sha)),
a_hash_including('image' => image_url(second_design, version.sha))
a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230)),
a_hash_including('imageV432x230' => design_image_url(second_design, ref: version.sha, size: :v432x230))
)
end
......@@ -271,10 +309,11 @@ describe 'Getting designs related to an issue' do
context 'viewing the last version, when one design was deleted and one was updated' do
let(:version) { all_versions.last }
let!(:second_design_update) do
create(:design_action, :with_image_v432x230, design: second_design, version: version, event: 'modification')
end
before do
second_design.actions.create!(version: version, event: 'modification')
post_graphql(query, current_user: current_user)
end
......@@ -289,10 +328,17 @@ describe 'Getting designs related to an issue' do
)
end
it 'returns the correct versions of the design images' do
it 'returns the correct full-sized design images' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => design_image_url(design, ref: version.sha)),
a_hash_including('image' => design_image_url(second_design, ref: version.sha))
)
end
it 'returns the correct v432x230-sized design images' do
expect(design_nodes).to contain_exactly(
a_hash_including('image' => image_url(design, version.sha)),
a_hash_including('image' => image_url(second_design, version.sha))
a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230)),
a_hash_including('imageV432x230' => design_image_url(second_design, ref: version.sha, size: :v432x230))
)
end
......
......@@ -16,6 +16,7 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
filename
full_path
image
image_v432x230
diff_refs
event
notes_count
......
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