diff --git a/app/models/design_management/design_collection.rb b/app/models/design_management/design_collection.rb index 96d5f4c2419fc36985e3a126804785df73f2e74b..c48b36588c9d1a64defc2bf1c8a1f48a91140e39 100644 --- a/app/models/design_management/design_collection.rb +++ b/app/models/design_management/design_collection.rb @@ -6,8 +6,34 @@ class DesignCollection delegate :designs, :project, to: :issue + state_machine :copy_state, initial: :ready, namespace: :copy do + after_transition any => any, do: :update_stored_copy_state! + + event :start do + transition ready: :in_progress + end + + event :end do + transition in_progress: :ready + end + + event :error do + transition in_progress: :error + end + + event :reset do + transition any => :ready + end + end + def initialize(issue) + super() # Necessary to initialize state_machine + @issue = issue + + if stored_copy_state = get_stored_copy_state + @copy_state = stored_copy_state + end end def ==(other) @@ -30,5 +56,39 @@ def repository def designs_by_filename(filenames) designs.current.where(filename: filenames) end + + private + + def update_stored_copy_state! + # As "ready" is the initial copy state we can clear the cached value + # rather than persist it. + if copy_ready? + unset_store_copy_state! + else + set_stored_copy_state! + end + end + + def copy_state_cache_key + "DesignCollection/copy_state/issue=#{issue.id}" + end + + def get_stored_copy_state + Gitlab::Redis::SharedState.with do |redis| + redis.get(copy_state_cache_key) + end + end + + def set_stored_copy_state! + Gitlab::Redis::SharedState.with do |redis| + redis.set(copy_state_cache_key, copy_state) + end + end + + def unset_store_copy_state! + Gitlab::Redis::SharedState.with do |redis| + redis.del(copy_state_cache_key) + end + end end end diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb index 33f9d3b988f08c3b94f523c7bedbe450a328402f..8575cc80b5b5df90f4f81d86a56ba9502442a87c 100644 --- a/spec/models/design_management/design_collection_spec.rb +++ b/spec/models/design_management/design_collection_spec.rb @@ -3,8 +3,9 @@ RSpec.describe DesignManagement::DesignCollection do include DesignManagementTestHelpers + using RSpec::Parameterized::TableSyntax - let_it_be(:issue, reload: true) { create(:issue) } + let_it_be(:issue, refind: true) { create(:issue) } subject(:collection) { described_class.new(issue) } @@ -45,6 +46,61 @@ end end + describe "#copy_state", :clean_gitlab_redis_shared_state do + it "defaults to ready" do + expect(collection).to be_copy_ready + end + + it "persists its state changes between initializations" do + collection.start_copy! + + expect(described_class.new(issue)).to be_copy_in_progress + end + + where(:state, :can_start, :can_end, :can_error, :can_reset) do + "ready" | true | false | true | true + "in_progress" | false | true | true | true + "error" | false | false | false | true + end + + with_them do + it "maintains state machine transition rules", :aggregate_failures do + collection.copy_state = state + + expect(collection.can_start_copy?).to eq(can_start) + expect(collection.can_end_copy?).to eq(can_end) + end + end + + describe "clearing the redis cached state when state changes back to ready" do + def redis_copy_state + Gitlab::Redis::SharedState.with do |redis| + redis.get(collection.send(:copy_state_cache_key)) + end + end + + def fire_state_events(*events) + events.each do |event| + collection.fire_copy_state_event(event) + end + end + + it "clears the cached state on end_copy!", :aggregate_failures do + fire_state_events(:start) + + expect { collection.end_copy! }.to change { redis_copy_state }.from("in_progress").to(nil) + expect(collection).to be_copy_ready + end + + it "clears the cached state on reset_copy!", :aggregate_failures do + fire_state_events(:start, :error) + + expect { collection.reset_copy! }.to change { redis_copy_state }.from("error").to(nil) + expect(collection).to be_copy_ready + end + end + end + describe "#versions" do it "includes versions for all designs" do version_1 = create(:design_version)