Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-07 09:09:13 +00:00
parent b6724a211e
commit 419f9c0ac3
52 changed files with 516 additions and 145 deletions

View file

@ -1144,15 +1144,6 @@ Rails/SaveBang:
- 'spec/support/shared_contexts/email_shared_context.rb'
- 'spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb'
- 'spec/support/shared_contexts/mailers/notify_shared_context.rb'
- 'spec/support/shared_examples/controllers/cache_control_shared_examples.rb'
- 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb'
- 'spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb'
- 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb'
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
- 'spec/support/shared_examples/policies/project_policy_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb'
- 'spec/support/shared_examples/serializers/note_entity_shared_examples.rb'
- 'spec/tasks/gitlab/web_hook_rake_spec.rb'
- 'spec/uploaders/file_uploader_spec.rb'
- 'spec/uploaders/object_storage_spec.rb'

View file

@ -40,7 +40,7 @@ export default {
},
inject: {
boardId: {
type: String,
default: '',
},
},
data() {

View file

@ -58,7 +58,7 @@ export default {
},
inject: {
boardId: {
type: String,
default: '',
},
},
data() {

View file

@ -22,11 +22,7 @@ export default {
required: true,
},
},
inject: {
groupId: {
type: Number,
},
},
inject: ['groupId'],
data() {
return {
title: '',

View file

@ -1,7 +1,6 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '../../../../locale';
import tooltip from '../../../../vue_shared/directives/tooltip';
const directions = {
up: 'up',
@ -10,7 +9,7 @@ const directions = {
export default {
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
@ -46,7 +45,7 @@ export default {
<template>
<div
v-tooltip
v-gl-tooltip
:title="tooltipTitle"
class="controllers-buttons"
data-container="body"

View file

@ -19,7 +19,6 @@ import Api from '~/api';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { s__, __ } from '~/locale';
import { urlParamsToObject } from '~/lib/utils/common_utils';
@ -113,7 +112,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
inject: [
'projectPath',
'newIssuePath',
@ -335,10 +333,7 @@ export default {
return Boolean(assignees.nodes?.length);
},
navigateToIncidentDetails({ iid }) {
const path = this.glFeatures.issuesIncidentDetails
? joinPaths(this.issuePath, INCIDENT_DETAILS_PATH)
: this.issuePath;
return visitUrl(joinPaths(path, iid));
return visitUrl(joinPaths(this.issuePath, INCIDENT_DETAILS_PATH, iid));
},
handlePageChange(page) {
const { startCursor, endCursor } = this.incidents.pageInfo;

View file

@ -36,7 +36,7 @@ class Admin::HooksController < Admin::ApplicationController
end
def destroy
hook.destroy
destroy_hook(hook)
redirect_to admin_hooks_path, status: :found
end

View file

@ -5,6 +5,21 @@ module HooksExecution
private
def destroy_hook(hook)
result = WebHooks::DestroyService.new(current_user).execute(hook)
if result[:status] == :success
flash[:notice] =
if result[:async]
_("%{hook_type} was scheduled for deletion") % { hook_type: hook.model_name.human }
else
_("%{hook_type} was deleted") % { hook_type: hook.model_name.human }
end
else
flash[:alert] = result[:message]
end
end
def set_hook_execution_notice(result)
http_status = result[:http_status]
message = result[:message]

View file

@ -50,7 +50,7 @@ class Projects::HooksController < Projects::ApplicationController
end
def destroy
hook.destroy
destroy_hook(hook)
redirect_to action: :index, status: :found
end

View file

@ -5,13 +5,8 @@ class Projects::IncidentsController < Projects::ApplicationController
include Gitlab::Utils::StrongMemoize
before_action :authorize_read_issue!
before_action :check_feature_flag, only: [:show]
before_action :load_incident, only: [:show]
before_action do
push_frontend_feature_flag(:issues_incident_details, @project)
end
def index
end
@ -45,8 +40,4 @@ class Projects::IncidentsController < Projects::ApplicationController
def serializer
IssueSerializer.new(current_user: current_user, project: incident.project)
end
def check_feature_flag
render_404 unless Feature.enabled?(:issues_incident_details, @project)
end
end

View file

@ -2,8 +2,14 @@
module Types
class PackageTypeEnum < BaseEnum
PACKAGE_TYPE_NAMES = {
pypi: 'PyPI',
npm: 'NPM'
}.freeze
::Packages::Package.package_types.keys.each do |package_type|
value package_type.to_s.upcase, "Packages from the #{package_type.capitalize} package manager", value: package_type.to_s
type_name = PACKAGE_TYPE_NAMES.fetch(package_type.to_sym, package_type.capitalize)
value package_type.to_s.upcase, "Packages from the #{type_name} package manager", value: package_type.to_s
end
end
end

View file

@ -21,11 +21,6 @@ module AlertManagement
ignored: 3
}.freeze
OPEN_STATUSES = [
:triggered,
:acknowledged
].freeze
belongs_to :project
belongs_to :issue, optional: true
belongs_to :prometheus_alert, optional: true
@ -118,7 +113,7 @@ module AlertManagement
scope :for_fingerprint, -> (project, fingerprint) { where(project: project, fingerprint: fingerprint) }
scope :for_environment, -> (environment) { where(environment: environment) }
scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
scope :open, -> { with_status(OPEN_STATUSES) }
scope :open, -> { with_status(open_statuses) }
scope :not_resolved, -> { without_status(:resolved) }
scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
@ -183,6 +178,14 @@ module AlertManagement
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end
def self.open_statuses
[:triggered, :acknowledged]
end
def self.open_status?(status)
open_statuses.include?(status)
end
def status_event_for(status)
self.class.state_machines[:status].events.transitions_for(self, to: status.to_s.to_sym).first&.event
end

View file

@ -144,7 +144,7 @@ module AlertManagement
def filter_duplicate
# Only need to check if changing to an open status
return unless params[:status_event] && AlertManagement::Alert::OPEN_STATUSES.include?(status_key)
return unless params[:status_event] && AlertManagement::Alert.open_status?(status_key)
param_errors << unresolved_alert_error if duplicate_alert?
end

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
module WebHooks
class DestroyService
include BaseServiceUtility
BATCH_SIZE = 1000
LOG_COUNT_THRESHOLD = 10000
DestroyError = Class.new(StandardError)
attr_accessor :current_user, :web_hook
def initialize(current_user)
@current_user = current_user
end
def execute(web_hook)
@web_hook = web_hook
async = false
# For a better user experience, it's better if the Web hook is
# destroyed right away without waiting for Sidekiq. However, if
# there are a lot of Web hook logs, we will need more time to
# clean them up, so schedule a Sidekiq job to do this.
if needs_async_destroy?
Gitlab::AppLogger.info("User #{current_user&.id} scheduled a deletion of hook ID #{web_hook.id}")
async_destroy(web_hook)
async = true
else
sync_destroy(web_hook)
end
success({ async: async })
end
def sync_destroy(web_hook)
@web_hook = web_hook
delete_web_hook_logs
result = web_hook.destroy
if result
success({ async: false })
else
error("Unable to destroy #{web_hook.model_name.human}")
end
end
private
def async_destroy(web_hook)
WebHooks::DestroyWorker.perform_async(current_user.id, web_hook.id)
end
# rubocop: disable CodeReuse/ActiveRecord
def needs_async_destroy?
web_hook.web_hook_logs.limit(LOG_COUNT_THRESHOLD).count == LOG_COUNT_THRESHOLD
end
# rubocop: enable CodeReuse/ActiveRecord
def delete_web_hook_logs
loop do
count = delete_web_hook_logs_in_batches
break if count < BATCH_SIZE
end
end
# rubocop: disable CodeReuse/ActiveRecord
def delete_web_hook_logs_in_batches
# We can't use EachBatch because that does an ORDER BY id, which can
# easily time out. We don't actually care about ordering when
# we are deleting these rows.
web_hook.web_hook_logs.limit(BATCH_SIZE).delete_all
end
# rubocop: enable CodeReuse/ActiveRecord
end
end

View file

@ -1940,6 +1940,14 @@
:weight: 1
:idempotent:
:tags: []
- :name: web_hooks_destroy
:feature_category: :integrations
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: x509_certificate_revoke
:feature_category: :source_code_management
:has_external_dependencies:

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module WebHooks
class DestroyWorker
include ApplicationWorker
feature_category :integrations
urgency :low
idempotent!
def perform(user_id, web_hook_id)
user = User.find_by_id(user_id)
hook = WebHook.find_by_id(web_hook_id)
return unless user && hook
result = ::WebHooks::DestroyService.new(user).sync_destroy(hook)
return result if result[:status] == :success
e = ::WebHooks::DestroyService::DestroyError.new(result[:message])
Gitlab::ErrorTracking.track_exception(e, web_hook_id: hook.id)
raise e
end
end
end

View file

@ -0,0 +1,5 @@
---
title: Allow users to navigate to the incidents show details page wrapper through `/issues/incidents/:id` from the Incident list
merge_request: 44438
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix Rails/SaveBang offenses for spec files in spec/support/shared_example/*
merge_request: 44424
author: matthewbried
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix Web hook deletion not working when many hook logs are present
merge_request: 43464
author:
type: fixed

View file

@ -128,12 +128,12 @@ module Gitlab
/^description$/,
/^note$/,
/^text$/,
/^title$/
/^title$/,
/^hook$/
]
config.filter_parameters += %i(
certificate
encrypted_key
hook
import_url
elasticsearch_url
otp_attempt

View file

@ -1,7 +1,7 @@
---
name: ci_instance_variables_ui
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33510
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View file

@ -1,7 +0,0 @@
---
name: issues_incident_details
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43459
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/257842
type: development
group: group::health
default_enabled: false

View file

@ -1,7 +1,7 @@
---
name: merge_request_draft_filter
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35942
rollout_issue_url:
group: group::source code
type: development
default_enabled: true

View file

@ -1,7 +1,7 @@
---
name: new_variables_ui
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25260
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View file

@ -310,5 +310,7 @@
- 1
- - web_hook
- 1
- - web_hooks_destroy
- 1
- - x509_certificate_revoke
- 1

View file

@ -1,4 +1,5 @@
# frozen_string_literal: true
# rubocop:disable Style/SignalException
PATTERNS = %w[
createFlash
@ -12,6 +13,9 @@ PATTERNS = %w[
initDeprecatedJQueryDropdown
].freeze
BLOCKING_PATTERNS = %w[
].freeze
def get_added_lines(files)
lines = []
files.each do |file|
@ -25,19 +29,34 @@ changed_vue_haml_files = helper.changed_files(/.vue$|.haml$/)
return if changed_vue_haml_files.empty?
changed_lines_in_mr = get_added_lines(changed_vue_haml_files)
has_deprecated_components = changed_lines_in_mr.select { |i| i[/#{PATTERNS.join("|")}/] }
deprecated_components_in_mr = PATTERNS.select { |s| has_deprecated_components.join(" ")[s] }
deprecated_components_in_mr = PATTERNS.select { |pattern| changed_lines_in_mr.any? { |line| line[pattern] } }
blocking_components_in_mr = BLOCKING_PATTERNS.select { |pattern| changed_lines_in_mr.any? { |line| line[pattern] } }
return if deprecated_components_in_mr.empty?
warn "This merge request contains deprecated components. Please consider using Pajamas components instead."
return if (deprecated_components_in_mr + blocking_components_in_mr).empty?
markdown(<<~MARKDOWN)
## Deprecated components
The following components are deprecated:
* #{deprecated_components_in_mr.join("\n* ")}
Please consider using [Pajamas components](https://design.gitlab.com/components/status/) instead.
MARKDOWN
if blocking_components_in_mr.any?
markdown(<<~MARKDOWN)
These deprecated components have already been migrated and can no longer be used. Please use [Pajamas components](https://design.gitlab.com/components/status/) instead.
* #{blocking_components_in_mr.join("\n* ")}
MARKDOWN
fail "This merge request contains deprecated components that have been migrated and can no longer be used. Please use Pajamas components instead."
end
if deprecated_components_in_mr.any?
markdown(<<~MARKDOWN)
These deprecated components are in the process of being migrated. Please consider using [Pajamas components](https://design.gitlab.com/components/status/) instead.
* #{deprecated_components_in_mr.join("\n* ")}
MARKDOWN
warn "This merge request contains deprecated components. Please consider using Pajamas components instead."
end

View file

@ -12464,7 +12464,7 @@ enum PackageTypeEnum {
MAVEN
"""
Packages from the Npm package manager
Packages from the NPM package manager
"""
NPM
@ -12474,7 +12474,7 @@ enum PackageTypeEnum {
NUGET
"""
Packages from the Pypi package manager
Packages from the PyPI package manager
"""
PYPI
}

View file

@ -36751,7 +36751,7 @@
},
{
"name": "NPM",
"description": "Packages from the Npm package manager",
"description": "Packages from the NPM package manager",
"isDeprecated": false,
"deprecationReason": null
},
@ -36769,7 +36769,7 @@
},
{
"name": "PYPI",
"description": "Packages from the Pypi package manager",
"description": "Packages from the PyPI package manager",
"isDeprecated": false,
"deprecationReason": null
},

View file

@ -3458,9 +3458,9 @@ Values for sorting projects.
| `GENERIC` | Packages from the Generic package manager |
| `GOLANG` | Packages from the Golang package manager |
| `MAVEN` | Packages from the Maven package manager |
| `NPM` | Packages from the Npm package manager |
| `NPM` | Packages from the NPM package manager |
| `NUGET` | Packages from the Nuget package manager |
| `PYPI` | Packages from the Pypi package manager |
| `PYPI` | Packages from the PyPI package manager |
### PipelineConfigSourceEnum

View file

@ -7,11 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Repositories Analytics **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215104) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
> - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-repositories-analytics). **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215104) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
@ -35,25 +31,6 @@ For each day that a coverage report was generated by a job in a project's pipeli
If the project's code coverage was calculated more than once in a day, we will take the last value from that day.
## Enable or disable Repositories Analytics **(CORE ONLY)**
Repositories Analytics is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:group_coverage_reports)
```
To disable it:
```ruby
Feature.disable(:group_coverage_reports)
```
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View file

@ -104,7 +104,9 @@ module API
delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
destroy_conditionally!(hook)
destroy_conditionally!(hook) do
WebHooks::DestroyService.new(current_user).execute(hook)
end
end
end
end

View file

@ -70,7 +70,9 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
destroy_conditionally!(hook)
destroy_conditionally!(hook) do
WebHooks::DestroyService.new(current_user).execute(hook)
end
end
# rubocop: enable CodeReuse/ActiveRecord
end

View file

@ -37,7 +37,10 @@ namespace :gitlab do
web_hooks.find_each do |hook|
next unless hook.url == web_hook_url
hook.destroy!
result = WebHooks::DestroyService.new(nil).sync_destroy(hook)
raise "Unable to destroy Web hook" unless result[:status] == :success
count += 1
end

View file

@ -498,6 +498,12 @@ msgstr ""
msgid "%{group_name}&%{epic_iid} &middot; opened %{epic_created} by %{author}"
msgstr ""
msgid "%{hook_type} was deleted"
msgstr ""
msgid "%{hook_type} was scheduled for deletion"
msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""

View file

@ -29,4 +29,12 @@ RSpec.describe Admin::HooksController do
expect(SystemHook.first).to have_attributes(hook_params)
end
end
describe 'DELETE #destroy' do
let!(:hook) { create(:system_hook) }
let!(:log) { create(:web_hook_log, web_hook: hook) }
let(:params) { { id: hook } }
it_behaves_like 'Web hook destroyer'
end
end

View file

@ -48,6 +48,14 @@ RSpec.describe Projects::HooksController do
end
end
describe 'DELETE #destroy' do
let!(:hook) { create(:project_hook, project: project) }
let!(:log) { create(:web_hook_log, web_hook: hook) }
let(:params) { { namespace_id: project.namespace, project_id: project, id: hook } }
it_behaves_like 'Web hook destroyer'
end
describe '#test' do
let(:hook) { create(:project_hook, project: project) }

View file

@ -83,14 +83,6 @@ RSpec.describe Projects::IncidentsController do
expect(assigns(:noteable)).to eq(assigns(:incident))
end
context 'with feature flag disabled' do
before do
stub_feature_flags(issues_incident_details: false)
end
it_behaves_like 'not found'
end
context 'with non existing id' do
let(:resource) { non_existing_record_id }

View file

@ -0,0 +1,82 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Merge Requests > User filters', :js do
include FilteredSearchHelpers
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { project.creator }
let_it_be(:group_user) { create(:user) }
let_it_be(:first_user) { create(:user) }
before do
sign_in(user)
visit project_merge_requests_path(project)
end
context 'by "approved by"' do
let_it_be(:merge_request) { create(:merge_request, title: 'Bugfix3', source_project: project, source_branch: 'bugfix3') }
let_it_be(:merge_request_with_first_user_approval) do
create(:merge_request, source_project: project, title: 'Bugfix5').tap do |mr|
create(:approval, merge_request: mr, user: first_user)
end
end
let_it_be(:merge_request_with_group_user_approved) do
group = create(:group)
group.add_developer(group_user)
create(:merge_request, source_project: project, title: 'Bugfix6', source_branch: 'bugfix6').tap do |mr|
create(:approval, merge_request: mr, user: group_user)
end
end
context 'filtering by approved-by:none' do
it 'applies the filter' do
input_filtered_search('approved-by:=none')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).not_to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix6'
expect(page).to have_content 'Bugfix3'
end
end
context 'filtering by approved-by:any' do
it 'applies the filter' do
input_filtered_search('approved-by:=any')
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
expect(page).to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix3'
end
end
context 'filtering by approved-by:@username' do
it 'applies the filter' do
input_filtered_search("approved-by:=@#{first_user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix3'
end
end
context 'filtering by an approver from a group' do
it 'applies the filter' do
input_filtered_search("approved-by:=@#{group_user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix6'
expect(page).not_to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix3'
end
end
end
end

View file

@ -31,7 +31,7 @@ describe('IDE job log scroll button', () => {
});
it('returns proper title', () => {
expect(wrapper.attributes('data-original-title')).toBe(title);
expect(wrapper.attributes('title')).toBe(title);
});
});

View file

@ -87,7 +87,6 @@ describe('Incidents List', () => {
textQuery: '',
authorUsernamesQuery: '',
assigneeUsernamesQuery: '',
issuesIncidentDetails: false,
},
stubs: {
GlButton: true,
@ -194,22 +193,7 @@ describe('Incidents List', () => {
expect(findSeverity().length).toBe(mockIncidents.length);
});
it('contains a link to the issue details page', () => {
findTableRows()
.at(0)
.trigger('click');
expect(visitUrl).toHaveBeenCalledWith(joinPaths(`/project/issues/`, mockIncidents[0].iid));
});
it('contains a link to the incident details page', async () => {
beforeEach(() =>
mountComponent({
data: { incidents: { list: mockIncidents }, incidentsCount: {} },
loading: false,
provide: { glFeatures: { issuesIncidentDetails: true } },
}),
);
findTableRows()
.at(0)
.trigger('click');

View file

@ -363,6 +363,24 @@ RSpec.describe AlertManagement::Alert do
end
end
describe '.open_status?' do
using RSpec::Parameterized::TableSyntax
where(:status, :is_open_status) do
:triggered | true
:acknowledged | true
:resolved | false
:ignored | false
nil | false
end
with_them do
it 'returns true when the status is open status' do
expect(described_class.open_status?(status)).to eq(is_open_status)
end
end
end
describe '#to_reference' do
it { expect(triggered_alert.to_reference).to eq("^alert##{triggered_alert.iid}") }
end

View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WebHooks::DestroyService do
let_it_be(:user) { create(:user) }
subject { described_class.new(user) }
shared_examples 'batched destroys' do
it 'destroys all hooks in batches' do
stub_const("#{described_class}::BATCH_SIZE", 1)
expect(subject).to receive(:delete_web_hook_logs_in_batches).exactly(4).times.and_call_original
expect do
status = subject.execute(hook)
expect(status[:async]).to be false
end
.to change { WebHook.count }.from(1).to(0)
.and change { WebHookLog.count }.from(3).to(0)
end
it 'returns an error if sync destroy fails' do
expect(hook).to receive(:destroy).and_return(false)
result = subject.sync_destroy(hook)
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Unable to destroy #{hook.model_name.human}")
end
it 'schedules an async delete' do
stub_const('WebHooks::DestroyService::LOG_COUNT_THRESHOLD', 1)
expect(WebHooks::DestroyWorker).to receive(:perform_async).with(user.id, hook.id).and_call_original
status = subject.execute(hook)
expect(status[:async]).to be true
end
end
context 'with system hook' do
let_it_be(:hook) { create(:system_hook, url: "http://example.com") }
let_it_be(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
it_behaves_like 'batched destroys'
end
context 'with project hook' do
let_it_be(:hook) { create(:project_hook) }
let_it_be(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
it_behaves_like 'batched destroys'
end
end

View file

@ -2,7 +2,7 @@
RSpec.shared_examples 'project cache control headers' do
before do
project.update(visibility_level: visibility_level)
project.update!(visibility_level: visibility_level)
end
context 'when project is public' do

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
RSpec.shared_examples 'Web hook destroyer' do
it 'displays a message about synchronous delete', :aggregate_failures do
expect_next_instance_of(WebHooks::DestroyService) do |instance|
expect(instance).to receive(:execute).with(anything).and_call_original
end
delete :destroy, params: params
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to eq("#{hook.model_name.human} was deleted")
end
it 'displays a message about async delete', :aggregate_failures do
expect_next_instance_of(WebHooks::DestroyService) do |instance|
expect(instance).to receive(:execute).with(anything).and_return({ status: :success, async: true } )
end
delete :destroy, params: params
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to eq("#{hook.model_name.human} was scheduled for deletion")
end
it 'displays an error if deletion failed', :aggregate_failures do
expect_next_instance_of(WebHooks::DestroyService) do |instance|
expect(instance).to receive(:execute).with(anything).and_return({ status: :error, async: true, message: "failed" } )
end
delete :destroy, params: params
expect(response).to have_gitlab_http_status(:found)
expect(flash[:alert]).to eq("failed")
end
end

View file

@ -262,7 +262,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the namespace is owned by the GitLab user" do
before do
user.username = other_username
user.save
user.save!
end
it "takes the existing namespace" do

View file

@ -44,7 +44,7 @@ RSpec.shared_examples 'authenticates sessionless user' do |path, format, params|
.to increment(:user_unauthenticated_counter)
end
personal_access_token.update(scopes: [:read_user])
personal_access_token.update!(scopes: [:read_user])
get path, params: default_params.merge(private_token: personal_access_token.token)

View file

@ -82,7 +82,7 @@ RSpec.shared_examples 'an editable merge request' do
end
it 'warns about version conflict' do
merge_request.update(title: "New title")
merge_request.update!(title: "New title")
fill_in 'merge_request_title', with: 'bug 345'
fill_in 'merge_request_description', with: 'bug description'

View file

@ -227,7 +227,7 @@ RSpec.shared_examples 'common trace features' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.project.update!(runners_token: token)
trace.append(token, 0)
end
@ -240,7 +240,7 @@ RSpec.shared_examples 'common trace features' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.update!(token: token)
trace.append(token, 0)
end
@ -531,7 +531,7 @@ RSpec.shared_examples 'trace with disabled live trace feature' do
context "when erase old trace with 'save'" do
before do
build.send(:write_attribute, :trace, nil)
build.save
build.save # rubocop:disable Rails/SaveBang
end
it 'old trace is not deleted' do

View file

@ -109,7 +109,7 @@ RSpec.shared_examples 'issuable quick actions' do
QuickAction.new(
action_text: "/unlock",
before_action: -> {
issuable.update(discussion_locked: true)
issuable.update!(discussion_locked: true)
},
expectation: ->(noteable, can_use_quick_action) {
if can_use_quick_action
@ -128,7 +128,7 @@ RSpec.shared_examples 'issuable quick actions' do
QuickAction.new(
action_text: "/remove_milestone",
before_action: -> {
issuable.update(milestone_id: milestone.id)
issuable.update!(milestone_id: milestone.id)
},
expectation: ->(noteable, can_use_quick_action) {
if can_use_quick_action
@ -171,7 +171,7 @@ RSpec.shared_examples 'issuable quick actions' do
QuickAction.new(
action_text: "/remove_estimate",
before_action: -> {
issuable.update(time_estimate: 30000)
issuable.update!(time_estimate: 30000)
},
expectation: ->(noteable, can_use_quick_action) {
if can_use_quick_action
@ -228,7 +228,7 @@ RSpec.shared_examples 'issuable quick actions' do
before do
project.add_developer(old_assignee)
issuable.update(assignees: [old_assignee])
issuable.update!(assignees: [old_assignee])
end
context 'when user can update issuable' do

View file

@ -52,7 +52,7 @@ RSpec.shared_examples 'merge quick action' do
context 'when the head diff changes in the meanwhile' do
before do
merge_request.source_branch = 'another_branch'
merge_request.save
merge_request.save!
sign_in(user)
visit project_merge_request_path(project, merge_request)
end

View file

@ -24,7 +24,7 @@ RSpec.shared_examples 'note entity' do
context 'when note was edited' do
before do
note.update(updated_at: 1.minute.from_now, updated_by: user)
note.update!(updated_at: 1.minute.from_now, updated_by: user)
end
it 'exposes last_edited_at and last_edited_by elements' do
@ -34,7 +34,7 @@ RSpec.shared_examples 'note entity' do
context 'when note is a system note' do
before do
note.update(system: true)
note.update!(system: true)
end
it 'exposes system_note_icon_name element' do

View file

@ -0,0 +1,59 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WebHooks::DestroyWorker do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
before_all do
project.add_maintainer(user)
end
subject { described_class.new }
describe "#perform" do
context 'with a Web hook' do
let!(:hook) { create(:project_hook, project: project) }
let!(:other_hook) { create(:project_hook, project: project) }
let!(:log) { create(:web_hook_log, web_hook: hook) }
let!(:other_log) { create(:web_hook_log, web_hook: other_hook) }
it "deletes the Web hook and logs", :aggregate_failures do
expect { subject.perform(user.id, hook.id) }
.to change { WebHookLog.count }.from(2).to(1)
.and change { WebHook.count }.from(2).to(1)
expect(WebHook.find(other_hook.id)).to be_present
expect(WebHookLog.find(other_log.id)).to be_present
end
it "raises and tracks an error if destroy failed" do
allow_next_instance_of(::WebHooks::DestroyService) do |instance|
expect(instance).to receive(:sync_destroy).with(anything).and_return({ status: :error, message: "failed" })
end
expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(an_instance_of(::WebHooks::DestroyService::DestroyError), web_hook_id: hook.id)
.and_call_original
expect { subject.perform(user.id, hook.id) }.to raise_error(::WebHooks::DestroyService::DestroyError)
end
context 'with unknown hook' do
it 'does not raise an error' do
expect { subject.perform(user.id, non_existing_record_id) }.not_to raise_error
expect(WebHook.count).to eq(2)
end
end
context 'with unknown user' do
it 'does not raise an error' do
expect { subject.perform(non_existing_record_id, hook.id) }.not_to raise_error
expect(WebHook.count).to eq(2)
end
end
end
end
end