Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
32e53ae7d7
commit
01d2d6c869
|
@ -247,10 +247,14 @@ danger-review:
|
|||
script:
|
||||
- >
|
||||
if [ -z "$DANGER_GITLAB_API_TOKEN" ]; then
|
||||
# Force danger to skip CI source GitLab and fallback to "local only git repo".
|
||||
unset GITLAB_CI
|
||||
# We need to base SHA to help danger determine the base commit for this shallow clone.
|
||||
run_timed_command "bundle exec danger dry_run --fail-on-errors=true --verbose --base='$CI_MERGE_REQUEST_DIFF_BASE_SHA'"
|
||||
run_timed_command danger_as_local
|
||||
else
|
||||
run_timed_command "bundle exec danger --fail-on-errors=true --verbose"
|
||||
fi
|
||||
|
||||
danger-review-local:
|
||||
extends:
|
||||
- danger-review
|
||||
- .review:rules:danger-local
|
||||
script:
|
||||
- run_timed_command danger_as_local
|
||||
|
|
|
@ -330,6 +330,12 @@
|
|||
- ".dockerignore"
|
||||
- "qa/**/*"
|
||||
|
||||
.code-backstage-danger-patterns: &code-backstage-danger-patterns
|
||||
# Backstage changes
|
||||
- "Dangerfile"
|
||||
- "danger/**/*"
|
||||
- "tooling/danger/**/*"
|
||||
|
||||
################
|
||||
# Shared rules #
|
||||
################
|
||||
|
@ -1284,6 +1290,11 @@
|
|||
rules:
|
||||
- if: '$CI_MERGE_REQUEST_IID'
|
||||
|
||||
.review:rules:danger-local:
|
||||
rules:
|
||||
- if: '$CI_MERGE_REQUEST_IID'
|
||||
changes: *code-backstage-danger-patterns
|
||||
|
||||
###############
|
||||
# Setup rules #
|
||||
###############
|
||||
|
|
|
@ -1 +1 @@
|
|||
14.0.1
|
||||
14.1.0
|
||||
|
|
|
@ -221,7 +221,7 @@ export default {
|
|||
}
|
||||
|
||||
if (this.visibilityLevel !== visibilityOptions.PUBLIC) {
|
||||
options.push([visibilityOptions.PUBLIC, PAGE_FEATURE_ACCESS_LEVEL]);
|
||||
options.push([30, PAGE_FEATURE_ACCESS_LEVEL]);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
|
|
|
@ -3,6 +3,35 @@ import $ from 'jquery';
|
|||
/**
|
||||
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
|
||||
* and controllable by a public API.
|
||||
*
|
||||
* This component has two intervals:
|
||||
*
|
||||
* - current interval - when the page is visible - defined by `startingInterval`, `maxInterval`, and `incrementByFactorOf`
|
||||
* - Example:
|
||||
* - `startingInterval: 10000`, `maxInterval: 240000`, `incrementByFactorOf: 2`
|
||||
* - results in `10s, 20s, 40s, 80s, ..., 240s`, it stops increasing at `240s` and keeps this interval indefinitely.
|
||||
* - hidden interval - when the page is not visible
|
||||
*
|
||||
* Visibility transitions:
|
||||
*
|
||||
* - `visible -> not visible`
|
||||
* - `document.addEventListener('visibilitychange', () => ...)`
|
||||
*
|
||||
* > This event fires with a visibilityState of hidden when a user navigates to a new page, switches tabs, closes the tab, minimizes or closes the browser, or, on mobile, switches from the browser to a different app.
|
||||
*
|
||||
* Source [Document: visibilitychange event - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event)
|
||||
*
|
||||
* - `window.addEventListener('blur', () => ...)` - every time user clicks somewhere else then in the browser page
|
||||
* - `not visible -> visible`
|
||||
* - `document.addEventListener('visibilitychange', () => ...)` same as the transition `visible -> not visible`
|
||||
* - `window.addEventListener('focus', () => ...)`
|
||||
*
|
||||
* The combination of these two listeners can result in an unexpected resumption of polling:
|
||||
*
|
||||
* - switch to a different window (causes `blur`)
|
||||
* - switch to a different desktop (causes `visibilitychange` (not visible))
|
||||
* - switch back to the original desktop (causes `visibilitychange` (visible))
|
||||
* - *now the polling happens even in window that user doesn't work in*
|
||||
*/
|
||||
|
||||
export default class SmartInterval {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
/* eslint-disable @gitlab/vue-require-i18n-strings */
|
||||
import { GlLoadingIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
|
||||
|
@ -8,7 +8,6 @@ import modalEventHub from '~/projects/commit/event_hub';
|
|||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetMerged',
|
||||
|
@ -17,7 +16,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
MrWidgetAuthorTime,
|
||||
statusIcon,
|
||||
GlIcon,
|
||||
ClipboardButton,
|
||||
GlLoadingIcon,
|
||||
GlButton,
|
||||
|
@ -116,7 +115,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="success" />
|
||||
<gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
|
||||
<div class="media-body">
|
||||
<div class="space-children">
|
||||
<mr-widget-author-time
|
||||
|
@ -131,7 +130,6 @@ export default {
|
|||
:title="revertTitle"
|
||||
size="small"
|
||||
category="secondary"
|
||||
variant="warning"
|
||||
data-qa-selector="revert_button"
|
||||
@click="openRevertModal"
|
||||
>
|
||||
|
@ -144,7 +142,6 @@ export default {
|
|||
:title="revertTitle"
|
||||
size="small"
|
||||
category="secondary"
|
||||
variant="warning"
|
||||
data-method="post"
|
||||
>
|
||||
{{ revertLabel }}
|
||||
|
@ -169,6 +166,15 @@ export default {
|
|||
>
|
||||
{{ cherryPickLabel }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-if="shouldShowRemoveSourceBranch"
|
||||
:disabled="isMakingRequest"
|
||||
size="small"
|
||||
class="js-remove-branch-button"
|
||||
@click="removeSourceBranch"
|
||||
>
|
||||
{{ s__('mrWidget|Delete source branch') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
<section class="mr-info-list" data-qa-selector="merged_status_content">
|
||||
<p>
|
||||
|
@ -196,17 +202,6 @@ export default {
|
|||
<p v-if="mr.sourceBranchRemoved">
|
||||
{{ s__('mrWidget|The source branch has been deleted') }}
|
||||
</p>
|
||||
<p v-if="shouldShowRemoveSourceBranch" class="space-children">
|
||||
<span>{{ s__('mrWidget|You can delete the source branch now') }}</span>
|
||||
<gl-button
|
||||
:disabled="isMakingRequest"
|
||||
size="small"
|
||||
class="js-remove-branch-button"
|
||||
@click="removeSourceBranch"
|
||||
>
|
||||
{{ s__('mrWidget|Delete source branch') }}
|
||||
</gl-button>
|
||||
</p>
|
||||
<p v-if="shouldShowSourceBranchRemoving">
|
||||
<gl-loading-icon size="sm" :inline="true" />
|
||||
<span> {{ s__('mrWidget|The source branch is being deleted') }} </span>
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
module Mailgun
|
||||
class PermanentFailuresController < ApplicationController
|
||||
respond_to :json
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
before_action :ensure_feature_enabled!
|
||||
before_action :authenticate_signature!
|
||||
before_action :validate_invite_email!
|
||||
|
||||
feature_category :authentication_and_authorization
|
||||
|
||||
def create
|
||||
webhook_processor.execute
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_feature_enabled!
|
||||
render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled?
|
||||
end
|
||||
|
||||
def authenticate_signature!
|
||||
access_denied! unless valid_signature?
|
||||
end
|
||||
|
||||
def valid_signature?
|
||||
return false if Gitlab::CurrentSettings.mailgun_signing_key.blank?
|
||||
|
||||
# per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
|
||||
digest = OpenSSL::Digest.new('SHA256')
|
||||
data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join
|
||||
|
||||
hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data)
|
||||
|
||||
ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest)
|
||||
end
|
||||
|
||||
def validate_invite_email!
|
||||
# permanent_failures webhook does not provide a way to filter failures, so we'll get them all on this endpoint
|
||||
# and we only care about our invite_emails
|
||||
render_406 unless payload[:tags]&.include?(::Members::Mailgun::INVITE_EMAIL_TAG)
|
||||
end
|
||||
|
||||
def webhook_processor
|
||||
::Members::Mailgun::ProcessWebhookService.new(payload)
|
||||
end
|
||||
|
||||
def payload
|
||||
@payload ||= params.permit!['event-data']
|
||||
end
|
||||
|
||||
def render_406
|
||||
# failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
|
||||
head :not_acceptable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,8 +25,6 @@ class ContainerRepositoriesFinder
|
|||
end
|
||||
|
||||
def project_repositories
|
||||
return unless @subject.container_registry_enabled
|
||||
|
||||
@subject.container_repositories
|
||||
end
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ module PackagesHelper
|
|||
def show_cleanup_policy_on_alert(project)
|
||||
Gitlab.com? &&
|
||||
Gitlab.config.registry.enabled &&
|
||||
project.container_registry_enabled &&
|
||||
project.feature_available?(:container_registry, current_user) &&
|
||||
!Gitlab::CurrentSettings.container_expiration_policies_enable_historic_entries &&
|
||||
Feature.enabled?(:container_expiration_policies_historic_entry, project) &&
|
||||
project.container_expiration_policy.nil? &&
|
||||
|
|
|
@ -26,6 +26,12 @@ module SidebarsHelper
|
|||
Sidebars::Projects::Context.new(**context_data)
|
||||
end
|
||||
|
||||
def group_sidebar_context(group, user)
|
||||
context_data = group_sidebar_context_data(group, user)
|
||||
|
||||
Sidebars::Groups::Context.new(**context_data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_attributes_for_object(object)
|
||||
|
@ -89,6 +95,13 @@ module SidebarsHelper
|
|||
show_cluster_hint: show_gke_cluster_integration_callout?(project)
|
||||
}
|
||||
end
|
||||
|
||||
def group_sidebar_context_data(group, user)
|
||||
{
|
||||
current_user: user,
|
||||
container: group
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
SidebarsHelper.prepend_mod_with('SidebarsHelper')
|
||||
|
|
|
@ -150,10 +150,10 @@ module Emails
|
|||
end
|
||||
|
||||
def invite_email_headers
|
||||
if Gitlab.dev_env_or_com?
|
||||
if Gitlab::CurrentSettings.mailgun_events_enabled?
|
||||
{
|
||||
'X-Mailgun-Tag' => 'invite_email',
|
||||
'X-Mailgun-Variables' => { 'invite_token' => @token }.to_json
|
||||
'X-Mailgun-Tag' => ::Members::Mailgun::INVITE_EMAIL_TAG,
|
||||
'X-Mailgun-Variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => @token }.to_json
|
||||
}
|
||||
else
|
||||
{}
|
||||
|
|
|
@ -37,12 +37,20 @@ module Ci
|
|||
next [] unless processable.pipeline_id # we don't have any dependency when creating the pipeline
|
||||
|
||||
deps = model_class.where(pipeline_id: processable.pipeline_id).latest
|
||||
deps = from_previous_stages(deps)
|
||||
deps = from_needs(deps)
|
||||
deps = find_dependencies(processable, deps)
|
||||
|
||||
from_dependencies(deps).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def find_dependencies(processable, deps)
|
||||
if processable.scheduling_type_dag?
|
||||
from_needs(deps)
|
||||
else
|
||||
from_previous_stages(deps)
|
||||
end
|
||||
end
|
||||
|
||||
# Dependencies from the same parent-pipeline hierarchy excluding
|
||||
# the current job's pipeline
|
||||
def cross_pipeline
|
||||
|
@ -125,8 +133,6 @@ module Ci
|
|||
end
|
||||
|
||||
def from_needs(scope)
|
||||
return scope unless processable.scheduling_type_dag?
|
||||
|
||||
needs_names = processable.needs.artifacts.select(:name)
|
||||
scope.where(name: needs_names)
|
||||
end
|
||||
|
|
|
@ -9,4 +9,15 @@ class ErrorTracking::Error < ApplicationRecord
|
|||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
validates :actor, presence: true
|
||||
|
||||
def self.report_error(name:, description:, actor:, platform:, timestamp:)
|
||||
safe_find_or_create_by(
|
||||
name: name,
|
||||
description: description,
|
||||
actor: actor,
|
||||
platform: platform
|
||||
) do |error|
|
||||
error.update!(last_seen_at: timestamp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -377,6 +377,8 @@ class Project < ApplicationRecord
|
|||
has_one :operations_feature_flags_client, class_name: 'Operations::FeatureFlagsClient'
|
||||
has_many :operations_feature_flags_user_lists, class_name: 'Operations::FeatureFlags::UserList'
|
||||
|
||||
has_many :error_tracking_errors, inverse_of: :project, class_name: 'ErrorTracking::Error'
|
||||
|
||||
has_many :timelogs
|
||||
|
||||
accepts_nested_attributes_for :variables, allow_destroy: true
|
||||
|
|
|
@ -10,8 +10,16 @@ module Ci
|
|||
private
|
||||
|
||||
def process_subsequent_jobs(processable)
|
||||
processable.pipeline.processables.skipped.after_stage(processable.stage_idx).find_each do |processable|
|
||||
process(processable)
|
||||
if Feature.enabled?(:ci_same_stage_job_needs, processable.project, default_enabled: :yaml)
|
||||
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
|
||||
.each do |processable|
|
||||
process(processable)
|
||||
end
|
||||
else
|
||||
skipped_jobs(processable).after_stage(processable.stage_idx)
|
||||
.find_each do |job|
|
||||
process(job)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -24,5 +32,17 @@ module Ci
|
|||
processable.process(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def skipped_jobs(processable)
|
||||
processable.pipeline.processables.skipped
|
||||
end
|
||||
|
||||
def stage_dependent_jobs(processable)
|
||||
skipped_jobs(processable).scheduling_type_stage.after_stage(processable.stage_idx)
|
||||
end
|
||||
|
||||
def needs_dependent_jobs(processable)
|
||||
skipped_jobs(processable).scheduling_type_dag.with_needs([processable.name])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ErrorTracking
|
||||
class CollectErrorService < ::BaseService
|
||||
def execute
|
||||
# Error is a way to group events based on common data like name or cause
|
||||
# of exception. We need to keep a sane balance here between taking too little
|
||||
# and too much data into group logic.
|
||||
error = project.error_tracking_errors.report_error(
|
||||
name: exception['type'], # Example: ActionView::MissingTemplate
|
||||
description: exception['value'], # Example: Missing template posts/show in...
|
||||
actor: event['transaction'], # Example: PostsController#show
|
||||
platform: event['platform'], # Example: ruby
|
||||
timestamp: event['timestamp']
|
||||
)
|
||||
|
||||
# The payload field contains all the data on error including stacktrace in jsonb.
|
||||
# Together with occured_at these are 2 main attributes that we need to save here.
|
||||
error.events.create!(
|
||||
environment: event['environment'],
|
||||
description: exception['type'],
|
||||
level: event['level'],
|
||||
occurred_at: event['timestamp'],
|
||||
payload: event
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def event
|
||||
params[:event]
|
||||
end
|
||||
|
||||
def exception
|
||||
event['exception']['values'].first
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
module Mailgun
|
||||
INVITE_EMAIL_TAG = 'invite_email'
|
||||
INVITE_EMAIL_TOKEN_KEY = :invite_token
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
module Mailgun
|
||||
class ProcessWebhookService
|
||||
ProcessWebhookServiceError = Class.new(StandardError)
|
||||
|
||||
def initialize(payload)
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
def execute
|
||||
@member = Member.find_by_invite_token(invite_token)
|
||||
update_member_and_log if member
|
||||
rescue ProcessWebhookServiceError => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :payload, :member
|
||||
|
||||
def update_member_and_log
|
||||
log_update_event if member.update(invite_email_success: false)
|
||||
end
|
||||
|
||||
def log_update_event
|
||||
Gitlab::AppLogger.info "UPDATED MEMBER INVITE_EMAIL_SUCCESS: member_id: #{member.id}"
|
||||
end
|
||||
|
||||
def invite_token
|
||||
# may want to validate schema in some way using ::JSONSchemer.schema(SCHEMA_PATH).valid?(message) if this
|
||||
# gets more complex
|
||||
payload.dig('user-variables', ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY) ||
|
||||
raise(ProcessWebhookServiceError, "Failed to receive #{::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY} in user-variables: #{payload}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,3 @@
|
|||
- return unless Feature.enabled?(:mailgun_events_receiver)
|
||||
|
||||
- expanded = integration_expanded?('mailgun_')
|
||||
%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- autodevops_help_url = help_page_path('topics/autodevops/index.md', anchor: 'using-multiple-kubernetes-clusters')
|
||||
- autodevops_help_url = help_page_path('topics/autodevops/index.md', anchor: 'use-multiple-kubernetes-clusters')
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
|
||||
- help_link_end = '</a>'.html_safe
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
= link_to _('Delete'), project, data: { confirm: remove_project_message(project) }, method: :delete, class: "btn gl-button btn-danger"
|
|
@ -0,0 +1,2 @@
|
|||
- if project.archived
|
||||
%span.badge.badge-warning.badge-pill.gl-badge.md= _('archived')
|
|
@ -15,13 +15,12 @@
|
|||
.controls
|
||||
= link_to _('Members'), project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn gl-button"
|
||||
= link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn gl-button"
|
||||
= link_to _('Delete'), project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn gl-button btn-danger"
|
||||
= render 'delete_project_button', project: project
|
||||
|
||||
.stats
|
||||
%span.badge.badge-pill
|
||||
= storage_counter(project.statistics&.storage_size)
|
||||
- if project.archived
|
||||
%span.badge.badge-warning archived
|
||||
= render 'project_badges', project: project
|
||||
|
||||
.title
|
||||
= link_to(project_path(project)) do
|
||||
|
|
|
@ -1,176 +1,3 @@
|
|||
- issues_count = cached_issuables_count(@group, type: :issues)
|
||||
- merge_requests_count = cached_issuables_count(@group, type: :merge_requests)
|
||||
- aside_title = @group.subgroup? ? _('Subgroup navigation') : _('Group navigation')
|
||||
|
||||
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(@group), 'aria-label': aside_title }
|
||||
.nav-sidebar-inner-scroll
|
||||
%ul.sidebar-top-level-items.qa-group-sidebar
|
||||
= nav_link(path: ['groups#show', 'groups#details'], html_options: { class: 'context-header' }) do
|
||||
= link_to group_path(@group), title: @group.name, data: { qa_selector: 'group_scope_link' } do
|
||||
%span{ class: ['avatar-container', 'rect-avatar', 'group-avatar' , 's32'] }
|
||||
= group_icon(@group, class: ['avatar', 'avatar-tile', 's32'])
|
||||
%span.sidebar-context-title
|
||||
= @group.name
|
||||
= render_if_exists 'layouts/nav/sidebar/group_trial_status_widget', group: @group
|
||||
|
||||
- if group_sidebar_link?(:overview)
|
||||
- paths = group_overview_nav_link_paths
|
||||
= nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do
|
||||
= link_to activity_group_path(@group), class: 'has-sub-items', data: { qa_selector: 'group_information_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('group')
|
||||
%span.nav-item-name
|
||||
= group_information_title(@group)
|
||||
|
||||
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_information_submenu'} }
|
||||
= nav_link(path: paths, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to activity_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= group_information_title(@group)
|
||||
%li.divider.fly-out-top-item
|
||||
|
||||
- if group_sidebar_link?(:activity)
|
||||
= nav_link(path: 'groups#activity') do
|
||||
= link_to activity_group_path(@group), title: _('Activity') do
|
||||
%span
|
||||
= _('Activity')
|
||||
|
||||
- if group_sidebar_link?(:labels)
|
||||
= nav_link(path: 'labels#index') do
|
||||
= link_to group_labels_path(@group), title: _('Labels') do
|
||||
%span
|
||||
= _('Labels')
|
||||
|
||||
- if group_sidebar_link?(:group_members)
|
||||
= nav_link(path: 'group_members#index') do
|
||||
= link_to group_group_members_path(@group), title: _('Members'), data: { qa_selector: 'group_members_item' } do
|
||||
%span
|
||||
= _('Members')
|
||||
|
||||
= render_if_exists "layouts/nav/ee/epic_link", group: @group
|
||||
|
||||
- if group_sidebar_link?(:issues)
|
||||
= nav_link(path: group_issues_sub_menu_items, unless: -> { current_path?('issues_analytics#show') }) do
|
||||
= link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' }, class: 'has-sub-items' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('issues')
|
||||
%span.nav-item-name
|
||||
= _('Issues')
|
||||
%span.badge.badge-pill.count= issues_count
|
||||
|
||||
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
|
||||
= nav_link(path: group_issues_sub_menu_items, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to issues_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Issues')
|
||||
%span.badge.badge-pill.count.issue_counter.fly-out-badge= issues_count
|
||||
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
|
||||
= link_to issues_group_path(@group), title: _('List') do
|
||||
%span
|
||||
= _('List')
|
||||
|
||||
- if group_sidebar_link?(:boards)
|
||||
= nav_link(path: ['boards#index', 'boards#show']) do
|
||||
= link_to group_boards_path(@group), title: boards_link_text, data: { qa_selector: 'group_issue_boards_link' } do
|
||||
%span
|
||||
= boards_link_text
|
||||
|
||||
- if group_sidebar_link?(:milestones)
|
||||
= nav_link(path: 'milestones#index') do
|
||||
= link_to group_milestones_path(@group), title: _('Milestones'), data: { qa_selector: 'group_milestones_link' } do
|
||||
%span
|
||||
= _('Milestones')
|
||||
|
||||
= render_if_exists 'layouts/nav/sidebar/group_iterations_link'
|
||||
|
||||
- if group_sidebar_link?(:merge_requests)
|
||||
= nav_link(path: 'groups#merge_requests') do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('git-merge')
|
||||
%span.nav-item-name
|
||||
= _('Merge requests')
|
||||
%span.badge.badge-pill.count= merge_requests_count
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Merge requests')
|
||||
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= merge_requests_count
|
||||
|
||||
= render_if_exists "layouts/nav/ee/security_link" # EE-specific
|
||||
|
||||
= render_if_exists "layouts/nav/ee/push_rules_link" # EE-specific
|
||||
|
||||
- if group_sidebar_link?(:kubernetes)
|
||||
= nav_link(controller: [:clusters]) do
|
||||
= link_to group_clusters_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('cloud-gear')
|
||||
%span.nav-item-name
|
||||
= _('Kubernetes')
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: [:clusters], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Kubernetes')
|
||||
|
||||
= render 'groups/sidebar/packages'
|
||||
|
||||
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
|
||||
|
||||
- if group_sidebar_link?(:wiki)
|
||||
= render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url
|
||||
|
||||
- if group_sidebar_link?(:settings)
|
||||
= nav_link(path: group_settings_nav_link_paths) do
|
||||
= link_to edit_group_path(@group), class: 'has-sub-items' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('settings')
|
||||
%span.nav-item-name{ data: { qa_selector: 'group_settings' } }
|
||||
= _('Settings')
|
||||
%ul.sidebar-sub-level-items.qa-group-sidebar-submenu{ data: { testid: 'group-settings-menu' } }
|
||||
= nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show groups/applications#index], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to edit_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Settings')
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#edit') do
|
||||
= link_to edit_group_path(@group), title: _('General'), data: { qa_selector: 'general_settings_link' } do
|
||||
%span
|
||||
= _('General')
|
||||
|
||||
= nav_link(controller: :integrations) do
|
||||
= link_to group_settings_integrations_path(@group), title: _('Integrations') do
|
||||
%span
|
||||
= _('Integrations')
|
||||
|
||||
= nav_link(path: 'groups#projects') do
|
||||
= link_to projects_group_path(@group), title: _('Projects') do
|
||||
%span
|
||||
= _('Projects')
|
||||
|
||||
= nav_link(controller: :repository) do
|
||||
= link_to group_settings_repository_path(@group), title: _('Repository') do
|
||||
%span
|
||||
= _('Repository')
|
||||
|
||||
= nav_link(controller: [:ci_cd, 'groups/runners']) do
|
||||
= link_to group_settings_ci_cd_path(@group), title: _('CI/CD') do
|
||||
%span
|
||||
= _('CI/CD')
|
||||
|
||||
= nav_link(controller: :applications) do
|
||||
= link_to group_settings_applications_path(@group), title: _('Applications') do
|
||||
%span
|
||||
= _('Applications')
|
||||
|
||||
= render 'groups/sidebar/packages_settings'
|
||||
|
||||
= render_if_exists "groups/ee/settings_nav"
|
||||
|
||||
= render_if_exists "groups/ee/administration_nav"
|
||||
|
||||
= render 'shared/sidebar_toggle_button'
|
||||
-# We're migration the group sidebar to a logical model based structure. If you need to update
|
||||
-# any of the existing menus, you can find them in app/views/layouts/nav/sidebar/_group_menus.html.haml.
|
||||
= render partial: 'shared/nav/sidebar', object: Sidebars::Groups::Panel.new(group_sidebar_context(@group, current_user))
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
- issues_count = cached_issuables_count(@group, type: :issues)
|
||||
- merge_requests_count = cached_issuables_count(@group, type: :merge_requests)
|
||||
|
||||
= render_if_exists 'layouts/nav/sidebar/group_trial_status_widget', group: @group
|
||||
|
||||
- if group_sidebar_link?(:overview)
|
||||
- paths = group_overview_nav_link_paths
|
||||
= nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do
|
||||
= link_to activity_group_path(@group), class: 'has-sub-items', data: { qa_selector: 'group_information_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('group')
|
||||
%span.nav-item-name
|
||||
= group_information_title(@group)
|
||||
|
||||
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_information_submenu'} }
|
||||
= nav_link(path: paths, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to activity_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= group_information_title(@group)
|
||||
%li.divider.fly-out-top-item
|
||||
|
||||
- if group_sidebar_link?(:activity)
|
||||
= nav_link(path: 'groups#activity') do
|
||||
= link_to activity_group_path(@group), title: _('Activity') do
|
||||
%span
|
||||
= _('Activity')
|
||||
|
||||
- if group_sidebar_link?(:labels)
|
||||
= nav_link(path: 'labels#index') do
|
||||
= link_to group_labels_path(@group), title: _('Labels') do
|
||||
%span
|
||||
= _('Labels')
|
||||
|
||||
- if group_sidebar_link?(:group_members)
|
||||
= nav_link(path: 'group_members#index') do
|
||||
= link_to group_group_members_path(@group), title: _('Members'), data: { qa_selector: 'group_members_item' } do
|
||||
%span
|
||||
= _('Members')
|
||||
|
||||
= render_if_exists "layouts/nav/ee/epic_link", group: @group
|
||||
|
||||
- if group_sidebar_link?(:issues)
|
||||
= nav_link(path: group_issues_sub_menu_items, unless: -> { current_path?('issues_analytics#show') }) do
|
||||
= link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' }, class: 'has-sub-items' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('issues')
|
||||
%span.nav-item-name
|
||||
= _('Issues')
|
||||
%span.badge.badge-pill.count= issues_count
|
||||
|
||||
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
|
||||
= nav_link(path: group_issues_sub_menu_items, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to issues_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Issues')
|
||||
%span.badge.badge-pill.count.issue_counter.fly-out-badge= issues_count
|
||||
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
|
||||
= link_to issues_group_path(@group), title: _('List') do
|
||||
%span
|
||||
= _('List')
|
||||
|
||||
- if group_sidebar_link?(:boards)
|
||||
= nav_link(path: ['boards#index', 'boards#show']) do
|
||||
= link_to group_boards_path(@group), title: boards_link_text, data: { qa_selector: 'group_issue_boards_link' } do
|
||||
%span
|
||||
= boards_link_text
|
||||
|
||||
- if group_sidebar_link?(:milestones)
|
||||
= nav_link(path: 'milestones#index') do
|
||||
= link_to group_milestones_path(@group), title: _('Milestones'), data: { qa_selector: 'group_milestones_link' } do
|
||||
%span
|
||||
= _('Milestones')
|
||||
|
||||
= render_if_exists 'layouts/nav/sidebar/group_iterations_link'
|
||||
|
||||
- if group_sidebar_link?(:merge_requests)
|
||||
= nav_link(path: 'groups#merge_requests') do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('git-merge')
|
||||
%span.nav-item-name
|
||||
= _('Merge requests')
|
||||
%span.badge.badge-pill.count= merge_requests_count
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Merge requests')
|
||||
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= merge_requests_count
|
||||
|
||||
= render_if_exists "layouts/nav/ee/security_link" # EE-specific
|
||||
|
||||
= render_if_exists "layouts/nav/ee/push_rules_link" # EE-specific
|
||||
|
||||
- if group_sidebar_link?(:kubernetes)
|
||||
= nav_link(controller: [:clusters]) do
|
||||
= link_to group_clusters_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('cloud-gear')
|
||||
%span.nav-item-name
|
||||
= _('Kubernetes')
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: [:clusters], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Kubernetes')
|
||||
|
||||
= render 'groups/sidebar/packages'
|
||||
|
||||
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
|
||||
|
||||
- if group_sidebar_link?(:wiki)
|
||||
= render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url
|
||||
|
||||
- if group_sidebar_link?(:settings)
|
||||
= nav_link(path: group_settings_nav_link_paths) do
|
||||
= link_to edit_group_path(@group), class: 'has-sub-items' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('settings')
|
||||
%span.nav-item-name{ data: { qa_selector: 'group_settings' } }
|
||||
= _('Settings')
|
||||
%ul.sidebar-sub-level-items{ data: { testid: 'group-settings-menu', qa_selector: 'group_sidebar_submenu' } }
|
||||
= nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show groups/applications#index], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to edit_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Settings')
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#edit') do
|
||||
= link_to edit_group_path(@group), title: _('General'), data: { qa_selector: 'general_settings_link' } do
|
||||
%span
|
||||
= _('General')
|
||||
|
||||
= nav_link(controller: :integrations) do
|
||||
= link_to group_settings_integrations_path(@group), title: _('Integrations') do
|
||||
%span
|
||||
= _('Integrations')
|
||||
|
||||
= nav_link(path: 'groups#projects') do
|
||||
= link_to projects_group_path(@group), title: _('Projects') do
|
||||
%span
|
||||
= _('Projects')
|
||||
|
||||
= nav_link(controller: :repository) do
|
||||
= link_to group_settings_repository_path(@group), title: _('Repository') do
|
||||
%span
|
||||
= _('Repository')
|
||||
|
||||
= nav_link(controller: [:ci_cd, 'groups/runners']) do
|
||||
= link_to group_settings_ci_cd_path(@group), title: _('CI/CD') do
|
||||
%span
|
||||
= _('CI/CD')
|
||||
|
||||
= nav_link(controller: :applications) do
|
||||
= link_to group_settings_applications_path(@group), title: _('Applications') do
|
||||
%span
|
||||
= _('Applications')
|
||||
|
||||
= render 'groups/sidebar/packages_settings'
|
||||
|
||||
= render_if_exists "groups/ee/settings_nav"
|
||||
|
||||
= render_if_exists "groups/ee/administration_nav"
|
||||
|
||||
= render 'shared/sidebar_toggle_button'
|
|
@ -0,0 +1,6 @@
|
|||
= nav_link(path: ['groups#show', 'groups#details'], html_options: { class: 'context-header' }) do
|
||||
= link_to group_path(@group), title: @group.name, data: { qa_selector: 'group_scope_link' } do
|
||||
%span{ class: ['avatar-container', 'rect-avatar', 'group-avatar' , 's32'] }
|
||||
= group_icon(@group, class: ['avatar', 'avatar-tile', 's32'])
|
||||
%span.sidebar-context-title
|
||||
= @group.name
|
|
@ -1,13 +1,14 @@
|
|||
%aside.nav-sidebar{ class: ('sidebar-collapsed-desktop' if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(sidebar.container), 'aria-label': sidebar.aria_label }
|
||||
.nav-sidebar-inner-scroll
|
||||
- if sidebar.render_raw_scope_menu_partial
|
||||
= render sidebar.render_raw_scope_menu_partial
|
||||
|
||||
%ul.sidebar-top-level-items{ data: { qa_selector: sidebar_qa_selector(sidebar.container) } }
|
||||
- if sidebar.scope_menu
|
||||
- if sidebar.render_raw_scope_menu_partial
|
||||
= render sidebar.render_raw_scope_menu_partial
|
||||
- elsif sidebar.scope_menu
|
||||
= render partial: 'shared/nav/scope_menu', object: sidebar.scope_menu
|
||||
|
||||
- if sidebar.renderable_menus.any?
|
||||
= render partial: 'shared/nav/sidebar_menu', collection: sidebar.renderable_menus
|
||||
|
||||
- if sidebar.render_raw_menus_partial
|
||||
= render sidebar.render_raw_menus_partial
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ci_same_stage_job_needs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59668
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/328253
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: integrated_error_tracking
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65767
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335846
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::monitor
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: load_balancing_for_update_all_mirrors_worker
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64526
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334162
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: mailgun_events_receiver
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64249
|
||||
rollout_issue_url:
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::expansion
|
||||
default_enabled: false
|
|
@ -222,6 +222,7 @@ Rails.application.routes.draw do
|
|||
|
||||
draw :snippets
|
||||
draw :profile
|
||||
draw :members
|
||||
|
||||
# Product analytics collector
|
||||
match '/collector/i', to: ProductAnalytics::CollectorApp.new, via: :all
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :members do
|
||||
namespace :mailgun do
|
||||
resources :permanent_failures, only: [:create]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddInviteEmailSuccessToMembers < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :members, :invite_email_success, :boolean, null: false, default: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDevopsAdoptionVulnerabilityManagementUsedCount < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :analytics_devops_adoption_snapshots, :vulnerability_management_used_count, :integer
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddVulnerabilitiesCreatedAtIndex < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'idx_vulnerabilities_partial_devops_adoption'
|
||||
|
||||
def up
|
||||
add_concurrent_index :vulnerabilities, [:project_id, :created_at], where: 'state != 1', name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
8d1777941e1a4b5f9f8f5f5e3ae416d6d02aaee1174eff1f9b4b38a6cdf0103a
|
|
@ -0,0 +1 @@
|
|||
d7f8f7f5d8a6cf03d500825ef43234c69f7ad36908c0bade337591b05985c2fe
|
|
@ -0,0 +1 @@
|
|||
699ac7f8b9253920271686c497b57521bf4b0d26c802ca2a57447e4929cd147f
|
|
@ -9139,6 +9139,7 @@ CREATE TABLE analytics_devops_adoption_snapshots (
|
|||
dast_enabled_count integer,
|
||||
dependency_scanning_enabled_count integer,
|
||||
coverage_fuzzing_enabled_count integer,
|
||||
vulnerability_management_used_count integer,
|
||||
CONSTRAINT check_3f472de131 CHECK ((namespace_id IS NOT NULL))
|
||||
);
|
||||
|
||||
|
@ -14661,7 +14662,8 @@ CREATE TABLE members (
|
|||
requested_at timestamp without time zone,
|
||||
expires_at date,
|
||||
ldap boolean DEFAULT false NOT NULL,
|
||||
override boolean DEFAULT false NOT NULL
|
||||
override boolean DEFAULT false NOT NULL,
|
||||
invite_email_success boolean DEFAULT true NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE members_id_seq
|
||||
|
@ -22717,6 +22719,8 @@ CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON v
|
|||
|
||||
CREATE UNIQUE INDEX idx_vuln_signatures_uniqueness_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, algorithm_type, signature_sha);
|
||||
|
||||
CREATE INDEX idx_vulnerabilities_partial_devops_adoption ON vulnerabilities USING btree (project_id, created_at) WHERE (state <> 1);
|
||||
|
||||
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue ON vulnerability_external_issue_links USING btree (vulnerability_id, external_type, external_project_key, external_issue_key);
|
||||
|
||||
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_link_type ON vulnerability_external_issue_links USING btree (vulnerability_id, link_type) WHERE (link_type = 1);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
stage: Growth
|
||||
group: Expansion
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Mailgun and GitLab **(FREE SELF)**
|
||||
|
||||
When you use Mailgun to send emails for your GitLab instance and [Mailgun](https://www.mailgun.com/)
|
||||
integration is enabled and configured in GitLab, you can receive their webhook for
|
||||
permanent invite email failures. To set up the integration, you must:
|
||||
|
||||
1. [Configure your Mailgun domain](#configure-your-mailgun-domain).
|
||||
1. [Enable Mailgun integration](#enable-mailgun-integration).
|
||||
|
||||
After completing the integration, Mailgun `permanent_failure` webhooks are sent to your GitLab instance.
|
||||
|
||||
## Configure your Mailgun domain
|
||||
|
||||
Before you can enable Mailgun in GitLab, set up your own Mailgun permanent failure endpoint to receive the webhooks.
|
||||
|
||||
Using the [Mailgun webhook guide](https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks/):
|
||||
|
||||
1. Add a webhook with the **Event type** set to **Permanent Failure**.
|
||||
1. Fill in the URL of your instance and include the `/-/members/mailgun/permanent_failures` path.
|
||||
- Example: `https://myinstance.gitlab.com/-/members/mailgun/permanent_failures`
|
||||
|
||||
## Enable Mailgun integration
|
||||
|
||||
After configuring your Mailgun domain for the permanent failures endpoint,
|
||||
you're ready to enable the Mailgun integration:
|
||||
|
||||
1. Sign in to GitLab as an [Administrator](../../user/permissions.md) user.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, go to **Settings > General** and expand the **Mailgun** section.
|
||||
1. Select the **Enable Mailgun** check box.
|
||||
1. Enter the Mailgun HTTP webhook signing key as described in
|
||||
[the Mailgun documentation](https://documentation.mailgun.com/en/latest/user_manual.html#webhooks) and
|
||||
shown in the [API security](https://app.mailgun.com/app/account/security/api_keys) section for your Mailgun account.
|
||||
1. Select **Save changes**.
|
|
@ -8555,6 +8555,7 @@ Snapshot.
|
|||
| <a id="devopsadoptionsnapshotsecurityscansucceeded"></a>`securityScanSucceeded` | [`Boolean!`](#boolean) | At least one security scan succeeded. |
|
||||
| <a id="devopsadoptionsnapshotstarttime"></a>`startTime` | [`Time!`](#time) | The start time for the snapshot where the data points were collected. |
|
||||
| <a id="devopsadoptionsnapshottotalprojectscount"></a>`totalProjectsCount` | [`Int`](#int) | Total number of projects. |
|
||||
| <a id="devopsadoptionsnapshotvulnerabilitymanagementusedcount"></a>`vulnerabilityManagementUsedCount` | [`Int`](#int) | Total number of projects with vulnerability management used at least once. |
|
||||
|
||||
### `DiffPosition`
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.|
|
||||
| `keep_latest_artifact` | boolean | no | Prevent the deletion of the artifacts from the most recent successful jobs, regardless of the expiry time. Enabled by default. |
|
||||
| `local_markdown_version` | integer | no | Increase this value when any cached Markdown should be invalidated. |
|
||||
| `mailgun_signing_key` | string | no | The Mailgun HTTP webhook signing key for receiving events from webhook |
|
||||
| `mailgun_signing_key` | string | no | The Mailgun HTTP webhook signing key for receiving events from webhook. |
|
||||
| `mailgun_events_enabled` | boolean | no | Enable Mailgun event receiver. |
|
||||
| `maintenance_mode_message` | string | no | **(PREMIUM)** Message displayed when instance is in maintenance mode. |
|
||||
| `maintenance_mode` | boolean | no | **(PREMIUM)** When instance is in maintenance mode, non-administrative users can sign in with read-only access and make read-only API requests. |
|
||||
|
|
|
@ -1563,6 +1563,14 @@ production:
|
|||
|
||||
#### Requirements and limitations
|
||||
|
||||
- In [GitLab 14.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632)
|
||||
you can refer to jobs in the same stage as the job you are configuring. This feature
|
||||
is [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
- Disabled on GitLab.com.
|
||||
- Not recommended for production use.
|
||||
- For GitLab self-managed instances, GitLab adminsitrators
|
||||
can choose to [disable it](#enable-or-disable-needs-for-jobs-in-the-same-stage)
|
||||
- In GitLab 14.0 and older, you can only refer to jobs in earlier stages.
|
||||
- In GitLab 13.9 and older, if `needs:` refers to a job that might not be added to
|
||||
a pipeline because of `only`, `except`, or `rules`, the pipeline might fail to create.
|
||||
- The maximum number of jobs that a single job can need in the `needs:` array is limited:
|
||||
|
@ -1579,6 +1587,22 @@ production:
|
|||
- Stages must be explicitly defined for all jobs
|
||||
that have the keyword `needs:` or are referred to by one.
|
||||
|
||||
##### Enable or disable `needs` for jobs in the same stage **(FREE SELF)**
|
||||
|
||||
`needs` for jobs in the same stage is under development but ready for production use.
|
||||
It is deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails
|
||||
console](../../administration/feature_flags.md)
|
||||
can opt to disable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
`Feature.enable(:ci_same_stage_job_needs)`
|
||||
|
||||
To disable it:
|
||||
|
||||
`Feature.disable(:ci_same_stage_job_needs)`
|
||||
|
||||
##### Changing the `needs:` job limit **(FREE SELF)**
|
||||
|
||||
The maximum number of jobs that can be defined in `needs:` defaults to 50.
|
||||
|
|
|
@ -26,6 +26,11 @@ and the advantage of the [special searches](../user/search/advanced_search.md).
|
|||
| GitLab Enterprise Edition 9.0 through 11.4 | Elasticsearch 5.1 through 5.5 |
|
||||
| GitLab Enterprise Edition 8.4 through 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
|
||||
|
||||
The Elasticsearch Integration is designed to work with supported versions of
|
||||
Elasticsearch and follows Elasticsearch's [End of Life Policy](https://www.elastic.co/support/eol).
|
||||
When we change Elasticsearch supported versions in GitLab, we announce them in [deprecation notes](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations) in monthly release posts
|
||||
before the actual removal.
|
||||
|
||||
## System requirements
|
||||
|
||||
Elasticsearch requires additional resources in excess of those documented in the
|
||||
|
|
|
@ -9,9 +9,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - Introduced in GitLab 11.0 for general availability.
|
||||
|
||||
GitLab Auto DevOps helps to reduce the complexity of software delivery by
|
||||
setting up pipelines and integrations for you. Instead of requiring you to
|
||||
manually configure your entire GitLab environment, Auto DevOps configures
|
||||
many of these areas for you, including security auditing and vulnerability
|
||||
setting up pipelines and integrations for you. Auto DevOps configures
|
||||
GitLab CI/CD pipelines including security auditing and vulnerability
|
||||
testing.
|
||||
|
||||
Using Auto DevOps, you can:
|
||||
|
@ -54,17 +53,17 @@ following levels:
|
|||
| GitLab SaaS | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| GitLab self-managed | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
|
||||
When you enable AutoDevOps for your instance, it attempts to run on all
|
||||
pipelines in each project, but will automatically disable itself for individual
|
||||
When you enable Auto DevOps for your instance, it attempts to run on all
|
||||
pipelines in each project. The Auto DevOps setting automatically disables itself for individual
|
||||
projects on their first pipeline failure. An instance administrator can enable
|
||||
or disable this default in the [Auto DevOps settings](../../user/admin_area/settings/continuous_integration.md#auto-devops).
|
||||
|
||||
Since [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/issues/26655),
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26655) in GitLab 12.7,
|
||||
Auto DevOps runs on pipelines automatically only if a [`Dockerfile` or matching buildpack](stages.md#auto-build)
|
||||
exists.
|
||||
|
||||
If a [CI/CD configuration file](../../ci/yaml/index.md) is present in the
|
||||
project, it isn't changed and won't be affected by Auto DevOps.
|
||||
project, it remains unchanged and Auto DevOps doesn't affect it.
|
||||
|
||||
### At the project level
|
||||
|
||||
|
@ -88,9 +87,8 @@ After enabling the feature, an Auto DevOps pipeline is triggered on the default
|
|||
|
||||
Only administrators and group owners can enable or disable Auto DevOps at the group level.
|
||||
|
||||
When enabling or disabling Auto DevOps at group level, group configuration is
|
||||
implicitly used for the subgroups and projects inside that group, unless Auto DevOps
|
||||
is specifically enabled or disabled on the subgroup or project.
|
||||
When you enable Auto DevOps at group level, the subgroups and projects in that group inherit the configuration. Auto DevOps
|
||||
can be specifically enabled or disabled individually for projects and subgroups.
|
||||
|
||||
To enable or disable Auto DevOps at the group level:
|
||||
|
||||
|
@ -138,12 +136,12 @@ to minimize downtime and risk.
|
|||
|
||||
## Quick start
|
||||
|
||||
If you're using GitLab.com, see the [quick start guide](quick_start_guide.md)
|
||||
for setting up Auto DevOps with GitLab.com and a Kubernetes cluster on Google Kubernetes
|
||||
For GitLab.com users, see the [quick start guide](quick_start_guide.md)
|
||||
for setting up Auto DevOps deploying to a Kubernetes cluster on Google Kubernetes
|
||||
Engine (GKE).
|
||||
|
||||
If you use a self-managed instance of GitLab, you must configure the
|
||||
[Google OAuth2 OmniAuth Provider](../../integration/google.md) before
|
||||
[Google OAuth 2.0 OmniAuth Provider](../../integration/google.md) before
|
||||
configuring a cluster on GKE. After configuring the provider, you can follow
|
||||
the steps in the [quick start guide](quick_start_guide.md) to get started.
|
||||
|
||||
|
@ -174,7 +172,7 @@ NOTE:
|
|||
Depending on your target platform, some features might not be available to you.
|
||||
|
||||
Comprised of a set of [stages](stages.md), Auto DevOps brings these best practices to your
|
||||
project in a simple and automatic way:
|
||||
project automatically:
|
||||
|
||||
- [Auto Browser Performance Testing](stages.md#auto-browser-performance-testing)
|
||||
- [Auto Build](stages.md#auto-build)
|
||||
|
@ -233,8 +231,7 @@ any of the following places:
|
|||
|
||||
The base domain variable `KUBE_INGRESS_BASE_DOMAIN` follows the same order of precedence
|
||||
as other environment [variables](../../ci/variables/index.md#cicd-variable-precedence).
|
||||
If the CI/CD variable is not set and the cluster setting is left blank, the instance-wide **Auto DevOps domain**
|
||||
setting is used if set.
|
||||
If this variable isn't set and the cluster setting is left blank, the instance-wide domain is used if set for your instance.
|
||||
|
||||
Auto DevOps requires a wildcard DNS A record matching the base domain(s). For
|
||||
a base domain of `example.com`, you'd need a DNS entry like:
|
||||
|
@ -259,14 +256,14 @@ to the Kubernetes pods running your application.
|
|||
|
||||
See [Auto DevOps requirements for Amazon ECS](requirements.md#auto-devops-requirements-for-amazon-ecs).
|
||||
|
||||
## Using multiple Kubernetes clusters
|
||||
## Use multiple Kubernetes clusters
|
||||
|
||||
When using Auto DevOps, you can deploy different environments to
|
||||
different Kubernetes clusters, due to the 1:1 connection
|
||||
[existing between them](../../user/project/clusters/multiple_kubernetes_clusters.md).
|
||||
|
||||
The [Deploy Job template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml)
|
||||
used by Auto DevOps currently defines 3 environment names:
|
||||
used by Auto DevOps defines 3 environment names:
|
||||
|
||||
- `review/` (every environment starting with `review/`)
|
||||
- `staging`
|
||||
|
@ -297,8 +294,8 @@ To add a different cluster for each environment:
|
|||
1. Navigate to each cluster's page, through **Infrastructure > Kubernetes clusters**,
|
||||
and add the domain based on its Ingress IP address.
|
||||
|
||||
After completing configuration, you can test your setup by creating a merge request
|
||||
and verifying your application is deployed as a Review App in the Kubernetes
|
||||
After completing configuration, test your setup by creating a merge request.
|
||||
Verify whether your application deployed as a Review App in the Kubernetes
|
||||
cluster with the `review/*` environment scope. Similarly, you can check the
|
||||
other environments.
|
||||
|
||||
|
@ -338,5 +335,23 @@ spec:
|
|||
value: "PUT_YOUR_HTTPS_PROXY_HERE"
|
||||
```
|
||||
|
||||
## Upgrade Auto DevOps dependencies when updating GitLab
|
||||
|
||||
When updating GitLab, you may need to upgrade Auto DevOps dependencies to
|
||||
match your new GitLab version:
|
||||
|
||||
- [Upgrading Auto DevOps resources](upgrading_auto_deploy_dependencies.md):
|
||||
- Auto DevOps template.
|
||||
- Auto Deploy template.
|
||||
- Auto Deploy image.
|
||||
- Helm.
|
||||
- Kubernetes.
|
||||
- Environment variables.
|
||||
- [Upgrading PostgreSQL](upgrading_postgresql.md).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See [troubleshooting Auto DevOps](troubleshooting.md).
|
||||
|
||||
<!-- DO NOT ADD TROUBLESHOOTING INFO HERE -->
|
||||
<!-- Troubleshooting information has moved to troubleshooting.md -->
|
||||
|
|
|
@ -314,7 +314,7 @@ all in GitLab. Despite its automatic nature, Auto DevOps can also be configured
|
|||
and customized to fit your workflow. Here are some helpful resources for further reading:
|
||||
|
||||
1. [Auto DevOps](index.md)
|
||||
1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters)
|
||||
1. [Multiple Kubernetes clusters](index.md#use-multiple-kubernetes-clusters)
|
||||
1. [Incremental rollout to production](customize.md#incremental-rollout-to-production) **(PREMIUM)**
|
||||
1. [Disable jobs you don't need with CI/CD variables](customize.md#cicd-variables)
|
||||
1. [Use your own buildpacks to build your application](customize.md#custom-buildpacks)
|
||||
|
|
|
@ -39,6 +39,7 @@ To access the default page for Admin Area settings:
|
|||
| ------ | ----------- |
|
||||
| [Elasticsearch](../../../integration/elasticsearch.md#enabling-advanced-search) | Elasticsearch integration. Elasticsearch AWS IAM. |
|
||||
| [Kroki](../../../administration/integration/kroki.md#enable-kroki-in-gitlab) | Allow rendering of diagrams in AsciiDoc and Markdown documents using [kroki.io](https://kroki.io). |
|
||||
| [Mailgun](../../../administration/integration/mailgun.md) | Enable your GitLab instance to receive invite email bounce events from Mailgun, if it is your email provider. |
|
||||
| [PlantUML](../../../administration/integration/plantuml.md) | Allow rendering of PlantUML diagrams in documents. |
|
||||
| [Slack application](../../../user/project/integrations/gitlab_slack_application.md#configuration) **(FREE SAAS)** | Slack integration allows you to interact with GitLab via slash commands in a chat window. This option is only available on GitLab.com, though it may be [available for self-managed instances in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/28164). |
|
||||
| [Third party offers](third_party_offers.md) | Control the display of third party offers. |
|
||||
|
|
|
@ -167,6 +167,7 @@ module API
|
|||
mount ::API::Deployments
|
||||
mount ::API::Environments
|
||||
mount ::API::ErrorTracking
|
||||
mount ::API::ErrorTrackingCollector
|
||||
mount ::API::Events
|
||||
mount ::API::FeatureFlags
|
||||
mount ::API::FeatureFlagsUserLists
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
# This API is responsible for collecting error tracking information
|
||||
# from sentry client. It allows us to use GitLab as an alternative to
|
||||
# sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596.
|
||||
class ErrorTrackingCollector < ::API::Base
|
||||
feature_category :error_tracking
|
||||
|
||||
content_type :envelope, 'application/x-sentry-envelope'
|
||||
default_format :envelope
|
||||
|
||||
before do
|
||||
not_found!('Project') unless project
|
||||
not_found! unless feature_enabled?
|
||||
end
|
||||
|
||||
helpers do
|
||||
def project
|
||||
@project ||= find_project(params[:id])
|
||||
end
|
||||
|
||||
def feature_enabled?
|
||||
::Feature.enabled?(:integrated_error_tracking, project) &&
|
||||
project.error_tracking_setting&.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Submit error tracking event to the project' do
|
||||
detail 'This feature was introduced in GitLab 14.1.'
|
||||
end
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
post 'error_tracking/collector/api/:id/envelope' do
|
||||
# There is a reason why we have such uncommon path.
|
||||
# We depend on a client side error tracking software which
|
||||
# modifies URL for its own reasons.
|
||||
#
|
||||
# When we give user a URL like this
|
||||
# HOST/api/v4/error_tracking/collector/123
|
||||
#
|
||||
# Then error tracking software will convert it like this:
|
||||
# HOST/api/v4/error_tracking/collector/api/123/envelope/
|
||||
|
||||
begin
|
||||
parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request)
|
||||
rescue StandardError
|
||||
render_api_error!('Failed to parse sentry request', 400)
|
||||
end
|
||||
|
||||
type = parsed_request[:request_type]
|
||||
|
||||
# Sentry sends 2 requests on each exception: transaction and event.
|
||||
# Everything else is not a desired behavior.
|
||||
unless type == 'transaction' || type == 'event'
|
||||
render_api_error!('400 Bad Request', 400)
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
# We don't have use for transaction request yet,
|
||||
# so we record only event one.
|
||||
if type == 'event'
|
||||
::ErrorTracking::CollectErrorService
|
||||
.new(project, nil, event: parsed_request[:event])
|
||||
.execute
|
||||
end
|
||||
|
||||
no_content!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ErrorTracking
|
||||
module Collector
|
||||
class SentryRequestParser
|
||||
def self.parse(request)
|
||||
# Request body can be "" or "gzip".
|
||||
# If later then body was compressed with Zlib.gzip
|
||||
encoding = request.headers['Content-Encoding']
|
||||
|
||||
body = if encoding == 'gzip'
|
||||
Zlib.gunzip(request.body.read)
|
||||
else
|
||||
request.body.read
|
||||
end
|
||||
|
||||
# Request body contains 3 json objects merged together in one StringIO.
|
||||
# We need to separate and parse them into array of hash objects.
|
||||
json_objects = []
|
||||
parser = Yajl::Parser.new
|
||||
|
||||
parser.parse(body) do |json_object|
|
||||
json_objects << json_object
|
||||
end
|
||||
|
||||
# The request contains 3 objects: sentry metadata, type data and event data.
|
||||
# We need only last two. Type to decide what to do with the request.
|
||||
# And event data as it contains all information about the exception.
|
||||
_, type, event = json_objects
|
||||
|
||||
{
|
||||
request_type: type['type'],
|
||||
event: event
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,11 +11,16 @@ module Gitlab
|
|||
|
||||
delegate :dig, to: :@seed_attributes
|
||||
|
||||
def initialize(context, attributes, previous_stages)
|
||||
def initialize(context, attributes, previous_stages, current_stage)
|
||||
@context = context
|
||||
@pipeline = context.pipeline
|
||||
@seed_attributes = attributes
|
||||
@previous_stages = previous_stages
|
||||
@stages_for_needs_lookup = if Feature.enabled?(:ci_same_stage_job_needs, @pipeline.project, default_enabled: :yaml)
|
||||
(previous_stages + [current_stage]).compact
|
||||
else
|
||||
previous_stages
|
||||
end
|
||||
|
||||
@needs_attributes = dig(:needs_attributes)
|
||||
@resource_group_key = attributes.delete(:resource_group_key)
|
||||
@job_variables = @seed_attributes.delete(:job_variables)
|
||||
|
@ -148,14 +153,18 @@ module Gitlab
|
|||
@needs_attributes.flat_map do |need|
|
||||
next if need[:optional]
|
||||
|
||||
result = @previous_stages.any? do |stage|
|
||||
stage.seeds_names.include?(need[:name])
|
||||
end
|
||||
result = need_present?(need)
|
||||
|
||||
"'#{name}' job needs '#{need[:name]}' job, but it was not added to the pipeline" unless result
|
||||
"'#{name}' job needs '#{need[:name]}' job, but '#{need[:name]}' is not in any previous stage" unless result
|
||||
end.compact
|
||||
end
|
||||
|
||||
def need_present?(need)
|
||||
@stages_for_needs_lookup.any? do |stage|
|
||||
stage.seeds_names.include?(need[:name])
|
||||
end
|
||||
end
|
||||
|
||||
def max_needs_allowed
|
||||
@pipeline.project.actual_limits.ci_needs_size_limit
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
@previous_stages = previous_stages
|
||||
|
||||
@builds = attributes.fetch(:builds).map do |attributes|
|
||||
Seed::Build.new(context, attributes, previous_stages)
|
||||
Seed::Build.new(context, attributes, previous_stages, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -46,6 +46,10 @@ module Gitlab
|
|||
@jobs.each do |name, job|
|
||||
validate_job!(name, job)
|
||||
end
|
||||
|
||||
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
|
||||
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_job!(name, job)
|
||||
|
@ -99,10 +103,16 @@ module Gitlab
|
|||
job_stage_index = stage_index(name)
|
||||
dependency_stage_index = stage_index(dependency)
|
||||
|
||||
# A dependency might be defined later in the configuration
|
||||
# with a stage that does not exist
|
||||
unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in prior stages")
|
||||
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
|
||||
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
|
||||
end
|
||||
else
|
||||
# A dependency might be defined later in the configuration
|
||||
# with a stage that does not exist
|
||||
unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in prior stages")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Represents Dag pipeline
|
||||
module Gitlab
|
||||
module Ci
|
||||
class YamlProcessor
|
||||
class Dag
|
||||
include TSort
|
||||
|
||||
MissingNodeError = Class.new(StandardError)
|
||||
|
||||
def initialize(nodes)
|
||||
@nodes = nodes
|
||||
end
|
||||
|
||||
def self.check_circular_dependencies!(jobs)
|
||||
nodes = jobs.values.to_h do |job|
|
||||
name = job[:name].to_s
|
||||
needs = job.dig(:needs, :job).to_a
|
||||
|
||||
[name, needs.map { |need| need[:name].to_s }]
|
||||
end
|
||||
|
||||
new(nodes).tsort
|
||||
rescue TSort::Cyclic
|
||||
raise ValidationError, 'The pipeline has circular dependencies.'
|
||||
rescue MissingNodeError
|
||||
end
|
||||
|
||||
def tsort_each_child(node, &block)
|
||||
raise MissingNodeError, "node #{node} is missing" unless @nodes[node]
|
||||
|
||||
@nodes[node].each(&block)
|
||||
end
|
||||
|
||||
def tsort_each_node(&block)
|
||||
@nodes.each_key(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Groups
|
||||
class Context < ::Sidebars::Context
|
||||
def initialize(current_user:, container:, **args)
|
||||
super(current_user: current_user, container: container, group: container, **args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Groups
|
||||
class Panel < ::Sidebars::Panel
|
||||
override :render_raw_scope_menu_partial
|
||||
def render_raw_scope_menu_partial
|
||||
'layouts/nav/sidebar/group_scope_menu'
|
||||
end
|
||||
|
||||
override :render_raw_menus_partial
|
||||
def render_raw_menus_partial
|
||||
'layouts/nav/sidebar/group_menus'
|
||||
end
|
||||
|
||||
override :aria_label
|
||||
def aria_label
|
||||
context.group.subgroup? ? _('Subgroup navigation') : _('Group navigation')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39411,9 +39411,6 @@ msgstr ""
|
|||
msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|You can delete the source branch now"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|You can merge after removing denied licenses"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ module QA
|
|||
class Menu < Page::Base
|
||||
include SubMenus::Common
|
||||
|
||||
view 'app/views/layouts/nav/sidebar/_group.html.haml' do
|
||||
view 'app/views/layouts/nav/sidebar/_group_menus.html.haml' do
|
||||
element :general_settings_link
|
||||
element :group_issues_item
|
||||
element :group_members_item
|
||||
|
|
|
@ -12,8 +12,8 @@ module QA
|
|||
super
|
||||
|
||||
base.class_eval do
|
||||
view 'app/views/layouts/nav/sidebar/_group.html.haml' do
|
||||
element :group_sidebar
|
||||
view 'app/views/shared/nav/_sidebar.html.haml' do
|
||||
element :group_sidebar, 'qa_selector: sidebar_qa_selector(sidebar.container)' # rubocop:disable QA/ElementWithPattern
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ function bundle_install_script() {
|
|||
bundle config set path 'vendor'
|
||||
bundle config set clean 'true'
|
||||
|
||||
echo $BUNDLE_WITHOUT
|
||||
echo "${BUNDLE_WITHOUT}"
|
||||
bundle config
|
||||
|
||||
run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS} ${extra_install_args} && bundle check"
|
||||
|
@ -134,3 +134,10 @@ function fail_pipeline_early() {
|
|||
scripts/api/cancel_pipeline.rb
|
||||
fi
|
||||
}
|
||||
|
||||
function danger_as_local() {
|
||||
# Force danger to skip CI source GitLab and fallback to "local only git repo".
|
||||
unset GITLAB_CI
|
||||
# We need to base SHA to help danger determine the base commit for this shallow clone.
|
||||
bundle exec danger dry_run --fail-on-errors=true --verbose --base="${CI_MERGE_REQUEST_DIFF_BASE_SHA}"
|
||||
}
|
||||
|
|
|
@ -269,10 +269,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
end
|
||||
|
||||
context 'Integrations page' do
|
||||
let(:mailgun_events_receiver_enabled) { true }
|
||||
|
||||
before do
|
||||
stub_feature_flags(mailgun_events_receiver: mailgun_events_receiver_enabled)
|
||||
visit general_admin_application_settings_path
|
||||
end
|
||||
|
||||
|
@ -286,26 +283,16 @@ RSpec.describe 'Admin updates settings' do
|
|||
expect(current_settings.hide_third_party_offers).to be true
|
||||
end
|
||||
|
||||
context 'when mailgun_events_receiver feature flag is enabled' do
|
||||
it 'enabling Mailgun events', :aggregate_failures do
|
||||
page.within('.as-mailgun') do
|
||||
check 'Enable Mailgun event receiver'
|
||||
fill_in 'Mailgun HTTP webhook signing key', with: 'MAILGUN_SIGNING_KEY'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(page).to have_content 'Application settings saved successfully'
|
||||
expect(current_settings.mailgun_events_enabled).to be true
|
||||
expect(current_settings.mailgun_signing_key).to eq 'MAILGUN_SIGNING_KEY'
|
||||
it 'enabling Mailgun events', :aggregate_failures do
|
||||
page.within('.as-mailgun') do
|
||||
check 'Enable Mailgun event receiver'
|
||||
fill_in 'Mailgun HTTP webhook signing key', with: 'MAILGUN_SIGNING_KEY'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when mailgun_events_receiver feature flag is disabled' do
|
||||
let(:mailgun_events_receiver_enabled) { false }
|
||||
|
||||
it 'does not have mailgun' do
|
||||
expect(page).not_to have_selector('.as-mailgun')
|
||||
end
|
||||
expect(page).to have_content 'Application settings saved successfully'
|
||||
expect(current_settings.mailgun_events_enabled).to be true
|
||||
expect(current_settings.mailgun_signing_key).to eq 'MAILGUN_SIGNING_KEY'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe 'User browse group projects page' do
|
|||
visit projects_group_path(group)
|
||||
|
||||
expect(page).to have_link project.name
|
||||
expect(page).to have_xpath("//span[@class='badge badge-warning']", text: 'archived')
|
||||
expect(page).to have_css('span.badge.badge-warning', text: 'archived')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,12 +7,14 @@ RSpec.describe ContainerRepositoriesFinder do
|
|||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:project_repository) { create(:container_repository, name: 'my_image', project: project) }
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(container_registry_access_level: ProjectFeature::PRIVATE)
|
||||
|
||||
group.add_reporter(reporter)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
@ -77,6 +79,14 @@ RSpec.describe ContainerRepositoriesFinder do
|
|||
|
||||
it_behaves_like 'with name search'
|
||||
it_behaves_like 'with sorting'
|
||||
|
||||
context 'when project has container registry disabled' do
|
||||
before do
|
||||
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([other_repository]) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when subject_type is project' do
|
||||
|
@ -86,6 +96,14 @@ RSpec.describe ContainerRepositoriesFinder do
|
|||
|
||||
it_behaves_like 'with name search'
|
||||
it_behaves_like 'with sorting'
|
||||
|
||||
context 'when project has container registry disabled' do
|
||||
before do
|
||||
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid subject_type' do
|
||||
|
@ -96,9 +114,19 @@ RSpec.describe ContainerRepositoriesFinder do
|
|||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
subject { described_class.new(user: guest, subject: group).execute }
|
||||
subject { described_class.new(user: guest, subject: subject_type).execute }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
context 'when subject_type is group' do
|
||||
let(:subject_type) { group }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
|
||||
context 'when subject_type is project' do
|
||||
let(:subject_type) { project }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{"event_id":"7c9ae6e58f03442b9203bbdcf6ae904c","dsn":"http://1fedb514e17f4b958435093deb03048c@localhost:3000/api/v4/projects/7/error_tracking/collector/7","sdk":{"name":"sentry.ruby","version":"4.5.1"},"sent_at":"2021-07-08T12:59:16Z"}
|
||||
{"type":"event","content_type":"application/json"}
|
||||
{"event_id":"7c9ae6e58f03442b9203bbdcf6ae904c","level":"error","timestamp":"2021-07-08T12:59:16Z","release":"db853d7","environment":"development","server_name":"MacBook.local","modules":{"rake":"13.0.3","concurrent-ruby":"1.1.9","i18n":"1.8.10","minitest":"5.14.4","thread_safe":"0.3.6","tzinfo":"1.2.9","uglifier":"4.2.0","web-console":"3.7.0"},"message":"","user":{},"tags":{"request_id":"4253dcd9-5e48-474a-89b4-0e945ab825af"},"contexts":{"os":{"name":"Darwin","version":"Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64","build":"20.5.0","kernel_version":"Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64"},"runtime":{"name":"ruby","version":"ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin19]"},"trace":{"trace_id":"d82b93fbc39e4d13b85762afa2e3ff36","span_id":"4a3ed8701e7f4ea4","parent_span_id":null,"description":null,"op":"rails.request","status":null}},"extra":{},"fingerprint":[],"breadcrumbs":{"values":[{"category":"start_processing.action_controller","data":{"controller":"PostsController","action":"error2","params":{"controller":"posts","action":"error2"},"format":"html","method":"GET","path":"/posts/error2","start_timestamp":1625749156.5553},"level":null,"message":"","timestamp":1625749156,"type":null},{"category":"process_action.action_controller","data":{"controller":"PostsController","action":"error2","params":{"controller":"posts","action":"error2"},"format":"html","method":"GET","path":"/posts/error2","start_timestamp":1625749156.55539,"view_runtime":null,"db_runtime":0},"level":null,"message":"","timestamp":1625749156,"type":null}]},"transaction":"PostsController#error2","platform":"ruby","sdk":{"name":"sentry.ruby.rails","version":"4.5.1"},"request":{"url":"http://localhost/posts/error2","method":"GET","headers":{},"env":{"SERVER_NAME":"localhost","SERVER_PORT":"4444"}},"exception":{"values":[{"type":"ActionView::MissingTemplate","value":"Missing template posts/error2, application/error2 with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:\n * \"/Users/developer/rails-project/app/views\"\n","module":"ActionView","thread_id":70254489510160,"stacktrace":{"frames":[{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-3.12.6/lib/puma/thread_pool.rb","function":"block in spawn_thread","lineno":135,"in_app":false,"filename":"puma/thread_pool.rb","pre_context":[" end\n","\n"," begin\n"],"context_line":" block.call(work, *extra)\n","post_context":[" rescue Exception => e\n"," STDERR.puts \"Error reached top of thread-pool: #{e.message} (#{e.class})\"\n"," end\n"]},{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-3.12.6/lib/puma/server.rb","function":"block in run","lineno":334,"in_app":false,"filename":"puma/server.rb","pre_context":[" client.close\n"," else\n"," if process_now\n"],"context_line":" process_client client, buffer\n","post_context":[" else\n"," client.set_timeout @first_data_timeout\n"," @reactor.add client\n"]},{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actionview-5.2.6/lib/action_view/path_set.rb","function":"find","lineno":48,"in_app":false,"filename":"action_view/path_set.rb","pre_context":[" end\n","\n"," def find(*args)\n"],"context_line":" find_all(*args).first || raise(MissingTemplate.new(self, *args))\n","post_context":[" end\n","\n"," def find_file(path, prefixes = [], *args)\n"]}]}}]}}
|
|
@ -0,0 +1 @@
|
|||
{"event_id":"7c9ae6e58f03442b9203bbdcf6ae904c","level":"error","timestamp":"2021-07-08T12:59:16Z","release":"db853d7","environment":"development","server_name":"MacBook.local","modules":{"rake":"13.0.3","concurrent-ruby":"1.1.9","i18n":"1.8.10","minitest":"5.14.4","thread_safe":"0.3.6","tzinfo":"1.2.9","uglifier":"4.2.0","web-console":"3.7.0"},"message":"","user":{},"tags":{"request_id":"4253dcd9-5e48-474a-89b4-0e945ab825af"},"contexts":{"os":{"name":"Darwin","version":"Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64","build":"20.5.0","kernel_version":"Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64"},"runtime":{"name":"ruby","version":"ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin19]"},"trace":{"trace_id":"d82b93fbc39e4d13b85762afa2e3ff36","span_id":"4a3ed8701e7f4ea4","parent_span_id":null,"description":null,"op":"rails.request","status":null}},"extra":{},"fingerprint":[],"breadcrumbs":{"values":[{"category":"start_processing.action_controller","data":{"controller":"PostsController","action":"error2","params":{"controller":"posts","action":"error2"},"format":"html","method":"GET","path":"/posts/error2","start_timestamp":1625749156.5553},"level":null,"message":"","timestamp":1625749156,"type":null},{"category":"process_action.action_controller","data":{"controller":"PostsController","action":"error2","params":{"controller":"posts","action":"error2"},"format":"html","method":"GET","path":"/posts/error2","start_timestamp":1625749156.55539,"view_runtime":null,"db_runtime":0},"level":null,"message":"","timestamp":1625749156,"type":null}]},"transaction":"PostsController#error2","platform":"ruby","sdk":{"name":"sentry.ruby.rails","version":"4.5.1"},"request":{"url":"http://localhost/posts/error2","method":"GET","headers":{},"env":{"SERVER_NAME":"localhost","SERVER_PORT":"4444"}},"exception":{"values":[{"type":"ActionView::MissingTemplate","value":"Missing template posts/error2, application/error2 with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:\n * \"/Users/developer/rails-project/app/views\"\n","module":"ActionView","thread_id":70254489510160,"stacktrace":{"frames":[{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-3.12.6/lib/puma/thread_pool.rb","function":"block in spawn_thread","lineno":135,"in_app":false,"filename":"puma/thread_pool.rb","pre_context":[" end\n","\n"," begin\n"],"context_line":" block.call(work, *extra)\n","post_context":[" rescue Exception => e\n"," STDERR.puts \"Error reached top of thread-pool: #{e.message} (#{e.class})\"\n"," end\n"]},{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-3.12.6/lib/puma/server.rb","function":"block in run","lineno":334,"in_app":false,"filename":"puma/server.rb","pre_context":[" client.close\n"," else\n"," if process_now\n"],"context_line":" process_client client, buffer\n","post_context":[" else\n"," client.set_timeout @first_data_timeout\n"," @reactor.add client\n"]},{"project_root":"/Users/developer/rails-project","abs_path":"/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actionview-5.2.6/lib/action_view/path_set.rb","function":"find","lineno":48,"in_app":false,"filename":"action_view/path_set.rb","pre_context":[" end\n","\n"," def find(*args)\n"],"context_line":" find_all(*args).first || raise(MissingTemplate.new(self, *args))\n","post_context":[" end\n","\n"," def find_file(path, prefixes = [], *args)\n"]}]}}]}}
|
|
@ -0,0 +1,3 @@
|
|||
{"event_id":"4a304dbdf3404e87962e99bced2f6c8b","dsn":"","sdk":{"name":"sentry.ruby","version":"4.5.1"},"sent_at":"2021-07-08T12:58:29Z"}
|
||||
{"type":"transaction","content_type":"application/json"}
|
||||
{}
|
|
@ -0,0 +1,3 @@
|
|||
{"event_id":"7c9ae6e58f03442b9203bbdcf6ae904c","dsn":"","sdk":{"name":"sentry.ruby","version":"4.5.1"},"sent_at":"2021-07-08T12:59:16Z"}
|
||||
{"type":"unknown","content_type":"application/json"}
|
||||
{}
|
|
@ -483,11 +483,11 @@ describe('Settings Panel', () => {
|
|||
it.each`
|
||||
visibilityLevel | pagesAccessControlForced | output
|
||||
${visibilityOptions.PRIVATE} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
|
||||
${visibilityOptions.PRIVATE} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [visibilityOptions.PUBLIC, 'Everyone']]}
|
||||
${visibilityOptions.PRIVATE} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
|
||||
${visibilityOptions.INTERNAL} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
|
||||
${visibilityOptions.INTERNAL} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [visibilityOptions.PUBLIC, 'Everyone']]}
|
||||
${visibilityOptions.INTERNAL} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
|
||||
${visibilityOptions.PUBLIC} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
|
||||
${visibilityOptions.PUBLIC} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [visibilityOptions.PUBLIC, 'Everyone']]}
|
||||
${visibilityOptions.PUBLIC} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
|
||||
`(
|
||||
'renders correct options when pagesAccessControlForced is $pagesAccessControlForced and visibilityLevel is $visibilityLevel',
|
||||
async ({ visibilityLevel, pagesAccessControlForced, output }) => {
|
||||
|
|
|
@ -217,7 +217,6 @@ describe('MRWidgetMerged', () => {
|
|||
vm.mr.sourceBranchRemoved = false;
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.innerText).toContain('You can delete the source branch now');
|
||||
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
|
||||
done();
|
||||
});
|
||||
|
@ -229,7 +228,6 @@ describe('MRWidgetMerged', () => {
|
|||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.innerText).toContain('The source branch is being deleted');
|
||||
expect(vm.$el.innerText).not.toContain('You can delete the source branch now');
|
||||
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -66,6 +66,7 @@ RSpec.describe PackagesHelper do
|
|||
end
|
||||
|
||||
describe '#show_cleanup_policy_on_alert' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:container_repository) { create(:container_repository) }
|
||||
|
||||
subject { helper.show_cleanup_policy_on_alert(project.reload) }
|
||||
|
@ -203,9 +204,10 @@ RSpec.describe PackagesHelper do
|
|||
|
||||
with_them do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
allow(Gitlab).to receive(:com?).and_return(com)
|
||||
stub_config(registry: { enabled: config_registry })
|
||||
allow(project).to receive(:container_registry_enabled).and_return(project_registry)
|
||||
allow(project).to receive(:feature_available?).with(:container_registry, user).and_return(project_registry)
|
||||
stub_application_setting(container_expiration_policies_enable_historic_entries: historic_entries)
|
||||
stub_feature_flags(container_expiration_policies_historic_entry: false)
|
||||
stub_feature_flags(container_expiration_policies_historic_entry: project) if historic_entry
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ErrorTracking::Collector::SentryRequestParser do
|
||||
describe '.parse' do
|
||||
let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') }
|
||||
let_it_be(:parsed_event) { Gitlab::Json.parse(fixture_file('error_tracking/parsed_event.json')) }
|
||||
|
||||
let(:body) { raw_event }
|
||||
let(:headers) { { 'Content-Encoding' => '' } }
|
||||
let(:request) { double('request', headers: headers, body: StringIO.new(body)) }
|
||||
|
||||
subject { described_class.parse(request) }
|
||||
|
||||
RSpec.shared_examples 'valid parser' do
|
||||
it 'returns a valid hash' do
|
||||
parsed_request = subject
|
||||
|
||||
expect(parsed_request[:request_type]).to eq('event')
|
||||
expect(parsed_request[:event]).to eq(parsed_event)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty body content' do
|
||||
let(:body) { '' }
|
||||
|
||||
it 'fails with exception' do
|
||||
expect { subject }.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'plain text sentry request' do
|
||||
it_behaves_like 'valid parser'
|
||||
end
|
||||
|
||||
context 'gzip encoded sentry request' do
|
||||
let(:headers) { { 'Content-Encoding' => 'gzip' } }
|
||||
let(:body) { Zlib.gzip(raw_event) }
|
||||
|
||||
it_behaves_like 'valid parser'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -247,7 +247,7 @@ RSpec.describe Gitlab::Ci::Lint do
|
|||
include_context 'advanced validations' do
|
||||
it 'runs advanced logical validations' do
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors).to eq(["'test' job needs 'build' job, but it was not added to the pipeline"])
|
||||
expect(subject.errors).to eq(["'test' job needs 'build' job, but 'build' is not in any previous stage"])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) }
|
||||
let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } }
|
||||
let(:previous_stages) { [] }
|
||||
let(:current_stage) { double(seeds_names: [attributes[:name]]) }
|
||||
|
||||
let(:seed_build) { described_class.new(seed_context, attributes, previous_stages) }
|
||||
let(:seed_build) { described_class.new(seed_context, attributes, previous_stages, current_stage) }
|
||||
|
||||
describe '#attributes' do
|
||||
subject { seed_build.attributes }
|
||||
|
@ -1079,7 +1080,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it "returns an error" do
|
||||
expect(subject.errors).to contain_exactly(
|
||||
"'rspec' job needs 'build' job, but it was not added to the pipeline")
|
||||
"'rspec' job needs 'build' job, but 'build' is not in any previous stage")
|
||||
end
|
||||
|
||||
context 'when the needed job is optional' do
|
||||
|
@ -1115,6 +1116,28 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when build job is part of the same stage' do
|
||||
let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) }
|
||||
|
||||
it 'is included' do
|
||||
is_expected.to be_included
|
||||
end
|
||||
|
||||
it 'does not have errors' do
|
||||
expect(subject.errors).to be_empty
|
||||
end
|
||||
|
||||
context 'when ci_same_stage_job_needs FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it 'has errors' do
|
||||
expect(subject.errors).to contain_exactly("'rspec' job needs 'build' job, but 'build' is not in any previous stage")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using 101 needs' do
|
||||
let(:needs_count) { 101 }
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
|
|||
described_class.new(seed_context, stages_attributes)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
describe '#stages' do
|
||||
it 'returns the stage resources' do
|
||||
stages = seed.stages
|
||||
|
@ -65,7 +69,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
|
|||
}
|
||||
|
||||
expect(seed.errors).to contain_exactly(
|
||||
"'invalid_job' job needs 'non-existent' job, but it was not added to the pipeline")
|
||||
"'invalid_job' job needs 'non-existent' job, but 'non-existent' is not in any previous stage")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::YamlProcessor::Dag do
|
||||
let(:nodes) { {} }
|
||||
|
||||
subject(:result) { described_class.new(nodes).tsort }
|
||||
|
||||
context 'when it is a regular pipeline' do
|
||||
let(:nodes) do
|
||||
{ 'job_c' => %w(job_b job_d), 'job_d' => %w(job_a), 'job_b' => %w(job_a), 'job_a' => %w() }
|
||||
end
|
||||
|
||||
it 'returns ordered jobs' do
|
||||
expect(result).to eq(%w(job_a job_b job_d job_c))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a circular dependency' do
|
||||
let(:nodes) do
|
||||
{ 'job_a' => %w(job_c), 'job_b' => %w(job_a), 'job_c' => %w(job_b) }
|
||||
end
|
||||
|
||||
it 'raises TSort::Cyclic' do
|
||||
expect { result }.to raise_error(TSort::Cyclic, /topological sort failed/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a missing job' do
|
||||
let(:nodes) do
|
||||
{ 'job_a' => %w(job_d), 'job_b' => %w(job_a) }
|
||||
end
|
||||
|
||||
it 'raises MissingNodeError' do
|
||||
expect { result }.to raise_error(
|
||||
Gitlab::Ci::YamlProcessor::Dag::MissingNodeError, 'node job_d is missing'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -595,7 +595,15 @@ module Gitlab
|
|||
EOYML
|
||||
end
|
||||
|
||||
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
|
||||
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in current or prior stages/
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1858,7 +1866,7 @@ module Gitlab
|
|||
build2: { stage: 'build', script: 'test' },
|
||||
test1: { stage: 'test', script: 'test', dependencies: dependencies },
|
||||
test2: { stage: 'test', script: 'test' },
|
||||
deploy: { stage: 'test', script: 'test' }
|
||||
deploy: { stage: 'deploy', script: 'test' }
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -1891,7 +1899,15 @@ module Gitlab
|
|||
context 'dependencies to deploy' do
|
||||
let(:dependencies) { ['deploy'] }
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages'
|
||||
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in current or prior stages'
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a job depends on another job that references a not-yet defined stage' do
|
||||
|
@ -1916,7 +1932,7 @@ module Gitlab
|
|||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', /is not defined in prior stages/
|
||||
it_behaves_like 'returns errors', /is not defined in current or prior stages/
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1931,7 +1947,7 @@ module Gitlab
|
|||
parallel: { stage: 'build', script: 'test', parallel: 2 },
|
||||
test1: { stage: 'test', script: 'test', needs: needs, dependencies: dependencies },
|
||||
test2: { stage: 'test', script: 'test' },
|
||||
deploy: { stage: 'test', script: 'test' }
|
||||
deploy: { stage: 'deploy', script: 'test' }
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -1941,6 +1957,45 @@ module Gitlab
|
|||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'needs a job from the same stage' do
|
||||
let(:needs) { %w(test2) }
|
||||
|
||||
it 'creates jobs with valid specifications' do
|
||||
expect(subject.builds.size).to eq(7)
|
||||
expect(subject.builds[0]).to eq(
|
||||
stage: 'build',
|
||||
stage_idx: 1,
|
||||
name: 'build1',
|
||||
only: { refs: %w[branches tags] },
|
||||
options: {
|
||||
script: ['test']
|
||||
},
|
||||
when: 'on_success',
|
||||
allow_failure: false,
|
||||
yaml_variables: [],
|
||||
job_variables: [],
|
||||
root_variables_inheritance: true,
|
||||
scheduling_type: :stage
|
||||
)
|
||||
expect(subject.builds[4]).to eq(
|
||||
stage: 'test',
|
||||
stage_idx: 2,
|
||||
name: 'test1',
|
||||
only: { refs: %w[branches tags] },
|
||||
options: { script: ['test'] },
|
||||
needs_attributes: [
|
||||
{ name: 'test2', artifacts: true, optional: false }
|
||||
],
|
||||
when: 'on_success',
|
||||
allow_failure: false,
|
||||
yaml_variables: [],
|
||||
job_variables: [],
|
||||
root_variables_inheritance: true,
|
||||
scheduling_type: :dag
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'needs two builds' do
|
||||
let(:needs) { %w(build1 build2) }
|
||||
|
||||
|
@ -2096,7 +2151,15 @@ module Gitlab
|
|||
context 'needs to deploy' do
|
||||
let(:needs) { ['deploy'] }
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages'
|
||||
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in current or prior stages'
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages'
|
||||
end
|
||||
end
|
||||
|
||||
context 'needs and dependencies that are mismatching' do
|
||||
|
@ -2767,6 +2830,29 @@ module Gitlab
|
|||
|
||||
it_behaves_like 'returns errors', 'jobs:rspec:parallel should be an integer or a hash'
|
||||
end
|
||||
|
||||
context 'when the pipeline has a circular dependency' do
|
||||
let(:config) do
|
||||
<<~YAML
|
||||
job_a:
|
||||
stage: test
|
||||
script: build
|
||||
needs: [job_c]
|
||||
|
||||
job_b:
|
||||
stage: test
|
||||
script: test
|
||||
needs: [job_a]
|
||||
|
||||
job_c:
|
||||
stage: test
|
||||
script: deploy
|
||||
needs: [job_b]
|
||||
YAML
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', 'The pipeline has circular dependencies.'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
|
|
|
@ -578,6 +578,7 @@ project:
|
|||
- merge_request_metrics
|
||||
- security_orchestration_policy_configuration
|
||||
- timelogs
|
||||
- error_tracking_errors
|
||||
award_emoji:
|
||||
- awardable
|
||||
- user
|
||||
|
|
|
@ -167,6 +167,7 @@ ProjectMember:
|
|||
- expires_at
|
||||
- ldap
|
||||
- override
|
||||
- invite_email_success
|
||||
User:
|
||||
- id
|
||||
- username
|
||||
|
|
|
@ -827,15 +827,15 @@ RSpec.describe Notify do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when on gitlab.com' do
|
||||
context 'when mailgun events are enabled' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:dev_env_or_com?).and_return(true)
|
||||
stub_application_setting(mailgun_events_enabled: true)
|
||||
end
|
||||
|
||||
it 'has custom headers' do
|
||||
aggregate_failures do
|
||||
expect(subject).to have_header('X-Mailgun-Tag', 'invite_email')
|
||||
expect(subject).to have_header('X-Mailgun-Variables', { 'invite_token' => project_member.invite_token }.to_json)
|
||||
expect(subject).to have_header('X-Mailgun-Tag', ::Members::Mailgun::INVITE_EMAIL_TAG)
|
||||
expect(subject).to have_header('X-Mailgun-Variables', { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => project_member.invite_token }.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,6 +55,24 @@ RSpec.describe Ci::BuildDependencies do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when needs refer to jobs from the same stage' do
|
||||
let(:job) do
|
||||
create(:ci_build,
|
||||
pipeline: pipeline,
|
||||
name: 'dag_job',
|
||||
scheduling_type: :dag,
|
||||
stage_idx: 2,
|
||||
stage: 'deploy'
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:ci_build_need, build: job, name: 'staging', artifacts: true)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(staging) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'jobs from specified dependencies' do
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ErrorTrackingCollector do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:setting) { create(:project_error_tracking_setting, project: project) }
|
||||
|
||||
describe "POST /error_tracking/collector/api/:id/envelope" do
|
||||
let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') }
|
||||
let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/envelope" }
|
||||
|
||||
let(:params) { raw_event }
|
||||
|
||||
subject { post api(url), params: params }
|
||||
|
||||
RSpec.shared_examples 'not found' do
|
||||
it 'reponds with 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'bad request' do
|
||||
it 'responds with 400' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'error tracking feature is disabled' do
|
||||
before do
|
||||
setting.update!(enabled: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
|
||||
context 'feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(integrated_error_tracking: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
|
||||
context 'empty body' do
|
||||
let(:params) { '' }
|
||||
|
||||
it_behaves_like 'bad request'
|
||||
end
|
||||
|
||||
context 'unknown request type' do
|
||||
let(:params) { fixture_file('error_tracking/unknown.txt') }
|
||||
|
||||
it_behaves_like 'bad request'
|
||||
end
|
||||
|
||||
context 'transaction request type' do
|
||||
let(:params) { fixture_file('error_tracking/transaction.txt') }
|
||||
|
||||
it 'does nothing and returns no content' do
|
||||
expect { subject }.not_to change { ErrorTracking::ErrorEvent.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
it 'writes to the database and returns no content' do
|
||||
expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,128 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'receive a permanent failure' do
|
||||
describe 'POST /members/mailgun/permanent_failures', :aggregate_failures do
|
||||
let_it_be(:member) { create(:project_member, :invited) }
|
||||
|
||||
let(:raw_invite_token) { member.raw_invite_token }
|
||||
let(:mailgun_events) { true }
|
||||
let(:mailgun_signing_key) { 'abc123' }
|
||||
|
||||
subject(:post_request) { post members_mailgun_permanent_failures_path(standard_params) }
|
||||
|
||||
before do
|
||||
stub_application_setting(mailgun_events_enabled: mailgun_events, mailgun_signing_key: mailgun_signing_key)
|
||||
end
|
||||
|
||||
it 'marks the member invite email success as false' do
|
||||
expect { post_request }.to change { member.reload.invite_email_success }.from(true).to(false)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when the change to a member is not made' do
|
||||
context 'with incorrect signing key' do
|
||||
context 'with incorrect signing key' do
|
||||
let(:mailgun_signing_key) { '_foobar_' }
|
||||
|
||||
it 'does not change member status and responds as not_found' do
|
||||
expect { post_request }.not_to change { member.reload.invite_email_success }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with nil signing key' do
|
||||
let(:mailgun_signing_key) { nil }
|
||||
|
||||
it 'does not change member status and responds as not_found' do
|
||||
expect { post_request }.not_to change { member.reload.invite_email_success }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature is not enabled' do
|
||||
let(:mailgun_events) { false }
|
||||
|
||||
it 'does not change member status and responds as expected' do
|
||||
expect { post_request }.not_to change { member.reload.invite_email_success }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_acceptable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is not an invite email' do
|
||||
before do
|
||||
stub_const('::Members::Mailgun::INVITE_EMAIL_TAG', '_foobar_')
|
||||
end
|
||||
|
||||
it 'does not change member status and responds as expected' do
|
||||
expect { post_request }.not_to change { member.reload.invite_email_success }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_acceptable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def standard_params
|
||||
{
|
||||
"signature": {
|
||||
"timestamp": "1625056677",
|
||||
"token": "eb944d0ace7227667a1b97d2d07276ae51d2b849ed2cfa68f3",
|
||||
"signature": "9790cc6686eb70f0b1f869180d906870cdfd496d27fee81da0aa86b9e539e790"
|
||||
},
|
||||
"event-data": {
|
||||
"severity": "permanent",
|
||||
"tags": ["invite_email"],
|
||||
"timestamp": 1521233195.375624,
|
||||
"storage": {
|
||||
"url": "_anything_",
|
||||
"key": "_anything_"
|
||||
},
|
||||
"log-level": "error",
|
||||
"id": "_anything_",
|
||||
"campaigns": [],
|
||||
"reason": "suppress-bounce",
|
||||
"user-variables": {
|
||||
"invite_token": raw_invite_token
|
||||
},
|
||||
"flags": {
|
||||
"is-routed": false,
|
||||
"is-authenticated": true,
|
||||
"is-system-test": false,
|
||||
"is-test-mode": false
|
||||
},
|
||||
"recipient-domain": "example.com",
|
||||
"envelope": {
|
||||
"sender": "bob@mg.gitlab.com",
|
||||
"transport": "smtp",
|
||||
"targets": "alice@example.com"
|
||||
},
|
||||
"message": {
|
||||
"headers": {
|
||||
"to": "Alice <alice@example.com>",
|
||||
"message-id": "20130503192659.13651.20287@mg.gitlab.com",
|
||||
"from": "Bob <bob@mg.gitlab.com>",
|
||||
"subject": "Test permanent_fail webhook"
|
||||
},
|
||||
"attachments": [],
|
||||
"size": 111
|
||||
},
|
||||
"recipient": "alice@example.com",
|
||||
"event": "failed",
|
||||
"delivery-status": {
|
||||
"attempt-no": 1,
|
||||
"message": "",
|
||||
"code": 605,
|
||||
"description": "Not delivering to previously bounced address",
|
||||
"session-seconds": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,9 +8,9 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
|
||||
let!(:test1) { create(:ci_build, :success, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test2) { create(:ci_build, :skipped, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 0, name: 'build') }
|
||||
|
||||
subject(:execute_service) { described_class.new(project, user).execute(build) }
|
||||
|
||||
|
@ -24,6 +24,34 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
expect(test2.reload).to be_created
|
||||
end
|
||||
|
||||
context 'when there is a job need from the same stage' do
|
||||
let!(:test3) do
|
||||
create(:ci_build,
|
||||
:skipped,
|
||||
pipeline: pipeline,
|
||||
stage_idx: 0,
|
||||
scheduling_type: :dag)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:ci_build_need, build: test3, name: 'build')
|
||||
end
|
||||
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect { execute_service }.to change { test3.reload.status }.from('skipped').to('created')
|
||||
end
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it 'does nothing with the build' do
|
||||
expect { execute_service }.not_to change { test3.reload.status }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is a downstream pipeline and the bridge is depended' do
|
||||
let!(:trigger_job) { create(:ci_bridge, :strategy_depend, status: 'success') }
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
end
|
||||
|
||||
it 'contains both errors and warnings' do
|
||||
error_message = 'build job: need test is not defined in prior stages'
|
||||
error_message = 'build job: need test is not defined in current or prior stages'
|
||||
warning_message = /jobs:test may allow multiple pipelines to run/
|
||||
|
||||
expect(pipeline.yaml_errors).to eq(error_message)
|
||||
|
|
|
@ -84,7 +84,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
it_behaves_like 'returns a non persisted pipeline'
|
||||
|
||||
it 'returns a pipeline with errors', :aggregate_failures do
|
||||
error_message = 'build job: need test is not defined in prior stages'
|
||||
error_message = 'build job: need test is not defined in current or prior stages'
|
||||
|
||||
expect(subject.error_messages.map(&:content)).to eq([error_message])
|
||||
expect(subject.errors).not_to be_empty
|
||||
|
@ -109,7 +109,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
it_behaves_like 'returns a non persisted pipeline'
|
||||
|
||||
it 'returns a pipeline with errors', :aggregate_failures do
|
||||
error_message = "'test' job needs 'build' job, but it was not added to the pipeline"
|
||||
error_message = "'test' job needs 'build' job, but 'build' is not in any previous stage"
|
||||
|
||||
expect(subject.error_messages.map(&:content)).to eq([error_message])
|
||||
expect(subject.errors).not_to be_empty
|
||||
|
|
|
@ -257,7 +257,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
|
||||
it 'returns error' do
|
||||
expect(pipeline.yaml_errors)
|
||||
.to eq("'test' job needs 'build' job, but it was not added to the pipeline")
|
||||
.to eq("'test' job needs 'build' job, but 'build' is not in any previous stage")
|
||||
end
|
||||
|
||||
context 'when need is optional' do
|
||||
|
|
|
@ -252,7 +252,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
|
|||
end
|
||||
|
||||
it_behaves_like 'creation failure' do
|
||||
let(:expected_error) { /test job: dependency generator is not defined in prior stages/ }
|
||||
let(:expected_error) { /test job: dependency generator is not defined in current or prior stages/ }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1715,7 +1715,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
it 'contains the expected errors' do
|
||||
expect(pipeline.builds).to be_empty
|
||||
|
||||
error_message = "'test_a' job needs 'build_a' job, but it was not added to the pipeline"
|
||||
error_message = "'test_a' job needs 'build_a' job, but 'build_a' is not in any previous stage"
|
||||
expect(pipeline.yaml_errors).to eq(error_message)
|
||||
expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
|
||||
expect(pipeline.errors[:base]).to contain_exactly(error_message)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
config:
|
||||
build:
|
||||
stage: test
|
||||
script: exit 0
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script: exit 0
|
||||
needs: [build]
|
||||
|
||||
deploy:
|
||||
stage: test
|
||||
script: exit 0
|
||||
needs: [test]
|
||||
|
||||
init:
|
||||
expect:
|
||||
pipeline: pending
|
||||
stages:
|
||||
test: pending
|
||||
jobs:
|
||||
build: pending
|
||||
test: created
|
||||
deploy: created
|
||||
|
||||
transitions:
|
||||
- event: success
|
||||
jobs: [build]
|
||||
expect:
|
||||
pipeline: running
|
||||
stages:
|
||||
test: running
|
||||
jobs:
|
||||
build: success
|
||||
test: pending
|
||||
deploy: created
|
||||
|
||||
- event: success
|
||||
jobs: [test]
|
||||
expect:
|
||||
pipeline: running
|
||||
stages:
|
||||
test: running
|
||||
jobs:
|
||||
build: success
|
||||
test: success
|
||||
deploy: pending
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ErrorTracking::CollectErrorService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:parsed_event) { Gitlab::Json.parse(fixture_file('error_tracking/parsed_event.json')) }
|
||||
|
||||
subject { described_class.new(project, nil, event: parsed_event) }
|
||||
|
||||
describe '#execute' do
|
||||
it 'creates Error and creates ErrorEvent' do
|
||||
expect { subject.execute }
|
||||
.to change { ErrorTracking::Error.count }.by(1)
|
||||
.and change { ErrorTracking::ErrorEvent.count }.by(1)
|
||||
end
|
||||
|
||||
it 'updates Error and created ErrorEvent on second hit' do
|
||||
subject.execute
|
||||
|
||||
expect { subject.execute }.not_to change { ErrorTracking::Error.count }
|
||||
expect { subject.execute }.to change { ErrorTracking::ErrorEvent.count }.by(1)
|
||||
end
|
||||
|
||||
it 'has correct values set' do
|
||||
subject.execute
|
||||
|
||||
event = ErrorTracking::ErrorEvent.last
|
||||
error = event.error
|
||||
|
||||
expect(error.name).to eq 'ActionView::MissingTemplate'
|
||||
expect(error.description).to start_with 'Missing template posts/error2'
|
||||
expect(error.actor).to eq 'PostsController#error2'
|
||||
expect(error.platform).to eq 'ruby'
|
||||
expect(error.last_seen_at).to eq '2021-07-08T12:59:16Z'
|
||||
|
||||
expect(event.description).to eq 'ActionView::MissingTemplate'
|
||||
expect(event.occurred_at).to eq '2021-07-08T12:59:16Z'
|
||||
expect(event.level).to eq 'error'
|
||||
expect(event.environment).to eq 'development'
|
||||
expect(event.payload).to eq parsed_event
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Members::Mailgun::ProcessWebhookService do
|
||||
describe '#execute', :aggregate_failures do
|
||||
let_it_be(:member) { create(:project_member, :invited) }
|
||||
|
||||
let(:raw_invite_token) { member.raw_invite_token }
|
||||
let(:payload) { { 'user-variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => raw_invite_token } } }
|
||||
|
||||
subject(:service) { described_class.new(payload).execute }
|
||||
|
||||
it 'marks the member invite email success as false' do
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/).and_call_original
|
||||
|
||||
expect { service }.to change { member.reload.invite_email_success }.from(true).to(false)
|
||||
end
|
||||
|
||||
context 'when member can not be found' do
|
||||
let(:raw_invite_token) { '_foobar_' }
|
||||
|
||||
it 'does not change member status' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/)
|
||||
|
||||
expect { service }.not_to change { member.reload.invite_email_success }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite token is not found in payload' do
|
||||
let(:payload) { {} }
|
||||
|
||||
it 'does not change member status and logs an error' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/)
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
|
||||
an_instance_of(described_class::ProcessWebhookServiceError))
|
||||
|
||||
expect { service }.not_to change { member.reload.invite_email_success }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue