Commit 4fb64e97 authored by Luke Duncalfe's avatar Luke Duncalfe Committed by Jan Provaznik

Expose new `event` field for `DesignType`

`event` corresponds with the mutation event of the `Design` in the
stateful `Version` (either the `Version` from the `atVersion` argument,
or the latest `Version` for the issue if no argument).

https://gitlab.com/gitlab-org/gitlab-ee/issues/12705
https://gitlab.com/gitlab-org/gitlab-ee/issues/13353
parent 8c4ba156
...@@ -21,26 +21,58 @@ module Types ...@@ -21,26 +21,58 @@ module Types
description: 'The total count of user-created notes for this design' description: 'The total count of user-created notes for this design'
field :filename, GraphQL::STRING_TYPE, null: false field :filename, GraphQL::STRING_TYPE, null: false
field :full_path, GraphQL::STRING_TYPE, null: false field :full_path, GraphQL::STRING_TYPE, null: false
field :event,
Types::DesignManagement::DesignVersionEventEnum,
null: false,
description: 'The change that happened to the design at this version',
extras: [:parent]
field :image, GraphQL::STRING_TYPE, null: false, extras: [:parent] field :image, GraphQL::STRING_TYPE, null: false, extras: [:parent]
field :diff_refs, Types::DiffRefsType, null: false, calls_gitaly: true field :diff_refs, Types::DiffRefsType, null: false, calls_gitaly: true
field :versions, field :versions,
Types::DesignManagement::VersionType.connection_type, Types::DesignManagement::VersionType.connection_type,
resolver: Resolvers::DesignManagement::VersionResolver, resolver: Resolvers::DesignManagement::VersionResolver,
description: "All versions related to this design ordered newest first", description: 'All versions related to this design ordered newest first',
extras: [:parent] extras: [:parent]
def image(parent:) def image(parent:)
# Find an `at_version` argument passed to a parent node. sha = cached_stateful_version(parent).sha
#
# If no argument is found then a nil value for sha is fine Gitlab::Routing.url_helpers.project_design_url(design.project, design, sha)
# and the image displayed will be the latest version end
version_id = Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4)
sha = version_id ? GitlabSchema.object_from_id(version_id)&.sync&.sha : nil def event(parent:)
version = cached_stateful_version(parent)
design_version = cached_design_versions_for_version(version)[design.id]
design_version&.event || Types::DesignManagement::DesignVersionEventEnum::NONE
end
project = Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, design.project_id).find # Returns a `DesignManagement::Version` for this query based on the
project = project&.sync # `atVersion` argument passed to a parent node if present, or otherwise
# the most recent `Version` for the issue.
def cached_stateful_version(parent_node)
version_gid = Gitlab::Graphql::FindArgumentInParent.find(parent_node, :at_version)
# Caching is scoped to an `issue_id` to allow us to cache the
# most recent `Version` for an issue
Gitlab::SafeRequestStore.fetch([request_cache_base_key, 'stateful_version', object.issue_id, version_gid]) do
if version_gid
GitlabSchema.object_from_id(version_gid)&.sync
else
object.issue.design_versions.most_recent
end
end
end
def cached_design_versions_for_version(version)
Gitlab::SafeRequestStore.fetch([request_cache_base_key, 'design_versions_for_version', version.id]) do
version.design_versions.to_h { |dv| [dv.design_id, dv] }
end
end
Gitlab::Routing.url_helpers.project_design_url(project, design, sha) def request_cache_base_key
self.class.name
end end
end end
end end
......
# frozen_string_literal: true
module Types
module DesignManagement
class DesignVersionEventEnum < BaseEnum
graphql_name 'DesignVersionEvent'
description 'Mutation event of a Design within a Version'
NONE = 'NONE'
value NONE, 'No change'
::DesignManagement::DesignVersion.events.keys.each do |event_name|
value event_name.upcase, value: event_name, description: "A #{event_name} event"
end
end
end
end
...@@ -19,6 +19,11 @@ module EE ...@@ -19,6 +19,11 @@ module EE
has_one :epic_issue has_one :epic_issue
has_one :epic, through: :epic_issue has_one :epic, through: :epic_issue
has_many :designs, class_name: "DesignManagement::Design", inverse_of: :issue has_many :designs, class_name: "DesignManagement::Design", inverse_of: :issue
has_many :design_versions, class_name: "DesignManagement::Version", inverse_of: :issue do
def most_recent
ordered.first
end
end
validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 } validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 }
end end
......
---
title: Expose a new events property of DesignType in GraphQL that represents the change that happened to a Design within a given version.
merge_request: 15561
author:
type: added
...@@ -11,6 +11,7 @@ describe GitlabSchema.types['Design'] do ...@@ -11,6 +11,7 @@ describe GitlabSchema.types['Design'] do
expected_fields = %i[ expected_fields = %i[
diff_refs diff_refs
discussions discussions
event
filename filename
full_path full_path
id id
......
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['DesignVersionEvent'] do
it { expect(described_class.graphql_name).to eq('DesignVersionEvent') }
it 'exposes the correct event states' do
expect(described_class.values.keys).to include(*%w[CREATION MODIFICATION DELETION NONE])
end
end
...@@ -30,8 +30,19 @@ describe Issue do ...@@ -30,8 +30,19 @@ describe Issue do
end end
end end
describe "relations" do describe 'relations' do
it { is_expected.to have_many(:designs) } it { is_expected.to have_many(:designs) }
it { is_expected.to have_many(:design_versions) }
describe 'versions.most_recent' do
it 'returns the most recent version' do
issue = create(:issue)
create_list(:design_version, 2, issue: issue)
last_version = create(:design_version, issue: issue)
expect(issue.design_versions.most_recent).to eq(last_version)
end
end
end end
it_behaves_like 'an editable mentionable with EE-specific mentions' do it_behaves_like 'an editable mentionable with EE-specific mentions' do
......
...@@ -6,8 +6,8 @@ describe "Getting designs related to an issue" do ...@@ -6,8 +6,8 @@ describe "Getting designs related to an issue" do
include GraphqlHelpers include GraphqlHelpers
include DesignManagementTestHelpers include DesignManagementTestHelpers
let(:design) { create(:design, :with_file, versions_count: 1) } set(:design) { create(:design, :with_file, versions_count: 1) }
let(:current_user) { design.project.owner } set(:current_user) { design.project.owner }
let(:design_query) do let(:design_query) do
<<~NODE <<~NODE
designs { designs {
...@@ -70,7 +70,7 @@ describe "Getting designs related to an issue" do ...@@ -70,7 +70,7 @@ describe "Getting designs related to an issue" do
end end
context "with versions" do context "with versions" do
let(:version) { design.versions.take } set(:version) { design.versions.take }
let(:design_query) do let(:design_query) do
<<~NODE <<~NODE
designs { designs {
...@@ -109,15 +109,18 @@ describe "Getting designs related to an issue" do ...@@ -109,15 +109,18 @@ describe "Getting designs related to an issue" do
end end
describe "viewing a design board at a particular version" do describe "viewing a design board at a particular version" do
let(:issue) { design.issue } set(:issue) { design.issue }
let(:all_versions) { issue.design_collection.versions.ordered } set(:second_design) { create(:design, :with_file, issue: issue, versions_count: 1) }
let!(:second_design) { create(:design, :with_file, issue: issue, versions_count: 1) } set(: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 let(:design_query) do
<<~NODE <<~NODE
designs(atVersion: "#{version.to_global_id}") { designs(atVersion: "#{version.to_global_id}") {
edges { edges {
node { node {
id
image image
event
versions { versions {
edges { edges {
node { node {
...@@ -138,14 +141,14 @@ describe "Getting designs related to an issue" do ...@@ -138,14 +141,14 @@ describe "Getting designs related to an issue" do
Gitlab::Routing.url_helpers.project_design_url(design.project, design, sha) Gitlab::Routing.url_helpers.project_design_url(design.project, design, sha)
end end
def version_global_id(version) def global_id(object)
version.to_global_id.to_s object.to_global_id.to_s
end end
# Filters just design nodes from the larger `design_response` # Filters just design nodes from the larger `design_response`
def design_nodes def design_nodes
design_response.each do |response| design_response.map do |response|
response['node'].delete('versions') response['node']
end end
end end
...@@ -156,57 +159,124 @@ describe "Getting designs related to an issue" do ...@@ -156,57 +159,124 @@ describe "Getting designs related to an issue" do
end end
end end
context "viewing the original version" do context "viewing the original version, when one design was created" do
let(:version) { all_versions.last } let(:version) { all_versions.first }
it "only returns the first design, with the correct version of the design image" do before do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end
expect(design_nodes).to eql( it "only returns the first design" do
[{ "node" => { "image" => image_url(design, version.sha) } }] expect(design_nodes).to contain_exactly(
a_hash_including("id" => global_id(design))
) )
end end
it "only returns one version record for the design (the original version)" do it "returns the correct version of the design image" do
post_graphql(query, current_user: current_user) expect(design_nodes).to contain_exactly(
a_hash_including("image" => image_url(design, version.sha))
)
end
expect(version_nodes).to eq( it "returns the correct event for the design in this version" do
[ expect(design_nodes).to contain_exactly(
[{ "node" => { "id" => version_global_id(version) } }] a_hash_including("event" => "CREATION")
]
) )
end end
it "only returns one version record for the design (the original version)" do
expect(version_nodes).to eq([
[{ "node" => { "id" => global_id(version) } }]
])
end
end end
context "viewing the newest version" do context "viewing the second version, when one design was created" do
let(:version) { all_versions.first } let(:version) { all_versions.second }
it "returns both designs, with the correct version of the design images" do before do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end
expect(design_nodes).to eq( it "only returns the first two designs" do
[ expect(design_nodes).to contain_exactly(
{ "node" => { "image" => image_url(design, version.sha) } }, a_hash_including("id" => global_id(design)),
{ "node" => { "image" => image_url(second_design, version.sha) } } a_hash_including("id" => global_id(second_design))
] )
end
it "returns the correct versions of the 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))
)
end
it "returns the correct events for the designs in this version" do
expect(design_nodes).to contain_exactly(
a_hash_including("event" => "NONE"),
a_hash_including("event" => "CREATION")
) )
end end
it "returns the correct versions records for both designs" do it "returns the correct versions records for both designs" do
expect(version_nodes).to eq([
[{ "node" => { "id" => global_id(design.versions.first) } }],
[{ "node" => { "id" => global_id(second_design.versions.first) } }]
])
end
end
context "viewing the last version, when one design was deleted and one was updated" do
let(:version) { all_versions.last }
before do
second_design.design_versions.create!(version: version, event: "modification")
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end
it "does not include the deleted design" do
# The design does exist in the version
expect(version.designs).to include(deleted_design)
expect(version_nodes).to eq( # But the GraphQL API does not include it in these results
expect(design_nodes).to contain_exactly(
a_hash_including("id" => global_id(design)),
a_hash_including("id" => global_id(second_design))
)
end
it "returns the correct versions of the 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))
)
end
it "returns the correct events for the designs in this version" do
expect(design_nodes).to contain_exactly(
a_hash_including("event" => "NONE"),
a_hash_including("event" => "MODIFICATION")
)
end
it "returns all versions records for the designs" do
expect(version_nodes).to eq([
[ [
[{ "node" => { "id" => version_global_id(design.versions.first) } }], { "node" => { "id" => global_id(design.versions.first) } }
[{ "node" => { "id" => version_global_id(second_design.versions.first) } }] ],
[
{ "node" => { "id" => global_id(second_design.versions.second) } },
{ "node" => { "id" => global_id(second_design.versions.first) } }
] ]
) ])
end end
end end
end end
describe 'a design with note annotations' do describe 'a design with note annotations' do
let!(:note) { create(:diff_note_on_design, noteable: design, project: design.project) } set(:note) { create(:diff_note_on_design, noteable: design, project: design.project) }
let(:design_query) do let(:design_query) do
<<~NODE <<~NODE
......
...@@ -23,6 +23,7 @@ issues: ...@@ -23,6 +23,7 @@ issues:
- epic_issue - epic_issue
- epic - epic
- designs - designs
- design_versions
events: events:
- author - author
- project - project
......
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