Commit 16bd8409 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 4a45f0ef
......@@ -61,7 +61,7 @@ export default {
</script>
<template>
<svg :class="[iconSizeClass, iconTestClass]" aria-hidden="true">
<svg :class="[iconSizeClass, iconTestClass]" aria-hidden="true" v-on="$listeners">
<use v-bind="{ 'xlink:href': spriteHref }" />
</svg>
</template>
......@@ -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
......@@ -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
---
title: Fix unable to expand or collapse files in merge request by clicking caret
merge_request: 19222
author: Brian T
type: fixed
---
title: Show Tree UI containing child Epics and Issues within an Epic
merge_request: 19812
author:
type: added
---
title: Add migrations for 'soft-delete for groups' feature
merge_request: 19342
author:
type: added
---
title: Disable pull mirror if repository is in read-only state
merge_request: 19182
author:
type: fixed
# 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
# 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
......@@ -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
......
......@@ -35,6 +35,10 @@ module Gitlab
query
end
def label_based?
false
end
private
attr_reader :params
......
......@@ -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
......
......@@ -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 ""
......
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();
});
});
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Build::Rules::Rule do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Build::Rules do
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'gitlab_chronic_duration'
require 'support/helpers/stub_feature_flags'
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Status::Composite do
......
# 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
......
# 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) }
......
......@@ -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
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