Commit b908ce0b authored by Thong Kuah's avatar Thong Kuah

Merge branch '13495-a-design-at-version-model-changes' into 'master'

13495-a: Create DesignAtVersion model

See merge request gitlab-org/gitlab!21119
parents 570b9e5b e88fd1a4
......@@ -24,6 +24,7 @@ module DesignManagement
items = design_or_collection.versions
items = by_earlier_or_equal_to(items)
items = by_sha(items)
items.ordered
end
......@@ -34,5 +35,11 @@ module DesignManagement
items.earlier_or_equal_to(params[:earlier_or_equal_to])
end
def by_sha(items)
return items unless params[:sha]
items.by_sha(params[:sha])
end
end
end
......@@ -107,9 +107,7 @@ module DesignManagement
end
def diff_refs
strong_memoize(:diff_refs) do
head_version.presence && repository.commit(head_version.sha).diff_refs
end
strong_memoize(:diff_refs) { head_version&.diff_refs }
end
def clear_version_cache
......@@ -158,7 +156,7 @@ module DesignManagement
def user_notes_count_service
strong_memoize(:user_notes_count_service) do
DesignManagement::DesignUserNotesCountService.new(self) # rubocop: disable CodeReuse/ServiceClass
::DesignManagement::DesignUserNotesCountService.new(self) # rubocop: disable CodeReuse/ServiceClass
end
end
end
......
# frozen_string_literal: true
# Tuple of design and version
# * has a composite ID, with lazy_find
module DesignManagement
class DesignAtVersion
include ActiveModel::Validations
include GlobalID::Identification
include Gitlab::Utils::StrongMemoize
attr_reader :version
attr_reader :design
validates_presence_of :version
validates_presence_of :design
validate :design_and_version_belong_to_the_same_issue
validate :design_and_version_have_issue_id
def initialize(design: nil, version: nil)
@design, @version = design, version
end
def self.instantiate(attrs)
new(attrs).tap { |obj| obj.validate! }
end
# The ID, needed by GraphQL types and as part of the Lazy-fetch
# protocol, includes information about both the design and the version.
#
# The particular format is not interesting, and should be treated as opaque
# by all callers.
def id
"#{design.id}.#{version.id}"
end
def self.lazy_find(id)
BatchLoader.for(id).batch do |ids, callback|
find(ids).each do |record|
callback.call(record.id, record)
end
end
end
def self.find(ids)
pairs = ids.map { |id| id.split('.').map(&:to_i) }
design_ids = pairs.map(&:first).uniq
version_ids = pairs.map(&:second).uniq
designs = ::DesignManagement::Design
.where(id: design_ids)
.index_by(&:id)
versions = ::DesignManagement::Version
.where(id: version_ids)
.index_by(&:id)
pairs.map do |(design_id, version_id)|
design = designs[design_id]
version = versions[version_id]
obj = new(design: design, version: version)
obj if obj.valid?
end.compact
end
def status
if not_created_yet?
:not_created_yet
elsif deleted?
:deleted
else
:current
end
end
def deleted?
action&.deletion?
end
def not_created_yet?
action.nil?
end
private
def action
strong_memoize(:most_recent_action) do
::DesignManagement::Action
.most_recent.up_to_version(version)
.where(design: design)
.first
end
end
def design_and_version_belong_to_the_same_issue
id_a, id_b = [design, version].map { |obj| obj&.issue_id }
return if id_a == id_b
errors.add(:issue, "must be the same on design and version")
end
def design_and_version_have_issue_id
return if [design, version].all? { |obj| obj.try(:issue_id).present? }
errors.add(:issue, "must be present on both design and version")
end
end
end
......@@ -4,6 +4,7 @@ module DesignManagement
class Version < ApplicationRecord
include Importable
include ShaAttribute
include Gitlab::Utils::StrongMemoize
NotSameIssue = Class.new(StandardError)
......@@ -50,11 +51,12 @@ module DesignManagement
delegate :project, to: :issue
scope :for_designs, -> (designs) do
where(id: Action.where(design_id: designs).select(:version_id)).distinct
where(id: ::DesignManagement::Action.where(design_id: designs).select(:version_id)).distinct
end
scope :earlier_or_equal_to, -> (version) { where('id <= ?', version) }
scope :ordered, -> { order(id: :desc) }
scope :for_issue, -> (issue) { where(issue: issue) }
scope :by_sha, -> (sha) { where(sha: sha) }
# This is the one true way to create a Version.
#
......@@ -80,7 +82,7 @@ module DesignManagement
rows = design_actions.map { |action| action.row_attrs(version) }
Gitlab::Database.bulk_insert(Action.table_name, rows)
Gitlab::Database.bulk_insert(::DesignManagement::Action.table_name, rows)
version.designs.reset
version.validate!
design_actions.each(&:performed)
......@@ -102,6 +104,15 @@ module DesignManagement
super || (commit_author if persisted?)
end
def diff_refs
strong_memoize(:diff_refs) { commit&.diff_refs }
end
def reset
%i[diff_refs commit].each { |k| clear_memoization(k) }
super
end
private
def commit_author
......@@ -109,7 +120,7 @@ module DesignManagement
end
def commit
@commit ||= issue.project.design_repository.commit(sha)
strong_memoize(:commit) { issue.project.design_repository.commit(sha) }
end
end
end
# frozen_string_literal: true
module DesignManagement
class DesignAtVersionPolicy < ::BasePolicy
delegate { @subject.version }
delegate { @subject.design }
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :design_at_version, class: DesignManagement::DesignAtVersion do
skip_create # This is not an Active::Record model.
design { nil }
version { nil }
transient do
issue { design&.issue || version&.issue || create(:issue) }
end
initialize_with do
attrs = attributes.dup
attrs[:design] ||= create(:design, issue: issue)
attrs[:version] ||= create(:design_version, issue: issue)
new(attrs)
end
end
end
......@@ -3,13 +3,25 @@
FactoryBot.define do
factory :design, class: DesignManagement::Design do
issue { create(:issue) }
project { issue.project }
project { issue&.project || create(:project) }
sequence(:filename) { |n| "homescreen-#{n}.jpg" }
transient do
author { issue.author }
end
trait :importing do
issue { nil }
importing { true }
imported { false }
end
trait :imported do
importing { false }
imported { true }
end
create_versions = ->(design, evaluator, commit_version) do
unless evaluator.versions_count.zero?
project = design.project
......@@ -37,6 +49,8 @@ FactoryBot.define do
# and maybe a deletion
run_action[DesignManagement::DesignAction.new(design, :delete)] if evaluator.deleted
end
design.clear_version_cache
end
trait :with_lfs_file do
......
......@@ -4,7 +4,7 @@ FactoryBot.define do
factory :design_version, class: DesignManagement::Version do
sequence(:sha) { |n| Digest::SHA1.hexdigest("commit-like-#{n}") }
issue { designs.first&.issue || create(:issue) }
author { issue.author || create(:user) }
author { issue&.author || create(:user) }
transient do
designs_count { 1 }
......@@ -18,6 +18,19 @@ FactoryBot.define do
designs_count { 0 }
end
trait :importing do
issue { nil }
designs_count { 0 }
importing { true }
imported { false }
end
trait :imported do
importing { false }
imported { true }
end
after(:build) do |version, evaluator|
# By default all designs are created_designs, so just add them.
specific_designs = [].concat(
......
......@@ -8,7 +8,7 @@ FactoryBot.modify do
end
trait :on_design do
noteable { create(:design, :with_file, project: project) }
noteable { create(:design, :with_file, issue: create(:issue, project: project)) }
end
trait :with_review do
......
......@@ -79,6 +79,20 @@ describe DesignManagement::VersionsFinder do
it { is_expected.to contain_exactly(version_1, version_2) }
end
end
describe 'returning versions by SHA' do
context 'when argument is the first version' do
let(:params) { { sha: version_1.sha } }
it { is_expected.to contain_exactly(version_1) }
end
context 'when argument is the second version' do
let(:params) { { sha: version_2.sha } }
it { is_expected.to contain_exactly(version_2) }
end
end
end
end
end
......
This diff is collapsed.
......@@ -2,6 +2,8 @@
require 'spec_helper'
describe DesignManagement::Version do
let_it_be(:issue) { create(:issue) }
describe 'relations' do
it { is_expected.to have_many(:actions) }
it { is_expected.to have_many(:designs).through(:actions) }
......@@ -60,6 +62,14 @@ describe DesignManagement::Version do
end
end
end
describe '.by_sha' do
it 'can find versions by sha' do
[version_1, version_2].each do |version|
expect(described_class.by_sha(version.sha)).to contain_exactly(version)
end
end
end
end
describe ".create_for_designs" do
......@@ -74,7 +84,6 @@ describe DesignManagement::Version do
end
let_it_be(:author) { create(:user) }
let_it_be(:issue) { create(:issue) }
let_it_be(:design_a) { create(:design, issue: issue) }
let_it_be(:design_b) { create(:design, issue: issue) }
let_it_be(:designs) { [design_a, design_b] }
......@@ -283,4 +292,51 @@ describe DesignManagement::Version do
expect(version.author).to eq(commit_user)
end
end
describe '#diff_refs' do
let(:project) { issue.project }
before do
expect(project.design_repository).to receive(:commit)
.once
.with(sha)
.and_return(commit)
end
subject { create(:design_version, issue: issue, sha: sha) }
context 'there is a commit in the repo by the SHA' do
let(:commit) { build(:commit) }
let(:sha) { commit.id }
it { is_expected.to have_attributes(diff_refs: commit.diff_refs) }
it 'memoizes calls to #diff_refs' do
expect(subject.diff_refs).to eq(subject.diff_refs)
end
end
context 'there is no commit in the repo by the SHA' do
let(:commit) { nil }
let(:sha) { Digest::SHA1.hexdigest("points to nothing") }
it { is_expected.to have_attributes(diff_refs: be_nil) }
end
end
describe '#reset' do
subject { create(:design_version, issue: issue) }
it 'removes memoized values' do
expect(subject).to receive(:commit).twice.and_return(nil)
subject.diff_refs
subject.diff_refs
subject.reset
subject.diff_refs
subject.diff_refs
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