Commit 48166eb0 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 10d28998 b829ab9f
......@@ -32,7 +32,6 @@ Rails/SaveBang:
- 'ee/spec/models/approval_project_rule_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
- 'ee/spec/models/epic_spec.rb'
- 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/label_note_spec.rb'
......
......@@ -105,8 +105,7 @@ class Projects::BranchesController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def destroy
@branch_name = Addressable::URI.unescape(params[:id])
result = ::Branches::DeleteService.new(project, current_user).execute(@branch_name)
result = ::Branches::DeleteService.new(project, current_user).execute(params[:id])
respond_to do |format|
format.html do
......
......@@ -10,7 +10,14 @@ module LearnGitlabHelper
def onboarding_actions_data(project)
attributes = onboarding_progress(project).attributes.symbolize_keys
action_urls.to_h do |action, url|
urls_to_use = nil
experiment(:change_continuous_onboarding_link_urls) do |e|
e.use { urls_to_use = action_urls }
e.try { urls_to_use = new_action_urls(project) }
end
urls_to_use.to_h do |action, url|
[
action,
url: url,
......@@ -46,6 +53,17 @@ module LearnGitlabHelper
.merge(LearnGitlab::Onboarding::ACTION_DOC_URLS)
end
def new_action_urls(project)
action_urls.merge(
issue_created: project_issues_path(project),
git_write: project_path(project),
pipeline_created: project_pipelines_path(project),
merge_request_created: project_merge_requests_path(project),
user_added: project_members_url(project),
security_scan_enabled: project_security_configuration_path(project)
)
end
def learn_gitlab_project
@learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project
end
......@@ -54,3 +72,5 @@ module LearnGitlabHelper
OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
end
end
LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper')
......@@ -23,6 +23,7 @@ module Ci
serialize :config_options, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
serialize :config_variables, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
serialize :runtime_runner_features, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
chronic_duration_attr_reader :timeout_human_readable, :timeout
......@@ -47,6 +48,14 @@ module Ci
update(timeout: timeout.value, timeout_source: timeout.source)
end
def set_cancel_gracefully
runtime_runner_features.merge!( { cancel_gracefully: true } )
end
def cancel_gracefully?
runtime_runner_features[:cancel_gracefully] == true
end
private
def set_build_project
......
......@@ -20,6 +20,8 @@ module Ci
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
delegate :cancel_gracefully?, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end
......
......@@ -15,13 +15,21 @@ module Uploads
end
def delete_keys(keys)
keys.each do |key|
connection.delete_object(bucket_name, key)
end
keys.each { |key| delete_object(key) }
end
private
def delete_object(key)
connection.delete_object(bucket_name, key)
# So far, only GoogleCloudStorage raises an exception when the file is not found.
# Other providers support idempotent requests and does not raise an error
# when the file is missing.
rescue ::Google::Apis::ClientError => e
Gitlab::ErrorTracking.log_exception(e)
end
def object_store
Gitlab.config.uploads.object_store
end
......
---
name: change_continuous_onboarding_link_urls
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71408
rollout_issue_url:
milestone: '14.5'
type: experiment
group: group::conversion
default_enabled: false
......@@ -27,6 +27,7 @@
# include_descendant_groups: boolean
# starts_with_iid: string (containing a number)
# confidential: boolean
# hierarchy_order: :desc or :acs, default :acs when searched by child_id
class EpicsFinder < IssuableFinder
include TimeFrameFilter
......@@ -198,8 +199,10 @@ class EpicsFinder < IssuableFinder
def by_child(items)
return items unless child_id?
ancestor_ids = Epic.find(params[:child_id]).ancestors.reselect(:id)
items.where(id: ancestor_ids)
hierarchy_order = params[:hierarchy_order] || :asc
ancestors = Epic.find(params[:child_id]).ancestors(hierarchy_order: hierarchy_order)
ancestors.where(id: items.select(:id))
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -272,4 +275,11 @@ class EpicsFinder < IssuableFinder
def feature_flag_scope
params.group
end
override :sort
def sort(items)
return items if params[:hierarchy_order]
super
end
end
......@@ -9,12 +9,18 @@ module Resolvers
description: 'Include epics from ancestor groups.',
default_value: true
def resolve_with_lookahead(**args)
items = super
offset_pagination(items)
end
private
def relative_param
return {} unless parent
{ child_id: parent.id }
{ child_id: parent.id, hierarchy_order: :desc }
end
end
end
# frozen_string_literal: true
module EE
module LearnGitlabHelper
extend ::Gitlab::Utils::Override
GITLAB_COM = 'gitlab.com'
ONBOARDING_START_TRIAL = 'onboarding-start-trial'
ONBOARDING_REQUIRE_MR_APPROVALS = 'onboarding-require-merge-approvals'
ONBOARDING_CODE_OWNERS = 'onboarding-code-owners'
private
override :new_action_urls
def new_action_urls(project)
urls = super(project)
return urls unless ::Gitlab::CurrentSettings.should_check_namespace_plan?
glm_params = { glm_source: GITLAB_COM }
urls.merge(
trial_started: new_trial_path(glm_params.merge(glm_content: ONBOARDING_START_TRIAL)),
required_mr_approvals_enabled: new_trial_path(glm_params.merge(glm_content: ONBOARDING_REQUIRE_MR_APPROVALS)),
code_owners_enabled: new_trial_path(glm_params.merge(glm_content: ONBOARDING_CODE_OWNERS))
)
end
end
end
......@@ -425,10 +425,10 @@ module EE
end
end
def ancestors
def ancestors(hierarchy_order: :asc)
return self.class.none unless parent_id
hierarchy.ancestors(hierarchy_order: :asc)
hierarchy.ancestors(hierarchy_order: hierarchy_order)
end
def max_hierarchy_depth_achieved?
......
......@@ -9,8 +9,9 @@ RSpec.describe EpicsFinder do
let_it_be(:another_group) { create(:group) }
let_it_be(:reference_time) { Time.parse('2020-09-15 01:00') } # Arbitrary time used for time/date range filters
let_it_be(:epic1) { create(:epic, :opened, group: group, title: 'This is awesome epic', created_at: 1.week.before(reference_time), end_date: 10.days.before(reference_time)) }
let_it_be(:epic2) { create(:epic, :opened, group: group, created_at: 4.days.before(reference_time), author: user, start_date: 2.days.before(reference_time), end_date: 3.days.since(reference_time), parent: epic1) }
let_it_be(:epic3) { create(:epic, :closed, group: group, description: 'not so awesome', start_date: 5.days.before(reference_time), end_date: 3.days.before(reference_time), parent: epic2) }
let_it_be(:epic3, reload: true) { create(:epic, :closed, group: group, description: 'not so awesome', start_date: 5.days.before(reference_time), end_date: 3.days.before(reference_time)) }
let_it_be(:epic2, reload: true) { create(:epic, :opened, group: group, created_at: 4.days.before(reference_time), author: user, start_date: 2.days.before(reference_time), end_date: 3.days.since(reference_time)) }
let_it_be(:epic5) { create(:epic, group: group, start_date: 6.days.before(reference_time), end_date: 6.days.before(reference_time), parent: epic3) }
let_it_be(:epic4) { create(:epic, :closed, group: another_group) }
describe '#execute' do
......@@ -55,7 +56,7 @@ RSpec.describe EpicsFinder do
end
it 'returns all epics that belong to the given group' do
expect(epics).to contain_exactly(epic1, epic2, epic3)
expect(epics).to contain_exactly(epic1, epic2, epic3, epic5)
end
it 'does not execute more than 5 SQL queries' do
......@@ -64,11 +65,11 @@ RSpec.describe EpicsFinder do
context 'sorting' do
it 'sorts correctly when supported sorting param provided' do
expect(epics(sort: :start_date_asc)).to eq([epic3, epic2, epic1])
expect(epics(sort: :start_date_asc)).to eq([epic5, epic3, epic2, epic1])
end
it 'sorts by id when not supported sorting param provided' do
expect(epics(sort: :not_supported_param)).to eq([epic3, epic2, epic1])
expect(epics(sort: :not_supported_param)).to eq([epic5, epic2, epic3, epic1])
end
end
......@@ -78,7 +79,7 @@ RSpec.describe EpicsFinder do
end
it 'returns all epics created after the given date' do
expect(epics(created_after: 2.days.before(reference_time))).to contain_exactly(epic3)
expect(epics(created_after: 2.days.before(reference_time))).to contain_exactly(epic3, epic5)
end
it 'returns all epics created within the given interval' do
......@@ -146,7 +147,7 @@ RSpec.describe EpicsFinder do
end
it 'does not add any filter' do
expect(epics(or: { author_username: [epic2.author.username, epic3.author.username] })).to contain_exactly(epic1, epic2, epic3)
expect(epics(or: { author_username: [epic2.author.username, epic3.author.username] })).to contain_exactly(epic1, epic2, epic3, epic5)
end
end
end
......@@ -195,7 +196,7 @@ RSpec.describe EpicsFinder do
end
it 'returns all epics that belong to the given group and its subgroups' do
expect(epics).to contain_exactly(epic1, epic2, epic3, subgroup_epic, subgroup2_epic)
expect(epics).to contain_exactly(epic1, epic2, epic3, subgroup_epic, subgroup2_epic, epic5)
end
describe 'hierarchy params' do
......@@ -223,7 +224,7 @@ RSpec.describe EpicsFinder do
subject { finder.execute }
it 'gets only epics from the project ancestor groups' do
is_expected.to contain_exactly(epic1, epic2, epic3, subgroup3_epic)
is_expected.to contain_exactly(epic1, epic2, epic3, subgroup3_epic, epic5)
end
end
......@@ -237,7 +238,7 @@ RSpec.describe EpicsFinder do
context 'and include_ancestor_groups is true' do
let(:finder_params) { { include_descendant_groups: false, include_ancestor_groups: true } }
it { is_expected.to contain_exactly(subgroup_epic, epic1, epic2, epic3) }
it { is_expected.to contain_exactly(subgroup_epic, epic1, epic2, epic3, epic5) }
context "when user does not have permission to view ancestor groups" do
let(:finder_params) { { group_id: subgroup.id, include_descendant_groups: false, include_ancestor_groups: true } }
......@@ -259,7 +260,7 @@ RSpec.describe EpicsFinder do
context 'and include_ancestor_groups is true' do
let(:finder_params) { { include_ancestor_groups: true } }
it { is_expected.to contain_exactly(subgroup_epic, subgroup2_epic, epic1, epic2, epic3) }
it { is_expected.to contain_exactly(subgroup_epic, subgroup2_epic, epic1, epic2, epic3, epic5) }
context "when user does not have permission to view ancestor groups" do
let(:finder_params) { { group_id: subgroup.id, include_ancestor_groups: true } }
......@@ -344,6 +345,11 @@ RSpec.describe EpicsFinder do
end
context 'by parent' do
before do
epic3.update!(parent_id: epic2.id)
epic2.update!(parent_id: epic1.id)
end
it 'returns direct children of the parent' do
params = { parent_id: epic1.id }
......@@ -352,10 +358,21 @@ RSpec.describe EpicsFinder do
end
context 'by child' do
it 'returns ancestors of the child epic' do
params = { child_id: epic3.id }
before do
epic3.update!(parent_id: epic2.id)
epic2.update!(parent_id: epic1.id)
end
expect(epics(params)).to contain_exactly(epic1, epic2)
it 'returns ancestors of the child epic ordered from the bottom' do
params = { child_id: epic5.id, hierarchy_order: :asc }
expect(epics(params)).to eq([epic3, epic2, epic1])
end
it 'returns ancestors of the child epic ordered from the top if requested' do
params = { child_id: epic5.id, hierarchy_order: :desc }
expect(epics(params)).to eq([epic1, epic2, epic3])
end
end
......@@ -741,11 +758,11 @@ RSpec.describe EpicsFinder do
let_it_be(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
it 'returns all epics if no negated labels are present' do
expect(epics).to contain_exactly(negated_epic, negated_epic2, epic1, epic2, epic3)
expect(epics).to contain_exactly(negated_epic, negated_epic2, epic1, epic2, epic3, epic5)
end
it 'returns all epics without negated label' do
expect(epics(params)).to contain_exactly(epic1, epic2, epic3)
expect(epics(params)).to contain_exactly(epic1, epic2, epic3, epic5)
end
end
......@@ -755,11 +772,11 @@ RSpec.describe EpicsFinder do
let_it_be(:params) { { not: { author_id: author.id } } }
it 'returns all epics if no negated author is present' do
expect(epics).to contain_exactly(authored_epic, epic1, epic2, epic3)
expect(epics).to contain_exactly(authored_epic, epic1, epic2, epic3, epic5)
end
it 'returns all epics without given author' do
expect(epics(params)).to contain_exactly(epic1, epic2, epic3)
expect(epics(params)).to contain_exactly(epic1, epic2, epic3, epic5)
end
end
......@@ -768,7 +785,7 @@ RSpec.describe EpicsFinder do
let_it_be(:params) { { not: { my_reaction_emoji: awarded_emoji.name } } }
it 'returns all epics without given emoji name' do
expect(epics(params)).to contain_exactly(epic1, epic2)
expect(epics(params)).to contain_exactly(epic1, epic2, epic5)
end
end
end
......@@ -820,7 +837,7 @@ RSpec.describe EpicsFinder do
it 'returns correct counts' do
results = described_class.new(search_user, group_id: group.id).count_by_state
expect(results).to eq('opened' => 2, 'closed' => 1, 'all' => 3)
expect(results).to eq('opened' => 3, 'closed' => 1, 'all' => 4)
end
it 'returns -1 if the query times out' do
......
......@@ -54,8 +54,8 @@ RSpec.describe Resolvers::EpicAncestorsResolver do
sub_group.add_developer(current_user)
end
it 'returns all ancestors' do
expect(resolve_ancestors(epic4, args)).to contain_exactly(epic1, epic2, epic3)
it 'returns all ancestors in the correct order' do
expect(resolve_ancestors(epic4, args)).to eq([epic1, epic2, epic3])
end
it 'does not return parent group epics when include_ancestor_groups is false' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LearnGitlabHelper do
include Devise::Test::ControllerHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME, namespace: user.namespace) }
let_it_be(:namespace) { project.namespace }
before do
allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab|
allow(learn_gitlab).to receive(:project).and_return(project)
end
OnboardingProgress.onboard(namespace)
end
describe '#onboarding_actions_data' do
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
context 'when in the new action URLs experiment' do
before do
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
end
context 'for trial- and subscription-related actions' do
context 'when namespace plans are not enabled' do
before do
stub_application_setting(check_namespace_plan: false)
end
it 'provides the default URLs' do
expect(onboarding_actions_data).to include(
trial_started: a_hash_including(
url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/2})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/10})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/11})
)
)
end
end
context 'when namespace plans are enabled' do
before do
stub_application_setting(check_namespace_plan: true)
end
it 'provides URLs to start a trial for the appropariate actions' do
expect(onboarding_actions_data).to include(
trial_started: a_hash_including(
url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-start-trial')
),
code_owners_enabled: a_hash_including(
url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-code-owners')
),
required_mr_approvals_enabled: a_hash_including(
url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-require-merge-approvals')
)
)
end
end
end
end
end
end
......@@ -361,13 +361,22 @@ RSpec.describe Epic do
end
context 'hierarchy' do
let(:epic1) { create(:epic, group: group) }
let(:epic2) { create(:epic, group: group, parent: epic1) }
let(:epic3) { create(:epic, group: group, parent: epic2) }
let_it_be(:epic2, reload: true) { create(:epic, group: group) }
let_it_be(:epic3) { create(:epic, group: group, parent: epic2) }
let_it_be(:epic4) { create(:epic, group: group, parent: epic3) }
let_it_be(:epic1) { create(:epic, group: group) }
before do
epic2.update!(parent_id: epic1.id)
end
describe '#ancestors' do
it 'returns all ancestors for an epic' do
expect(epic3.ancestors).to eq [epic2, epic1]
it 'returns all ancestors for an epic ordered correctly' do
expect(epic4.ancestors).to eq([epic3, epic2, epic1])
end
it 'returns all ancestors for an epic ordered correctly with the hierarchy_order param' do
expect(epic4.ancestors(hierarchy_order: :desc)).to eq([epic1, epic2, epic3])
end
it 'returns an empty array if an epic does not have any parent' do
......@@ -377,11 +386,11 @@ RSpec.describe Epic do
describe '#descendants' do
it 'returns all descendants for an epic' do
expect(epic1.descendants).to match_array([epic2, epic3])
expect(epic1.descendants).to match_array([epic2, epic3, epic4])
end
it 'returns an empty array if an epic does not have any descendants' do
expect(epic3.descendants).to be_empty
expect(epic4.descendants).to be_empty
end
end
end
......@@ -705,8 +714,7 @@ RSpec.describe Epic do
end
before do
epic.description = ref_text
epic.save
epic.update!(description: ref_text)
end
it 'creates new system notes for cross references' do
......
......@@ -106,6 +106,21 @@ RSpec.describe 'getting epics information' do
end
end
context 'query for epics with ancestors' do
let_it_be(:parent_epic) { create(:epic, group: group) }
let_it_be(:epic) { create(:epic, group: group, parent: parent_epic) }
it 'returns the ancestors' do
query_epic_with_ancestors(epic.iid)
ancestors = graphql_data['group']['epic']['ancestors']['nodes']
expect(ancestors.count).to eq(1)
expect(ancestors.first['id']).to eq(parent_epic.to_global_id.to_s)
expect(graphql_errors).to be_nil
end
end
describe 'N+1 query checks' do
let_it_be(:epic_a) { create(:epic, group: group) }
let_it_be(:epic_b) { create(:epic, group: group) }
......@@ -157,6 +172,24 @@ RSpec.describe 'getting epics information' do
)
end
def query_epic_with_ancestors(epic_iid)
epics_field = <<~NODE
epic(iid: #{epic_iid}) {
id
ancestors {
nodes {
id
}
}
}
NODE
post_graphql(
graphql_query_for('group', { 'fullPath' => group.full_path }, epics_field),
current_user: user
)
end
def epics_query(group, field, value)
epics_query_by_hash(group, field => value)
end
......
......@@ -356,7 +356,7 @@ RSpec.describe Projects::BranchesController do
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
it { expect(response).to have_gitlab_http_status(:ok) }
it { expect(response).to have_gitlab_http_status(:not_found) }
it { expect(response.body).to be_blank }
end
......@@ -396,10 +396,10 @@ RSpec.describe Projects::BranchesController do
let(:branch) { 'improve%2Fawesome' }
it 'returns JSON response with message' do
expect(json_response).to eql('message' => 'Branch was deleted')
expect(json_response).to eql('message' => 'No such branch')
end
it { expect(response).to have_gitlab_http_status(:ok) }
it { expect(response).to have_gitlab_http_status(:not_found) }
end
context 'invalid branch name, valid ref' do
......
......@@ -11,9 +11,6 @@ RSpec.describe LearnGitlabHelper do
let_it_be(:namespace) { project.namespace }
before do
project.add_developer(user)
allow(helper).to receive(:user).and_return(user)
allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab|
allow(learn_gitlab).to receive(:project).and_return(project)
end
......@@ -22,38 +19,115 @@ RSpec.describe LearnGitlabHelper do
OnboardingProgress.register(namespace, :git_write)
end
describe '.onboarding_actions_data' do
describe '#onboarding_actions_data' do
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
:required_mr_approvals_enabled,
:code_owners_enabled,
:security_scan_enabled
)
shared_examples 'has all actions' do
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
:required_mr_approvals_enabled,
:code_owners_enabled,
:security_scan_enabled
)
end
end
it 'sets correct path and completion status' do
expect(onboarding_actions_data[:git_write]).to eq({
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:git_write]),
completed: true,
svg: helper.image_path("learn_gitlab/git_write.svg")
it_behaves_like 'has all actions'
it 'sets correct paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/4\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/6\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/7\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/8\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/9\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z})
)
})
expect(onboarding_actions_data[:pipeline_created]).to eq({
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:pipeline_created]),
completed: false,
svg: helper.image_path("learn_gitlab/pipeline_created.svg")
end
it 'sets correct completion statuses' do
expect(onboarding_actions_data).to match({
issue_created: a_hash_including(completed: false),
git_write: a_hash_including(completed: true),
pipeline_created: a_hash_including(completed: false),
merge_request_created: a_hash_including(completed: false),
user_added: a_hash_including(completed: false),
trial_started: a_hash_including(completed: false),
required_mr_approvals_enabled: a_hash_including(completed: false),
code_owners_enabled: a_hash_including(completed: false),
security_scan_enabled: a_hash_including(completed: false)
})
end
context 'when in the new action URLs experiment' do
before do
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
end
it_behaves_like 'has all actions'
it 'sets mostly new paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/pipelines\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/project_members\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
)
})
end
end
end
describe '.learn_gitlab_enabled?' do
describe '#learn_gitlab_enabled?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
......@@ -89,7 +163,7 @@ RSpec.describe LearnGitlabHelper do
end
end
describe '.onboarding_sections_data' do
describe '#onboarding_sections_data' do
subject(:sections) { helper.onboarding_sections_data }
it 'has the right keys' do
......
......@@ -121,4 +121,16 @@ RSpec.describe Ci::BuildMetadata do
end
end
end
describe 'set_cancel_gracefully' do
it 'sets cancel_gracefully' do
build.set_cancel_gracefully
expect(build.cancel_gracefully?).to be true
end
it 'returns false' do
expect(build.cancel_gracefully?).to be false
end
end
end
......@@ -35,6 +35,8 @@ RSpec.describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
it { is_expected.to respond_to(:set_cancel_gracefully) }
it { is_expected.to respond_to(:cancel_gracefully?) }
it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
......@@ -5386,4 +5388,23 @@ RSpec.describe Ci::Build do
create(:ci_build)
end
end
describe '#runner_features' do
subject do
build.save!
build.cancel_gracefully?
end
let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
it 'cannot cancel gracefully' do
expect(subject).to be false
end
it 'can cancel gracefully' do
build.set_cancel_gracefully
expect(subject).to be true
end
end
end
......@@ -40,7 +40,9 @@ RSpec.describe Uploads::Fog do
end
describe '#delete_keys' do
let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
let(:keys) { data_store.keys(relation) }
let(:paths) { relation.pluck(:path) }
let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
subject { data_store.delete_keys(keys) }
......@@ -50,17 +52,32 @@ RSpec.describe Uploads::Fog do
end
it 'deletes multiple data' do
paths = relation.pluck(:path)
paths.each do |path|
expect(connection.get_object('uploads', path)[:body]).not_to be_nil
end
subject
paths.each do |path|
expect { connection.get_object('uploads', path)[:body] }.to raise_error(Excon::Error::NotFound)
end
end
::Fog::Storage.new(FileUploader.object_store_credentials).tap do |connection|
context 'when one of keys is missing' do
let(:keys) { ['unknown'] + super() }
it 'deletes only existing keys' do
paths.each do |path|
expect(connection.get_object('uploads', path)[:body]).not_to be_nil
end
end
subject
expect_next_instance_of(::Fog::Storage) do |storage|
allow(storage).to receive(:delete_object).and_call_original
expect(storage).to receive(:delete_object).with('uploads', keys.first).and_raise(::Google::Apis::ClientError, 'NotFound')
end
subject
::Fog::Storage.new(FileUploader.object_store_credentials).tap do |connection|
paths.each do |path|
expect { connection.get_object('uploads', path)[:body] }.to raise_error(Excon::Error::NotFound)
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