From 16bd8409bcb61d2331227d1df539c40683b6bda3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 11 Nov 2019 12:06:23 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../vue_shared/components/icon.vue | 2 +- .../cycle_analytics/project_stage.rb | 11 ++ .../analytics/cycle_analytics/stage.rb | 60 +++++++- ...les-in-merge-request-by-clicking-caret.yml | 5 + .../32685-remove-epics-tree-feature-flag.yml | 5 + ...-via-soft-delete-for-groups-db-changes.yml | 5 - .../sh-guard-repository-mirrors-read-only.yml | 5 + ...358_add_mark_for_deletion_to_namespaces.rb | 10 -- ...mark_for_deletion_indices_to_namespaces.rb | 20 --- db/schema.rb | 5 - .../stage_events/stage_event.rb | 4 + lib/gitlab/import_export/import_export.yml | 2 - locale/gitlab.pot | 21 +++ .../vue_shared/components/icon_spec.js | 13 ++ spec/lib/gitlab/ci/build/rules/rule_spec.rb | 2 + spec/lib/gitlab/ci/build/rules_spec.rb | 2 + .../gitlab/ci/config/entry/rules/rule_spec.rb | 2 + spec/lib/gitlab/ci/config/entry/rules_spec.rb | 2 + spec/lib/gitlab/ci/status/composite_spec.rb | 2 + spec/lib/gitlab/ci/trace/stream_spec.rb | 10 +- .../cycle_analytics_event_shared_examples.rb | 3 +- .../cycle_analytics_stage_shared_examples.rb | 137 +++++++++++++++++- 22 files changed, 267 insertions(+), 61 deletions(-) create mode 100644 changelogs/unreleased/25188-unable-to-expand-collapse-files-in-merge-request-by-clicking-caret.yml create mode 100644 changelogs/unreleased/32685-remove-epics-tree-feature-flag.yml delete mode 100644 changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-changes.yml create mode 100644 changelogs/unreleased/sh-guard-repository-mirrors-read-only.yml delete mode 100644 db/migrate/20191029060358_add_mark_for_deletion_to_namespaces.rb delete mode 100644 db/migrate/20191029061556_add_mark_for_deletion_indices_to_namespaces.rb diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 73f4dfef062..80908cbbc9c 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -61,7 +61,7 @@ export default { diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb index 0cdf199a146..b2c16444a2a 100644 --- a/app/models/analytics/cycle_analytics/project_stage.rb +++ b/app/models/analytics/cycle_analytics/project_stage.rb @@ -11,6 +11,10 @@ module Analytics alias_attribute :parent, :project alias_attribute :parent_id, :project_id + delegate :group, to: :project + + validate :validate_project_group_for_label_events, if: -> { start_event_label_based? || end_event_label_based? } + def self.relative_positioning_query_base(stage) where(project_id: stage.project_id) end @@ -18,6 +22,13 @@ module Analytics def self.relative_positioning_parent_column :project_id end + + private + + # Project should belong to a group when the stage has Label based events since only GroupLabels are allowed. + def validate_project_group_for_label_events + errors.add(:project, s_('CycleAnalyticsStage|should be under a group')) unless project.group + end end end end diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb index 1376dd97a49..0e07806dd6f 100644 --- a/app/models/concerns/analytics/cycle_analytics/stage.rb +++ b/app/models/concerns/analytics/cycle_analytics/stage.rb @@ -5,13 +5,20 @@ module Analytics module Stage extend ActiveSupport::Concern include RelativePositioning + include Gitlab::Utils::StrongMemoize included do + belongs_to :start_event_label, class_name: 'GroupLabel', optional: true + belongs_to :end_event_label, class_name: 'GroupLabel', optional: true + validates :name, presence: true validates :name, exclusion: { in: Gitlab::Analytics::CycleAnalytics::DefaultStages.names }, if: :custom? validates :start_event_identifier, presence: true validates :end_event_identifier, presence: true + validates :start_event_label, presence: true, if: :start_event_label_based? + validates :end_event_label, presence: true, if: :end_event_label_based? validate :validate_stage_event_pairs + validate :validate_labels enum start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :start_event_identifier enum end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :end_event_identifier @@ -30,19 +37,41 @@ module Analytics end def start_event - Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event) + strong_memoize(:start_event) do + Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event) + end end def end_event - Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event) + strong_memoize(:end_event) do + Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event) + end + end + + def start_event_label_based? + start_event_identifier && start_event.label_based? + end + + def end_event_label_based? + end_event_identifier && end_event.label_based? + end + + def start_event_identifier=(identifier) + clear_memoization(:start_event) + super + end + + def end_event_identifier=(identifier) + clear_memoization(:end_event) + super end def params_for_start_event - {} + start_event_label.present? ? { label: start_event_label } : {} end def params_for_end_event - {} + end_event_label.present? ? { label: end_event_label } : {} end def default_stage? @@ -70,13 +99,34 @@ module Analytics return if start_event_identifier.nil? || end_event_identifier.nil? unless pairing_rules.fetch(start_event.class, []).include?(end_event.class) - errors.add(:end_event, :not_allowed_for_the_given_start_event) + errors.add(:end_event, s_('CycleAnalytics|not allowed for the given start event')) end end def pairing_rules Gitlab::Analytics::CycleAnalytics::StageEvents.pairing_rules end + + def validate_labels + validate_label_within_group(:start_event_label, start_event_label_id) if start_event_label_id_changed? + validate_label_within_group(:end_event_label, end_event_label_id) if end_event_label_id_changed? + end + + def validate_label_within_group(association_name, label_id) + return unless label_id + return unless group + + unless label_available_for_group?(label_id) + errors.add(association_name, s_('CycleAnalyticsStage|is not available for the selected group')) + end + end + + def label_available_for_group?(label_id) + LabelsFinder.new(nil, { group_id: group.id, include_ancestor_groups: true, only_group_labels: true }) + .execute(skip_authorization: true) + .by_ids(label_id) + .exists? + end end end end diff --git a/changelogs/unreleased/25188-unable-to-expand-collapse-files-in-merge-request-by-clicking-caret.yml b/changelogs/unreleased/25188-unable-to-expand-collapse-files-in-merge-request-by-clicking-caret.yml new file mode 100644 index 00000000000..59a0efee013 --- /dev/null +++ b/changelogs/unreleased/25188-unable-to-expand-collapse-files-in-merge-request-by-clicking-caret.yml @@ -0,0 +1,5 @@ +--- +title: Fix unable to expand or collapse files in merge request by clicking caret +merge_request: 19222 +author: Brian T +type: fixed diff --git a/changelogs/unreleased/32685-remove-epics-tree-feature-flag.yml b/changelogs/unreleased/32685-remove-epics-tree-feature-flag.yml new file mode 100644 index 00000000000..5c30150e66a --- /dev/null +++ b/changelogs/unreleased/32685-remove-epics-tree-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Show Tree UI containing child Epics and Issues within an Epic +merge_request: 19812 +author: +type: added diff --git a/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-changes.yml b/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-changes.yml deleted file mode 100644 index 7b785b04fdf..00000000000 --- a/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-changes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add migrations for 'soft-delete for groups' feature -merge_request: 19342 -author: -type: added diff --git a/changelogs/unreleased/sh-guard-repository-mirrors-read-only.yml b/changelogs/unreleased/sh-guard-repository-mirrors-read-only.yml new file mode 100644 index 00000000000..5dc916e1082 --- /dev/null +++ b/changelogs/unreleased/sh-guard-repository-mirrors-read-only.yml @@ -0,0 +1,5 @@ +--- +title: Disable pull mirror if repository is in read-only state +merge_request: 19182 +author: +type: fixed diff --git a/db/migrate/20191029060358_add_mark_for_deletion_to_namespaces.rb b/db/migrate/20191029060358_add_mark_for_deletion_to_namespaces.rb deleted file mode 100644 index b66e3b7e62a..00000000000 --- a/db/migrate/20191029060358_add_mark_for_deletion_to_namespaces.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -class AddMarkForDeletionToNamespaces < ActiveRecord::Migration[5.2] - DOWNTIME = false - - def change - add_column :namespaces, :marked_for_deletion_at, :date - add_column :namespaces, :marked_for_deletion_by_user_id, :integer - end -end diff --git a/db/migrate/20191029061556_add_mark_for_deletion_indices_to_namespaces.rb b/db/migrate/20191029061556_add_mark_for_deletion_indices_to_namespaces.rb deleted file mode 100644 index ce01791bc2e..00000000000 --- a/db/migrate/20191029061556_add_mark_for_deletion_indices_to_namespaces.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class AddMarkForDeletionIndicesToNamespaces < ActiveRecord::Migration[5.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - disable_ddl_transaction! - - def up - add_concurrent_foreign_key :namespaces, :users, column: :marked_for_deletion_by_user_id, on_delete: :nullify - add_concurrent_index :namespaces, :marked_for_deletion_by_user_id, where: 'marked_for_deletion_by_user_id IS NOT NULL' - add_concurrent_index :namespaces, :marked_for_deletion_at, where: 'marked_for_deletion_at IS NOT NULL' - end - - def down - remove_foreign_key_if_exists :namespaces, column: :marked_for_deletion_by_user_id - remove_concurrent_index :namespaces, :marked_for_deletion_by_user_id - remove_concurrent_index :namespaces, :marked_for_deletion_at - end -end diff --git a/db/schema.rb b/db/schema.rb index 368a1b91e06..c0b74841834 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2492,15 +2492,11 @@ ActiveRecord::Schema.define(version: 2019_11_05_094625) do t.boolean "emails_disabled" t.integer "max_pages_size" t.integer "max_artifacts_size" - t.date "marked_for_deletion_at" - t.integer "marked_for_deletion_by_user_id" t.index ["created_at"], name: "index_namespaces_on_created_at" t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)" t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id" t.index ["ldap_sync_last_successful_update_at"], name: "index_namespaces_on_ldap_sync_last_successful_update_at" t.index ["ldap_sync_last_update_at"], name: "index_namespaces_on_ldap_sync_last_update_at" - t.index ["marked_for_deletion_at"], name: "index_namespaces_on_marked_for_deletion_at", where: "(marked_for_deletion_at IS NOT NULL)" - t.index ["marked_for_deletion_by_user_id"], name: "index_namespaces_on_marked_for_deletion_by_user_id", where: "(marked_for_deletion_by_user_id IS NOT NULL)" t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true t.index ["name"], name: "index_namespaces_on_name_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["owner_id"], name: "index_namespaces_on_owner_id" @@ -4367,7 +4363,6 @@ ActiveRecord::Schema.define(version: 2019_11_05_094625) do add_foreign_key "namespaces", "namespaces", column: "custom_project_templates_group_id", name: "fk_e7a0b20a6b", on_delete: :nullify add_foreign_key "namespaces", "plans", name: "fk_fdd12e5b80", on_delete: :nullify add_foreign_key "namespaces", "projects", column: "file_template_project_id", name: "fk_319256d87a", on_delete: :nullify - add_foreign_key "namespaces", "users", column: "marked_for_deletion_by_user_id", name: "fk_9ff61b4c22", on_delete: :nullify add_foreign_key "note_diff_files", "notes", column: "diff_note_id", on_delete: :cascade add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade add_foreign_key "notes", "reviews", name: "fk_2e82291620", on_delete: :nullify diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb index aa392140eb5..667d6def414 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb @@ -35,6 +35,10 @@ module Gitlab query end + def label_based? + false + end + private attr_reader :params diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 61a314d0576..1aafe5804c0 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -153,8 +153,6 @@ excluded_attributes: namespaces: - :runners_token - :runners_token_encrypted - - :marked_for_deletion_at - - :marked_for_deletion_by_user_id project_import_state: - :last_error - :jid diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a45e513d9b2..bb22b7cbe18 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5081,9 +5081,21 @@ msgstr "" msgid "CycleAnalyticsEvent|Issue first mentioned in a commit" msgstr "" +msgid "CycleAnalyticsEvent|Issue label was added" +msgstr "" + +msgid "CycleAnalyticsEvent|Issue label was removed" +msgstr "" + msgid "CycleAnalyticsEvent|Issue last edited" msgstr "" +msgid "CycleAnalyticsEvent|Merge Request label was added" +msgstr "" + +msgid "CycleAnalyticsEvent|Merge Request label was removed" +msgstr "" + msgid "CycleAnalyticsEvent|Merge request closed" msgstr "" @@ -5126,6 +5138,12 @@ msgstr "" msgid "CycleAnalyticsStage|Test" msgstr "" +msgid "CycleAnalyticsStage|is not available for the selected group" +msgstr "" + +msgid "CycleAnalyticsStage|should be under a group" +msgstr "" + msgid "CycleAnalytics|%{projectName}" msgid_plural "CycleAnalytics|%d projects selected" msgstr[0] "" @@ -5145,6 +5163,9 @@ msgstr "" msgid "CycleAnalytics|group dropdown filter" msgstr "" +msgid "CycleAnalytics|not allowed for the given start event" +msgstr "" + msgid "CycleAnalytics|project dropdown filter" msgstr "" diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js index 7390798afa8..ecaef414464 100644 --- a/spec/javascripts/vue_shared/components/icon_spec.js +++ b/spec/javascripts/vue_shared/components/icon_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import Icon from '~/vue_shared/components/icon.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; describe('Sprite Icon Component', function() { describe('Initialization', function() { @@ -57,4 +58,16 @@ describe('Sprite Icon Component', function() { expect(Icon.props.name.validator('commit')).toBe(true); }); }); + + it('should call registered listeners when they are triggered', () => { + const clickHandler = jasmine.createSpy('clickHandler'); + const wrapper = mount(Icon, { + propsData: { name: 'commit' }, + listeners: { click: clickHandler }, + }); + + wrapper.find('svg').trigger('click'); + + expect(clickHandler).toHaveBeenCalled(); + }); }); diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb index 99852bd4228..e0f341461fb 100644 --- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Rules::Rule do diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb index d7793ebc806..b783bbb8287 100644 --- a/spec/lib/gitlab/ci/build/rules_spec.rb +++ b/spec/lib/gitlab/ci/build/rules_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Rules do diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb index 9d4f7153cd0..1f54f6ec537 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fast_spec_helper' require 'gitlab_chronic_duration' require 'support/helpers/stub_feature_flags' diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb index 291e7373daf..926d3fd1678 100644 --- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fast_spec_helper' require 'support/helpers/stub_feature_flags' require_dependency 'active_model' diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb index 1725d954b92..857483a9e0a 100644 --- a/spec/lib/gitlab/ci/status/composite_spec.rb +++ b/spec/lib/gitlab/ci/status/composite_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Status::Composite do diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 1baea13299b..45b59541ce6 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do @@ -100,7 +102,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do describe '#append' do shared_examples_for 'appends' do it "truncates and append content" do - stream.append("89", 4) + stream.append(+"89", 4) stream.seek(0) expect(stream.size).to eq(6) @@ -108,7 +110,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do end it 'appends in binary mode' do - '😺'.force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| + (+'😺').force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| stream.append(byte, offset) end @@ -154,7 +156,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do describe '#set' do shared_examples_for 'sets' do before do - stream.set("8901") + stream.set(+"8901") end it "overwrite content" do @@ -168,7 +170,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do context 'when stream is StringIO' do let(:stream) do described_class.new do - StringIO.new("12345678") + StringIO.new(+"12345678") end end diff --git a/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb b/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb index dce1dbe1cd1..028b8da94a6 100644 --- a/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb +++ b/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true shared_examples_for 'cycle analytics event' do - let(:instance) { described_class.new({}) } + let(:params) { {} } + let(:instance) { described_class.new(params) } it { expect(described_class.name).to be_a_kind_of(String) } it { expect(described_class.identifier).to be_a_kind_of(Symbol) } diff --git a/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb index afa035d039a..c781f72ff11 100644 --- a/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb +++ b/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb @@ -10,6 +10,11 @@ shared_examples_for 'cycle analytics stage' do } end + describe 'associations' do + it { is_expected.to belong_to(:end_event_label) } + it { is_expected.to belong_to(:start_event_label) } + end + describe 'validation' do it 'is valid' do expect(described_class.new(valid_params)).to be_valid @@ -18,22 +23,22 @@ shared_examples_for 'cycle analytics stage' do it 'validates presence of parent' do stage = described_class.new(valid_params.except(:parent)) - expect(stage).not_to be_valid - expect(stage.errors.details[parent_name]).to eq([{ error: :blank }]) + expect(stage).to be_invalid + expect(stage.errors[parent_name]).to include("can't be blank") end it 'validates presence of start_event_identifier' do stage = described_class.new(valid_params.except(:start_event_identifier)) - expect(stage).not_to be_valid - expect(stage.errors.details[:start_event_identifier]).to eq([{ error: :blank }]) + expect(stage).to be_invalid + expect(stage.errors[:start_event_identifier]).to include("can't be blank") end it 'validates presence of end_event_identifier' do stage = described_class.new(valid_params.except(:end_event_identifier)) - expect(stage).not_to be_valid - expect(stage.errors.details[:end_event_identifier]).to eq([{ error: :blank }]) + expect(stage).to be_invalid + expect(stage.errors[:end_event_identifier]).to include("can't be blank") end it 'is invalid when end_event is not allowed for the given start_event' do @@ -43,8 +48,8 @@ shared_examples_for 'cycle analytics stage' do ) stage = described_class.new(invalid_params) - expect(stage).not_to be_valid - expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }]) + expect(stage).to be_invalid + expect(stage.errors[:end_event]).to include(s_('CycleAnalytics|not allowed for the given start event')) end context 'disallows default stage names when creating custom stage' do @@ -105,3 +110,119 @@ shared_examples_for 'cycle analytics stage' do end end end + +shared_examples_for 'cycle analytics label based stage' do + context 'when creating label based event' do + context 'when the label id is not passed' do + it 'returns validation error when `start_event_label_id` is missing' do + stage = described_class.new({ + name: 'My Stage', + parent: parent, + start_event_identifier: :issue_label_added, + end_event_identifier: :issue_closed + }) + + expect(stage).to be_invalid + expect(stage.errors[:start_event_label]).to include("can't be blank") + end + + it 'returns validation error when `end_event_label_id` is missing' do + stage = described_class.new({ + name: 'My Stage', + parent: parent, + start_event_identifier: :issue_closed, + end_event_identifier: :issue_label_added + }) + + expect(stage).to be_invalid + expect(stage.errors[:end_event_label]).to include("can't be blank") + end + end + + context 'when group label is defined on the root group' do + it 'succeeds' do + stage = described_class.new({ + name: 'My Stage', + parent: parent, + start_event_identifier: :issue_label_added, + start_event_label: group_label, + end_event_identifier: :issue_closed + }) + + expect(stage).to be_valid + end + end + + context 'when subgroup is given' do + it 'succeeds' do + stage = described_class.new({ + name: 'My Stage', + parent: parent_in_subgroup, + start_event_identifier: :issue_label_added, + start_event_label: group_label, + end_event_identifier: :issue_closed + }) + + expect(stage).to be_valid + end + end + + context 'when label is defined for a different group' do + let(:error_message) { s_('CycleAnalyticsStage|is not available for the selected group') } + + it 'returns validation for `start_event_label`' do + stage = described_class.new({ + name: 'My Stage', + parent: parent_outside_of_group_label_scope, + start_event_identifier: :issue_label_added, + start_event_label: group_label, + end_event_identifier: :issue_closed + }) + + expect(stage).to be_invalid + expect(stage.errors[:start_event_label]).to include(error_message) + end + + it 'returns validation for `end_event_label`' do + stage = described_class.new({ + name: 'My Stage', + parent: parent_outside_of_group_label_scope, + start_event_identifier: :issue_closed, + end_event_identifier: :issue_label_added, + end_event_label: group_label + }) + + expect(stage).to be_invalid + expect(stage.errors[:end_event_label]).to include(error_message) + end + end + + context 'when `ProjectLabel is given' do + let_it_be(:label) { create(:label) } + + it 'raises error when `ProjectLabel` is given for `start_event_label`' do + params = { + name: 'My Stage', + parent: parent, + start_event_identifier: :issue_label_added, + start_event_label: label, + end_event_identifier: :issue_closed + } + + expect { described_class.new(params) }.to raise_error(ActiveRecord::AssociationTypeMismatch) + end + + it 'raises error when `ProjectLabel` is given for `end_event_label`' do + params = { + name: 'My Stage', + parent: parent, + start_event_identifier: :issue_closed, + end_event_identifier: :issue_label_added, + end_event_label: label + } + + expect { described_class.new(params) }.to raise_error(ActiveRecord::AssociationTypeMismatch) + end + end + end +end