Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-02 12:08:31 +00:00
parent 6c4491e936
commit 082a21ddd4
19 changed files with 821 additions and 75 deletions

View File

@ -549,3 +549,7 @@ gem 'parslet', '~> 1.8'
gem 'ipynbdiff', path: 'vendor/gems/ipynbdiff'
gem 'ed25519', '~> 1.3.0'
# Error Tracking OpenAPI client
# See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature
gem 'error_tracking_open_api', path: 'vendor/gems/error_tracking_open_api'

View File

@ -1,3 +1,9 @@
PATH
remote: vendor/gems/error_tracking_open_api
specs:
error_tracking_open_api (1.0.0)
typhoeus (~> 1.0, >= 1.0.1)
PATH
remote: vendor/gems/ipynbdiff
specs:
@ -1520,6 +1526,7 @@ DEPENDENCIES
elasticsearch-rails (~> 7.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
error_tracking_open_api!
erubi (~> 1.9.0)
escape_utils (~> 1.1)
factory_bot_rails (~> 6.2.0)

View File

@ -150,6 +150,12 @@ export default {
paginationRequired() {
return !isEmpty(this.pagination);
},
previousPage() {
return this.pagination.previous ? this.$options.PREV_PAGE : null;
},
nextPage() {
return this.pagination.next ? this.$options.NEXT_PAGE : null;
},
errorTrackingHelpUrl() {
return helpPagePath('operations/error_tracking');
},
@ -430,8 +436,8 @@ export default {
<gl-pagination
v-show="!loading"
v-if="paginationRequired"
:prev-page="$options.PREV_PAGE"
:next-page="$options.NEXT_PAGE"
:prev-page="previousPage"
:next-page="nextPage"
:value="pageValue"
align="center"
@input="goToPage"

View File

@ -16,7 +16,7 @@ class ErrorTracking::ClientKey < ApplicationRecord
end
def sentry_dsn
@sentry_dsn ||= ErrorTracking::Collector::Dsn.build_url(public_key, project_id)
@sentry_dsn ||= ::Gitlab::ErrorTracking::ErrorRepository.build(project).dsn_url(public_key)
end
private

View File

@ -2,7 +2,11 @@
module ErrorTracking
class ErrorEntity < Grape::Entity
expose :id, :title, :type, :user_count, :count,
expose :id do |error|
error.id.to_s
end
expose :title, :type, :user_count, :count,
:first_seen, :last_seen, :message, :culprit,
:external_url, :project_id, :project_name, :project_slug,
:short_id, :status, :frequency

View File

@ -75,6 +75,7 @@ module ErrorTracking
# For now we implement the bare minimum for rendering the list in UI.
list_opts = {
filters: { status: opts[:issue_status] },
query: opts[:search_term],
sort: opts[:sort],
limit: opts[:limit],
cursor: opts[:cursor]

View File

@ -1,30 +0,0 @@
# frozen_string_literal: true
module ErrorTracking
module Collector
class Dsn
# Build a sentry compatible DSN URL for GitLab collector.
#
# The expected URL looks like that:
# https://PUBLIC_KEY@gitlab.example.com/api/v4/error_tracking/collector/PROJECT_ID
#
def self.build_url(public_key, project_id)
gitlab = Settings.gitlab
custom_port = Settings.gitlab_on_standard_port? ? nil : ":#{gitlab.port}"
base_url = [
gitlab.protocol,
"://",
public_key,
'@',
gitlab.host,
custom_port,
gitlab.relative_url_root
].join('')
"#{base_url}/api/v4/error_tracking/collector/#{project_id}"
end
end
end
end

View File

@ -15,7 +15,12 @@ module Gitlab
#
# @return [self]
def self.build(project)
strategy = ActiveRecordStrategy.new(project)
strategy =
if Feature.enabled?(:use_click_house_database_for_error_tracking, project)
OpenApiStrategy.new(project)
else
ActiveRecordStrategy.new(project)
end
new(strategy)
end
@ -72,14 +77,15 @@ module Gitlab
# @param sort [String] order list by 'first_seen', 'last_seen', or 'frequency'
# @param filters [Hash<Symbol, String>] filter list by
# @option filters [String] :status error status
# @params query [String, nil] free text search
# @param limit [Integer, String] limit result
# @param cursor [Hash] pagination information
#
# @return [Array<Array<Gitlab::ErrorTracking::Error>, Pagination>]
def list_errors(sort: 'last_seen', filters: {}, limit: 20, cursor: {})
def list_errors(sort: 'last_seen', filters: {}, query: nil, limit: 20, cursor: {})
limit = [limit.to_i, 100].min
strategy.list_errors(filters: filters, sort: sort, limit: limit, cursor: cursor)
strategy.list_errors(filters: filters, query: query, sort: sort, limit: limit, cursor: cursor)
end
# Fetches last event for error +id+.
@ -105,6 +111,10 @@ module Gitlab
strategy.update_error(id, status: status)
end
def dsn_url(public_key)
strategy.dsn_url(public_key)
end
private
attr_reader :strategy

View File

@ -39,11 +39,12 @@ module Gitlab
handle_exceptions(e)
end
def list_errors(filters:, sort:, limit:, cursor:)
def list_errors(filters:, query:, sort:, limit:, cursor:)
errors = project_errors
errors = filter_by_status(errors, filters[:status])
errors = sort(errors, sort)
errors = errors.keyset_paginate(cursor: cursor, per_page: limit)
# query is not supported
pagination = ErrorRepository::Pagination.new(errors.cursor_for_next_page, errors.cursor_for_previous_page)
@ -60,6 +61,24 @@ module Gitlab
project_error(id).update(attributes)
end
def dsn_url(public_key)
gitlab = Settings.gitlab
custom_port = Settings.gitlab_on_standard_port? ? nil : ":#{gitlab.port}"
base_url = [
gitlab.protocol,
"://",
public_key,
'@',
gitlab.host,
custom_port,
gitlab.relative_url_root
].join('')
"#{base_url}/api/v4/error_tracking/collector/#{project.id}"
end
private
attr_reader :project

View File

@ -0,0 +1,247 @@
# frozen_string_literal: true
module Gitlab
module ErrorTracking
class ErrorRepository
class OpenApiStrategy
def initialize(project)
@project = project
api_url = configured_api_url
open_api.configure do |config|
config.scheme = api_url.scheme
config.host = [api_url.host, api_url.port].compact.join(':')
config.server_index = nil
config.logger = Gitlab::AppLogger
end
end
def report_error(
name:, description:, actor:, platform:,
environment:, level:, occurred_at:, payload:
)
raise NotImplementedError, 'Use ingestion endpoint'
end
def find_error(id)
api = open_api::ErrorsApi.new
error = api.get_error(project_id, id)
to_sentry_detailed_error(error)
rescue ErrorTrackingOpenAPI::ApiError => e
log_exception(e)
nil
end
def list_errors(filters:, query:, sort:, limit:, cursor:)
opts = {
sort: "#{sort}_desc",
status: filters[:status],
query: query,
cursor: cursor,
limit: limit
}.compact
api = open_api::ErrorsApi.new
errors, _status, headers = api.list_errors_with_http_info(project_id, opts)
pagination = pagination_from_headers(headers)
if errors.size < limit
# Don't show next link if amount of errors is less then requested.
# This a workaround until the Golang backend returns link cursor
# only if there is a next page.
pagination.next = nil
end
[errors.map { to_sentry_error(_1) }, pagination]
rescue ErrorTrackingOpenAPI::ApiError => e
log_exception(e)
[[], ErrorRepository::Pagination.new]
end
def last_event_for(id)
event = newest_event_for(id)
return unless event
api = open_api::ErrorsApi.new
error = api.get_error(project_id, id)
return unless error
to_sentry_error_event(event, error)
rescue ErrorTrackingOpenAPI::ApiError => e
log_exception(e)
nil
end
def update_error(id, **attributes)
opts = attributes.slice(:status)
body = open_api::ErrorUpdatePayload.new(opts)
api = open_api::ErrorsApi.new
api.update_error(project_id, id, body)
true
rescue ErrorTrackingOpenAPI::ApiError => e
log_exception(e)
false
end
def dsn_url(public_key)
config = open_api::Configuration.default
base_url = [
config.scheme,
"://",
public_key,
'@',
config.host,
config.base_path
].join('')
"#{base_url}/projects/api/#{project_id}"
end
private
def event_for(id, sort:)
opts = { sort: sort, limit: 1 }
api = open_api::ErrorsApi.new
api.list_events(project_id, id, opts).first
rescue ErrorTrackingOpenAPI::ApiError => e
log_exception(e)
nil
end
def newest_event_for(id)
event_for(id, sort: 'occurred_at_desc')
end
def oldest_event_for(id)
event_for(id, sort: 'occurred_at_asc')
end
def to_sentry_error(error)
Gitlab::ErrorTracking::Error.new(
id: error.fingerprint.to_s,
title: error.name,
message: error.description,
culprit: error.actor,
first_seen: error.first_seen_at,
last_seen: error.last_seen_at,
status: error.status,
count: error.event_count,
user_count: error.approximated_user_count
)
end
def to_sentry_detailed_error(error)
Gitlab::ErrorTracking::DetailedError.new(
id: error.fingerprint.to_s,
title: error.name,
message: error.description,
culprit: error.actor,
first_seen: error.first_seen_at.to_s,
last_seen: error.last_seen_at.to_s,
count: error.event_count,
user_count: error.approximated_user_count,
project_id: error.project_id,
status: error.status,
tags: { level: nil, logger: nil },
external_url: external_url(error.fingerprint),
external_base_url: external_base_url,
integrated: true,
first_release_version: release_from(oldest_event_for(error.fingerprint)),
last_release_version: release_from(newest_event_for(error.fingerprint))
)
end
def to_sentry_error_event(event, error)
Gitlab::ErrorTracking::ErrorEvent.new(
issue_id: event.fingerprint.to_s,
date_received: error.last_seen_at,
stack_trace_entries: build_stacktrace(event)
)
end
def pagination_from_headers(headers)
links = headers['link'].to_s.split(', ')
pagination_hash = links.map { parse_pagination_link(_1) }.compact.to_h
ErrorRepository::Pagination.new(pagination_hash['next'], pagination_hash['prev'])
end
LINK_PATTERN = %r{cursor=(?<cursor>[^&]+).*; rel="(?<direction>\w+)"}.freeze
def parse_pagination_link(content)
match = LINK_PATTERN.match(content)
return unless match
[match['direction'], CGI.unescape(match['cursor'])]
end
def build_stacktrace(event)
payload = parse_json(event.payload)
return [] unless payload
::ErrorTracking::StacktraceBuilder.new(payload).stacktrace
end
def parse_json(payload)
Gitlab::Json.parse(payload)
rescue JSON::ParserError
end
def release_from(event)
return unless event
payload = parse_json(event.payload)
return unless payload
payload['release']
end
def project_id
@project.id
end
def open_api
ErrorTrackingOpenAPI
end
# For compatibility with sentry integration
def external_url(id)
Gitlab::Routing.url_helpers.details_namespace_project_error_tracking_index_url(
namespace_id: @project.namespace,
project_id: @project,
issue_id: id)
end
# For compatibility with sentry integration
def external_base_url
Gitlab::Routing.url_helpers.project_url(@project)
end
def configured_api_url
url = ENV.fetch('ERROR_TRACKING_API_URL', 'http://localhost:8080')
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_localhost: true)
URI(url)
end
def log_exception(exception)
params = {
http_code: exception.code,
response_body: exception.response_body&.truncate(100)
}
Gitlab::AppLogger.error(Gitlab::Utils::InlineHash.merge_keys(params, prefix: 'open_api'))
end
end
end
end
end

View File

@ -55,6 +55,8 @@ namespace :gems do
write_file(gem_dir / 'LICENSE', license)
write_file(gem_dir / "#{gem_name}.gemspec") do |content|
replace_string(content, 'Unlicense', 'MIT')
replace_string(content, /(\.files\s*=).*/, '\1 Dir.glob("lib/**/*")')
replace_string(content, /(\.test_files\s*=).*/, '\1 []')
end
remove_entry_secure(gem_dir / '.rubocop.yml')

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
FactoryBot.define do
factory :error_tracking_open_api_error, class: 'ErrorTrackingOpenAPI::Error' do
fingerprint { 1 }
project_id { 2 }
name { 'ActionView::MissingTemplate' }
description { 'Missing template posts/edit' }
actor { 'PostsController#edit' }
event_count { 3 }
approximated_user_count { 4 }
first_seen_at { Time.now.iso8601 }
last_seen_at { Time.now.iso8601 }
status { 'unresolved' }
skip_create
end
factory :error_tracking_open_api_error_event, class: 'ErrorTrackingOpenAPI::ErrorEvent' do
fingerprint { 1 }
project_id { 2 }
payload { File.read(Rails.root.join('spec/fixtures/error_tracking/parsed_event.json')) }
name { 'ActionView::MissingTemplate' }
description { 'Missing template posts/edit' }
actor { 'PostsController#edit' }
environment { 'development' }
platform { 'ruby' }
trait :golang do
payload { File.read(Rails.root.join('spec/fixtures/error_tracking/go_parsed_event.json')) }
platform { 'go' }
end
trait :browser do
payload { File.read(Rails.root.join('spec/fixtures/error_tracking/browser_event.json')) }
platform { 'javascript' }
end
skip_create
end
end

View File

@ -398,6 +398,30 @@ describe('ErrorTrackingList', () => {
});
describe('When pagination is required', () => {
describe('and previous cursor is not available', () => {
beforeEach(async () => {
store.state.list.loading = false;
delete store.state.list.pagination.previous;
mountComponent();
});
it('disables Prev button in the pagination', async () => {
expect(findPagination().props('prevPage')).toBe(null);
expect(findPagination().props('nextPage')).not.toBe(null);
});
});
describe('and next cursor is not available', () => {
beforeEach(async () => {
store.state.list.loading = false;
delete store.state.list.pagination.next;
mountComponent();
});
it('disables Next button in the pagination', async () => {
expect(findPagination().props('prevPage')).not.toBe(null);
expect(findPagination().props('nextPage')).toBe(null);
});
});
describe('and the user is not on the first page', () => {
describe('and the previous button is clicked', () => {
beforeEach(async () => {

View File

@ -1,34 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ErrorTracking::Collector::Dsn do
describe '.build_url' do
let(:setting) do
{
protocol: 'https',
https: true,
port: 443,
host: 'gitlab.example.com',
relative_url_root: nil
}
end
subject { described_class.build_url('abcdef1234567890', 778) }
it 'returns a valid URL without explicit port' do
stub_config_setting(setting)
is_expected.to eq('https://abcdef1234567890@gitlab.example.com/api/v4/error_tracking/collector/778')
end
context 'with non-standard port' do
it 'returns a valid URL with custom port' do
setting[:port] = 4567
stub_config_setting(setting)
is_expected.to eq('https://abcdef1234567890@gitlab.example.com:4567/api/v4/error_tracking/collector/778')
end
end
end
end

View File

@ -0,0 +1,436 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::ErrorRepository::OpenApiStrategy do
include AfterNextHelpers
let(:project) { build_stubbed(:project) }
let(:api_exception) { ErrorTrackingOpenAPI::ApiError.new(code: 500, response_body: 'b' * 101) }
subject(:repository) { Gitlab::ErrorTracking::ErrorRepository.build(project) }
before do
# Disabled in spec_helper by default thus we need to enable it here.
stub_feature_flags(use_click_house_database_for_error_tracking: true)
end
shared_examples 'exception logging' do
it 'logs error' do
expect(Gitlab::AppLogger).to receive(:error).with(
'open_api.http_code' => api_exception.code,
'open_api.response_body' => api_exception.response_body.truncate(100)
)
subject
end
end
shared_examples 'no logging' do
it 'does not log anything' do
expect(Gitlab::AppLogger).not_to receive(:debug)
expect(Gitlab::AppLogger).not_to receive(:info)
expect(Gitlab::AppLogger).not_to receive(:error)
end
end
describe '#report_error' do
let(:params) do
{
name: 'anything',
description: 'anything',
actor: 'anything',
platform: 'anything',
environment: 'anything',
level: 'anything',
occurred_at: Time.zone.now,
payload: {}
}
end
subject { repository.report_error(**params) }
it 'is not implemented' do
expect { subject }.to raise_error(NotImplementedError, 'Use ingestion endpoint')
end
end
describe '#find_error' do
let(:error) { build(:error_tracking_open_api_error, project_id: project.id) }
subject { repository.find_error(error.fingerprint) }
before do
allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
.and_return(error)
allow(open_api).to receive(:list_events)
.with(project.id, error.fingerprint, sort: 'occurred_at_asc', limit: 1)
.and_return(list_events_asc)
allow(open_api).to receive(:list_events)
.with(project.id, error.fingerprint, sort: 'occurred_at_desc', limit: 1)
.and_return(list_events_desc)
end
end
context 'when request succeeds' do
context 'without events returned' do
let(:list_events_asc) { [] }
let(:list_events_desc) { [] }
include_examples 'no logging'
it 'returns detailed error' do
is_expected.to have_attributes(
id: error.fingerprint.to_s,
title: error.name,
message: error.description,
culprit: error.actor,
first_seen: error.first_seen_at.to_s,
last_seen: error.last_seen_at.to_s,
count: error.event_count,
user_count: error.approximated_user_count,
project_id: error.project_id,
status: error.status,
tags: { level: nil, logger: nil },
external_url: "http://localhost/#{project.full_path}/-/error_tracking/#{error.fingerprint}/details",
external_base_url: "http://localhost/#{project.full_path}",
integrated: true
)
end
it 'returns no first and last release version' do
is_expected.to have_attributes(
first_release_version: nil,
last_release_version: nil
)
end
end
context 'with events returned' do
let(:first_event) { build(:error_tracking_open_api_error_event, project_id: project.id) }
let(:first_release) { parse_json(first_event.payload).fetch('release') }
let(:last_event) { build(:error_tracking_open_api_error_event, :golang, project_id: project.id) }
let(:last_release) { parse_json(last_event.payload).fetch('release') }
let(:list_events_asc) { [first_event] }
let(:list_events_desc) { [last_event] }
include_examples 'no logging'
it 'returns first and last release version' do
expect(first_release).to be_present
expect(last_release).to be_present
is_expected.to have_attributes(
first_release_version: first_release,
last_release_version: last_release
)
end
def parse_json(content)
Gitlab::Json.parse(content)
end
end
end
context 'when request fails' do
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:get_error)
.with(project.id, error.fingerprint)
.and_raise(api_exception)
end
include_examples 'exception logging'
it { is_expected.to be_nil }
end
end
describe '#list_errors' do
let(:errors) { [] }
let(:response_with_info) { [errors, 200, headers] }
let(:result_errors) { result.first }
let(:result_pagination) { result.last }
let(:headers) do
{
'link' => [
'<url?cursor=next_cursor&param>; rel="next"',
'<url?cursor=prev_cursor&param>; rel="prev"'
].join(', ')
}
end
subject(:result) { repository.list_errors(**params) }
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
.with(project.id, kind_of(Hash))
.and_return(response_with_info)
end
context 'with errors' do
let(:limit) { 3 }
let(:params) { { limit: limit } }
let(:errors_size) { limit }
let(:errors) { build_list(:error_tracking_open_api_error, errors_size, project_id: project.id) }
include_examples 'no logging'
it 'maps errors to models' do
# All errors are identical
error = errors.first
expect(result_errors).to all(
have_attributes(
id: error.fingerprint.to_s,
title: error.name,
message: error.description,
culprit: error.actor,
first_seen: error.first_seen_at,
last_seen: error.last_seen_at,
status: error.status,
count: error.event_count,
user_count: error.approximated_user_count
))
end
context 'when n errors are returned' do
let(:errors_size) { limit }
include_examples 'no logging'
it 'returns the amount of errors' do
expect(result_errors.size).to eq(3)
end
it 'cursor links are preserved' do
expect(result_pagination).to have_attributes(
prev: 'prev_cursor',
next: 'next_cursor'
)
end
end
context 'when less errors than requested are returned' do
let(:errors_size) { limit - 1 }
include_examples 'no logging'
it 'returns the amount of errors' do
expect(result_errors.size).to eq(2)
end
it 'cursor link for next is removed' do
expect(result_pagination).to have_attributes(
prev: 'prev_cursor',
next: nil
)
end
end
end
context 'with params' do
let(:params) do
{
filters: { status: 'resolved', something: 'different' },
query: 'search term',
sort: 'first_seen',
limit: 2,
cursor: 'abc'
}
end
include_examples 'no logging'
it 'passes provided params to client' do
passed_params = {
sort: 'first_seen_desc',
status: 'resolved',
query: 'search term',
cursor: 'abc',
limit: 2
}
expect_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
.with(project.id, passed_params)
.and_return(response_with_info)
subject
end
end
context 'without explicit params' do
let(:params) { {} }
include_examples 'no logging'
it 'passes default params to client' do
passed_params = {
sort: 'last_seen_desc',
limit: 20,
cursor: {}
}
expect_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
.with(project.id, passed_params)
.and_return(response_with_info)
subject
end
end
context 'when request fails' do
let(:params) { {} }
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
.with(project.id, kind_of(Hash))
.and_raise(api_exception)
end
include_examples 'exception logging'
specify do
expect(result_errors).to eq([])
expect(result_pagination).to have_attributes(
next: nil,
prev: nil
)
end
end
end
describe '#last_event_for' do
let(:params) { { sort: 'occurred_at_desc', limit: 1 } }
let(:event) { build(:error_tracking_open_api_error_event, project_id: project.id) }
let(:error) { build(:error_tracking_open_api_error, project_id: project.id, fingerprint: event.fingerprint) }
subject { repository.last_event_for(error.fingerprint) }
context 'when both event and error is returned' do
before do
allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
.and_return([event])
allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
.and_return(error)
end
end
include_examples 'no logging'
it 'returns mapped error event' do
is_expected.to have_attributes(
issue_id: event.fingerprint.to_s,
date_received: error.last_seen_at,
stack_trace_entries: kind_of(Array)
)
end
end
context 'when event is not returned' do
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_events)
.with(project.id, event.fingerprint, params)
.and_return([])
end
include_examples 'no logging'
it { is_expected.to be_nil }
end
context 'when list_events request fails' do
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_events)
.with(project.id, event.fingerprint, params)
.and_raise(api_exception)
end
include_examples 'exception logging'
it { is_expected.to be_nil }
end
context 'when error is not returned' do
before do
allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
.and_return([event])
allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
.and_return(nil)
end
end
include_examples 'no logging'
it { is_expected.to be_nil }
end
context 'when get_error request fails' do
before do
allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
.and_return([event])
allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
.and_raise(api_exception)
end
end
include_examples 'exception logging'
it { is_expected.to be_nil }
end
end
describe '#update_error' do
let(:error) { build(:error_tracking_open_api_error, project_id: project.id) }
let(:update_params) { { status: 'resolved' } }
let(:passed_body) { ErrorTrackingOpenAPI::ErrorUpdatePayload.new(update_params) }
subject { repository.update_error(error.fingerprint, **update_params) }
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:update_error)
.with(project.id, error.fingerprint, passed_body)
.and_return(:anything)
end
context 'when update succeeds' do
include_examples 'no logging'
it { is_expected.to eq(true) }
end
context 'when update fails' do
before do
allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:update_error)
.with(project.id, error.fingerprint, passed_body)
.and_raise(api_exception)
end
include_examples 'exception logging'
it { is_expected.to eq(false) }
end
end
describe '#dsn_url' do
let(:public_key) { 'abc' }
let(:config) { ErrorTrackingOpenAPI::Configuration.default }
subject { repository.dsn_url(public_key) }
it do
is_expected
.to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/api/#{project.id}")
end
end
end

View File

@ -63,6 +63,11 @@ RSpec.describe API::Internal::Base do
post api('/internal/error_tracking/allowed'), params: params, headers: headers
end
before do
# Because the feature flag is disabled in specs we have to enable it explicitly.
stub_feature_flags(use_click_house_database_for_error_tracking: true)
end
context 'when the secret header is missing' do
let(:headers) { {} }

View File

@ -289,6 +289,10 @@ RSpec.configure do |config|
stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: false)
stub_feature_flags(ci_queueing_disaster_recovery_disable_quota: false)
# It's disabled in specs because we don't support certain features which
# cause spec failures.
stub_feature_flags(use_click_house_database_for_error_tracking: false)
enable_rugged = example.metadata[:enable_rugged].present?
# Disable Rugged features by default

View File

@ -1,4 +1,4 @@
# Generated by `rake gems:error_tracking_open_api:generate` at 2022-07-01
# Generated by `rake gems:error_tracking_open_api:generate` on 2022-07-02
See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature

View File

@ -31,8 +31,8 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rspec', '~> 3.6', '>= 3.6.0'
s.files = `find *`.split("\n").uniq.sort.select { |f| !f.empty? }
s.test_files = `find spec/*`.split("\n")
s.files = Dir.glob("lib/**/*")
s.test_files = []
s.executables = []
s.require_paths = ["lib"]
end