Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
259c0cc0c4
commit
bffcdf9bca
|
@ -11,18 +11,19 @@ import {
|
|||
GlModalDirective,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
|
||||
import { s__ } from '~/locale';
|
||||
import createFlash from '~/flash';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
|
||||
import invalidUrl from '~/lib/utils/invalid_url';
|
||||
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
|
||||
import DateTimePicker from './date_time_picker/date_time_picker.vue';
|
||||
import MonitorTimeSeriesChart from './charts/time_series.vue';
|
||||
import MonitorSingleStatChart from './charts/single_stat.vue';
|
||||
import GraphGroup from './graph_group.vue';
|
||||
import EmptyState from './empty_state.vue';
|
||||
import { getTimeDiff, isValidDate } from '../utils';
|
||||
import TrackEventDirective from '~/vue_shared/directives/track_event';
|
||||
import { getTimeDiff, isValidDate, getAddMetricTrackingOptions } from '../utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -43,6 +44,7 @@ export default {
|
|||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
GlTooltip: GlTooltipDirective,
|
||||
TrackEvent: TrackEventDirective,
|
||||
},
|
||||
props: {
|
||||
externalDashboardUrl: {
|
||||
|
@ -298,6 +300,7 @@ export default {
|
|||
onDateTimePickerApply(timeWindowUrlParams) {
|
||||
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
|
||||
},
|
||||
getAddMetricTrackingOptions,
|
||||
},
|
||||
addMetric: {
|
||||
title: s__('Metrics|Add metric'),
|
||||
|
@ -389,9 +392,10 @@ export default {
|
|||
</gl-button>
|
||||
<gl-button
|
||||
v-if="addingMetricsAvailable"
|
||||
ref="addMetricBtn"
|
||||
v-gl-modal="$options.addMetric.modalId"
|
||||
variant="outline-success"
|
||||
class="mr-2 mt-1 js-add-metric-button"
|
||||
class="mr-2 mt-1"
|
||||
>
|
||||
{{ $options.addMetric.title }}
|
||||
</gl-button>
|
||||
|
@ -411,6 +415,8 @@ export default {
|
|||
<div slot="modal-footer">
|
||||
<gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button>
|
||||
<gl-button
|
||||
ref="submitCustomMetricsFormBtn"
|
||||
v-track-event="getAddMetricTrackingOptions()"
|
||||
:disabled="!formIsValid"
|
||||
variant="success"
|
||||
@click="submitCustomMetricsForm"
|
||||
|
|
|
@ -115,6 +115,7 @@ export const generateLinkToChartOptions = chartLink => {
|
|||
/**
|
||||
* Tracks snowplow event when user downloads CSV of cluster metric
|
||||
* @param {String} chart title that will be sent as a property for the event
|
||||
* @return {Object} config object for event tracking
|
||||
*/
|
||||
export const downloadCSVOptions = title => {
|
||||
const isCLusterHealthBoard = isClusterHealthBoard();
|
||||
|
@ -129,6 +130,18 @@ export const downloadCSVOptions = title => {
|
|||
return { category, action, label: 'Chart title', property: title };
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate options for snowplow to track adding a new metric via the dashboard
|
||||
* custom metric modal
|
||||
* @return {Object} config object for event tracking
|
||||
*/
|
||||
export const getAddMetricTrackingOptions = () => ({
|
||||
category: document.body.dataset.page,
|
||||
action: 'click_button',
|
||||
label: 'add_new_metric',
|
||||
property: 'modal',
|
||||
});
|
||||
|
||||
/**
|
||||
* This function validates the graph data contains exactly 3 metrics plus
|
||||
* value validations from graphDataValidatorForValues.
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
}
|
||||
|
||||
.tree-controls {
|
||||
display: flex;
|
||||
text-align: right;
|
||||
|
||||
.btn {
|
||||
|
|
|
@ -55,6 +55,7 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
|
|||
|
||||
render json: {
|
||||
errors: serialize_errors(result[:issues]),
|
||||
pagination: result[:pagination],
|
||||
external_url: service.external_url
|
||||
}
|
||||
end
|
||||
|
@ -111,7 +112,7 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def list_issues_params
|
||||
params.permit([:search_term, :sort])
|
||||
params.permit(:search_term, :sort, :cursor)
|
||||
end
|
||||
|
||||
def list_projects_params
|
||||
|
|
|
@ -301,7 +301,8 @@ module ApplicationSettingsHelper
|
|||
:snowplow_iglu_registry_url,
|
||||
:push_event_hooks_limit,
|
||||
:push_event_activities_limit,
|
||||
:custom_http_clone_url_root
|
||||
:custom_http_clone_url_root,
|
||||
:snippet_size_limit
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -226,6 +226,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
validates :push_event_activities_limit,
|
||||
numericality: { greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :snippet_size_limit, numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
SUPPORTED_KEY_TYPES.each do |type|
|
||||
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
||||
end
|
||||
|
|
|
@ -26,7 +26,8 @@ module ApplicationSettingImplementation
|
|||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token'
|
||||
'/import/github/personal_access_token',
|
||||
'/admin/session'
|
||||
].freeze
|
||||
|
||||
class_methods do
|
||||
|
@ -139,7 +140,8 @@ module ApplicationSettingImplementation
|
|||
snowplow_app_id: nil,
|
||||
snowplow_iglu_registry_url: nil,
|
||||
custom_http_clone_url_root: nil,
|
||||
productivity_analytics_start_date: Time.now
|
||||
productivity_analytics_start_date: Time.now,
|
||||
snippet_size_limit: 50.megabytes
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ module ErrorTracking
|
|||
def calculate_reactive_cache(request, opts)
|
||||
case request
|
||||
when 'list_issues'
|
||||
{ issues: sentry_client.list_issues(**opts.symbolize_keys) }
|
||||
sentry_client.list_issues(**opts.symbolize_keys)
|
||||
when 'issue_details'
|
||||
{
|
||||
issue: sentry_client.issue_details(**opts.symbolize_keys)
|
||||
|
|
|
@ -170,6 +170,7 @@ class Project < ApplicationRecord
|
|||
has_one :microsoft_teams_service
|
||||
has_one :packagist_service
|
||||
has_one :hangouts_chat_service
|
||||
has_one :unify_circuit_service
|
||||
|
||||
has_one :root_of_fork_network,
|
||||
foreign_key: 'root_project_id',
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UnifyCircuitService < ChatNotificationService
|
||||
def title
|
||||
'Unify Circuit'
|
||||
end
|
||||
|
||||
def description
|
||||
'Receive event notifications in Unify Circuit'
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'unify_circuit'
|
||||
end
|
||||
|
||||
def help
|
||||
'This service sends notifications about projects events to a Unify Circuit conversation.<br />
|
||||
To set up this service:
|
||||
<ol>
|
||||
<li><a href="https://www.circuit.com/unifyportalfaqdetail?articleId=164448">Set up an incoming webhook for your conversation</a>. All notifications will come to this conversation.</li>
|
||||
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
|
||||
<li>Select events below to enable notifications.</li>
|
||||
</ol>'
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
end
|
||||
|
||||
def default_channel_placeholder
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
%w[push issue confidential_issue merge_request note confidential_note tag_push
|
||||
pipeline wiki_page]
|
||||
end
|
||||
|
||||
def default_fields
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: "e.g. https://circuit.com/rest/v2/webhooks/incoming/…", required: true },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
{ type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify(message, opts)
|
||||
response = Gitlab::HTTP.post(webhook, body: {
|
||||
subject: message.project_name,
|
||||
text: message.pretext,
|
||||
markdown: true
|
||||
}.to_json)
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
||||
def custom_data(data)
|
||||
super(data).merge(markdown: true)
|
||||
end
|
||||
end
|
|
@ -289,6 +289,7 @@ class Service < ApplicationRecord
|
|||
slack
|
||||
teamcity
|
||||
microsoft_teams
|
||||
unify_circuit
|
||||
]
|
||||
|
||||
if Rails.env.development?
|
||||
|
|
|
@ -46,6 +46,18 @@ class Snippet < ApplicationRecord
|
|||
length: { maximum: 255 }
|
||||
|
||||
validates :content, presence: true
|
||||
validates :content,
|
||||
length: {
|
||||
maximum: ->(_) { Gitlab::CurrentSettings.snippet_size_limit },
|
||||
message: -> (_, data) do
|
||||
current_value = ActiveSupport::NumberHelper.number_to_human_size(data[:value].size)
|
||||
max_size = ActiveSupport::NumberHelper.number_to_human_size(Gitlab::CurrentSettings.snippet_size_limit)
|
||||
|
||||
_("is too long (%{current_value}). The maximum size is %{max_size}.") % { current_value: current_value, max_size: max_size }
|
||||
end
|
||||
},
|
||||
if: :content_changed?
|
||||
|
||||
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
|
||||
|
||||
# Scopes
|
||||
|
|
|
@ -6,37 +6,24 @@ module ErrorTracking
|
|||
DEFAULT_LIMIT = 20
|
||||
DEFAULT_SORT = 'last_seen'
|
||||
|
||||
def execute
|
||||
return error('Error Tracking is not enabled') unless enabled?
|
||||
return error('Access denied', :unauthorized) unless can_read?
|
||||
|
||||
result = project_error_tracking_setting.list_sentry_issues(
|
||||
issue_status: issue_status,
|
||||
limit: limit,
|
||||
search_term: search_term,
|
||||
sort: sort
|
||||
)
|
||||
|
||||
# our results are not yet ready
|
||||
unless result
|
||||
return error('Not ready. Try again later', :no_content)
|
||||
end
|
||||
|
||||
if result[:error].present?
|
||||
return error(result[:error], http_status_for(result[:error_type]))
|
||||
end
|
||||
|
||||
success(issues: result[:issues])
|
||||
end
|
||||
|
||||
def external_url
|
||||
project_error_tracking_setting&.sentry_external_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch
|
||||
project_error_tracking_setting.list_sentry_issues(
|
||||
issue_status: issue_status,
|
||||
limit: limit,
|
||||
search_term: params[:search_term].presence,
|
||||
sort: sort,
|
||||
cursor: params[:cursor].presence
|
||||
)
|
||||
end
|
||||
|
||||
def parse_response(response)
|
||||
{ issues: response[:issues] }
|
||||
response.slice(:issues, :pagination)
|
||||
end
|
||||
|
||||
def issue_status
|
||||
|
@ -47,18 +34,6 @@ module ErrorTracking
|
|||
params[:limit] || DEFAULT_LIMIT
|
||||
end
|
||||
|
||||
def search_term
|
||||
params[:search_term].presence
|
||||
end
|
||||
|
||||
def enabled?
|
||||
project_error_tracking_setting&.enabled?
|
||||
end
|
||||
|
||||
def can_read?
|
||||
can?(current_user, :read_sentry_issue, project)
|
||||
end
|
||||
|
||||
def sort
|
||||
params[:sort] || DEFAULT_SORT
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
%li.breadcrumb-item
|
||||
= link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path)
|
||||
|
||||
.tree-controls
|
||||
.tree-controls<
|
||||
= link_to download_project_job_artifacts_path(@project, @build),
|
||||
rel: 'nofollow', download: '', class: 'btn btn-default download' do
|
||||
= sprite_icon('download')
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
- else
|
||||
= link_to title, project_tree_path(@project, tree_join(@ref, path))
|
||||
|
||||
.tree-controls
|
||||
.tree-controls<
|
||||
= render 'projects/find_file_link'
|
||||
|
||||
.btn-group{ role: "group" }<
|
||||
-# only show normal/blame view links for text files
|
||||
- if blob.readable_text?
|
||||
- if blame
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
|
||||
|
||||
- if show_menu
|
||||
.project-action-button.dropdown.inline
|
||||
.project-action-button.dropdown.inline<
|
||||
%a.btn.dropdown-toggle.has-tooltip.qa-create-new-dropdown{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' }
|
||||
= icon('plus')
|
||||
= icon("caret-down")
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
= commits_breadcrumbs
|
||||
.tree-controls.d-none.d-sm-none.d-md-block
|
||||
.tree-controls.d-none.d-sm-none.d-md-block<
|
||||
- if @merge_request.present?
|
||||
.control
|
||||
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
= link_to new_project_tag_path(@project) do
|
||||
#{ _('New tag') }
|
||||
|
||||
.tree-controls
|
||||
.tree-controls<
|
||||
= render_if_exists 'projects/tree/lock_link'
|
||||
- if vue_file_list_enabled?
|
||||
#js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
|
||||
|
@ -85,7 +85,6 @@
|
|||
= render 'projects/find_file_link'
|
||||
|
||||
- if can_create_mr_from_fork
|
||||
= succeed " " do
|
||||
- if can_collaborate || current_user&.already_forked?(@project)
|
||||
- if vue_file_list_enabled?
|
||||
#js-tree-web-ide-link.d-inline-block
|
||||
|
@ -98,7 +97,7 @@
|
|||
= render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
|
||||
|
||||
- if show_xcode_link?(@project)
|
||||
.project-action-button.project-xcode.inline
|
||||
.project-action-button.project-xcode.inline<
|
||||
= render "projects/buttons/xcode_link"
|
||||
|
||||
= render 'projects/buttons/download', project: @project, ref: @ref
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Track adding metric via monitoring dashboard
|
||||
merge_request: 20818
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove whitespaces between tree-controls elements
|
||||
merge_request: 20952
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add admin mode controller path to Rack::Attack defaults
|
||||
merge_request: 20735
|
||||
author: Diego Louzán
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Unify Circuit project integration service
|
||||
merge_request: 19849
|
||||
author: Fabio Huser
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add created_before/after filter to group/project audit events
|
||||
merge_request: 20641
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add limit for snippet content size
|
||||
merge_request: 20346
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Upgrade auto-deploy-image for helm default values file
|
||||
merge_request: 20588
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSnippetSizeLimitToApplicationSettings < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
add_column :application_settings, :snippet_size_limit, :bigint, default: 50.megabytes, null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :application_settings, :snippet_size_limit
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAdminModeProtectedPath < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
ADMIN_MODE_ENDPOINT = '/admin/session'
|
||||
|
||||
OLD_DEFAULT_PROTECTED_PATHS = [
|
||||
'/users/password',
|
||||
'/users/sign_in',
|
||||
'/api/v3/session.json',
|
||||
'/api/v3/session',
|
||||
'/api/v4/session.json',
|
||||
'/api/v4/session',
|
||||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token'
|
||||
]
|
||||
|
||||
NEW_DEFAULT_PROTECTED_PATHS = OLD_DEFAULT_PROTECTED_PATHS.dup << ADMIN_MODE_ENDPOINT
|
||||
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
change_column_default :application_settings, :protected_paths, NEW_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
# schema allows nulls for protected_paths
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
unless application_setting.protected_paths.include?(ADMIN_MODE_ENDPOINT)
|
||||
updated_protected_paths = application_setting.protected_paths << ADMIN_MODE_ENDPOINT
|
||||
|
||||
application_setting.update(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_default :application_settings, :protected_paths, OLD_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
# schema allows nulls for protected_paths
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
if application_setting.protected_paths.include?(ADMIN_MODE_ENDPOINT)
|
||||
updated_protected_paths = application_setting.protected_paths - [ADMIN_MODE_ENDPOINT]
|
||||
|
||||
application_setting.update(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -328,7 +328,7 @@ ActiveRecord::Schema.define(version: 2019_11_25_140458) do
|
|||
t.boolean "throttle_protected_paths_enabled", default: false, null: false
|
||||
t.integer "throttle_protected_paths_requests_per_period", default: 10, null: false
|
||||
t.integer "throttle_protected_paths_period_in_seconds", default: 60, null: false
|
||||
t.string "protected_paths", limit: 255, default: ["/users/password", "/users/sign_in", "/api/v3/session.json", "/api/v3/session", "/api/v4/session.json", "/api/v4/session", "/users", "/users/confirmation", "/unsubscribes/", "/import/github/personal_access_token"], array: true
|
||||
t.string "protected_paths", limit: 255, default: ["/users/password", "/users/sign_in", "/api/v3/session.json", "/api/v3/session", "/api/v4/session.json", "/api/v4/session", "/users", "/users/confirmation", "/unsubscribes/", "/import/github/personal_access_token", "/admin/session"], array: true
|
||||
t.boolean "throttle_incident_management_notification_enabled", default: false, null: false
|
||||
t.integer "throttle_incident_management_notification_period_in_seconds", default: 3600
|
||||
t.integer "throttle_incident_management_notification_per_period", default: 3600
|
||||
|
@ -361,6 +361,7 @@ ActiveRecord::Schema.define(version: 2019_11_25_140458) do
|
|||
t.string "encrypted_slack_app_secret_iv", limit: 255
|
||||
t.text "encrypted_slack_app_verification_token"
|
||||
t.string "encrypted_slack_app_verification_token_iv", limit: 255
|
||||
t.bigint "snippet_size_limit", default: 52428800, null: false
|
||||
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
|
||||
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
|
||||
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
|
||||
|
|
|
@ -158,6 +158,10 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only): Limit the usage of pipeline minutes for Shared Runners. **(STARTER ONLY)**
|
||||
- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enablingdisabling-auto-devops): Enable or disable Auto DevOps for your instance.
|
||||
|
||||
## Snippet settings
|
||||
|
||||
- [Setting snippet content size limit](snippets/index.md): Set a maximum size limit for snippets' content.
|
||||
|
||||
## Git configuration options
|
||||
|
||||
- [Custom Git hooks](custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough.
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Snippets settings **(CORE ONLY)**
|
||||
|
||||
Adjust the snippets' settings of your GitLab instance.
|
||||
|
||||
## Snippets content size limit
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31133) in GitLab 12.6.
|
||||
|
||||
You can set a content size max limit in snippets. This limit can prevent
|
||||
abuses of the feature. The default content size limit is **52428800 Bytes** (50MB).
|
||||
|
||||
### How does it work?
|
||||
|
||||
The content size limit will be applied when a snippet is created or
|
||||
updated. Nevertheless, in order not to break any existing snippet,
|
||||
the limit will only be enforced in stored snippets when the content
|
||||
is updated.
|
||||
|
||||
### Snippets size limit configuration
|
||||
|
||||
This setting is not available through the [Admin Area settings](../../user/admin_area/settings/index.md).
|
||||
In order to configure this setting, use either the Rails console
|
||||
or the [Application settings API](../../api/settings.md).
|
||||
|
||||
NOTE: **IMPORTANT:**
|
||||
The value of the limit **must** be in Bytes.
|
||||
|
||||
#### Through the Rails console
|
||||
|
||||
The steps to configure this setting through the Rails console are:
|
||||
|
||||
1. Start the Rails console:
|
||||
|
||||
```bash
|
||||
# For Omnibus installations
|
||||
sudo gitlab-rails console
|
||||
|
||||
# For installations from source
|
||||
sudo -u git -H bundle exec rails console production
|
||||
```
|
||||
|
||||
1. Update the snippets maximum file size:
|
||||
|
||||
```ruby
|
||||
ApplicationSetting.first.update!(snippet_size_limit: 50.megabytes)
|
||||
```
|
||||
|
||||
To retrieve the current value, start the Rails console and run:
|
||||
|
||||
```ruby
|
||||
Gitlab::CurrentSettings.snippet_size_limit
|
||||
```
|
||||
|
||||
#### Through the API
|
||||
|
||||
The process to set the snippets size limit through the Application Settings API is
|
||||
exactly the same as you would do to [update any other setting](../../api/settings.md#change-application-settings).
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings?snippet_size_limit=52428800
|
||||
```
|
||||
|
||||
You can also use the API to [retrieve the current value](../../api/settings.md#get-current-application-settings).
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings
|
||||
```
|
|
@ -229,6 +229,51 @@ Get Campfire service settings for a project.
|
|||
GET /projects/:id/services/campfire
|
||||
```
|
||||
|
||||
## Unify Circuit
|
||||
|
||||
Unify Circuit RTC and collaboration tool.
|
||||
|
||||
### Create/Edit Unify Circuit service
|
||||
|
||||
Set Unify Circuit service for a project.
|
||||
|
||||
```
|
||||
PUT /projects/:id/services/unify-circuit
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `webhook` | string | true | The Unify Circuit webhook. For example, `https://circuit.com/rest/v2/webhooks/incoming/...`. |
|
||||
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
|
||||
| `branches_to_be_notified` | string | all | Branches to send notifications for. Valid options are "all", "default", "protected", and "default_and_protected" |
|
||||
| `push_events` | boolean | false | Enable notifications for push events |
|
||||
| `issues_events` | boolean | false | Enable notifications for issue events |
|
||||
| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events |
|
||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||
| `note_events` | boolean | false | Enable notifications for note events |
|
||||
| `confidential_note_events` | boolean | false | Enable notifications for confidential note events |
|
||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||
|
||||
### Delete Unify Circuit service
|
||||
|
||||
Delete Unify Circuit service for a project.
|
||||
|
||||
```
|
||||
DELETE /projects/:id/services/unify-circuit
|
||||
```
|
||||
|
||||
### Get Unify Circuit service settings
|
||||
|
||||
Get Unify Circuit service settings for a project.
|
||||
|
||||
```
|
||||
GET /projects/:id/services/unify-circuit
|
||||
```
|
||||
|
||||
## Custom Issue Tracker
|
||||
|
||||
Custom issue tracker
|
||||
|
@ -480,6 +525,7 @@ Parameters:
|
|||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||
| `note_events` | boolean | false | Enable notifications for note events |
|
||||
| `confidential_note_events` | boolean | false | Enable notifications for confidential note events |
|
||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||
|
||||
|
@ -1088,6 +1134,7 @@ Parameters:
|
|||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||
| `note_events` | boolean | false | Enable notifications for note events |
|
||||
| `confidential_note_events` | boolean | false | Enable notifications for confidential note events |
|
||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||
| `push_channel` | string | false | The name of the channel to receive push events notifications |
|
||||
|
@ -1095,6 +1142,7 @@ Parameters:
|
|||
| `confidential_issue_channel` | string | false | The name of the channel to receive confidential issues events notifications |
|
||||
| `merge_request_channel` | string | false | The name of the channel to receive merge request events notifications |
|
||||
| `note_channel` | string | false | The name of the channel to receive note events notifications |
|
||||
| `confidential_note_channel` | boolean | The name of the channel to receive confidential note events notifications |
|
||||
| `tag_push_channel` | string | false | The name of the channel to receive tag push events notifications |
|
||||
| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
|
||||
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
|
||||
|
|
|
@ -350,3 +350,4 @@ are listed in the descriptions of the relevant settings.
|
|||
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
|
||||
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
|
||||
| `web_ide_clientside_preview_enabled` | boolean | no | Client side evaluation (allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation). |
|
||||
| `snippet_size_limit` | integer | no | Max snippet content size in **bytes**. Default: 52428800 Bytes (50MB).|
|
||||
|
|
|
@ -228,5 +228,5 @@ our very powerful [`only/except` rules system](../yaml/README.md#onlyexcept-basi
|
|||
|
||||
```yaml
|
||||
my_job:
|
||||
only: branches
|
||||
only: [branches]
|
||||
```
|
||||
|
|
|
@ -30,7 +30,7 @@ Pipelines for merge requests have the following requirements and limitations:
|
|||
|
||||
## Configuring pipelines for merge requests
|
||||
|
||||
To configure pipelines for merge requests, add the `only: merge_requests` parameter to
|
||||
To configure pipelines for merge requests, add the `only: [merge_requests]` parameter to
|
||||
the jobs that you want to run only for merge requests.
|
||||
|
||||
Then, when developers create or update merge requests, a pipeline runs
|
||||
|
@ -68,7 +68,7 @@ After the merge request is updated with new commits:
|
|||
- The pipeline fetches the latest code from the source branch and run tests against it.
|
||||
|
||||
In the above example, the pipeline contains only a `test` job.
|
||||
Since the `build` and `deploy` jobs don't have the `only: merge_requests` parameter,
|
||||
Since the `build` and `deploy` jobs don't have the `only: [merge_requests]` parameter,
|
||||
they will not run in the merge request.
|
||||
|
||||
Pipelines tagged with the **detached** badge indicate that they were triggered
|
||||
|
@ -86,7 +86,7 @@ Read the [documentation on Merge Trains](pipelines_for_merged_results/merge_trai
|
|||
|
||||
## Excluding certain jobs
|
||||
|
||||
The behavior of the `only: merge_requests` parameter is such that _only_ jobs with
|
||||
The behavior of the `only: [merge_requests]` parameter is such that _only_ jobs with
|
||||
that parameter are run in the context of a merge request; no other jobs will be run.
|
||||
|
||||
However, you may want to reverse this behavior, having all of your jobs to run _except_
|
||||
|
|
|
@ -780,7 +780,7 @@ it is possible to define a job to be created based on files modified
|
|||
in a merge request.
|
||||
|
||||
In order to deduce the correct base SHA of the source branch, we recommend combining
|
||||
this keyword with `only: merge_requests`. This way, file differences are correctly
|
||||
this keyword with `only: [merge_requests]`. This way, file differences are correctly
|
||||
calculated from any further commits, thus all changes in the merge requests are properly
|
||||
tested in pipelines.
|
||||
|
||||
|
@ -802,7 +802,7 @@ either files in `service-one` directory or the `Dockerfile`, GitLab creates
|
|||
and triggers the `docker build service one` job.
|
||||
|
||||
Note that if [pipelines for merge requests](../merge_request_pipelines/index.md) is
|
||||
combined with `only: change`, but `only: merge_requests` is omitted, there could be
|
||||
combined with `only: [change]`, but `only: [merge_requests]` is omitted, there could be
|
||||
unwanted behavior.
|
||||
|
||||
For example:
|
||||
|
|
|
@ -699,7 +699,7 @@ nicely on different mobile devices.
|
|||
## Code blocks
|
||||
|
||||
- Always wrap code added to a sentence in inline code blocks (`` ` ``).
|
||||
E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: master`.
|
||||
E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: [master]`.
|
||||
File names, commands, entries, and anything that refers to code should be added to code blocks.
|
||||
To make things easier for the user, always add a full code block for things that can be
|
||||
useful to copy and paste, as they can easily do it with the button on code blocks.
|
||||
|
|
|
@ -165,6 +165,54 @@ can quickly spiral out of control.
|
|||
There are some cases where this may be needed. If this is the case this should
|
||||
be clearly mentioned in the merge request description.
|
||||
|
||||
## Batch process
|
||||
|
||||
**Summary:** Iterating a single process to external services (e.g. PostgreSQL, Redis, Object Storage, etc)
|
||||
should be executed in a **batch-style** in order to reduce connection overheads.
|
||||
|
||||
For fetching rows from various tables in a batch-style, please see [Eager Loading](#eager-loading) section.
|
||||
|
||||
### Example: Delete multiple files from Object Storage
|
||||
|
||||
When you delete multiple files from object storage (e.g. GCS),
|
||||
executing a single REST API call multiple times is a quite expensive
|
||||
process. Ideally, this should be done in a batch-style, for example, S3 provides
|
||||
[batch deletion API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html),
|
||||
so it'd be a good idea to consider such an approach.
|
||||
|
||||
The `FastDestroyAll` module might help this situation. It's a
|
||||
small framework when you remove a bunch of database rows and its associated data
|
||||
in a batch style.
|
||||
|
||||
## Timeout
|
||||
|
||||
**Summary:** You should set a reasonable timeout when the system invokes HTTP calls
|
||||
to external services (e.g. Kubernetes), and it should be executed in Sidekiq, not
|
||||
in Puma/Unicorn threads.
|
||||
|
||||
Often, GitLab needs to communicate with an external service such as Kubernetes
|
||||
clusters. In this case, it's hard to estimate when the external service finishes
|
||||
the requested process, for example, if it's a user-owned cluster that is inactive for some reason,
|
||||
GitLab might wait for the response forever ([Example](https://gitlab.com/gitlab-org/gitlab/issues/31475)).
|
||||
This could result in Puma/Unicorn timeout and should be avoided at all cost.
|
||||
|
||||
You should set a reasonable timeout, gracefully handle exceptions and surface the
|
||||
errors in UI or logging internally.
|
||||
|
||||
Using [`ReactiveCaching`](https://docs.gitlab.com/ee/development/utilities.html#reactivecaching) is one of the best solutions to fetch external data.
|
||||
|
||||
## Keep database transaction minimal
|
||||
|
||||
**Summary:** You should avoid accessing to external services (e.g. Gitaly) during database
|
||||
transactions, otherwise it leads to severe contention problems
|
||||
as an open transaction basically blocks the release of a Postgres backend connection.
|
||||
|
||||
For keeping transaction as minimal as possible, please consider using `AfterCommitQueue`
|
||||
module or `after_commit` AR hook.
|
||||
|
||||
Here is [an example](https://gitlab.com/gitlab-org/gitlab/issues/36154#note_247228859)
|
||||
that one request to Gitaly instance during transaction triggered a P1 issue.
|
||||
|
||||
## Eager Loading
|
||||
|
||||
**Summary:** always eager load associations when retrieving more than one row.
|
||||
|
|
|
@ -53,7 +53,8 @@ default['gitlab']['gitlab-rails']['rack_attack_protected_paths'] = [
|
|||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token'
|
||||
'/import/github/personal_access_token',
|
||||
'/admin/session'
|
||||
]
|
||||
```
|
||||
|
||||
|
|
|
@ -651,6 +651,8 @@ procfile exec` to replicate the environment where your application will run.
|
|||
|
||||
#### Workers
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/30628) in GitLab 12.6, `.gitlab/auto-deploy-values.yaml` will be used by default for Helm upgrades.
|
||||
|
||||
Some web applications need to run extra deployments for "worker processes". For
|
||||
example, it is common in a Rails application to have a separate worker process
|
||||
to run background tasks like sending emails.
|
||||
|
@ -672,13 +674,18 @@ need to:
|
|||
- Set a CI variable `K8S_SECRET_REDIS_URL`, which the URL of this instance to
|
||||
ensure it's passed into your deployments.
|
||||
|
||||
Once you have configured your worker to respond to health checks, you will
|
||||
need to configure a CI variable `HELM_UPGRADE_EXTRA_ARGS` with the value
|
||||
`--values helm-values.yaml`.
|
||||
Once you have configured your worker to respond to health checks, run a Sidekiq
|
||||
worker for your Rails application. For:
|
||||
|
||||
Then you can, for example, run a Sidekiq worker for your Rails application
|
||||
by adding a file named `helm-values.yaml` to your repository with the following
|
||||
content:
|
||||
- GitLab 12.6 and later, either:
|
||||
- Add a file named `.gitlab/auto-deploy-values.yaml` to your repository. It will
|
||||
be automatically used if found.
|
||||
- Add a file with a different name or path to the repository, and override the value of the
|
||||
`HELM_UPGRADE_VALUES_FILE` variable with the path and name.
|
||||
- GitLab 12.5 and earlier, run the worker with the `--values` parameter that specifies
|
||||
a file in the repository.
|
||||
|
||||
In any case, the file must contain the following:
|
||||
|
||||
```yml
|
||||
workers:
|
||||
|
@ -983,6 +990,7 @@ applications.
|
|||
| `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. |
|
||||
| `CANARY_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md). Defaults to 1. |
|
||||
| `HELM_RELEASE_NAME` | From GitLab 12.1, allows the `helm` release name to be overridden. Can be used to assign unique release names when deploying multiple projects to a single namespace. |
|
||||
| `HELM_UPGRADE_VALUES_FILE` | From GitLab 12.6, allows the `helm upgrade` values file to be overridden. Defaults to `.gitlab/auto-deploy-values.yaml`. |
|
||||
| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy Helm chart](#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
|
||||
| `INCREMENTAL_ROLLOUT_MODE` | From GitLab 11.4, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. |
|
||||
| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
|
||||
|
|
|
@ -14,7 +14,8 @@ GitLab protects the following paths with Rack Attack by default:
|
|||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token'
|
||||
'/import/github/personal_access_token',
|
||||
'/admin/session'
|
||||
```
|
||||
|
||||
GitLab responds with HTTP status code `429` to POST requests at protected paths
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 268 KiB |
|
@ -54,6 +54,7 @@ Click on the service links to see further configuration instructions and details
|
|||
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
|
||||
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
|
||||
| [Redmine](redmine.md) | Redmine issue tracker |
|
||||
| [Unify Circuit](unify_circuit.md) | Receive events notifications in Unify Circuit |
|
||||
| [YouTrack](youtrack.md) | YouTrack issue tracker |
|
||||
|
||||
## Push hooks limit
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Unify Circuit service
|
||||
|
||||
The Unify Circuit service sends notifications from GitLab to the conversation for which the webhook was created.
|
||||
|
||||
## On Unify Circuit
|
||||
|
||||
1. Open the conversation in which you want to see the notifications.
|
||||
1. From the conversation menu, select **Configure Webhooks**.
|
||||
1. Click **ADD WEBHOOK** and fill in the name of the bot that will post the messages. Optionally define avatar.
|
||||
1. Click **SAVE** and copy the **Webhook URL** of your webhook.
|
||||
|
||||
For more information, see the [Unify Circuit documentation for configuring incoming webhooks](https://www.circuit.com/unifyportalfaqdetail?articleId=164448).
|
||||
|
||||
## On GitLab
|
||||
|
||||
When you have the **Webhook URL** for your Unify Circuit conversation webhook, you can set up the GitLab service.
|
||||
|
||||
1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services) in your project's settings, i.e. **Project > Settings > Integrations**.
|
||||
1. Select the **Unify Circuit** project service to configure it.
|
||||
1. Check the **Active** checkbox to turn on the service.
|
||||
1. Check the checkboxes corresponding to the GitLab events you want to receive in Unify Circuit.
|
||||
1. Paste the **Webhook URL** that you copied from the Unify Circuit configuration step.
|
||||
1. Configure the remaining options and click `Save changes`.
|
||||
|
||||
Your Unify Circuit conversation will now start receiving GitLab event notifications as configured.
|
||||
|
||||
![Unify Circuit configuration](img/unify_circuit_configuration.png)
|
|
@ -69,7 +69,7 @@ For example, to that on merge requests there is always a passing job even though
|
|||
|
||||
```yaml
|
||||
enable_merge:
|
||||
only: merge_requests
|
||||
only: [merge_requests]
|
||||
script:
|
||||
- echo true
|
||||
```
|
||||
|
|
|
@ -132,6 +132,12 @@ module API
|
|||
type: Boolean,
|
||||
desc: 'Enable notifications for note_events'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :confidential_note_events,
|
||||
type: Boolean,
|
||||
desc: 'Enable notifications for confidential_note_events'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :tag_push_events,
|
||||
|
@ -696,7 +702,16 @@ module API
|
|||
type: String,
|
||||
desc: 'The password of the user'
|
||||
}
|
||||
]
|
||||
],
|
||||
'unify-circuit' => [
|
||||
{
|
||||
required: true,
|
||||
name: :webhook,
|
||||
type: String,
|
||||
desc: 'The Unify Circuit webhook. e.g. https://circuit.com/rest/v2/webhooks/incoming/…'
|
||||
},
|
||||
chat_notification_events
|
||||
].flatten
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Gitaly
|
||||
class Server
|
||||
SHA_VERSION_REGEX = /\A\d+\.\d+\.\d+-\d+-g([a-f0-9]{8})\z/.freeze
|
||||
|
||||
class << self
|
||||
def all
|
||||
Gitlab.config.repositories.storages.keys.map { |s| Gitaly::Server.new(s) }
|
||||
|
@ -30,9 +32,10 @@ module Gitaly
|
|||
info.git_version
|
||||
end
|
||||
|
||||
def up_to_date?
|
||||
server_version == Gitlab::GitalyClient.expected_server_version
|
||||
def expected_version?
|
||||
server_version == Gitlab::GitalyClient.expected_server_version || matches_sha?
|
||||
end
|
||||
alias_method :up_to_date?, :expected_version?
|
||||
|
||||
def read_writeable?
|
||||
readable? && writeable?
|
||||
|
@ -62,6 +65,13 @@ module Gitaly
|
|||
@storage_status ||= info.storage_statuses.find { |s| s.storage_name == storage }
|
||||
end
|
||||
|
||||
def matches_sha?
|
||||
match = server_version.match(SHA_VERSION_REGEX)
|
||||
return false unless match
|
||||
|
||||
Gitlab::GitalyClient.expected_server_version.start_with?(match[1])
|
||||
end
|
||||
|
||||
def info
|
||||
@info ||=
|
||||
begin
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.auto-deploy:
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.7.0"
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.8.0"
|
||||
|
||||
review:
|
||||
extends: .auto-deploy
|
||||
|
|
|
@ -155,6 +155,7 @@ module Gitlab
|
|||
# column - The name of the column to create the foreign key on.
|
||||
# on_delete - The action to perform when associated data is removed,
|
||||
# defaults to "CASCADE".
|
||||
# name - The name of the foreign key.
|
||||
#
|
||||
# rubocop:disable Gitlab/RailsLogger
|
||||
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil)
|
||||
|
@ -164,25 +165,31 @@ module Gitlab
|
|||
raise 'add_concurrent_foreign_key can not be run inside a transaction'
|
||||
end
|
||||
|
||||
on_delete = 'SET NULL' if on_delete == :nullify
|
||||
options = {
|
||||
column: column,
|
||||
on_delete: on_delete,
|
||||
name: name.presence || concurrent_foreign_key_name(source, column)
|
||||
}
|
||||
|
||||
key_name = name || concurrent_foreign_key_name(source, column)
|
||||
|
||||
unless foreign_key_exists?(source, target, column: column)
|
||||
Rails.logger.warn "Foreign key not created because it exists already " \
|
||||
if foreign_key_exists?(source, target, options)
|
||||
warning_message = "Foreign key not created because it exists already " \
|
||||
"(this may be due to an aborted migration or similar): " \
|
||||
"source: #{source}, target: #{target}, column: #{column}"
|
||||
"source: #{source}, target: #{target}, column: #{options[:column]}, "\
|
||||
"name: #{options[:name]}, on_delete: #{options[:on_delete]}"
|
||||
|
||||
Rails.logger.warn warning_message
|
||||
else
|
||||
# Using NOT VALID allows us to create a key without immediately
|
||||
# validating it. This means we keep the ALTER TABLE lock only for a
|
||||
# short period of time. The key _is_ enforced for any newly created
|
||||
# data.
|
||||
|
||||
execute <<-EOF.strip_heredoc
|
||||
ALTER TABLE #{source}
|
||||
ADD CONSTRAINT #{key_name}
|
||||
FOREIGN KEY (#{column})
|
||||
ADD CONSTRAINT #{options[:name]}
|
||||
FOREIGN KEY (#{options[:column]})
|
||||
REFERENCES #{target} (id)
|
||||
#{on_delete ? "ON DELETE #{on_delete.upcase}" : ''}
|
||||
#{on_delete_statement(options[:on_delete])}
|
||||
NOT VALID;
|
||||
EOF
|
||||
end
|
||||
|
@ -193,18 +200,15 @@ module Gitlab
|
|||
#
|
||||
# Note this is a no-op in case the constraint is VALID already
|
||||
disable_statement_timeout do
|
||||
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
|
||||
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{options[:name]};")
|
||||
end
|
||||
end
|
||||
# rubocop:enable Gitlab/RailsLogger
|
||||
|
||||
def foreign_key_exists?(source, target = nil, column: nil)
|
||||
foreign_keys(source).any? do |key|
|
||||
if column
|
||||
key.options[:column].to_s == column.to_s
|
||||
else
|
||||
key.to_table.to_s == target.to_s
|
||||
end
|
||||
def foreign_key_exists?(source, target = nil, **options)
|
||||
foreign_keys(source).any? do |foreign_key|
|
||||
tables_match?(target.to_s, foreign_key.to_table.to_s) &&
|
||||
options_match?(foreign_key.options, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1050,6 +1054,21 @@ into similar problems in the future (e.g. when new tables are created).
|
|||
|
||||
private
|
||||
|
||||
def tables_match?(target_table, foreign_key_table)
|
||||
target_table.blank? || foreign_key_table == target_table
|
||||
end
|
||||
|
||||
def options_match?(foreign_key_options, options)
|
||||
options.all? { |k, v| foreign_key_options[k].to_s == v.to_s }
|
||||
end
|
||||
|
||||
def on_delete_statement(on_delete)
|
||||
return '' if on_delete.blank?
|
||||
return 'ON DELETE SET NULL' if on_delete == :nullify
|
||||
|
||||
"ON DELETE #{on_delete.upcase}"
|
||||
end
|
||||
|
||||
def create_column_from(table, old, new, type: nil)
|
||||
old_col = column_for(table, old)
|
||||
new_type = type || old_col.type
|
||||
|
|
|
@ -34,12 +34,18 @@ module Sentry
|
|||
end
|
||||
|
||||
def list_issues(**keyword_args)
|
||||
issues = get_issues(keyword_args)
|
||||
response = get_issues(keyword_args)
|
||||
|
||||
issues = response[:issues]
|
||||
pagination = response[:pagination]
|
||||
|
||||
validate_size(issues)
|
||||
|
||||
handle_mapping_exceptions do
|
||||
map_to_errors(issues)
|
||||
{
|
||||
issues: map_to_errors(issues),
|
||||
pagination: pagination
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -83,36 +89,40 @@ module Sentry
|
|||
end
|
||||
|
||||
def get_issues(**keyword_args)
|
||||
http_get(
|
||||
response = http_get(
|
||||
issues_api_url,
|
||||
query: list_issue_sentry_query(keyword_args)
|
||||
)
|
||||
|
||||
{
|
||||
issues: response[:body],
|
||||
pagination: Sentry::PaginationParser.parse(response[:headers])
|
||||
}
|
||||
end
|
||||
|
||||
def list_issue_sentry_query(issue_status:, limit:, sort: nil, search_term: '')
|
||||
def list_issue_sentry_query(issue_status:, limit:, sort: nil, search_term: '', cursor: nil)
|
||||
unless SENTRY_API_SORT_VALUE_MAP.key?(sort)
|
||||
raise BadRequestError, 'Invalid value for sort param'
|
||||
end
|
||||
|
||||
query_params = {
|
||||
{
|
||||
query: "is:#{issue_status} #{search_term}".strip,
|
||||
limit: limit,
|
||||
sort: SENTRY_API_SORT_VALUE_MAP[sort]
|
||||
}
|
||||
|
||||
query_params.compact
|
||||
sort: SENTRY_API_SORT_VALUE_MAP[sort],
|
||||
cursor: cursor
|
||||
}.compact
|
||||
end
|
||||
|
||||
def get_issue(issue_id:)
|
||||
http_get(issue_api_url(issue_id))
|
||||
http_get(issue_api_url(issue_id))[:body]
|
||||
end
|
||||
|
||||
def get_issue_latest_event(issue_id:)
|
||||
http_get(issue_latest_event_api_url(issue_id))
|
||||
http_get(issue_latest_event_api_url(issue_id))[:body]
|
||||
end
|
||||
|
||||
def get_projects
|
||||
http_get(projects_api_url)
|
||||
http_get(projects_api_url)[:body]
|
||||
end
|
||||
|
||||
def handle_request_exceptions
|
||||
|
@ -138,7 +148,7 @@ module Sentry
|
|||
raise_error "Sentry response status code: #{response.code}"
|
||||
end
|
||||
|
||||
response.parsed_response
|
||||
{ body: response.parsed_response, headers: response.headers }
|
||||
end
|
||||
|
||||
def raise_error(message)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sentry
|
||||
module PaginationParser
|
||||
PATTERN = /rel=\"(?<direction>\w+)\";\sresults=\"(?<results>\w+)\";\scursor=\"(?<cursor>.+)\"/.freeze
|
||||
|
||||
def self.parse(headers)
|
||||
links = headers['link'].to_s.split(',')
|
||||
|
||||
links.map { |link| parse_link(link) }.compact.to_h
|
||||
end
|
||||
|
||||
def self.parse_link(link)
|
||||
match = link.match(PATTERN)
|
||||
|
||||
return unless match
|
||||
return if match['results'] != "true"
|
||||
|
||||
[match['direction'], { 'cursor' => match['cursor'] }]
|
||||
end
|
||||
private_class_method :parse_link
|
||||
end
|
||||
end
|
|
@ -3361,6 +3361,9 @@ msgstr ""
|
|||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear chart filters"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear input"
|
||||
msgstr ""
|
||||
|
||||
|
@ -20934,6 +20937,9 @@ msgstr ""
|
|||
msgid "is not an email you own"
|
||||
msgstr ""
|
||||
|
||||
msgid "is too long (%{current_value}). The maximum size is %{max_size}."
|
||||
msgstr ""
|
||||
|
||||
msgid "is too long (maximum is 100 entries)"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ source 'https://rubygems.org'
|
|||
|
||||
gem 'gitlab-qa'
|
||||
gem 'activesupport', '5.2.3' # This should stay in sync with the root's Gemfile
|
||||
gem 'capybara', '~> 2.16.1'
|
||||
gem 'capybara-screenshot', '~> 1.0.18'
|
||||
gem 'capybara', '~> 3.29.0'
|
||||
gem 'capybara-screenshot', '~> 1.0.23'
|
||||
gem 'rake', '~> 12.3.0'
|
||||
gem 'rspec', '~> 3.7'
|
||||
gem 'selenium-webdriver', '~> 3.12'
|
||||
|
|
|
@ -6,8 +6,8 @@ GEM
|
|||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
airborne (0.2.13)
|
||||
activesupport
|
||||
rack
|
||||
|
@ -15,15 +15,16 @@ GEM
|
|||
rest-client (>= 1.7.3, < 3.0)
|
||||
rspec (~> 3.1)
|
||||
byebug (9.1.0)
|
||||
capybara (2.16.1)
|
||||
capybara (3.29.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
capybara-screenshot (1.0.18)
|
||||
capybara (>= 1.0, < 3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.5)
|
||||
xpath (~> 3.2)
|
||||
capybara-screenshot (1.0.23)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
childprocess (3.0.0)
|
||||
coderay (1.1.2)
|
||||
|
@ -50,7 +51,7 @@ GEM
|
|||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_mime (1.0.0)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
netrc (0.11.0)
|
||||
|
@ -65,11 +66,12 @@ GEM
|
|||
pry-byebug (3.5.1)
|
||||
byebug (~> 9.1)
|
||||
pry (~> 0.10)
|
||||
public_suffix (3.0.1)
|
||||
rack (2.0.6)
|
||||
rack-test (0.8.2)
|
||||
public_suffix (4.0.1)
|
||||
rack (2.0.7)
|
||||
rack-test (0.8.3)
|
||||
rack (>= 1.0, < 3)
|
||||
rake (12.3.3)
|
||||
rake (12.3.0)
|
||||
regexp_parser (1.6.0)
|
||||
rest-client (2.0.2)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
|
@ -103,8 +105,8 @@ GEM
|
|||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.4)
|
||||
xpath (2.1.0)
|
||||
nokogiri (~> 1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -112,8 +114,8 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
activesupport (= 5.2.3)
|
||||
airborne (~> 0.2.13)
|
||||
capybara (~> 2.16.1)
|
||||
capybara-screenshot (~> 1.0.18)
|
||||
capybara (~> 3.29.0)
|
||||
capybara-screenshot (~> 1.0.23)
|
||||
debase (~> 0.2.4.1)
|
||||
faker (~> 1.6, >= 1.6.6)
|
||||
gitlab-qa
|
||||
|
|
|
@ -141,6 +141,10 @@ module QA
|
|||
page.has_no_text? text
|
||||
end
|
||||
|
||||
def has_normalized_ws_text?(text, wait: Capybara.default_max_wait_time)
|
||||
page.has_text?(text.gsub(/\s+/, " "), wait: wait)
|
||||
end
|
||||
|
||||
def finished_loading?
|
||||
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
|
||||
end
|
||||
|
|
|
@ -14,11 +14,7 @@ module QA::Page
|
|||
def click_on_latest_pipeline
|
||||
css = '.js-pipeline-url-link'
|
||||
|
||||
link = wait(reload: false) do
|
||||
first(css)
|
||||
end
|
||||
|
||||
link.click
|
||||
first(css, wait: 60).click
|
||||
end
|
||||
|
||||
def wait_for_latest_pipeline_success
|
||||
|
|
|
@ -132,6 +132,10 @@ module QA
|
|||
config.default_max_wait_time = CAPYBARA_MAX_WAIT_TIME
|
||||
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
|
||||
config.save_path = ::File.expand_path('../../tmp', __dir__)
|
||||
|
||||
# Cabybara 3 does not normalize text by default, so older tests
|
||||
# fail because of unexpected line breaks and other white space
|
||||
config.default_normalize_ws = true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -52,16 +52,16 @@ module QA
|
|||
Page::Project::Show.perform(&:create_new_file!)
|
||||
Page::File::Form.perform do |form|
|
||||
form.select_template template[:file_name], template[:name]
|
||||
end
|
||||
|
||||
expect(page).to have_content(content[0..100])
|
||||
expect(form).to have_normalized_ws_text(content[0..100])
|
||||
|
||||
Page::File::Form.perform(&:commit_changes)
|
||||
form.commit_changes
|
||||
|
||||
expect(page).to have_content('The file has been successfully created.')
|
||||
expect(page).to have_content(template[:file_name])
|
||||
expect(page).to have_content('Add new file')
|
||||
expect(page).to have_content(content[0..100])
|
||||
expect(form).to have_content('The file has been successfully created.')
|
||||
expect(form).to have_content(template[:file_name])
|
||||
expect(form).to have_content('Add new file')
|
||||
expect(form).to have_normalized_ws_text(content[0..100])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,15 +54,15 @@ module QA
|
|||
ide.create_new_file_from_template template[:file_name], template[:name]
|
||||
|
||||
expect(ide.has_file?(template[:file_name])).to be_truthy
|
||||
end
|
||||
|
||||
expect(page).to have_button('Undo')
|
||||
expect(page).to have_content(content[0..100])
|
||||
expect(ide).to have_button('Undo')
|
||||
expect(ide).to have_normalized_ws_text(content[0..100])
|
||||
|
||||
Page::Project::WebIDE::Edit.perform(&:commit_changes)
|
||||
ide.commit_changes
|
||||
|
||||
expect(page).to have_content(template[:file_name])
|
||||
expect(page).to have_content(content[0..100])
|
||||
expect(ide).to have_content(template[:file_name])
|
||||
expect(ide).to have_normalized_ws_text(content[0..100])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,8 +50,6 @@ describe Projects::ErrorTrackingController do
|
|||
let(:external_url) { 'http://example.com' }
|
||||
|
||||
context 'no data' do
|
||||
let(:params) { project_params(format: :json) }
|
||||
|
||||
let(:permitted_params) do
|
||||
ActionController::Parameters.new({}).permit!
|
||||
end
|
||||
|
@ -72,11 +70,13 @@ describe Projects::ErrorTrackingController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a search_term and sort params' do
|
||||
let(:params) { project_params(format: :json, search_term: 'something', sort: 'last_seen') }
|
||||
|
||||
context 'with extra params' do
|
||||
let(:cursor) { '1572959139000:0:0' }
|
||||
let(:search_term) { 'something' }
|
||||
let(:sort) { 'last_seen' }
|
||||
let(:params) { project_params(format: :json, search_term: search_term, sort: sort, cursor: cursor) }
|
||||
let(:permitted_params) do
|
||||
ActionController::Parameters.new(search_term: 'something', sort: 'last_seen').permit!
|
||||
ActionController::Parameters.new(search_term: search_term, sort: sort, cursor: cursor).permit!
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -88,7 +88,7 @@ describe Projects::ErrorTrackingController do
|
|||
context 'service result is successful' do
|
||||
before do
|
||||
expect(list_issues_service).to receive(:execute)
|
||||
.and_return(status: :success, issues: [error])
|
||||
.and_return(status: :success, issues: [error], pagination: {})
|
||||
expect(list_issues_service).to receive(:external_url)
|
||||
.and_return(external_url)
|
||||
end
|
||||
|
@ -100,13 +100,16 @@ describe Projects::ErrorTrackingController do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('error_tracking/index')
|
||||
expect(json_response['external_url']).to eq(external_url)
|
||||
expect(json_response['errors']).to eq([error].as_json)
|
||||
expect(json_response).to eq(
|
||||
'errors' => [error].as_json,
|
||||
'pagination' => {},
|
||||
'external_url' => external_url
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without params' do
|
||||
context 'without extra params' do
|
||||
before do
|
||||
expect(ErrorTracking::ListIssuesService)
|
||||
.to receive(:new).with(project, user, {})
|
||||
|
@ -116,7 +119,7 @@ describe Projects::ErrorTrackingController do
|
|||
context 'service result is successful' do
|
||||
before do
|
||||
expect(list_issues_service).to receive(:execute)
|
||||
.and_return(status: :success, issues: [error])
|
||||
.and_return(status: :success, issues: [error], pagination: {})
|
||||
expect(list_issues_service).to receive(:external_url)
|
||||
.and_return(external_url)
|
||||
end
|
||||
|
@ -128,8 +131,11 @@ describe Projects::ErrorTrackingController do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('error_tracking/index')
|
||||
expect(json_response['external_url']).to eq(external_url)
|
||||
expect(json_response['errors']).to eq([error].as_json)
|
||||
expect(json_response).to eq(
|
||||
'errors' => [error].as_json,
|
||||
'pagination' => {},
|
||||
'external_url' => external_url
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"external_url",
|
||||
"pagination",
|
||||
"errors"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -9,6 +10,9 @@
|
|||
"errors": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "error.json" }
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
|
@ -20,6 +20,7 @@ describe('pipeline graph action component', () => {
|
|||
actionIcon: 'cancel',
|
||||
},
|
||||
sync: false,
|
||||
attachToDocument: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
|
||||
|
||||
describe('Pipelines Triggerer', () => {
|
||||
let wrapper;
|
||||
|
||||
const expectComponentWithProps = (Component, props = {}) => {
|
||||
const componentWrapper = wrapper.find(Component);
|
||||
expect(componentWrapper.isVisible()).toBe(true);
|
||||
expect(componentWrapper.props()).toEqual(expect.objectContaining(props));
|
||||
};
|
||||
|
||||
const mockData = {
|
||||
pipeline: {
|
||||
user: {
|
||||
|
@ -15,9 +22,10 @@ describe('Pipelines Triggerer', () => {
|
|||
};
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = mount(pipelineTriggerer, {
|
||||
wrapper = shallowMount(pipelineTriggerer, {
|
||||
propsData: mockData,
|
||||
sync: false,
|
||||
attachToDocument: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -33,14 +41,12 @@ describe('Pipelines Triggerer', () => {
|
|||
expect(wrapper.contains('.table-section')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render triggerer information when triggerer is provided', () => {
|
||||
const link = wrapper.find('.js-pipeline-url-user');
|
||||
|
||||
expect(link.attributes('href')).toEqual(mockData.pipeline.user.path);
|
||||
expect(link.find('.js-user-avatar-image-toolip').text()).toEqual(mockData.pipeline.user.name);
|
||||
expect(link.find('img.avatar').attributes('src')).toEqual(
|
||||
`${mockData.pipeline.user.avatar_url}?width=26`,
|
||||
);
|
||||
it('should pass triggerer information when triggerer is provided', () => {
|
||||
expectComponentWithProps(UserAvatarLink, {
|
||||
linkHref: mockData.pipeline.user.path,
|
||||
tooltipText: mockData.pipeline.user.name,
|
||||
imgSrc: mockData.pipeline.user.avatar_url,
|
||||
});
|
||||
});
|
||||
|
||||
it('should render "API" when no triggerer is provided', () => {
|
||||
|
@ -50,7 +56,7 @@ describe('Pipelines Triggerer', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -65,4 +65,26 @@ describe Gitaly::Server do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#expected_version?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:expected_version, :server_version, :result) do
|
||||
'1.1.1' | '1.1.1' | true
|
||||
'1.1.2' | '1.1.1' | false
|
||||
'1.73.0' | '1.73.0-18-gf756ebe2' | false
|
||||
'594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6-45-g594c3ea3' | true
|
||||
'594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6-46-gabc123ff' | false
|
||||
'594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6' | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it do
|
||||
allow(Gitlab::GitalyClient).to receive(:expected_server_version).and_return(expected_version)
|
||||
allow(server).to receive(:server_version).and_return(server_version)
|
||||
|
||||
expect(server.expected_version?).to eq(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -212,6 +212,54 @@ describe Gitlab::Database::MigrationHelpers do
|
|||
allow(model).to receive(:transaction_open?).and_return(false)
|
||||
end
|
||||
|
||||
context 'ON DELETE statements' do
|
||||
context 'on_delete: :nullify' do
|
||||
it 'appends ON DELETE SET NULL statement' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/RESET ALL/)
|
||||
|
||||
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users,
|
||||
column: :user_id,
|
||||
on_delete: :nullify)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on_delete: :cascade' do
|
||||
it 'appends ON DELETE CASCADE statement' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/RESET ALL/)
|
||||
|
||||
expect(model).to receive(:execute).with(/ON DELETE CASCADE/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users,
|
||||
column: :user_id,
|
||||
on_delete: :cascade)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on_delete: nil' do
|
||||
it 'appends no ON DELETE statement' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/RESET ALL/)
|
||||
|
||||
expect(model).not_to receive(:execute).with(/ON DELETE/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users,
|
||||
column: :user_id,
|
||||
on_delete: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no custom key name is supplied' do
|
||||
it 'creates a concurrent foreign key and validates it' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
|
@ -222,27 +270,23 @@ describe Gitlab::Database::MigrationHelpers do
|
|||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
|
||||
end
|
||||
|
||||
it 'appends a valid ON DELETE statement' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/RESET ALL/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users,
|
||||
column: :user_id,
|
||||
on_delete: :nullify)
|
||||
end
|
||||
|
||||
it 'does not create a foreign key if it exists already' do
|
||||
expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
|
||||
name = model.concurrent_foreign_key_name(:projects, :user_id)
|
||||
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
|
||||
column: :user_id,
|
||||
on_delete: :cascade,
|
||||
name: name).and_return(true)
|
||||
|
||||
expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows the use of a custom key name' do
|
||||
context 'when a custom key name is supplied' do
|
||||
context 'for creating a new foreign key for a column that does not presently exist' do
|
||||
it 'creates a new foreign key' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
|
@ -252,6 +296,36 @@ describe Gitlab::Database::MigrationHelpers do
|
|||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for creating a duplicate foreign key for a column that presently exists' do
|
||||
context 'when the supplied key name is the same as the existing foreign key name' do
|
||||
it 'does not create a new foreign key' do
|
||||
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
|
||||
name: :foo,
|
||||
on_delete: :cascade,
|
||||
column: :user_id).and_return(true)
|
||||
|
||||
expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
|
||||
expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the supplied key name is different from the existing foreign key name' do
|
||||
it 'creates a new foreign key' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+bar/)
|
||||
expect(model).to receive(:execute).with(/RESET ALL/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :bar)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#concurrent_foreign_key_name' do
|
||||
|
@ -266,23 +340,61 @@ describe Gitlab::Database::MigrationHelpers do
|
|||
|
||||
describe '#foreign_key_exists?' do
|
||||
before do
|
||||
key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id })
|
||||
key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id, name: :fk_projects_users_non_standard_id, on_delete: :cascade })
|
||||
allow(model).to receive(:foreign_keys).with(:projects).and_return([key])
|
||||
end
|
||||
|
||||
shared_examples_for 'foreign key checks' do
|
||||
it 'finds existing foreign keys by column' do
|
||||
expect(model.foreign_key_exists?(:projects, :users, column: :non_standard_id)).to be_truthy
|
||||
expect(model.foreign_key_exists?(:projects, target_table, column: :non_standard_id)).to be_truthy
|
||||
end
|
||||
|
||||
it 'finds existing foreign keys by name' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id)).to be_truthy
|
||||
end
|
||||
|
||||
it 'finds existing foreign_keys by name and column' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id)).to be_truthy
|
||||
end
|
||||
|
||||
it 'finds existing foreign_keys by name, column and on_delete' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :cascade)).to be_truthy
|
||||
end
|
||||
|
||||
it 'finds existing foreign keys by target table only' do
|
||||
expect(model.foreign_key_exists?(:projects, :users)).to be_truthy
|
||||
expect(model.foreign_key_exists?(:projects, target_table)).to be_truthy
|
||||
end
|
||||
|
||||
it 'compares by column name if given' do
|
||||
expect(model.foreign_key_exists?(:projects, :users, column: :user_id)).to be_falsey
|
||||
expect(model.foreign_key_exists?(:projects, target_table, column: :user_id)).to be_falsey
|
||||
end
|
||||
|
||||
it 'compares by target if no column given' do
|
||||
it 'compares by foreign key name if given' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'compares by foreign key name and column if given' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey
|
||||
end
|
||||
|
||||
it 'compares by foreign key name, column and on_delete if given' do
|
||||
expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :nullify)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'without specifying a target table' do
|
||||
let(:target_table) { nil }
|
||||
|
||||
it_behaves_like 'foreign key checks'
|
||||
end
|
||||
|
||||
context 'specifying a target table' do
|
||||
let(:target_table) { :users }
|
||||
|
||||
it_behaves_like 'foreign key checks'
|
||||
end
|
||||
|
||||
it 'compares by target table if no column given' do
|
||||
expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
|
|
@ -290,6 +290,7 @@ project:
|
|||
- microsoft_teams_service
|
||||
- mattermost_service
|
||||
- hangouts_chat_service
|
||||
- unify_circuit_service
|
||||
- buildkite_service
|
||||
- bamboo_service
|
||||
- teamcity_service
|
||||
|
|
|
@ -54,10 +54,20 @@ describe Sentry::Client do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'issues has correct return type' do |klass|
|
||||
it "returns objects of type #{klass}" do
|
||||
expect(subject[:issues]).to all( be_a(klass) )
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'has correct length' do |length|
|
||||
it { expect(subject.length).to eq(length) }
|
||||
end
|
||||
|
||||
shared_examples 'issues has correct length' do |length|
|
||||
it { expect(subject[:issues].length).to eq(length) }
|
||||
end
|
||||
|
||||
# Requires sentry_api_request and subject to be defined
|
||||
shared_examples 'calls sentry api' do
|
||||
it 'calls sentry api' do
|
||||
|
@ -95,26 +105,44 @@ describe Sentry::Client do
|
|||
let(:issue_status) { 'unresolved' }
|
||||
let(:limit) { 20 }
|
||||
let(:search_term) { '' }
|
||||
let(:cursor) { nil }
|
||||
let(:sort) { 'last_seen' }
|
||||
let(:sentry_api_response) { issues_sample_response }
|
||||
let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' }
|
||||
|
||||
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
|
||||
|
||||
subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: 'last_seen') }
|
||||
subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: sort, cursor: cursor) }
|
||||
|
||||
it_behaves_like 'calls sentry api'
|
||||
|
||||
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'has correct length', 1
|
||||
it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'issues has correct length', 1
|
||||
|
||||
shared_examples 'has correct external_url' do
|
||||
context 'external_url' do
|
||||
it 'is constructed correctly' do
|
||||
expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
|
||||
expect(subject[:issues][0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response has a pagination info' do
|
||||
let(:headers) do
|
||||
{
|
||||
link: '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"'
|
||||
}
|
||||
end
|
||||
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response, headers: headers) }
|
||||
|
||||
it 'parses the pagination' do
|
||||
expect(subject[:pagination]).to eq(
|
||||
'previous' => { 'cursor' => '1573556671000:0:1' },
|
||||
'next' => { 'cursor' => '1572959139000:0:0' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'error object created from sentry response' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
@ -137,7 +165,7 @@ describe Sentry::Client do
|
|||
end
|
||||
|
||||
with_them do
|
||||
it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) }
|
||||
it { expect(subject[:issues][0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) }
|
||||
end
|
||||
|
||||
it_behaves_like 'has correct external_url'
|
||||
|
@ -210,8 +238,8 @@ describe Sentry::Client do
|
|||
|
||||
it_behaves_like 'calls sentry api'
|
||||
|
||||
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'has correct length', 1
|
||||
it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'issues has correct length', 1
|
||||
|
||||
it_behaves_like 'has correct external_url'
|
||||
end
|
||||
|
@ -245,8 +273,18 @@ describe Sentry::Client do
|
|||
|
||||
it_behaves_like 'calls sentry api'
|
||||
|
||||
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'has correct length', 1
|
||||
it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'issues has correct length', 1
|
||||
end
|
||||
|
||||
context 'when cursor is present' do
|
||||
let(:cursor) { '1572959139000:0:0' }
|
||||
let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&cursor=#{cursor}&query=is:unresolved" }
|
||||
|
||||
it_behaves_like 'calls sentry api'
|
||||
|
||||
it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error
|
||||
it_behaves_like 'issues has correct length', 1
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'support/helpers/fixture_helpers'
|
||||
|
||||
describe Sentry::PaginationParser do
|
||||
include FixtureHelpers
|
||||
|
||||
describe '.parse' do
|
||||
subject { described_class.parse(headers) }
|
||||
|
||||
context 'when headers do not have "link" param' do
|
||||
let(:headers) { {} }
|
||||
|
||||
it 'returns empty hash' do
|
||||
is_expected.to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when headers.link has previous and next pages' do
|
||||
let(:headers) do
|
||||
{
|
||||
'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns info about both pages' do
|
||||
is_expected.to eq(
|
||||
'previous' => { 'cursor' => '1573556671000:0:1' },
|
||||
'next' => { 'cursor' => '1572959139000:0:0' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when headers.link has only next page' do
|
||||
let(:headers) do
|
||||
{
|
||||
'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="false"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns only info about the next page' do
|
||||
is_expected.to eq(
|
||||
'next' => { 'cursor' => '1572959139000:0:0' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when headers.link has only previous page' do
|
||||
let(:headers) do
|
||||
{
|
||||
'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="false"; cursor="1572959139000:0:0"'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns only info about the previous page' do
|
||||
is_expected.to eq(
|
||||
'previous' => { 'cursor' => '1573556671000:0:1' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'migrate', '20191125114345_add_admin_mode_protected_path.rb')
|
||||
|
||||
describe AddAdminModeProtectedPath, :migration do
|
||||
ADMIN_MODE_ENDPOINT = '/admin/session'
|
||||
|
||||
subject(:migration) { described_class.new }
|
||||
|
||||
let(:application_settings) { table(:application_settings) }
|
||||
|
||||
context 'no settings available' do
|
||||
it 'makes no changes' do
|
||||
expect { migrate! }.not_to change { application_settings.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'protected_paths is null' do
|
||||
before do
|
||||
application_settings.create!(protected_paths: nil)
|
||||
end
|
||||
|
||||
it 'makes no changes' do
|
||||
expect { migrate! }.not_to change { application_settings.first.protected_paths }
|
||||
end
|
||||
end
|
||||
|
||||
it 'appends admin mode endpoint' do
|
||||
application_settings.create!(protected_paths: '{a,b,c}')
|
||||
|
||||
protected_paths_before = %w[a b c]
|
||||
protected_paths_after = protected_paths_before.dup << ADMIN_MODE_ENDPOINT
|
||||
|
||||
expect { migrate! }.to change { application_settings.first.protected_paths }.from(protected_paths_before).to(protected_paths_after)
|
||||
end
|
||||
|
||||
it 'new default includes admin mode endpoint' do
|
||||
settings_before = application_settings.create!
|
||||
|
||||
expect(settings_before.protected_paths).not_to include(ADMIN_MODE_ENDPOINT)
|
||||
|
||||
migrate!
|
||||
|
||||
application_settings.reset_column_information
|
||||
settings_after = application_settings.create!
|
||||
|
||||
expect(settings_after.protected_paths).to include(ADMIN_MODE_ENDPOINT)
|
||||
end
|
||||
end
|
|
@ -66,6 +66,8 @@ describe ApplicationSetting do
|
|||
it { is_expected.not_to allow_value('three').for(:push_event_activities_limit) }
|
||||
it { is_expected.not_to allow_value(nil).for(:push_event_activities_limit) }
|
||||
|
||||
it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) }
|
||||
|
||||
context 'when snowplow is enabled' do
|
||||
before do
|
||||
setting.snowplow_enabled = true
|
||||
|
|
|
@ -153,9 +153,9 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
|
|||
|
||||
it 'returns cached issues' do
|
||||
expect(sentry_client).to receive(:list_issues).with(opts)
|
||||
.and_return(issues)
|
||||
.and_return(issues: issues, pagination: {})
|
||||
|
||||
expect(result).to eq(issues: issues)
|
||||
expect(result).to eq(issues: issues, pagination: {})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe UnifyCircuitService do
|
||||
it_behaves_like "chat service", "Unify Circuit" do
|
||||
let(:client_arguments) { webhook_url }
|
||||
let(:content_key) { :subject }
|
||||
end
|
||||
end
|
|
@ -33,6 +33,7 @@ describe Project do
|
|||
it { is_expected.to have_one(:microsoft_teams_service) }
|
||||
it { is_expected.to have_one(:mattermost_service) }
|
||||
it { is_expected.to have_one(:hangouts_chat_service) }
|
||||
it { is_expected.to have_one(:unify_circuit_service) }
|
||||
it { is_expected.to have_one(:packagist_service) }
|
||||
it { is_expected.to have_one(:pushover_service) }
|
||||
it { is_expected.to have_one(:asana_service) }
|
||||
|
|
|
@ -31,6 +31,62 @@ describe Snippet do
|
|||
it { is_expected.to validate_presence_of(:content) }
|
||||
|
||||
it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) }
|
||||
|
||||
it do
|
||||
allow(Gitlab::CurrentSettings).to receive(:snippet_size_limit).and_return(1)
|
||||
|
||||
is_expected
|
||||
.to validate_length_of(:content)
|
||||
.is_at_most(Gitlab::CurrentSettings.snippet_size_limit)
|
||||
.with_message("is too long (2 Bytes). The maximum size is 1 Byte.")
|
||||
end
|
||||
|
||||
context 'content validations' do
|
||||
context 'with existing snippets' do
|
||||
let(:snippet) { create(:personal_snippet, content: 'This is a valid content at the time of creation') }
|
||||
|
||||
before do
|
||||
expect(snippet).to be_valid
|
||||
|
||||
stub_application_setting(snippet_size_limit: 2)
|
||||
end
|
||||
|
||||
it 'does not raise a validation error if the content is not changed' do
|
||||
snippet.title = 'new title'
|
||||
|
||||
expect(snippet).to be_valid
|
||||
end
|
||||
|
||||
it 'raises and error if the content is changed and the size is bigger than limit' do
|
||||
snippet.content = snippet.content + "test"
|
||||
|
||||
expect(snippet).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'with new snippets' do
|
||||
let(:limit) { 15 }
|
||||
|
||||
before do
|
||||
stub_application_setting(snippet_size_limit: limit)
|
||||
end
|
||||
|
||||
it 'is valid when content is smaller than the limit' do
|
||||
snippet = build(:personal_snippet, content: 'Valid Content')
|
||||
|
||||
expect(snippet).to be_valid
|
||||
end
|
||||
|
||||
it 'raises error when content is bigger than setting limit' do
|
||||
snippet = build(:personal_snippet, content: 'This is an invalid content')
|
||||
|
||||
aggregate_failures do
|
||||
expect(snippet).not_to be_valid
|
||||
expect(snippet.errors[:content]).to include("is too long (#{snippet.content.size} Bytes). The maximum size is #{limit} Bytes.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_reference' do
|
||||
|
|
|
@ -36,6 +36,7 @@ describe API::Settings, 'Settings' do
|
|||
expect(json_response['allow_local_requests_from_system_hooks']).to be(true)
|
||||
expect(json_response).not_to have_key('performance_bar_allowed_group_path')
|
||||
expect(json_response).not_to have_key('performance_bar_enabled')
|
||||
expect(json_response['snippet_size_limit']).to eq(50.megabytes)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -85,7 +86,8 @@ describe API::Settings, 'Settings' do
|
|||
allow_local_requests_from_web_hooks_and_services: true,
|
||||
allow_local_requests_from_system_hooks: false,
|
||||
push_event_hooks_limit: 2,
|
||||
push_event_activities_limit: 2
|
||||
push_event_activities_limit: 2,
|
||||
snippet_size_limit: 5
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
|
@ -121,6 +123,7 @@ describe API::Settings, 'Settings' do
|
|||
expect(json_response['allow_local_requests_from_system_hooks']).to eq(false)
|
||||
expect(json_response['push_event_hooks_limit']).to eq(2)
|
||||
expect(json_response['push_event_activities_limit']).to eq(2)
|
||||
expect(json_response['snippet_size_limit']).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,13 +5,14 @@ require 'spec_helper'
|
|||
describe ErrorTracking::ListIssuesService do
|
||||
set(:user) { create(:user) }
|
||||
set(:project) { create(:project) }
|
||||
let(:params) { { search_term: 'something', sort: 'last_seen' } }
|
||||
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
|
||||
let(:list_sentry_issues_args) do
|
||||
{
|
||||
issue_status: 'unresolved',
|
||||
limit: 20,
|
||||
search_term: params[:search_term],
|
||||
sort: params[:sort]
|
||||
search_term: 'something',
|
||||
sort: 'last_seen',
|
||||
cursor: 'some-cursor'
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -40,11 +41,11 @@ describe ErrorTracking::ListIssuesService do
|
|||
expect(error_tracking_setting)
|
||||
.to receive(:list_sentry_issues)
|
||||
.with(list_sentry_issues_args)
|
||||
.and_return(issues: issues)
|
||||
.and_return(issues: issues, pagination: {})
|
||||
end
|
||||
|
||||
it 'returns the issues' do
|
||||
expect(result).to eq(status: :success, issues: issues)
|
||||
expect(result).to eq(status: :success, pagination: {}, issues: issues)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ shared_examples_for "chat service" do |service_name|
|
|||
|
||||
it_behaves_like "triggered #{service_name} service"
|
||||
|
||||
it "specifies the webhook when it is configured" do
|
||||
it "specifies the webhook when it is configured", if: defined?(client) do
|
||||
expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object)
|
||||
|
||||
subject.execute(sample_data)
|
||||
|
|
Loading…
Reference in New Issue