Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-02-20 15:11:24 +00:00
parent 92fa4c9f53
commit 07959a9d0d
61 changed files with 705 additions and 1115 deletions

View File

@ -34,7 +34,6 @@ Capybara/VisibilityMatcher:
- 'spec/features/merge_request/user_views_diffs_commit_spec.rb'
- 'spec/features/merge_request/user_views_diffs_spec.rb'
- 'spec/features/projects/blobs/blob_show_spec.rb'
- 'spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb'
- 'spec/features/projects/ci/lint_spec.rb'
- 'spec/features/projects/commit/comments/user_adds_comment_spec.rb'
- 'spec/features/projects/commits/multi_view_diff_spec.rb'

View File

@ -3117,7 +3117,6 @@ Layout/LineLength:
- 'spec/features/projects/blobs/blob_show_spec.rb'
- 'spec/features/projects/blobs/edit_spec.rb'
- 'spec/features/projects/blobs/shortcuts_blob_spec.rb'
- 'spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb'
- 'spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb'
- 'spec/features/projects/ci/editor_spec.rb'
- 'spec/features/projects/commit/cherry_pick_spec.rb'

View File

@ -21,11 +21,6 @@ export default {
type: Object,
required: true,
},
suggestCiYmlData: {
type: Object,
required: false,
default: undefined,
},
},
data() {
return {
@ -56,7 +51,6 @@ export default {
:filename="filename"
:templates="templates"
:initial-template="initialTemplate"
:suggest-ci-yml-data="suggestCiYmlData"
@selected="onTemplateSelected"
/>
</div>

View File

@ -1,6 +1,5 @@
<script>
import { GlCollapsibleListbox } from '@gitlab/ui';
import SuggestGitlabCiYml from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import { __ } from '~/locale';
import { DEFAULT_CI_CONFIG_PATH, CI_CONFIG_PATH_EXTENSION } from '~/lib/utils/constants';
@ -34,7 +33,6 @@ const templateSelectors = [
export default {
name: 'TemplateSelector',
components: {
SuggestGitlabCiYml,
GlCollapsibleListbox,
},
props: {
@ -51,11 +49,6 @@ export default {
required: false,
default: undefined,
},
suggestCiYmlData: {
type: Object,
required: false,
default: undefined,
},
},
data() {
return {
@ -97,9 +90,6 @@ export default {
showDropdown() {
return this.activeType && this.templateItems.length > 0;
},
showPopover() {
return this.activeType?.key === 'gitlab_ci_ymls' && this.suggestCiYmlData;
},
},
beforeMount() {
if (this.activeType) this.applyTemplate(this.initialTemplate);
@ -135,14 +125,6 @@ export default {
</script>
<template>
<div v-if="showDropdown">
<suggest-gitlab-ci-yml
v-if="showPopover"
target="template-selector"
:track-label="suggestCiYmlData.trackLabel"
:dismiss-key="suggestCiYmlData.dismissKey"
:merge-request-path="suggestCiYmlData.mergeRequestPath"
:human-access="suggestCiYmlData.humanAccess"
/>
<gl-collapsible-listbox
id="template-selector"
searchable

View File

@ -1,13 +1,6 @@
import Vue from 'vue';
import FilepathForm from './components/filepath_form.vue';
const getPopoverData = (el) => ({
trackLabel: el.dataset.trackLabel,
dismissKey: el.dataset.dismissKey,
mergeRequestPath: el.dataset.mergeRequestPath,
humanAccess: el.dataset.humanAccess,
});
const getInputOptions = (el) => {
const { testid, qa_selector: qaSelector, ...options } = JSON.parse(el.dataset.inputOptions);
return {
@ -19,15 +12,11 @@ const getInputOptions = (el) => {
export default ({ onTemplateSelected }) => {
const el = document.getElementById('js-template-selectors-menu');
const suggestCiYmlEl = document.querySelector('.js-suggest-gitlab-ci-yml');
const suggestCiYmlData = suggestCiYmlEl ? getPopoverData(suggestCiYmlEl) : undefined;
return new Vue({
el,
render(h) {
return h(FilepathForm, {
props: {
suggestCiYmlData,
inputOptions: getInputOptions(el),
templates: JSON.parse(el.dataset.templates),
initialTemplate: el.dataset.selected,

View File

@ -1,7 +1,6 @@
import $ from 'jquery';
import Api from '~/api';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { createAlert } from '~/alert';
import { __, sprintf } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast';
@ -33,7 +32,6 @@ export default class FilepathFormMediator {
selectTemplateFile(template, type, clearSelectedTemplate, stopLoading) {
const self = this;
const suggestCommitChanges = document.querySelector('.js-suggest-gitlab-ci-yml-commit-changes');
this.fetchFileTemplate(type.type, template.key, template)
.then((file) => {
@ -50,10 +48,6 @@ export default class FilepathFormMediator {
},
},
});
if (suggestCommitChanges) {
initPopover(suggestCommitChanges);
}
})
.catch((err) =>
createAlert({

View File

@ -1,143 +0,0 @@
<script>
import { GlModal, GlSprintf, GlLink, GlButton } from '@gitlab/ui';
import { getCookie, removeCookie } from '~/lib/utils/common_utils';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
export default {
beginnerLink:
'https://about.gitlab.com/blog/2018/01/22/a-beginners-guide-to-continuous-integration/',
goToTrackValuePipelines: 10,
goToTrackValueMergeRequest: 20,
trackEvent: 'click_button',
components: {
GlModal,
GlSprintf,
GlButton,
GlLink,
},
mixins: [trackingMixin],
props: {
goToPipelinesPath: {
type: String,
required: true,
},
projectMergeRequestsPath: {
type: String,
required: false,
default: '',
},
commitCookie: {
type: String,
required: true,
},
humanAccess: {
type: String,
required: true,
},
exampleLink: {
type: String,
required: true,
},
codeQualityLink: {
type: String,
required: true,
},
},
data() {
return {
trackLabel: 'congratulate_first_pipeline',
};
},
computed: {
tracking() {
return {
label: this.trackLabel,
property: this.humanAccess,
};
},
goToMergeRequestPath() {
return this.commitCookiePath || this.projectMergeRequestsPath;
},
commitCookiePath() {
const cookieVal = getCookie(this.commitCookie);
if (cookieVal !== 'true') return cookieVal;
return '';
},
},
i18n: {
modalTitle: __("That's it, well done!"),
pipelinesButton: s__('MR widget|See your pipeline in action'),
mergeRequestButton: s__('MR widget|Back to the merge request'),
bodyMessage: s__(
`MR widget|The pipeline will test your code on every commit. A %{codeQualityLinkStart}code quality report%{codeQualityLinkEnd} will appear in your merge requests to warn you about potential code degradations.`,
),
helpMessage: s__(
`MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to learn more.`,
),
},
mounted() {
this.track();
this.disableModalFromRenderingAgain();
},
methods: {
disableModalFromRenderingAgain() {
removeCookie(this.commitCookie);
},
},
};
</script>
<template>
<gl-modal visible size="sm" modal-id="success-pipeline-modal-id-not-used">
<template #modal-title>
{{ $options.i18n.modalTitle }}
<gl-emoji class="gl-vertical-align-baseline gl-reset-font-size gl-mr-1" data-name="tada" />
</template>
<p>
<gl-sprintf :message="$options.i18n.bodyMessage">
<template #codeQualityLink="{ content }">
<gl-link :href="codeQualityLink" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<gl-sprintf :message="$options.i18n.helpMessage">
<template #beginnerLink="{ content }">
<gl-link :href="$options.beginnerLink" target="_blank">
{{ content }}
</gl-link>
</template>
<template #exampleLink="{ content }">
<gl-link :href="exampleLink" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
<template #modal-footer>
<gl-button
v-if="projectMergeRequestsPath"
ref="goToMergeRequest"
:href="goToMergeRequestPath"
:data-track-property="humanAccess"
:data-track-value="$options.goToTrackValueMergeRequest"
:data-track-action="$options.trackEvent"
:data-track-label="trackLabel"
>
{{ $options.i18n.mergeRequestButton }}
</gl-button>
<gl-button
ref="goToPipelines"
:href="goToPipelinesPath"
variant="confirm"
:data-track-property="humanAccess"
:data-track-value="$options.goToTrackValuePipelines"
:data-track-action="$options.trackEvent"
:data-track-label="trackLabel"
>
{{ $options.i18n.pipelinesButton }}
</gl-button>
</template>
</gl-modal>
</template>

View File

@ -1,142 +0,0 @@
<script>
import { GlPopover, GlSprintf, GlButton } from '@gitlab/ui';
import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__(
`suggestPipeline|Were adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box.`,
),
footer: s__(
`suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code.`,
),
},
suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__(
`suggestPipeline|The template is ready! You can now commit it to create your first pipeline.`,
),
},
};
export default {
name: 'SuggestGitlabCiYml',
dismissTrackValue: 10,
clickTrackValue: 'click_button',
components: {
GlPopover,
GlSprintf,
GlButton,
},
mixins: [trackingMixin],
props: {
target: {
type: String,
required: true,
},
trackLabel: {
type: String,
required: true,
},
dismissKey: {
type: String,
required: true,
},
humanAccess: {
type: String,
required: true,
},
mergeRequestPath: {
type: String,
required: true,
},
},
data() {
return {
popoverDismissed: parseBoolean(getCookie(`${this.trackLabel}_${this.dismissKey}`)),
tracking: {
label: this.trackLabel,
property: this.humanAccess,
},
};
},
computed: {
suggestTitle() {
return popoverStates[this.trackLabel].title || '';
},
suggestContent() {
return popoverStates[this.trackLabel].content || '';
},
suggestFooter() {
return popoverStates[this.trackLabel].footer || '';
},
emoji() {
return popoverStates[this.trackLabel].emoji || '';
},
dismissCookieName() {
return `${this.trackLabel}_${this.dismissKey}`;
},
},
mounted() {
if (
this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' &&
!this.popoverDismissed
) {
scrollToElement(document.querySelector(this.target));
}
this.trackOnShow();
},
methods: {
onDismiss() {
this.popoverDismissed = true;
setCookie(this.dismissCookieName, this.popoverDismissed);
},
trackOnShow() {
if (!this.popoverDismissed) this.track();
},
},
};
</script>
<template>
<gl-popover
v-if="!popoverDismissed"
show
:target="target"
placement="right"
container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
>
<template #title>
<span>{{ suggestTitle }}</span>
<span class="ml-auto">
<gl-button
:aria-label="__('Close')"
class="btn-blank"
name="dismiss"
icon="close"
:data-track-property="humanAccess"
:data-track-value="$options.dismissTrackValue"
:data-track-action="$options.clickTrackValue"
:data-track-label="trackLabel"
@click="onDismiss"
/>
</span>
</template>
<gl-sprintf :message="suggestContent" />
<div class="mt-3">
<gl-sprintf :message="suggestFooter">
<template #bold="{ content }">
<strong> {{ content }} </strong>
</template>
</gl-sprintf>
</div>
</gl-popover>
</template>

View File

@ -1,18 +0,0 @@
import Vue from 'vue';
import Popover from './components/popover.vue';
export default (el) =>
new Vue({
el,
render(createElement) {
return createElement(Popover, {
props: {
target: el.dataset.target,
trackLabel: el.dataset.trackLabel,
dismissKey: el.dataset.dismissKey,
mergeRequestPath: el.dataset.mergeRequestPath,
humanAccess: el.dataset.humanAccess,
},
});
},
});

View File

@ -1,36 +1,7 @@
import $ from 'jquery';
import { createAlert } from '~/alert';
import { setCookie } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
import NewCommitForm from '../new_commit_form';
const initPopovers = () => {
const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
if (suggestEl) {
const commitButton = document.querySelector('#commit-changes');
if (commitButton) {
const { dismissKey, humanAccess } = suggestEl.dataset;
const urlParams = new URLSearchParams(window.location.search);
const mergeRequestPath = urlParams.get('mr_path') || true;
const commitCookieName = `suggest_gitlab_ci_yml_commit_${dismissKey}`;
const commitTrackLabel = 'suggest_gitlab_ci_yml_commit_changes';
const commitTrackValue = '20';
commitButton.addEventListener('click', () => {
setCookie(commitCookieName, mergeRequestPath);
Tracking.event(undefined, 'click_button', {
label: commitTrackLabel,
property: humanAccess,
value: commitTrackValue,
});
});
}
}
};
export default () => {
const editBlobForm = $('.js-edit-blob-form');
@ -59,7 +30,6 @@ export default () => {
isMarkdown,
previewMarkdownPath,
});
initPopovers();
})
.catch((e) =>
createAlert({

View File

@ -5,7 +5,6 @@ import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import { provideWebIdeLink } from 'ee_else_ce/pages/projects/shared/web_ide_link/provide_web_ide_link';
import TableOfContents from '~/blob/components/table_contents.vue';
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
import { BlobViewer, initAuxiliaryViewer } from '~/blob/viewer/index';
import GpgBadges from '~/gpg_badges';
import createDefaultClient from '~/lib/graphql';
@ -191,22 +190,6 @@ if (codeNavEl && !viewBlobEl) {
);
}
const successPipelineEl = document.querySelector('.js-success-pipeline-modal');
if (successPipelineEl) {
// eslint-disable-next-line no-new
new Vue({
el: successPipelineEl,
render(createElement) {
return createElement(PipelineTourSuccessModal, {
props: {
...successPipelineEl.dataset,
},
});
},
});
}
const tableContentsEl = document.querySelector('.js-table-contents');
if (tableContentsEl) {

View File

@ -30,40 +30,34 @@
margin-right: auto;
}
}
}
.new-session-tabs {
display: flex;
border-color: transparent;
.new-session-tabs {
display: flex;
border-color: transparent;
.nav-item {
border-bottom: 1px solid $gray-100;
.nav-item {
flex: 1;
text-align: center;
border-bottom: 1px solid $gray-100;
}
.nav-link.active {
.gl-dark & {
background-color: var(--gray-100);
}
}
}
.nav-link {
width: 100%;
font-size: 18px !important;
&.active {
cursor: default;
li {
flex: 1;
text-align: center;
a {
width: 100%;
font-size: 18px;
}
&.active > a {
cursor: default;
.gl-dark & {
background-color: var(--gray-100);
}
}
}
}
.devise-errors {
@include devise-errors;
}
.devise-errors {
@include devise-errors;
}
@include media-breakpoint-down(sm) {

View File

@ -69,7 +69,7 @@ module AuthHelper
end
def form_based_provider_priority
['crowd', /^ldap/, 'kerberos']
['crowd', /^ldap/]
end
def form_based_provider_with_highest_priority

View File

@ -300,20 +300,6 @@ module BlobHelper
end
end
def show_suggest_pipeline_creation_celebration?
@project.ci_config_path_or_default == @blob.path &&
@blob.auxiliary_viewer&.valid?(project: @project, sha: @commit.sha, user: current_user) &&
cookies[suggest_pipeline_commit_cookie_name].present?
end
def suggest_pipeline_commit_cookie_name
"suggest_gitlab_ci_yml_commit_#{@project.id}"
end
def human_access
@project.team.human_max_access(current_user&.id).try(:downcase)
end
def vue_blob_app_data(project, blob, ref)
{
blob_path: blob.path,

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
module SuggestPipelineHelper
def should_suggest_gitlab_ci_yml?
current_user && params[:suggest_gitlab_ci_yml] == 'true'
end
end

View File

@ -14,6 +14,7 @@ module TabHelper
gl_tabs_classes = %w[nav gl-tabs-nav]
html_options = html_options.merge(
role: 'tablist',
class: [*html_options[:class], gl_tabs_classes].join(' ')
)
@ -42,6 +43,7 @@ module TabHelper
end
html_options = html_options.merge(
role: 'tab',
class: [*html_options[:class], link_classes].join(' ')
)
@ -53,7 +55,7 @@ module TabHelper
extra_tab_classes = html_options.delete(:tab_class)
tab_class = %w[nav-item].push(*extra_tab_classes)
content_tag(:li, class: tab_class) do
content_tag(:li, role: 'presentation', class: tab_class) do
if block
link_to(options, html_options, &block)
else

View File

@ -11,8 +11,10 @@ module Ci
end
def execute
validate_catalog_resource
create_version
track_release_duration do
validate_catalog_resource
create_version
end
if errors.empty?
ServiceResponse.success
@ -25,6 +27,20 @@ module Ci
attr_reader :project, :errors, :release
def track_release_duration
name = :gitlab_ci_catalog_release_duration_seconds
comment = 'CI Catalog Release duration'
buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 240.0]
histogram = ::Gitlab::Metrics.histogram(name, comment, {}, buckets)
start_time = ::Gitlab::Metrics::System.monotonic_time
yield
duration = ::Gitlab::Metrics::System.monotonic_time - start_time
histogram.observe({}, duration.seconds)
end
def validate_catalog_resource
response = Ci::Catalog::Resources::ValidateService.new(project, release.sha).execute
return if response.success?

View File

@ -1,2 +1,2 @@
= gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do
= gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { testid: 'sign-in-tab' } }
= gl_tabs_nav({ class: 'nav-tabs nav-links new-session-tabs' }) do
= gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tabindex: '-1', data: { testid: 'sign-in-tab' } }

View File

@ -1,31 +1,28 @@
- render_standard_signin = admin_mode ? allow_admin_mode_password_authentication_for_web? : password_authentication_enabled_for_web?
%ul.nav-links.new-session-tabs.nav-tabs.nav.nav-links-unboxed
= gl_tabs_nav({ class: 'nav-tabs nav-links new-session-tabs' }) do
- if crowd_enabled?
%li.nav-item
= link_to _("Crowd"), "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab', role: 'tab'
= gl_tab_link_to _('Crowd'), '#crowd', { class: active_when(form_based_auth_provider_has_active_class?(:crowd)), data: { toggle: 'tab' } }
- ldap_servers.each_with_index do |server, i|
%li.nav-item
= link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', testid: 'ldap-tab' }, role: 'tab'
= gl_tab_link_to server['label'], "##{server['provider_name']}", { class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)), data: { toggle: 'tab', testid: 'ldap-tab' } }
= render_if_exists 'devise/shared/tab_smartcard'
- if render_standard_signin
%li.nav-item
= link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', testid: 'standard-tab' }, role: 'tab'
= gl_tab_link_to _('Standard'), '#login-pane', { data: { toggle: 'tab', testid: 'standard-tab' } }
.tab-content
- if crowd_enabled?
.login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
.tab-pane{ id: 'crowd', role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
= render 'devise/sessions/new_crowd', admin_mode: admin_mode
- ldap_servers.each_with_index do |server, i|
.login-box.tab-pane{ id: server['provider_name'], role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) }
.tab-pane{ id: server['provider_name'], role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) }
= render 'devise/sessions/new_ldap', server: server, admin_mode: admin_mode
= render_if_exists 'devise/sessions/new_smartcard'
- if render_standard_signin
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
.tab-pane{ id: 'login-pane', role: 'tabpanel' }
= render 'devise/sessions/new_base', admin_mode: admin_mode

View File

@ -17,13 +17,8 @@
= render 'filepath_form', input_options: input_options
- if current_action?(:new, :create)
- input_options = { id: 'file_name', name: 'file_name', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : ''), required: true, placeholder: "Filename", testid: 'file-name-field', class: 'new-file-name js-file-path-name-input' }
- input_options = { id: 'file_name', name: 'file_name', value: params[:file_name] || '', required: true, placeholder: "Filename", testid: 'file-name-field', class: 'new-file-name js-file-path-name-input' }
= render 'filepath_form', input_options: input_options
- if should_suggest_gitlab_ci_yml?
.js-suggest-gitlab-ci-yml{ data: { track_label: 'suggest_gitlab_ci_yml',
merge_request_path: params[:mr_path],
dismiss_key: @project.id,
human_access: human_access } }
- if Feature.enabled?(:source_editor_toolbar, current_user)
#editor-toolbar

View File

@ -1,6 +0,0 @@
.js-success-pipeline-modal{ data: { 'commit-cookie': suggest_pipeline_commit_cookie_name,
'go-to-pipelines-path': project_pipelines_path(@project),
'project-merge-requests-path': project_merge_requests_path(@project),
'example-link': help_page_path('ci/examples/index'),
'code-quality-link': help_page_path('ci/testing/code_quality'),
'human-access': @project.team.human_max_access(current_user&.id) } }

View File

@ -12,9 +12,3 @@
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
- if should_suggest_gitlab_ci_yml?
.js-suggest-gitlab-ci-yml-commit-changes{ data: { target: '#commit-changes',
merge_request_path: params[:mr_path],
track_label: 'suggest_commit_first_project_gitlab_ci_yml',
dismiss_key: @project.id,
human_access: human_access } }

View File

@ -13,7 +13,6 @@
#tree-holder.tree-holder.gl-pt-4
= render 'blob', blob: @blob
= render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration?
= render 'shared/web_ide_path'
-# https://gitlab.com/gitlab-org/gitlab/-/issues/408388#note_1578533983

View File

@ -16,7 +16,6 @@
window.gl.mrWidgetData.license_compliance_docs_path = '#{help_page_path('user/compliance/license_scanning_of_cyclonedx_files')}';
window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/approvals/rules.md', anchor: 'eligible-approvers')}';
window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/approvals/index.md")}';
window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/empty-state/empty-pipeline-md.svg')}';
window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
window.gl.mrWidgetData.false_positive_doc_url = '#{help_page_path('user/application_security/vulnerabilities/index')}';
window.gl.mrWidgetData.can_view_false_positive = '#{@merge_request.project.licensed_feature_available?(:sast_fp_reduction).to_s}';

View File

@ -1,20 +0,0 @@
description: "Show a congratulation on first pipeline"
category: default
action: generic
label_description: "`congratulate_first_pipeline`"
property_description: "`[admin | maintainer | developer | owner]`"
value_description: ""
extra_properties:
identifiers:
product_section: growth
product_stage: growth
product_group: group::expansion
milestone: "12.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28378
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,20 +0,0 @@
description: "Go to pipeline on pipeline celebration"
category: default
action: click_button
label_description: "`congratulate_first_pipeline`"
property_description: "`[admin | maintainer | developer | owner]`"
value_description: "`10`"
extra_properties:
identifiers:
product_section: growth
product_stage: growth
product_group: group::expansion
milestone: "12.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28378
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,20 +0,0 @@
description: "Dismiss GitLab CI suggestion popover"
category: default
action: click_button
label_description: "[ `suggest_commit_first_project_gitlab_ci_yml` ]"
property_description: "`[admin | maintainer | developer | owner]`"
value_description: "`10`"
extra_properties:
identifiers:
product_section: growth
product_stage: growth
product_group: group::expansion
milestone: "12.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26105
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,8 +0,0 @@
---
name: arkose_labs_trial_signup_challenge
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113985
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/395754
milestone: '15.10'
type: development
group: group::anti-abuse
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: use_primary_and_secondary_stores_for_repository_cache
feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2854
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144548
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442163
milestone: '16.10'
group: group::scalability
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: use_primary_store_as_default_for_repository_cache
feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2854
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144548
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442163
milestone: '16.10'
group: group::scalability
type: gitlab_com_derisk
default_enabled: false

View File

@ -4,7 +4,7 @@ key_path: redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_mo
description: Number of projects using 5 min production app CI template in last 7 days.
product_section: seg
product_stage: deploy
product_group: five_min_app
product_group: 5-min-app
value_type: number
status: removed
milestone_removed: '14.6'

View File

@ -4,7 +4,7 @@ key_path: redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_we
description: Number of projects using 5 min production app CI template in last 7 days.
product_section: seg
product_stage: deploy
product_group: five_min_app
product_group: 5-min-app
value_type: number
status: removed
milestone_removed: '14.6'

View File

@ -33,8 +33,7 @@
]
},
"product_group": {
"type": "string",
"pattern": "^$|^([a-z]+_)*[a-z]+$"
"type": "string"
},
"value_type": {
"type": "string",

View File

@ -0,0 +1,85 @@
---
status: ongoing
creation-date: "2024-01-29"
authors: [ "@jarv" ]
coach:
approvers: [ ]
---
# Disaster Recovery
This document is a work-in-progress and proposes architecture changes for the GitLab.com SaaS.
The goal of these changes are to maintain GitLab.com service continuity in the case a regional or zonal outage.
- A **zonal recovery** is required when all resources are unavailable in one of the three availability zones in `us-east1` or `us-central1`.
- A **regional recovery** is required when all resources become unavailable in one of the regions critical to operation of GitLab.com, either `us-east1` or `us-central1`.
## Services not included in the current DR strategy for FY24 and FY25
We have limited the scope of DR to services that support primary services (Web, API, Git, Pages, Sidekiq, CI, and Registry).
These services tie directly into our overall [availability score](https://dashboards.gitlab.net/d/general-slas/general3a-slas?orgId=1) (internal link) for GitLab.com.
For example, DR does not include the following:
- AI services including code suggestions
- Error tracking and other observability services like tracing
- CustomersDot, responsible for billing and new subscriptions
- Advanced Search
## DR Implementation Targets
The FY24 targets were:
| | Recovery Time Objective (RTO) | Recovery Point Objective (RPO) |
|--------------|-------------------------------|--------------------------------|
| **Zonal** | 2 hours | 1 hour |
| **Regional** | 96 hours | 2 hours |
The FY25 targets before cell architecture are:
| | Recovery Time Objective (RTO) | Recovery Point Objective (RPO) |
|--------------|-------------------------------|--------------------------------|
| **Zonal** | 0 minutes | 0 minutes |
| **Regional** | 48 hours | 0 minutes |
## Current Recovery Time Objective (RTO) and Recovery Point Objective (RPO) for Zonal Recovery
We have not yet simulated a full zonal outage on GitLab.com.
The following are RTO/RPO estimates based on what we have been able to test using the [disaster recovery runbook](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/disaster-recovery?ref_type=heads).
It is assumed that each service can be restored in parallel.
A parallel restore is the only way we are able to meet the FY24 RTO target of 2 hours for a zonal recovery.
| Service | RTO | RPO |
| --- | --- | --- |
| PostgreSQL | 1.5 hr | <=5 min |
| Redis [^1] | 0 | 0 |
| Gitaly | 30 min | <=1 hr |
| CI | 30 min | not applicable |
| Load balancing (HAProxy) | 30 min | not applicable |
| Frontend services (Web, API, Git, Pages, Registry) [^2] | 15 min | 0 |
| Monitoring (Prometheus, Thanos, Grafana, Alerting) | 0 | not applicable |
| Operations (Deployments, runbooks, operational tooling, Chef) [^3] | 30 min | 4 hr |
| PackageCloud (distribution of packages for self-managed) | 0 | 0 |
## Current Recovery Time Objective (RTO) and Recovery Point Objective (RPO) for Regional Recovery
Regional recovery requires a complete rebuild of GitLab.com using backups that are stored in multi-region buckets.
The recovery has not yet been validated end-to-end, so we don't know how long the RTO is for a regional failure.
Our target RTO for FY25 is to have a procedure to recover from a regional outage in under 48 hours.
The following are considerations for choosing multi-region buckets over dual-region buckets:
- We operate out of a single region so multi-region storage is only used for disaster recovery.
- Although Google recommends dual-region for disaster recovery, dual-region is [not an available storage type for disk snapshots](https://cloud.google.com/compute/docs/disks/snapshots#selecting_a_storage_location).
- To mitigate the bandwidth limitation of multi-region buckets, we spread Gitaly VMs infra across multiple projects.
## Proposals for Regional and Zonal Recovery
- [Regional](regional.md)
- [Zonal](zonal.md)
---
[^1]: Most of the Redis load is on the primary node, so losing replicas should not cause any service interruption
[^2]: We setup maximum replicas in our Kubernetes clusters servicing front-end traffic, this is done to avoid saturating downstream dependencies. For a zonal failure, a cluster reconfiguration is necessary to increase these maximums.
[^3]: There is a 4 hr RPO for Operations because Chef is an single point of failure in a single availability zone and our restore method uses disk snapshots, taken every 4 hours. While most of our Chef configuration is also stored in Git, some data (like node registrations) are only stored on the server.

View File

@ -0,0 +1,161 @@
---
status: ongoing
creation-date: "2024-01-29"
authors: [ "@jarv" ]
coach:
approvers: [ ]
---
# Regional Recovery
## Improving the Recovery Time Objective (RTO) and Recovery Point Objective (RPO) for Regional Recovery
The following list the top challenges that limit our ability to drive `RTO` to 48 hours for a regional recovery.
1. We have a large amount of legacy infrastructure managed using Chef. This configuration has been difficult for us to manage and would require a large a mount of manual copying and duplication to create new infrastructure in an alternate region.
1. Operational infrastructure is located in a single region, `us-central1`. For a regional failure in this region, it requires rebuilding the ops infrastructure with only local copies of runbooks and tooling scripts.
1. Observability is hosted in a single region.
1. The infrastructure (`dev.gitlab.org`) that builds Docker images and packages is located in a single region, and is a single point of failure.
1. There is no launch-pad that would allow us to get a head-start on a regional recovery. Our IaC (Infrastructure-as-Code) does not allow us to switch regions for provisioning.
1. We don't have confidence that Google can provide us with the capacity we need in a new region, specifically the large amount of SSD necessary to restore all of our customer Git data.
1. We use [Global DNS](https://cloud.google.com/compute/docs/internal-dns) for internal DNS making it difficult to use multiple instances with the same name across multiple regions, we also don't incorporate regions into DNS names for our internal endpoints (for example dashboards, logs, etc).
1. If we deploy replicas in another region to reduce RPO we are not yet sure of the latency or cloud spend impacts.
1. We have special/negotiated Quota increases for Compute, Network, and API with the Google Cloud Platform only for a single region, we have to match these quotas in a new region, and keep them in sync.
1. We have not standardized a way to divert traffic at the edge from 1 region to another.
1. In monitoring, and configuration we have places where we hardcode the region to `us-east1`.
## Regional recovery work-streams
The first step of our regional recovery plan creates new infrastructure in the recovery region that involves a large number of manual steps.
To give us a head-start on recovery, we propose a "regional bulkhead" deployment in a new GCP region.
A "regional bulkhead" meets the following requirements:
1. A specific region is allocated.
1. Quotas are set and synced so that we can duplicate all of us-east1 in the new region.
1. Subnets are allocated or reserved in the same VPC for us-east1.
1. Some infrastructure is deployed where it makes sense to lower RTO, while keeping cloud-spend low.
The following are work-streams that can be done mostly in parallel.
The end-goal of the regional recovery is to have a bulkhead that has the basic scaffolding for deployment in the alternate region.
This bulkhead can be used as a launching pad for a full data restore from `us-east1` to the alternate region.
### Select an alternate region
- Dependencies: none
- Teams: Ops
The following are considerations that need to be made when selecting an alternate region for DR:
1. Ensure there is enough capacity to meet compute usage.
1. Network and network latency requirements, if any.
1. Feature parity between regions.
### Deploy Kubernetes clusters supporting front-end services in a new region with deployments
- Dependencies: [External front-end load balancing](#external-front-end-load-balancing)
- Teams: Ops, Foundations, Delivery
GitLab.com has Web, API, Git, Git HTTPs, Git SSH, Pages, and Registry as front-end services.
All of these services are run in 4 Kubernetes clusters deployed in `us-east1`.
These services are either stateless or use multi-region storage buckets for data.
In the case of a failure in `us-east1`, we would need to rebuild these clusters in the alternate region and set them up for deployments.
### Switch from Global to Zonal DNS
- Dependencies: None
- Teams: Gitaly
Gitaly VMs are single points of failure that are deployed in `us-east1`.
The internal DNS naming of the nodes have the following convention:
```plaintext
gitaly-01-stor-gprd.c.gitlab-gitaly-gprd-ccb0.internal
^ name ^ project
```
By switching to zonal DNS, we can change the internal DNS entries so they have the zone in the DNS name:
```plaintext
gitaly-01-stor-gprd.c.us-east1-b.gitlab-gitaly-gprd-ccb0.internal
^ name ^ zone ^ project
```
Allowing us to keep the same name when recovering into a new region or zone.
```plaintext
gitaly-01-stor-gprd.c.us-east1-b.gitlab-gitaly-gprd-ccb0.internal
gitaly-01-stor-gprd.c.us-east4-a.gitlab-gitaly-gprd-ccb0.internal
```
For fleets of VMs outside of Kubernetes, these names allow us to have the same node names in the recovery region.
### Gitaly
- Dependencies: [Switch from Global to Zonal DNS](#switch-from-global-to-zonal-dns) (optional, but desired)
- Teams: Gitaly, Ops, Foundations
Restoring the entire Gitaly fleet requires a large number of VMs deployed in the alternate region.
It also requires a lot of bandwidth because restore is based on disk snapshots.
To ensure a successful Gitaly restore, quotas need to be synced with us-east1 and there needs to be end-to-end validation.
### PostgreSQL
- Dependencies: [Improve Chef provisioning time by using preconfigured golden OS images](zonal.md#improve-chef-provisioning-time-by-using-preconfigured-golden-os-images) (optional, but desired), local backups in the standby region (data disk snapshot and `WAL` archiving).
- Teams: Database Reliability, Ops
The configuration for Patroni provisioning only allows a single region per cluster.
There is networking infrastructure, Consul, and load balancers that need to be setup in the alternate region.
We may consider setting up a "cascaded cluster" for the databases to improve recovery time for replication.
### Redis
- Dependencies: [Improve Chef provisioning time by using preconfigured golden OS images](zonal.md#improve-chef-provisioning-time-by-using-preconfigured-golden-os-images) (optional, but desired)
- Teams: Ops
To provision Redis subnets need to be allocated in the alternate region with and end-to-end validation of the new deployments.
### External front-end load balancing
- Dependencies: HAProxy replacement, mostly likely [GKE Gateway and Istio](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/1157)
- Teams: Ops, Foundations
External front-end load balancing is necessary to validate the deployment in the alternate region.
This requires both external and internal LBs for all front-end-services.
### Monitoring
- Dependencies: [Eliminate X% Chef dependencies in Infra by moving infra away from Chef](zonal.md#eliminate-x-chef-dependencies-in-infra-by-moving-infra-away-from-chef) (migrate Prometheus infra to Kubernetes)
- Teams: Scalability:Observability, Ops, Foundations
Setup an alternate ops Kubernetes cluster in a different region that is scaled down to zero replicas.
### Runners
Dependencies: [Improve Chef provisioning time by using preconfigured golden OS images](zonal.md#improve-chef-provisioning-time-by-using-preconfigured-golden-os-images) (optional, but desired)
Teams: Scalability:Practices, Ops, Foundations
Ensure quotas are set and align with us-east1 in the alternate region for both runner managers and ephemeral VMs.
Setup and validate networking configuration with peering configuration.
### Ops and Packaging
- Dependencies: [Create an HA Chef server configuration to avoid an outage for a single zone failure](zonal.md#create-an-ha-chef-server-configuration-to-avoid-an-outage-for-a-single-zone-failure)
- Teams: Scalability:Practices, Ops, Foundations, Distribution
All image creation and packaging is done on a single VM, our operation tooling is also on a single VM.
Both of these are single points of failures that have data stored locally.
In the case of a regional outage, we would need to rebuild them from snapshot and lose about 4 hours of data.
The following are options to mitigate this risk:
- Move our packaging jobs to `ops.gitlab.net` so we eliminate `dev.gitlab.org` as a single point of failure.
- Use the Geo feature for `ops.gitlab.net`.
### Regional Recovery Gameday
- Dependencies: Recovery improvements
- Teams: Ops
Following the improvements for regional recovery, a Gameday needs to be executed for end-to-end testing of the procedure.
Once validated, it can be added to our existing [disaster recovery runbook](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/disaster-recovery?ref_type=heads).

View File

@ -0,0 +1,102 @@
---
status: ongoing
creation-date: "2024-01-29"
authors: [ "@jarv" ]
coach:
approvers: [ ]
---
# Zonal Recovery
## Improving the Recovery Time Objective (RTO) and Recovery Point Objective (RPO) for Zonal Recovery
The following represents our current DR challenges and are candidates for problems that we should address in this architecture blueprint.
1. Postgres replicas run close to capacity and are scaled manually. New instances must go through Terraform CI pipelines and Chef configuration. Over-provisioning to absorb a zone failure would add significant cloud-spend (see proposal section at the end of the document for details).
1. HAProxy (load balancing) is scaled manually and must go through Terraform CI pipelines and Chef configuration.
1. CI runner managers are present in 2 availability zones and scaled close to capacity. New instances must go through Terraform CI pipelines and Chef configuration.
1. In a zone there are saturation limits, like the number of replicas that need to be manually adjusted if load is shifted away from a failed availability zone.
1. Gitaly `RPO` is limited by the frequency of disk snapshots, `RTO` is limited by the time it takes to provision and configure through Terraform CI pipelines and Chef configuration.
1. Monitoring infrastructure that collects metrics from Chef managed VMs is redundant across 2 availability zones and scaled manually. New instances must go through Terraform CI pipelines and Chef configuration.
1. The Chef server which is responsible for all configuration of Chef managed VMs is a single point of failure located in `us-central1`. It has a local Postgres database and files on local disk.
1. The infrastructure (`dev.gitlab.org`) that builds Docker images and packages is located in a single region, and is a single point of failure.
## Zonal recovery work-streams
Improvements around zonal recovery revolve around improving the time it takes to provision for fleets that do not automatically scale.
There is already work in-progress to completely eliminate statically allocated VMs like HAProxy.
Additionally efforts can be made to shorten launch and configuration times for fleets that are not able to automatically scale like Gitaly, PostgreSQL and Redis.
### Over-provision to absorb a single zone failure
- Dependencies: None
- Teams: Ops, Scalability:Practices, Database Reliability
All of our Chef managed VM fleets run close to capacity and require manual scaling and provisioning using Terraform/Chef.
In the case of a zonal outage, it is necessary to provision more servers through Terraform which adds to our recovery time objective.
One way to avoid this is to over-provision so we have a full zone's worth of extra capacity.
1. Patroni Main (`n2-highmem-128` 6.5k/month): 3 additional nodes for +20k/month
1. Patroni CI (`n2-highmem-96` 5k/month): 3 additional nodes for +15k/month
1. HAProxy (`t2d-standard-8` 285/month): 20 additional nodes for +5k/month
1. CI Runner managers (`c2-standard-30` 1.3k/month) 60 additional nodes for +78k/month
The Kubernetes horizontal auto-scaler (`HPA`) has a maximum number of pods configured on front-end services.
It is configured to protect downstream dependencies like the database from saturation due to scaling events.
If we allow a zone to scale up rapidly, these limits need to be adjusted or re-evaluated in the context of disaster recovery.
### Remove HAProxy as a load balancing layer
- Dependencies: None
- Teams: Foundations
HAProxy is a fleet of Chef managed VMs that are statically allocated across 3 AZs in `us-east1`.
In the case of a zonal outage we would need to rapidly scale this fleet, adding to our RTO.
In FY24Q4 the Foundations team started working on a proof-of-concept to use [Istio in non-prod environments](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/1157).
We anticipate in FY25 to have a replacement for HAProxy using Istio and [GKE Gateway](https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api).
Completing this work reduces the impact to our LoadBalancing layer for zonal outages, as it eliminates the need to manually scale the HAProxy fleet.
Additionally, we spend around 17k/month on HAProxy nodes, so there may be a cloud-spend reduction if we are able to reduce this footprint.
### Create an HA Chef server configuration to avoid an outage for a single zone failure
- Dependencies: None
- Teams: Ops
Chef is responsible for configuring VMs that have workloads outside of Kubernetes.
It is a single point of failure that resides in `us-central1-b`.
Data is persisted locally on disk, and we have not yet investigated moving it to a highly available setup.
In the case of a zonal outage of `us-central1-b` the server would need to be rebuilt from snapshot, losing up to 4 hours of data.
### Create an HA Packaging server (`dev.gitlab.org`) configuration to avoid an outage for a single zone failure
- Dependencies: None
- Teams: Ops
In the case of a zonal outage of `us-east1-c` the server would need to be rebuilt from snapshot, losing up to 4 hours of data.
The additional challenge of this host is that it is a GitLab-CE instance so we would be limited in features.
The best approach here would likely be to move packaging CI pipelines to `ops.gitlab.net`.
### Improve Chef provisioning time by using preconfigured golden OS images
- Dependencies: None
- Teams: Ops
For the [Gitaly fleet upgrade in 2022](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/601) a scheduled CI pipeline was created to build a golden OS images.
We can revive this work and start generating images for Gitaly and other VMs to shorten configuration time.
We estimate that using an image can reduce our recovery time by about 15 minutes to improve RTO for zonal failures.
### Eliminate X% Chef dependencies in Infra by moving infra away from Chef
- Dependencies: None
- Teams: Ops, Scalability:Observability, Scalability:Practices
Gitaly, Postgres, CI runner managers, HAProxy, Bastion, CustomersDot, Deploy, DB Lab, Prometheus, Redis, SD Exporter Sentry, and Console servers are managed by Chef.
To help improve the speed of recoveries, we can move this infrastructure into Kubernetes or Ansible for configuration management.
### Write-ahead-log for Gitaly snapshot restores
- Dependencies: None
- Teams: Gitaly
There is [work planned in FY25Q1](https://gitlab.com/gitlab-com/gitlab-OKRs/-/work_items/5710) that adds a transaction log for Gitaly to reduce RPO.

View File

@ -254,7 +254,7 @@ Code Quality can be customized by defining available CI/CD variables:
| `ENGINE_MEMORY_LIMIT_BYTES` | Set the memory limit for engines. Default: 1,024,000,000 bytes. |
| `REPORT_STDOUT` | Set to print the report to `STDOUT` instead of generating the usual report file. |
| `REPORT_FORMAT` | Set to control the format of the generated report file. Either `json` or `html`. |
| `SOURCE_CODE` | Path to the source code to scan. |
| `SOURCE_CODE` | Path to the source code to scan. Must be the absolute path to a directory where cloned sources are stored. |
| `TIMEOUT_SECONDS` | Custom timeout per engine container for the `codeclimate analyze` command. Default: 900 seconds (15 minutes) |
## Output

View File

@ -20,7 +20,6 @@ Before you enable these features, ensure [hard email confirmation](../security/u
| `identity_verification_phone_number` | Turns on phone verification for medium risk users for all flows (the Arkose challenge flag for the specific flow and the `identity_verification` flag must be enabled for this to have effect) |
| `identity_verification_credit_card` | Turns on credit card verification for high risk users for all flows (the Arkose challenge flag for the specific flow and the `identity_verification` flag must be enabled for this to have effect) |
| `arkose_labs_signup_challenge` | Enables Arkose challenge for all flows, except the Trial and OAuth flows |
| `arkose_labs_trial_signup_challenge` | Enables Arkose challenge for the Trial flow (the `arkose_labs_signup_challenge` flag must be enabled as well for this to have effect) |
| `arkose_labs_oauth_signup_challenge` | Enables Arkose challenge for the OAuth flow |
## Logging

View File

@ -14,10 +14,7 @@ Alerts are a critical entity in your incident management workflow. They represen
## Alert list
Users with at least the Developer role can
access the Alert list at **Monitor > Alerts** in your project's
sidebar. The Alert list displays alerts sorted by start time, but
you can change the sort order by selecting the headers in the Alert list.
Users with at least the Developer role can access the Alert list at **Monitor > Alerts** in your project's sidebar. The Alert list displays alerts sorted by start time, but you can change the sort order by selecting the headers in the Alert list.
The alert list displays the following information:
@ -43,9 +40,7 @@ The alert list displays the following information:
## Alert severity
Each level of alert contains a uniquely shaped and color-coded icon to help
you identify the severity of a particular alert. These severity icons help you
immediately identify which alerts you should prioritize investigating:
Each level of alert contains a uniquely shaped and color-coded icon to help you identify the severity of a particular alert. These severity icons help you immediately identify which alerts you should prioritize investigating:
![Alert Management Severity System](img/alert_management_severity_v13_0.png)
@ -66,14 +61,9 @@ Alerts contain one of the following icons:
## Alert details page
Navigate to the Alert details view by visiting the [Alert list](alerts.md)
and selecting an alert from the list. You need at least the Developer role
to access alerts.
for this demo project. Select any alert in the list to examine its alert details
page.
Navigate to the Alert details view by visiting the [Alert list](alerts.md) and selecting an alert from the list. You need at least the Developer role to access alerts. Select any alert in the list to examine its alert details page.
Alerts provide **Overview** and **Alert details** tabs to give you the right
amount of information you need.
Alerts provide **Overview** and **Alert details** tabs to give you the right amount of information you need.
### Alert details tab
@ -84,8 +74,7 @@ The **Alert details** tab has two sections. The top section provides a short lis
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.2.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/340852) in GitLab 14.10. In GitLab 14.9 and earlier, this tab shows a metrics chart for alerts coming from Prometheus.
In many cases, alerts are associated to metrics. You can upload screenshots of metric
charts in the **Metrics** tab.
In many cases, alerts are associated to metrics. You can upload screenshots of metric charts in the **Metrics** tab.
To do so, either:
@ -102,8 +91,7 @@ If you add a link, it is shown above the uploaded image.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
The **Activity feed** tab is a log of activity on the alert. When you take action on an alert, this is logged as a system note. This gives you a linear
timeline of the alert's investigation and assignment history.
The **Activity feed** tab is a log of activity on the alert. When you take action on an alert, this is logged as a system note. This gives you a linear timeline of the alert's investigation and assignment history.
The following actions result in a system note:
@ -144,8 +132,7 @@ To change an alert's status:
1. On the right sidebar, select **Edit**.
1. Select a status.
To stop email notifications for alert recurrences in projects with [email notifications enabled](paging.md#email-notifications-for-alerts),
change the alert's status away from **Triggered**.
To stop email notifications for alert recurrences in projects with [email notifications enabled](paging.md#email-notifications-for-alerts), change the alert's status away from **Triggered**.
#### Resolve an alert by closing the linked incident
@ -153,9 +140,7 @@ Prerequisites:
- You must have at least the Reporter role.
When you [close an incident](manage_incidents.md#close-an-incident) that is linked to an alert,
GitLab [changes the alert's status](#change-an-alerts-status) to **Resolved**.
You are then credited with the alert's status change.
When you [close an incident](manage_incidents.md#close-an-incident) that is linked to an alert, GitLab [changes the alert's status](#change-an-alerts-status) to **Resolved**. You are then credited with the alert's status change.
#### As an on-call responder
@ -163,8 +148,7 @@ DETAILS:
**Tier:** Premium, Ultimate
**Offering:** SaaS, self-managed
On-call responders can respond to [alert pages](paging.md#escalating-an-alert)
by changing the alert status.
On-call responders can respond to [alert pages](paging.md#escalating-an-alert) by changing the alert status.
Changing the status has the following effects:
@ -172,16 +156,13 @@ Changing the status has the following effects:
- To **Resolved**: silences all on-call pages for the alert.
- From **Resolved** to **Triggered**: restarts the alert escalating.
In GitLab 15.1 and earlier, updating the status of an [alert with an associated incident](manage_incidents.md#from-an-alert)
also updates the incident status. In [GitLab 15.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/356057),
the incident status is independent and does not update when the alert status changes.
In GitLab 15.1 and earlier, updating the status of an [alert with an associated incident](manage_incidents.md#from-an-alert) also updates the incident status. In [GitLab 15.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/356057), the incident status is independent and does not update when the alert status changes.
### Assign an alert
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
In large teams, where there is shared ownership of an alert, it can be
difficult to track who is investigating and working on it. Assigning alerts eases collaboration and delegation by indicating which user is owning the alert. GitLab supports only a single assignee per alert.
In large teams, where there is shared ownership of an alert, it can be difficult to track who is investigating and working on it. Assigning alerts eases collaboration and delegation by indicating which user is owning the alert. GitLab supports only a single assignee per alert.
To assign an alert:
@ -201,16 +182,13 @@ To assign an alert:
From the list, select each user you want to assign to the alert.
GitLab creates a [to-do item](../../user/todos.md) for each user.
After completing their portion of investigating or fixing the alert, users can
unassign themselves from the alert. To remove an assignee, select **Edit** next to the **Assignee** dropdown list
and clear the user from the list of assignees, or select **Unassigned**.
After completing their portion of investigating or fixing the alert, users can unassign themselves from the alert. To remove an assignee, select **Edit** next to the **Assignee** dropdown list and clear the user from the list of assignees, or select **Unassigned**.
### Create a to-do item from an alert
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
You can manually create a [to-do item](../../user/todos.md) for yourself
from an alert, and view it later on your **To-Do List**.
You can manually create a [to-do item](../../user/todos.md) for yourself from an alert, and view it later on your **To-Do List**.
To add a to-do item, on the right sidebar, select **Add a to do**.

View File

@ -51,12 +51,11 @@ GitLab checks certificate revocation lists on a daily basis with a background wo
`subjectKeyIdentifier`, and `crlDistributionPoints` display as **Unverified**. We
recommend using certificates from a PKI that are in line with
[RFC 5280](https://www.rfc-editor.org/rfc/rfc5280).
- If you have more than one email in the Subject Alternative Name list in
- In GitLab 16.2 and earlier, if you have more than one email in the Subject Alternative Name list in
your signing certificate,
[only the first one is used to verify commits](https://gitlab.com/gitlab-org/gitlab/-/issues/336677).
- The `X509v3 Subject Key Identifier` (SKI) in the issuer certificate and the
signing certificate
[must be 40 characters long](https://gitlab.com/gitlab-org/gitlab/-/issues/332503).
- In GitLab 15.1 and earlier, the `X509v3 Subject Key Identifier` (SKI) in the issuer certificate and the
signing certificate [must be 40 characters long](https://gitlab.com/gitlab-org/gitlab/-/issues/332503).
If your SKI is shorter, commits don't show as verified in GitLab, and
short subject key identifiers may also
[cause errors when accessing the project](https://gitlab.com/gitlab-org/gitlab/-/issues/332464),
@ -254,9 +253,8 @@ To investigate why a commit shows as `Unverified`:
sigemail == commitemail
```
A [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/336677) exists:
only the first email in the `Subject Alternative Name` list is compared. To
display the `Subject Alternative Name` list, run:
In GitLab 16.2 and earlier, [only the first email](https://gitlab.com/gitlab-org/gitlab/-/issues/336677)
in the `Subject Alternative Name` list is compared. To display the `Subject Alternative Name` list, run:
```ruby
signature.__send__ :get_certificate_extension,'subjectAltName'
@ -347,12 +345,32 @@ step of the previous [main verification checks](#main-verification-checks).
1. After adding more certificates, (if these troubleshooting steps then pass)
run the Rake task to [re-verify commits](#re-verify-commits).
1. Display the certificates, including in the signature:
1. You can add additional certificates dynamically in the Rails console to check
if this resolves the problem.
1. Retest the signature with a trust store `cert_store` that can be modified.
It should still fail, with `false`:
```ruby
cert_store = signature.__send__ :cert_store
signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
```
1. Add an additional certificate, and re-test:
```ruby
cert_store.add_file("/etc/ssl/certs/my_new_root_ca.pem")
signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
```
1. Display the certificates that are included in the signature:
```ruby
pp signature.__send__(:p7).certificates ; nil
```
1. [Further investigation can be performed with OpenSSL on the command line](#smime-verification-with-openssl).
Ensure any additional intermediate certificates and the root certificate are added
to the certificate store. For consistency with how certificate chains are built on
web servers:
@ -364,3 +382,144 @@ web servers:
If you remove a root certificate from the GitLab
trust store, such as when it expires, commit signatures which chain back to that
root display as `unverified`.
#### S/MIME verification with OpenSSL
If there are issues with the signature, or if TLS trust fails, further debugging can
be performed with OpenSSL on the command line.
Export the signature and the signed text, from the [Rails console](../../../../administration/operations/rails_console.md#starting-a-rails-console-session):
1. The initial two steps from [the main verification checks](#main-verification-checks) are required so `signature` has been set.
1. OpenSSL requires that PKCS7 PEM formatted data is bounded with `BEGIN PKCS7` and `END PKCS7` so this usually needs to be fixed:
```ruby
pkcs7_text = signature.signature_text.sub('-----BEGIN SIGNED MESSAGE-----', '-----BEGIN PKCS7-----')
pkcs7_text = pkcs7_text.sub('-----END SIGNED MESSAGE-----', '-----END PKCS7-----')
```
1. Write out the signature and signed text:
```ruby
f1=File.new('/tmp/signature_text.pk7.pem','w')
f1 << pkcs7_text
f1.close
f2=File.new('/tmp/signed_text.txt','w')
f2 << signature.signed_text
f2.close
```
This data can now be investigated on the Linux command line using OpenSSL:
1. The PKCS #7 file containing the signature can be queried:
```shell
/opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
-in /tmp/signature_text.pk7.pem -print -noout
```
It should include at least one `cert` section in the output; the signer's certificate.
There's a lot of low level of detail in the output. Here's an example of some of the structure and headings that should be present:
```plaintext
PKCS7:
d.sign:
cert:
cert_info:
issuer:
validity:
notBefore:
notAfter:
subject:
```
If developers' code signing certificates are issued by an intermediate certificate authority,
there should be additional certificate details:
```plaintext
PKCS7:
d.sign:
cert:
cert_info:
cert:
cert_info:
```
1. Extract the certificate from the signature:
```shell
/opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
-in /tmp/signature_text.pk7.pem -out /tmp/signature_cert.pem
```
If this step fails, the signature might be missing the signer's certificate.
- Fix this issue on the Git client.
- The following step will fail, but if you copy the signer's certificate to the
GitLab server, you can use that to do some testing using `-nointern -certfile signerscertificate.pem`.
1. Partially verify the commit, using the extracted certificate:
```shell
/opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
-in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
-noverify -certfile /tmp/signature_cert.pem -nointern
```
The output usually includes:
- The parent commit
- The name, email, and timestamp from the commit
- The commit text
- `Verification successful` (or similar)
This check is not the same as the check GitLab performs, because:
- It does not verify the signer's certificate (`-noverify`)
- The verification is done using the supplied `-certfile` rather than the one in the message (`-nointern`)
1. Partially verify the commit using the certificate in the message:
```shell
/opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
-in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
-noverify
```
This should get the same result as the previous step, using the extracted certificate.
If the message is missing the certificate, the error will include `signer certificate not found`.
1. Fully verify the commit:
```shell
/opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
-in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt
```
If this step fails, verification will also fail in GitLab.
Resolve any errors, for example:
- `certificate verify error .. unable to get local issuer certificate`:
- The trust chain couldn't be established.
- This OpenSSL binary uses the GitLab trust store. Either the root certificate is missing from the trust store
or the signature is missing the intermediate certificate(s) and a chain to a trusted root can't be built.
- Intermediate certificates can be put in the trust store if it's not possible to include them in the signature.
- [The procedure for adding certificates](https://docs.gitlab.com/omnibus/settings/ssl/#install-custom-public-certificates)
to the trust store for packaged GitLab - using `/etc/gitlab/trusted-certs`.
- Test additional trusted certificates using OpenSSL with: `-CAfile /path/to/rootcertificate.pem`
- `unsupported certificate purpose`:
- The certificate must specify `Digital Signature` under `Key Usage`.
- This is usually in the `X509v3 Key Usage` section of the signer's certificate.
- There is also a `X509v3 Extended Key Usage` section: if this is specified, it must include `Digital Signature` as well.
See [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#page-44) for more details:
> If there is no purpose consistent with both (Key Usage) extensions, then the certificate MUST NOT be used for any purpose.
- `signer certificate not found`, either:
- You have added the `-nointern` argument, but not supplied `-certfile`.
- The signature is missing the signer's certificate.

View File

@ -15,7 +15,7 @@ code_quality:
DOCKER_SOCKET_PATH: /var/run/docker.sock
needs: []
script:
- export SOURCE_CODE=$PWD
- export SOURCE_CODE=${SOURCE_CODE:-$PWD}
- |
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" ] && [ -n "$KUBERNETES_PORT" ]; then

View File

@ -10,6 +10,7 @@ module Gitlab
ALL_CLASSES = [
Gitlab::Redis::BufferedCounter,
Gitlab::Redis::Cache,
Gitlab::Redis::ClusterRepositoryCache,
Gitlab::Redis::DbLoadBalancing,
Gitlab::Redis::FeatureFlag,
Gitlab::Redis::Queues,

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module Redis
class ClusterRepositoryCache < ::Gitlab::Redis::Wrapper
class << self
# The data we store on RepositoryCache used to be stored on Cache.
def config_fallback
Cache
end
end
end
end
end

View File

@ -144,7 +144,7 @@ module Gitlab
@primary_pool = primary_pool
@secondary_pool = secondary_pool
@borrow_counter = "multi_store_borrowed_connection_#{instance_name}".to_sym
@borrow_counter = :"multi_store_borrowed_connection_#{instance_name}"
validate_stores!
end
@ -252,18 +252,24 @@ module Gitlab
end
def use_primary_and_secondary_stores?
feature_flag = "use_primary_and_secondary_stores_for_#{instance_name.underscore}"
# We interpolate the feature flag name within `Feature.enabled?` instead of defining a variable to allow
# `RuboCop::Cop::Gitlab::MarkUsedFeatureFlags`'s optimistic matching to work.
feature_table_exists? &&
Feature.enabled?(feature_flag, type: feature_flag_type(feature_flag)) && # rubocop:disable Cop/FeatureFlagUsage -- The flags are dynamic
Feature.enabled?( # rubocop:disable Cop/FeatureFlagUsage -- The flags are dynamic
"use_primary_and_secondary_stores_for_#{instance_name.underscore}",
type: feature_flag_type("use_primary_and_secondary_stores_for_#{instance_name.underscore}")
) &&
!same_redis_store?
end
def use_primary_store_as_default?
feature_flag = "use_primary_store_as_default_for_#{instance_name.underscore}"
# We interpolate the feature flag name within `Feature.enabled?` instead of defining a variable to allow
# `RuboCop::Cop::Gitlab::MarkUsedFeatureFlags`'s optimistic matching to work.
feature_table_exists? &&
Feature.enabled?(feature_flag, type: feature_flag_type(feature_flag)) && # rubocop:disable Cop/FeatureFlagUsage -- The flags are dynamic
Feature.enabled?( # rubocop:disable Cop/FeatureFlagUsage -- The flags are dynamic
"use_primary_store_as_default_for_#{instance_name.underscore}",
type: feature_flag_type("use_primary_store_as_default_for_#{instance_name.underscore}")
) &&
!same_redis_store?
end

View File

@ -2,7 +2,7 @@
module Gitlab
module Redis
class RepositoryCache < ::Gitlab::Redis::Wrapper
class RepositoryCache < ::Gitlab::Redis::MultiStoreWrapper
# We create a subclass only for the purpose of differentiating between different stores in cache metrics
RepositoryCacheStore = Class.new(ActiveSupport::Cache::RedisCacheStore)
@ -21,6 +21,10 @@ module Gitlab
expires_in: Cache.default_ttl_seconds
)
end
def multistore
MultiStore.new(ClusterRepositoryCache.pool, pool, store_name)
end
end
end
end

View File

@ -29701,18 +29701,6 @@ msgstr ""
msgid "MLExperimentTracking|Delete experiment?"
msgstr ""
msgid "MR widget|Back to the merge request"
msgstr ""
msgid "MR widget|See your pipeline in action"
msgstr ""
msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to learn more."
msgstr ""
msgid "MR widget|The pipeline will test your code on every commit. A %{codeQualityLinkStart}code quality report%{codeQualityLinkEnd} will appear in your merge requests to warn you about potential code degradations."
msgstr ""
msgid "MRApprovals|Approvals"
msgstr ""
@ -49725,9 +49713,6 @@ msgstr ""
msgid "That's OK, I don't want to renew"
msgstr ""
msgid "That's it, well done!"
msgstr ""
msgid "The %{plan_name} is no longer available to purchase. For more information about how this will impact you, check our %{faq_link_start}frequently asked questions%{faq_link_end}."
msgstr ""
@ -60268,21 +60253,6 @@ msgstr ""
msgid "success"
msgstr ""
msgid "suggestPipeline|1/2: Choose a template"
msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
msgid "suggestPipeline|Were adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "supported SSH public key."
msgstr ""

View File

@ -2,6 +2,8 @@
# frozen_string_literal: true
ENV['RAILS_ENV'] = 'test'
require 'optparse'
require 'open3'
require 'fileutils'
@ -49,6 +51,7 @@ class SchemaRegenerator
#
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135085#note_1628210334 for more info.
#
load_tasks
drop_db
checkout_ref
checkout_clean_schema
@ -67,6 +70,11 @@ class SchemaRegenerator
private
def load_tasks
require_relative '../config/environment'
Gitlab::Application.load_tasks
end
##
# Git checkout +CI_COMMIT_SHA+.
#
@ -181,25 +189,25 @@ class SchemaRegenerator
##
# Run rake task to drop the database.
def drop_db
run %q(bin/rails db:drop RAILS_ENV=test)
run_rake_task 'db:drop'
end
##
# Run rake task to setup the database.
def setup_db
run %q(bin/rails db:setup RAILS_ENV=test)
run_rake_task 'db:setup'
end
##
# Run rake task to run migrations.
def migrate
run %q(bin/rails db:migrate RAILS_ENV=test)
run_rake_task 'db:migrate'
end
##
# Run rake task to dump schema.
def dump_schema
run %q(bin/rails db:schema:dump RAILS_ENV=test)
run_rake_task 'db:schema:dump'
end
##
@ -226,6 +234,15 @@ class SchemaRegenerator
stdout_str
end
def run_rake_task(*tasks, env: {})
Array.wrap(tasks).each do |task|
env.each { |k, v| ENV[k.to_s] = v.to_s }
puts "\e[32m$ bin/rails #{task} RAILS_ENV=test #{env.map { |m| m.join('=') }.join(' ')}\e[37m"
Rake::Task[task].invoke
end
end
##
# Return the base commit between source and target branch.
def merge_base

View File

@ -1,62 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled', :js, feature_category: :source_code_management do
include CookieHelper
let(:project) { create(:project, :empty_repo) }
let(:user) { project.first_owner }
describe 'viewing the new blob page' do
before do
sign_in(user)
end
context 'when the page is loaded from the link using the suggest_gitlab_ci_yml param' do
before do
visit namespace_project_new_blob_path(namespace_id: project.namespace, project_id: project, id: 'master', suggest_gitlab_ci_yml: 'true')
end
it 'pre-fills .gitlab-ci.yml for file name' do
file_name = page.find_by_id('file_name')
expect(file_name.value).to have_content('.gitlab-ci.yml')
end
it 'displays suggest_gitlab_ci_yml popover' do
popover_selector = '.suggest-gitlab-ci-yml'
expect(page).to have_css(popover_selector, visible: true)
page.within(popover_selector) do
expect(page).to have_content('1/2: Choose a template')
end
end
it 'sets the commit cookie when the Commit button is clicked' do
click_button 'Commit changes'
expect(get_cookie("suggest_gitlab_ci_yml_commit_#{project.id}")).to be_present
end
end
context 'when the page is visited without the param' do
before do
visit namespace_project_new_blob_path(namespace_id: project.namespace, project_id: project, id: 'master')
end
it 'does not pre-fill .gitlab-ci.yml for file name' do
file_name = page.find_by_id('file_name')
expect(file_name.value).not_to have_content('.gitlab-ci.yml')
end
it 'does not display suggest_gitlab_ci_yml popover' do
popover_selector = '.b-popover.suggest-gitlab-ci-yml'
expect(page).not_to have_css(popover_selector, visible: true)
end
end
end
end

View File

@ -1,10 +1,3 @@
export const SuggestCiYmlData = {
trackLabel: 'suggest_gitlab_ci_yml',
dismissKey: '10',
mergeRequestPath: 'mr_path',
humanAccess: 'owner',
};
export const Templates = {
licenses: {
Other: [

View File

@ -2,14 +2,12 @@ import { GlCollapsibleListbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import TemplateSelector from '~/blob/filepath_form/components/template_selector.vue';
import SuggestGitlabCiYml from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import { Templates as TemplatesMock, SuggestCiYmlData as SuggestCiYmlDataMock } from './mock_data';
import { Templates as TemplatesMock } from './mock_data';
describe('Template Selector component', () => {
let wrapper;
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findSuggestCiYmlPopover = () => wrapper.findComponent(SuggestGitlabCiYml);
const findDisplayedTemplates = () =>
findListbox()
.props('items')
@ -39,10 +37,6 @@ describe('Template Selector component', () => {
it('does not render listbox', () => {
expect(findListbox().exists()).toBe(false);
});
it('does not render suggest-ci-yml popover', () => {
expect(findSuggestCiYmlPopover().exists()).toBe(false);
});
});
describe.each`
@ -62,26 +56,6 @@ describe('Template Selector component', () => {
expect(findListbox().props('searchPlaceholder')).toBe('Filter');
expect(findDisplayedTemplates()).toEqual(getTemplateKeysFromMock(key));
});
it('does not render suggest-ci-yml popover', () => {
expect(findSuggestCiYmlPopover().exists()).toBe(false);
});
});
describe('when filename input is .gitlab-ci.yml with suggestCiYmlData prop', () => {
beforeEach(() => {
createComponent({ filename: '.gitlab-ci.yml', suggestCiYmlData: SuggestCiYmlDataMock });
});
it('renders listbox with correct props', () => {
expect(findListbox().exists()).toBe(true);
expect(findListbox().props('toggleText')).toBe('Apply a template');
expect(findListbox().props('searchPlaceholder')).toBe('Filter');
});
it('renders suggest-ci-yml popover', () => {
expect(findSuggestCiYmlPopover().exists()).toBe(true);
});
});
describe('has filename that matches template pattern', () => {

View File

@ -1,10 +0,0 @@
const modalProps = {
goToPipelinesPath: 'some_pipeline_path',
projectMergeRequestsPath: 'some_mr_path',
commitCookie: 'some_cookie',
humanAccess: 'maintainer',
exampleLink: '/example',
codeQualityLink: '/code-quality-link',
};
export default modalProps;

View File

@ -1,127 +0,0 @@
import { GlSprintf, GlModal, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Cookies from '~/lib/utils/cookies';
import { stubComponent } from 'helpers/stub_component';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import pipelineTourSuccess from '~/blob/pipeline_tour_success_modal.vue';
import modalProps from './pipeline_tour_success_mock_data';
describe('PipelineTourSuccessModal', () => {
let wrapper;
let cookieSpy;
let trackingSpy;
const GlEmoji = { template: '<img/>' };
const createComponent = () => {
wrapper = shallowMount(pipelineTourSuccess, {
propsData: modalProps,
stubs: {
GlModal: stubComponent(GlModal, {
template: `
<div>
<slot name="modal-title"></slot>
<slot></slot>
<slot name="modal-footer"></slot>
</div>`,
}),
GlSprintf,
GlEmoji,
},
});
};
beforeEach(() => {
document.body.dataset.page = 'projects:blob:show';
trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
cookieSpy = jest.spyOn(Cookies, 'remove');
createComponent();
});
afterEach(() => {
unmockTracking();
Cookies.remove(modalProps.commitCookie);
});
describe('when the commitCookie contains the mr path', () => {
const expectedMrPath = 'expected_mr_path';
beforeEach(() => {
Cookies.set(modalProps.commitCookie, expectedMrPath);
createComponent();
});
it('renders the path from the commit cookie for back to the merge request button', () => {
const goToMrBtn = wrapper.findComponent({ ref: 'goToMergeRequest' });
expect(goToMrBtn.attributes('href')).toBe(expectedMrPath);
});
});
describe('when the commitCookie does not contain mr path', () => {
const expectedMrPath = modalProps.projectMergeRequestsPath;
beforeEach(() => {
Cookies.set(modalProps.commitCookie, true);
createComponent();
});
it('renders the path from projectMergeRequestsPath for back to the merge request button', () => {
const goToMrBtn = wrapper.findComponent({ ref: 'goToMergeRequest' });
expect(goToMrBtn.attributes('href')).toBe(expectedMrPath);
});
});
it('has expected structure', () => {
const modal = wrapper.findComponent(GlModal);
const sprintf = modal.findComponent(GlSprintf);
const emoji = modal.findComponent(GlEmoji);
expect(wrapper.text()).toContain("That's it, well done!");
expect(sprintf.exists()).toBe(true);
expect(emoji.exists()).toBe(true);
});
it('renders the link for codeQualityLink', () => {
expect(wrapper.findComponent(GlLink).attributes('href')).toBe('/code-quality-link');
});
it('calls to remove cookie', () => {
wrapper.vm.disableModalFromRenderingAgain();
expect(cookieSpy).toHaveBeenCalledWith(modalProps.commitCookie);
});
describe('tracking', () => {
it('send event for basic view of modal', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
label: 'congratulate_first_pipeline',
property: modalProps.humanAccess,
});
});
it('send an event when go to pipelines is clicked', () => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
const goToBtn = wrapper.findComponent({ ref: 'goToPipelines' });
triggerEvent(goToBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'congratulate_first_pipeline',
property: modalProps.humanAccess,
value: '10',
});
});
it('sends an event when back to the merge request is clicked', () => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
const goToBtn = wrapper.findComponent({ ref: 'goToMergeRequest' });
triggerEvent(goToBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'congratulate_first_pipeline',
property: modalProps.humanAccess,
value: '20',
});
});
});
});

View File

@ -1,118 +0,0 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import * as utils from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils', () => ({
...jest.requireActual('~/lib/utils/common_utils'),
scrollToElement: jest.fn(),
}));
const target = 'gitlab-ci-yml-selector';
const dismissKey = '99';
const defaultTrackLabel = 'suggest_gitlab_ci_yml';
const commitTrackLabel = 'suggest_commit_first_project_gitlab_ci_yml';
const dismissCookie = 'suggest_gitlab_ci_yml_99';
const humanAccess = 'owner';
const mergeRequestPath = '/some/path';
describe('Suggest gitlab-ci.yml Popover', () => {
let wrapper;
function createWrapper(trackLabel) {
wrapper = shallowMount(Popover, {
propsData: {
target,
trackLabel,
dismissKey,
mergeRequestPath,
humanAccess,
},
stubs: {
'gl-popover': { template: '<div><slot name="title"></slot><slot></slot></div>' },
},
});
}
describe('when no dismiss cookie is set', () => {
beforeEach(() => {
createWrapper(defaultTrackLabel);
});
it('sets popoverDismissed to false', () => {
expect(wrapper.vm.popoverDismissed).toEqual(false);
});
});
describe('when the dismiss cookie is set', () => {
beforeEach(() => {
utils.setCookie(dismissCookie, true);
createWrapper(defaultTrackLabel);
});
it('sets popoverDismissed to true', () => {
expect(wrapper.vm.popoverDismissed).toEqual(true);
});
afterEach(() => {
utils.removeCookie(dismissCookie);
});
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
document.body.dataset.page = 'projects:blob:new';
trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
createWrapper(commitTrackLabel);
});
afterEach(() => {
unmockTracking();
});
it('sends a tracking event with the expected properties for the popover being viewed', () => {
const expectedCategory = undefined;
const expectedAction = undefined;
const expectedLabel = 'suggest_commit_first_project_gitlab_ci_yml';
const expectedProperty = 'owner';
expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, {
label: expectedLabel,
property: expectedProperty,
});
});
it('sends a tracking event when the popover is dismissed', () => {
const expectedLabel = commitTrackLabel;
const expectedAction = 'click_button';
const expectedProperty = 'owner';
const expectedValue = '10';
const dismissButton = wrapper.findComponent(GlButton);
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
triggerEvent(dismissButton.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', expectedAction, {
label: expectedLabel,
property: expectedProperty,
value: expectedValue,
});
});
});
describe('when the popover is mounted with the trackLabel of the Confirm button popover at the bottom of the page', () => {
it('calls scrollToElement so that the Confirm button and popover will be in sight', () => {
const scrollToElementSpy = jest.spyOn(utils, 'scrollToElement');
createWrapper(commitTrackLabel);
expect(scrollToElementSpy).toHaveBeenCalled();
});
});
});

View File

@ -1,6 +1,5 @@
import $ from 'jquery';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import blobBundle from '~/blob_edit/blob_bundle';
@ -64,46 +63,6 @@ describe('BlobBundle', () => {
});
});
describe('Suggest Popover', () => {
let trackingSpy;
beforeEach(() => {
setHTMLFixture(`
<div class="js-edit-blob-form" data-blob-filename="blah" id="target">
<div class="js-suggest-gitlab-ci-yml"
data-target="#target"
data-track-label="suggest_gitlab_ci_yml"
data-dismiss-key="1"
data-human-access="owner"
data-merge-request-path="path/to/mr">
<button id='commit-changes' class="js-commit-button"></button>
<button id='cancel-changes'></button>
</div>
</div>`);
trackingSpy = mockTracking('_category_', $('#commit-changes').element, jest.spyOn);
document.body.dataset.page = 'projects:blob:new';
blobBundle();
});
afterEach(() => {
unmockTracking();
resetHTMLFixture();
});
it('sends a tracking event when the commit button is clicked', () => {
$('#commit-changes').click();
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'suggest_gitlab_ci_yml_commit_changes',
property: 'owner',
value: '20',
});
});
});
describe('Error handling', () => {
let message;
beforeEach(() => {

View File

@ -207,83 +207,6 @@ RSpec.describe BlobHelper do
end
end
end
describe '#show_suggest_pipeline_creation_celebration?' do
let(:current_user) { create(:user) }
before do
assign(:project, project)
assign(:blob, blob)
assign(:commit, double('Commit', sha: 'whatever'))
helper.request.cookies["suggest_gitlab_ci_yml_commit_#{project.id}"] = 'true'
allow(helper).to receive(:current_user).and_return(current_user)
end
context 'when file is a pipeline config file' do
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: project.ci_config_path_or_default, data: data) }
it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end
context 'file is invalid format' do
let(:data) { 'foo' }
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'does not use the default ci config' do
before do
project.ci_config_path = 'something_bad'
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'does not have the needed cookie' do
before do
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'blob does not have auxiliary view' do
before do
allow(blob).to receive(:auxiliary_viewer).and_return(nil)
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end
context 'when file is not a pipeline config file' do
let(:blob) { fake_blob(path: 'LICENSE') }
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end
end
describe 'suggest_pipeline_commit_cookie_name' do
let(:project) { create(:project) }
it 'uses project id to make up the cookie name' do
assign(:project, project)
expect(helper.suggest_pipeline_commit_cookie_name).to eq "suggest_gitlab_ci_yml_commit_#{project.id}"
end
end
describe '#ide_edit_path' do

View File

@ -7,7 +7,7 @@ RSpec.describe TabHelper do
describe 'gl_tabs_nav' do
it 'creates a tabs navigation' do
expect(helper.gl_tabs_nav).to match(%r{<ul class="nav gl-tabs-nav"></ul>})
expect(helper.gl_tabs_nav).to match(%r{<ul role="tablist" class="nav gl-tabs-nav"></ul>})
end
it 'captures block output' do
@ -25,7 +25,7 @@ RSpec.describe TabHelper do
end
it 'creates a tab' do
expect(helper.gl_tab_link_to('Link', '/url')).to eq('<li class="nav-item"><a class="nav-link gl-tab-nav-item" href="/url">Link</a></li>')
expect(helper.gl_tab_link_to('Link', '/url')).to eq('<li role="presentation" class="nav-item"><a role="tab" class="nav-link gl-tab-nav-item" href="/url">Link</a></li>')
end
it 'creates a tab with block output' do
@ -33,19 +33,19 @@ RSpec.describe TabHelper do
end
it 'creates a tab with custom classes for enclosing list item without content block provided' do
expect(helper.gl_tab_link_to('Link', '/url', { tab_class: 'my-class' })).to match(/<li class=".*my-class.*"/)
expect(helper.gl_tab_link_to('Link', '/url', { tab_class: 'my-class' })).to match(/<li role="presentation" class=".*my-class.*"/)
end
it 'creates a tab with custom classes for enclosing list item with content block provided' do
expect(helper.gl_tab_link_to('/url', { tab_class: 'my-class' }) { 'Link' }).to match(/<li class=".*my-class.*"/)
expect(helper.gl_tab_link_to('/url', { tab_class: 'my-class' }) { 'Link' }).to match(/<li role="presentation" class=".*my-class.*"/)
end
it 'creates a tab with custom classes for anchor element' do
expect(helper.gl_tab_link_to('Link', '/url', { class: 'my-class' })).to match(/<a class=".*my-class.*"/)
expect(helper.gl_tab_link_to('Link', '/url', { class: 'my-class' })).to match(/<a class=".*my-class.*" role="tab"/)
end
it 'creates an active tab with item_active = true' do
expect(helper.gl_tab_link_to('Link', '/url', { item_active: true })).to match(/<a class=".*active gl-tab-nav-item-active.*"/)
expect(helper.gl_tab_link_to('Link', '/url', { item_active: true })).to match(/<a role="tab" class=".*active gl-tab-nav-item-active.*"/)
end
context 'when on the active page' do
@ -54,11 +54,11 @@ RSpec.describe TabHelper do
end
it 'creates an active tab' do
expect(helper.gl_tab_link_to('Link', '/url')).to match(/<a class=".*active gl-tab-nav-item-active.*"/)
expect(helper.gl_tab_link_to('Link', '/url')).to match(/<a role="tab" class=".*active gl-tab-nav-item-active.*"/)
end
it 'creates an inactive tab with item_active = false' do
expect(helper.gl_tab_link_to('Link', '/url', { item_active: false })).not_to match(/<a class=".*active.*"/)
expect(helper.gl_tab_link_to('Link', '/url', { item_active: false })).not_to match(/<a role="tab" class=".*active.*"/)
end
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Redis::ClusterRepositoryCache, feature_category: :scalability do
include_examples "redis_new_instance_shared_examples", 'cluster_repository_cache', Gitlab::Redis::Cache
end

View File

@ -4,10 +4,16 @@ require 'spec_helper'
RSpec.describe Gitlab::Redis::RepositoryCache, feature_category: :scalability do
include_examples "redis_new_instance_shared_examples", 'repository_cache', Gitlab::Redis::Cache
include_examples "multi_store_wrapper_shared_examples"
describe '.cache_store' do
it 'has a default ttl of 8 hours' do
expect(described_class.cache_store.options[:expires_in]).to eq(8.hours)
end
end
it 'migrates from self to ClusterRepositoryCache' do
expect(described_class.multistore.secondary_pool).to eq(described_class.pool)
expect(described_class.multistore.primary_pool).to eq(Gitlab::Redis::ClusterRepositoryCache.pool)
end
end

View File

@ -4,6 +4,31 @@ require 'spec_helper'
RSpec.describe Ci::Catalog::Resources::ReleaseService, feature_category: :pipeline_composition do
describe '#execute' do
context 'when executing release service' do
let(:histogram) { instance_double(Prometheus::Client::Histogram) }
before do
allow(Gitlab::Metrics).to receive(:histogram).and_call_original
allow(::Gitlab::Metrics).to receive(:histogram).with(
:gitlab_ci_catalog_release_duration_seconds,
'CI Catalog Release duration',
{},
[0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 240.0]
).and_return(histogram)
allow(::Gitlab::Metrics::System).to receive(:monotonic_time).and_call_original
end
it 'tracks release duration' do
project = create(:project, :catalog_resource_with_components)
release = create(:release, project: project, sha: project.repository.root_ref_sha)
expect(histogram).to receive(:observe).with({}, an_instance_of(Float))
described_class.new(release).execute
end
end
context 'with a valid catalog resource and release' do
it 'validates the catalog resource and creates a version' do
project = create(:project, :catalog_resource_with_components)

View File

@ -3733,7 +3733,6 @@
- './spec/features/projects/blobs/blob_show_spec.rb'
- './spec/features/projects/blobs/edit_spec.rb'
- './spec/features/projects/blobs/shortcuts_blob_spec.rb'
- './spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb'
- './spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb'
- './spec/features/projects/branches/download_buttons_spec.rb'
- './spec/features/projects/branches/new_branch_ref_dropdown_spec.rb'

View File

@ -62,7 +62,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do
render
expect(rendered).to have_selector('[data-testid="ldap-tab"]')
expect(rendered).to have_css('.login-box#ldapmain')
expect(rendered).to have_css('#ldapmain')
expect(rendered).to have_field(_('Username'))
expect(rendered).not_to have_content('No authentication methods configured')
end