Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
01a8b31afe
commit
6d9c4dc2ef
|
|
@ -1 +1 @@
|
|||
40d58655a42f71b6180a3cbaf369cc20b60e695a
|
||||
2ecaa5afbef22c84b4ce31fcda2a246753a966a3
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import { linkRegex } from '../../utils';
|
||||
|
||||
import LineNumber from './line_number.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -16,15 +18,45 @@ export default {
|
|||
render(h, { props }) {
|
||||
const { line, path } = props;
|
||||
|
||||
const chars = line.content.map(content => {
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
class: ['gl-white-space-pre-wrap', content.style],
|
||||
},
|
||||
content.text,
|
||||
);
|
||||
});
|
||||
let chars;
|
||||
if (gon?.features?.ciJobLineLinks) {
|
||||
chars = line.content.map(content => {
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
class: ['gl-white-space-pre-wrap', content.style],
|
||||
},
|
||||
// Simple "tokenization": Split text in chunks of text
|
||||
// which alternate between text and urls.
|
||||
content.text.split(linkRegex).map(chunk => {
|
||||
// Return normal string for non-links
|
||||
if (!chunk.match(linkRegex)) {
|
||||
return chunk;
|
||||
}
|
||||
return h(
|
||||
'a',
|
||||
{
|
||||
attrs: {
|
||||
href: chunk,
|
||||
rel: 'nofollow noopener noreferrer', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
},
|
||||
},
|
||||
chunk,
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
chars = line.content.map(content => {
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
class: ['gl-white-space-pre-wrap', content.style],
|
||||
},
|
||||
content.text,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return h('div', { class: 'js-line log-line' }, [
|
||||
h(LineNumber, {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
// capture anything starting with http:// or https://
|
||||
// up until a disallowed character or whitespace
|
||||
export const linkRegex = /(https?:\/\/[^"<>\\^`{|}\s]+)/g;
|
||||
export default { linkRegex };
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { GlTooltipDirective, GlIcon, GlButton, GlDropdownItem } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
|
||||
import ReplyButton from './note_actions/reply_button.vue';
|
||||
|
|
@ -14,7 +14,8 @@ export default {
|
|||
components: {
|
||||
GlIcon,
|
||||
ReplyButton,
|
||||
GlLoadingIcon,
|
||||
GlButton,
|
||||
GlDropdownItem,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -170,6 +171,15 @@ export default {
|
|||
name: this.projectName,
|
||||
});
|
||||
},
|
||||
resolveIcon() {
|
||||
if (!this.isResolving) {
|
||||
return this.isResolved ? 'check-circle-filled' : 'check-circle';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
resolveVariant() {
|
||||
return this.isResolved ? 'success' : 'default';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onEdit() {
|
||||
|
|
@ -233,24 +243,23 @@ export default {
|
|||
:title="displayContributorBadgeText"
|
||||
>{{ __('Contributor') }}</span
|
||||
>
|
||||
<div v-if="canResolve" class="note-actions-item">
|
||||
<button
|
||||
<div v-if="canResolve" class="gl-ml-2">
|
||||
<gl-button
|
||||
ref="resolveButton"
|
||||
v-gl-tooltip
|
||||
size="small"
|
||||
category="tertiary"
|
||||
:variant="resolveVariant"
|
||||
:class="{ 'is-disabled': !resolvable, 'is-active': isResolved }"
|
||||
:title="resolveButtonTitle"
|
||||
:aria-label="resolveButtonTitle"
|
||||
type="button"
|
||||
:icon="resolveIcon"
|
||||
:loading="isResolving"
|
||||
class="line-resolve-btn note-action-button"
|
||||
@click="onResolve"
|
||||
>
|
||||
<template v-if="!isResolving">
|
||||
<gl-icon :name="isResolved ? 'check-circle-filled' : 'check-circle'" />
|
||||
</template>
|
||||
<gl-loading-icon v-else inline />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="canAwardEmoji" class="note-actions-item">
|
||||
<div v-if="canAwardEmoji" class="gl-ml-3 gl-mr-2">
|
||||
<a
|
||||
v-gl-tooltip
|
||||
:class="{ 'js-user-authored': isAuthoredByCurrentUser }"
|
||||
|
|
@ -261,7 +270,7 @@ export default {
|
|||
>
|
||||
<gl-icon class="link-highlight award-control-icon-neutral" name="slight-smile" />
|
||||
<gl-icon class="link-highlight award-control-icon-positive" name="smiley" />
|
||||
<gl-icon class="link-highlight award-control-icon-super-positive" name="smiley" />
|
||||
<gl-icon class="link-highlight award-control-icon-super-positive" name="smile" />
|
||||
</a>
|
||||
</div>
|
||||
<reply-button
|
||||
|
|
@ -270,72 +279,57 @@ export default {
|
|||
class="js-reply-button"
|
||||
@startReplying="$emit('startReplying')"
|
||||
/>
|
||||
<div v-if="canEdit" class="note-actions-item">
|
||||
<button
|
||||
<div v-if="canEdit" class="gl-ml-2">
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
title="Edit comment"
|
||||
icon="pencil"
|
||||
size="small"
|
||||
category="tertiary"
|
||||
class="note-action-button js-note-edit btn btn-transparent"
|
||||
data-qa-selector="note_edit_button"
|
||||
@click="onEdit"
|
||||
>
|
||||
<gl-icon name="pencil" class="link-highlight" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showDeleteAction" class="note-actions-item">
|
||||
<button
|
||||
<div v-if="showDeleteAction" class="gl-ml-2">
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
title="Delete comment"
|
||||
size="small"
|
||||
icon="remove"
|
||||
category="tertiary"
|
||||
class="note-action-button js-note-delete btn btn-transparent"
|
||||
@click="onDelete"
|
||||
>
|
||||
<gl-icon name="remove" class="link-highlight" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item">
|
||||
<button
|
||||
<div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions gl-ml-2">
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
title="More actions"
|
||||
icon="ellipsis_v"
|
||||
size="small"
|
||||
category="tertiary"
|
||||
class="note-action-button more-actions-toggle btn btn-transparent"
|
||||
data-toggle="dropdown"
|
||||
@click="closeTooltip"
|
||||
>
|
||||
<gl-icon class="icon" name="ellipsis_v" />
|
||||
</button>
|
||||
/>
|
||||
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
|
||||
<li v-if="canReportAsAbuse">
|
||||
<a :href="reportAbusePath">{{ __('Report abuse to admin') }}</a>
|
||||
</li>
|
||||
<li v-if="noteUrl">
|
||||
<button
|
||||
:data-clipboard-text="noteUrl"
|
||||
type="button"
|
||||
class="btn-default btn-transparent js-btn-copy-note-link"
|
||||
>
|
||||
{{ __('Copy link') }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="canAssign">
|
||||
<button
|
||||
class="btn-default btn-transparent"
|
||||
data-testid="assign-user"
|
||||
type="button"
|
||||
@click="assignUser"
|
||||
>
|
||||
{{ displayAssignUserText }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="canEdit">
|
||||
<button
|
||||
class="btn btn-transparent js-note-delete js-note-delete"
|
||||
type="button"
|
||||
@click.prevent="onDelete"
|
||||
>
|
||||
<span class="text-danger">{{ __('Delete comment') }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<gl-dropdown-item v-if="canReportAsAbuse" :href="reportAbusePath">
|
||||
{{ __('Report abuse to admin') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
v-if="noteUrl"
|
||||
class="js-btn-copy-note-link"
|
||||
:data-clipboard-text="noteUrl"
|
||||
>
|
||||
{{ __('Copy link') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="canAssign" data-testid="assign-user" @click="assignUser">
|
||||
{{ displayAssignUserText }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="canEdit" class="js-note-delete" @click.prevent="onDelete">
|
||||
<span class="text-danger">{{ __('Delete comment') }}</span>
|
||||
</gl-dropdown-item>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="note-actions-item">
|
||||
<div class="gl-ml-2">
|
||||
<gl-button
|
||||
ref="button"
|
||||
v-gl-tooltip
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
});
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
});
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import initCreateCluster from '~/create_cluster/init_create_cluster';
|
||||
import initIntegrationForm from '~/clusters/forms/show/index';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initCreateCluster(document, gon);
|
||||
initIntegrationForm();
|
||||
});
|
||||
initCreateCluster(document, gon);
|
||||
initIntegrationForm();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import PersistentUserCallout from '~/persistent_user_callout';
|
||||
import initClustersListApp from '~/clusters_list';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const callout = document.querySelector('.gcp-signup-offer');
|
||||
PersistentUserCallout.factory(callout);
|
||||
initClustersListApp();
|
||||
});
|
||||
const callout = document.querySelector('.gcp-signup-offer');
|
||||
PersistentUserCallout.factory(callout);
|
||||
initClustersListApp();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
||||
initNewCluster();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
initClusterHealth();
|
||||
});
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
initClusterHealth();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Activities from '~/activities';
|
|||
import { localTimeAgo } from '~/lib/utils/datetime_utility';
|
||||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import { __ } from '~/locale';
|
||||
import { deprecatedCreateFlash as flash } from '~/flash';
|
||||
import ActivityCalendar from './activity_calendar';
|
||||
import UserOverviewBlock from './user_overview_block';
|
||||
|
||||
|
|
@ -63,9 +62,9 @@ import UserOverviewBlock from './user_overview_block';
|
|||
*/
|
||||
|
||||
const CALENDAR_TEMPLATE = `
|
||||
<div class="clearfix calendar">
|
||||
<div class="calendar">
|
||||
<div class="js-contrib-calendar"></div>
|
||||
<div class="calendar-hint bottom-right"></div>
|
||||
<div class="calendar-hint"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -214,7 +213,17 @@ export default class UserTabs {
|
|||
|
||||
AjaxCache.retrieve(calendarPath)
|
||||
.then(data => UserTabs.renderActivityCalendar(data, $calendarWrap))
|
||||
.catch(() => flash(__('There was an error loading users activity calendar.')));
|
||||
.catch(() => {
|
||||
const cWrap = $calendarWrap[0];
|
||||
cWrap.querySelector('.spinner').classList.add('invisible');
|
||||
cWrap.querySelector('.user-calendar-error').classList.remove('invisible');
|
||||
cWrap.querySelector('.user-calendar-error .js-retry-load').addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
cWrap.querySelector('.user-calendar-error').classList.add('invisible');
|
||||
cWrap.querySelector('.spinner').classList.remove('invisible');
|
||||
this.loadActivityCalendar();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static renderActivityCalendar(data, $calendarWrap) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlAlert, GlButton, GlEmptyState, GlSprintf } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
|
|
@ -17,11 +17,15 @@ export default {
|
|||
DagAnnotations,
|
||||
DagGraph,
|
||||
GlAlert,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
GlButton,
|
||||
GlEmptyState,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
},
|
||||
inject: {
|
||||
aboutDagDocPath: {
|
||||
default: null,
|
||||
},
|
||||
dagDocPath: {
|
||||
default: null,
|
||||
},
|
||||
|
|
@ -89,14 +93,14 @@ export default {
|
|||
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
|
||||
},
|
||||
emptyStateTexts: {
|
||||
title: __('Start using Directed Acyclic Graphs (DAG)'),
|
||||
title: __('Speed up your pipelines with Needs relationships'),
|
||||
firstDescription: __(
|
||||
"This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph.",
|
||||
'Using the %{codeStart}needs%{codeEnd} keyword makes jobs run before their stage is reached. Jobs run as soon as their %{codeStart}needs%{codeEnd} relationships are met, which speeds up your pipelines.',
|
||||
),
|
||||
secondDescription: __(
|
||||
'Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines.',
|
||||
"If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}.",
|
||||
),
|
||||
button: __('Learn more about job dependencies'),
|
||||
button: __('Learn more about Needs relationships'),
|
||||
},
|
||||
computed: {
|
||||
failure() {
|
||||
|
|
@ -222,6 +226,9 @@ export default {
|
|||
<template #code="{ content }">
|
||||
<code>{{ content }}</code>
|
||||
</template>
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="aboutDagDocPath">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ const createDagApp = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const { pipelineProjectPath, pipelineIid, emptySvgPath, dagDocPath } = el?.dataset;
|
||||
const {
|
||||
aboutDagDocPath,
|
||||
dagDocPath,
|
||||
emptySvgPath,
|
||||
pipelineProjectPath,
|
||||
pipelineIid,
|
||||
} = el?.dataset;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
|
|
@ -26,10 +32,11 @@ const createDagApp = () => {
|
|||
},
|
||||
apolloProvider,
|
||||
provide: {
|
||||
aboutDagDocPath,
|
||||
dagDocPath,
|
||||
emptySvgPath,
|
||||
pipelineProjectPath,
|
||||
pipelineIid,
|
||||
emptySvgPath,
|
||||
dagDocPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('dag', {});
|
||||
|
|
|
|||
|
|
@ -14,14 +14,11 @@
|
|||
.str-truncated {
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.user-calendar-activities-loading {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-calendar {
|
||||
text-align: center;
|
||||
min-height: 172px;
|
||||
|
||||
.calendar {
|
||||
display: inline-block;
|
||||
|
|
@ -42,12 +39,9 @@
|
|||
|
||||
.calendar-hint {
|
||||
font-size: 12px;
|
||||
|
||||
&.bottom-right {
|
||||
direction: ltr;
|
||||
margin-top: -23px;
|
||||
float: right;
|
||||
}
|
||||
direction: ltr;
|
||||
margin-top: -23px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.pika-single.gitlab-theme {
|
||||
|
|
|
|||
|
|
@ -337,11 +337,11 @@ table.pipeline-project-metrics tr td {
|
|||
}
|
||||
|
||||
.user-access-role {
|
||||
@include gl-px-3;
|
||||
display: inline-block;
|
||||
color: $gl-text-color-secondary;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
padding: 0 $label-padding;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $label-border-radius;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
|
|
|
|||
|
|
@ -801,7 +801,7 @@ $note-form-margin-left: 72px;
|
|||
}
|
||||
|
||||
.note-role {
|
||||
margin: 0 3px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -892,6 +892,15 @@ $note-form-margin-left: 72px;
|
|||
outline: 0;
|
||||
transition: color $general-hover-transition-duration $general-hover-transition-curve;
|
||||
|
||||
&[disabled] {
|
||||
padding: 0 8px !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
.gl-button-loading-indicator {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
@ -899,16 +908,22 @@ $note-form-margin-left: 72px;
|
|||
&:not(.is-disabled) {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $green-600;
|
||||
svg {
|
||||
color: $green-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $green-600;
|
||||
svg {
|
||||
@include gl-text-green-500;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $green-700;
|
||||
svg {
|
||||
color: $green-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ module Projects
|
|||
end
|
||||
|
||||
def integration
|
||||
return unless Feature.enabled?(:multiple_http_integrations, project)
|
||||
|
||||
AlertManagement::HttpIntegrationsFinder.new(
|
||||
project,
|
||||
endpoint_identifier: endpoint_identifier,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
class Projects::AvatarsController < Projects::ApplicationController
|
||||
include SendsBlob
|
||||
|
||||
skip_before_action :default_cache_headers, only: :show
|
||||
|
||||
before_action :authorize_admin_project!, only: [:destroy]
|
||||
|
||||
feature_category :projects
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
before_action :verify_api_request!, only: :terminal_websocket_authorize
|
||||
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
|
||||
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
|
||||
before_action do
|
||||
push_frontend_feature_flag(:ci_job_line_links, @project)
|
||||
end
|
||||
|
||||
layout 'project'
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ class Projects::RawController < Projects::ApplicationController
|
|||
include SendsBlob
|
||||
include StaticObjectExternalStorage
|
||||
|
||||
skip_before_action :default_cache_headers, only: :show
|
||||
|
||||
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:blob) }
|
||||
|
||||
before_action :require_non_empty_project
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class Projects::RepositoriesController < Projects::ApplicationController
|
|||
|
||||
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
|
||||
|
||||
skip_before_action :default_cache_headers, only: :archive
|
||||
|
||||
# Authorize
|
||||
before_action :require_non_empty_project, except: :create
|
||||
before_action :archive_rate_limit!, only: :archive
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ module Resolvers
|
|||
type Types::AlertManagement::IntegrationType.connection_type, null: true
|
||||
|
||||
def resolve(**args)
|
||||
return [] unless Feature.enabled?(:multiple_http_integrations, project)
|
||||
|
||||
http_integrations + prometheus_integrations
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ module Types
|
|||
description: 'List of participants in the issue'
|
||||
field :emails_disabled, GraphQL::BOOLEAN_TYPE, null: false,
|
||||
method: :project_emails_disabled?,
|
||||
description: 'Indicates if a project has email notifications disabled'
|
||||
description: 'Indicates if a project has email notifications disabled: `true` if email notifications are disabled'
|
||||
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
|
||||
description: 'Indicates the currently logged in user is subscribed to the issue'
|
||||
field :time_estimate, GraphQL::INT_TYPE, null: false,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module AlertManagement
|
|||
|
||||
def execute
|
||||
return error_no_permissions unless allowed?
|
||||
return error_multiple_integrations unless creation_allowed? && Feature.enabled?(:multiple_http_integrations, project)
|
||||
return error_multiple_integrations unless creation_allowed?
|
||||
|
||||
integration = project.alert_management_http_integrations.create(params)
|
||||
return error_in_create(integration) unless integration.valid?
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ module AlertManagement
|
|||
|
||||
def execute
|
||||
return error_no_permissions unless allowed?
|
||||
return error_multiple_integrations unless Feature.enabled?(:multiple_http_integrations, integration.project)
|
||||
|
||||
if integration.destroy
|
||||
success
|
||||
|
|
@ -40,10 +39,6 @@ module AlertManagement
|
|||
def error_no_permissions
|
||||
error(_('You have insufficient permissions to remove this HTTP integration'))
|
||||
end
|
||||
|
||||
def error_multiple_integrations
|
||||
error(_('Removing integrations is not supported for this project'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ module AlertManagement
|
|||
|
||||
def execute
|
||||
return error_no_permissions unless allowed?
|
||||
return error_multiple_integrations unless Feature.enabled?(:multiple_http_integrations, integration.project)
|
||||
|
||||
params[:token] = nil if params.delete(:regenerate_token)
|
||||
|
||||
|
|
@ -44,10 +43,6 @@ module AlertManagement
|
|||
def error_no_permissions
|
||||
error(_('You have insufficient permissions to update this HTTP integration'))
|
||||
end
|
||||
|
||||
def error_multiple_integrations
|
||||
error(_('Multiple HTTP integrations are not supported for this project'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ module Projects
|
|||
private
|
||||
|
||||
attr_reader :integration
|
||||
delegate :alerts_service, :alerts_service_activated?, to: :project
|
||||
|
||||
def process_alert
|
||||
if alert.persisted?
|
||||
|
|
@ -115,19 +114,11 @@ module Projects
|
|||
end
|
||||
|
||||
def active_integration?
|
||||
if Feature.enabled?(:multiple_http_integrations, project)
|
||||
return true if integration
|
||||
end
|
||||
|
||||
alerts_service_activated?
|
||||
integration&.active?
|
||||
end
|
||||
|
||||
def valid_token?(token)
|
||||
if Feature.enabled?(:multiple_http_integrations, project)
|
||||
return token == integration.token if integration
|
||||
end
|
||||
|
||||
token == alerts_service.token
|
||||
token == integration.token
|
||||
end
|
||||
|
||||
def bad_request
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
.settings-header
|
||||
%h4
|
||||
= _('Package Registry')
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _("Settings related to the use and experience of using GitLab's Package Registry.")
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
.settings-header
|
||||
%h4
|
||||
= _('Continuous Integration and Deployment')
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Auto DevOps, runners and job artifacts')
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
.settings-header
|
||||
%h4
|
||||
= _('Container Registry')
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Various container registry settings.')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
- show_invited_members = can_manage_members && @invited_members.exists?
|
||||
- show_access_requests = can_manage_members && @requesters.exists?
|
||||
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
|
||||
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group)
|
||||
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group, default_enabled: true)
|
||||
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
|
||||
|
||||
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
- if dag_pipeline_tab_enabled
|
||||
%li.js-dag-tab-link
|
||||
= link_to dag_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-dag', action: 'dag', toggle: 'tab' }, class: 'dag-tab' do
|
||||
= _('DAG')
|
||||
= _('Needs')
|
||||
%li.js-builds-tab-link
|
||||
= link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
|
||||
= _('Jobs')
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
- if dag_pipeline_tab_enabled
|
||||
#js-tab-dag.tab-pane
|
||||
#js-pipeline-dag-vue{ data: { pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), dag_doc_path: help_page_path('ci/yaml/README.md', anchor: 'needs')} }
|
||||
#js-pipeline-dag-vue{ data: { pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), about_dag_doc_path: help_page_path('ci/directed_acyclic_graph/index.md'), dag_doc_path: help_page_path('ci/yaml/README.md', anchor: 'needs')} }
|
||||
|
||||
#js-tab-tests.tab-pane
|
||||
#js-pipeline-tests-detail{ data: { summary_endpoint: summary_project_pipeline_tests_path(@project, @pipeline, format: :json),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
- activity_pane_class = Feature.enabled?(:security_auto_fix) && @user.bot? ? "col-12" : "col-md-12 col-lg-6"
|
||||
|
||||
.row
|
||||
.col-12
|
||||
.calendar-block.gl-mt-3.gl-mb-3
|
||||
.user-calendar.d-none.d-sm-block{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
|
||||
%h4.center.light
|
||||
.spinner.spinner-md
|
||||
.user-calendar-activities.d-none.d-sm-block
|
||||
.row.d-none.d-sm-flex
|
||||
.col-12.calendar-block.gl-my-3
|
||||
.user-calendar.light{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
|
||||
.spinner.spinner-md.gl-my-8
|
||||
.user-calendar-error.invisible
|
||||
= _('There was an error loading users activity calendar.')
|
||||
%a.js-retry-load{ href: '#' }
|
||||
= s_('UserProfile|Retry')
|
||||
.user-calendar-activities
|
||||
.row
|
||||
%div{ class: activity_pane_class }
|
||||
- if can?(current_user, :read_cross_project)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support fuzzing HTTP headers with API Fuzzing
|
||||
merge_request: 47727
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable `vue_group_members_list` feature flag by default
|
||||
merge_request: 47427
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Background migration for setting Jira tracker data deployment type
|
||||
merge_request: 46368
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add new text and tab name for DAG
|
||||
merge_request: 47415
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactor and UI-polish around activity calendar on user profile
|
||||
merge_request: 47797
|
||||
author: Takuya Noguchi
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable HTTP caching of repository raw, archive, and avatar endpoints
|
||||
merge_request: 47430
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose GraphQL API for managing HTTP alerting intergations
|
||||
merge_request: 47687
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: multiple_http_integrations
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44485
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255509
|
||||
name: ci_job_line_links
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47532
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281727
|
||||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::health
|
||||
group: group::continuous integration
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: usage_data_static_site_editor_commits
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone:
|
||||
type: development
|
||||
group: group::static_site_editor
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: usage_data_static_site_editor_merge_requests
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone:
|
||||
type: development
|
||||
group: group::static_site_editor
|
||||
default_enabled: false
|
||||
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241194
|
|||
milestone: '13.4'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillJiraTrackerDeploymentType2 < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
MIGRATION = 'BackfillJiraTrackerDeploymentType2'
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
class JiraTrackerData < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'jira_tracker_data'
|
||||
end
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
JiraTrackerData.where(deployment_type: 0),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: true)
|
||||
end
|
||||
|
||||
def down
|
||||
# NOOP
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
c51bf825045ef80714f3903f25321785883da3d612725f6fa67ec3ddd12d5808
|
||||
|
|
@ -232,16 +232,7 @@ that the **secondary** node can act on those notifications immediately.
|
|||
Be sure the _secondary_ node is running and accessible. You can sign in to the
|
||||
_secondary_ node with the same credentials as were used with the _primary_ node.
|
||||
|
||||
### Step 4. Enabling Hashed Storage
|
||||
|
||||
Using Hashed Storage significantly improves Geo replication. Project and group
|
||||
renames no longer require synchronization between nodes.
|
||||
|
||||
1. Visit the **primary** node's **Admin Area > Settings > Repository**
|
||||
(`/admin/application_settings/repository`) in your browser.
|
||||
1. In the **Repository storage** section, check **Use hashed storage paths for newly created and renamed projects**.
|
||||
|
||||
### Step 5. (Optional) Configuring the **secondary** node to trust the **primary** node
|
||||
### Step 4. (Optional) Configuring the **secondary** node to trust the **primary** node
|
||||
|
||||
You can safely skip this step if your **primary** node uses a CA-issued HTTPS certificate.
|
||||
|
||||
|
|
@ -251,14 +242,16 @@ certificate from the **primary** node and follow
|
|||
[these instructions](https://docs.gitlab.com/omnibus/settings/ssl.html)
|
||||
on the **secondary** node.
|
||||
|
||||
### Step 6. Enable Git access over HTTP/HTTPS
|
||||
### Step 5. Enable Git access over HTTP/HTTPS
|
||||
|
||||
Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
|
||||
method to be enabled. Navigate to **Admin Area > Settings**
|
||||
(`/admin/application_settings/general`) on the **primary** node, and set
|
||||
`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
|
||||
method to be enabled. This is enabled by default, but if converting an existing node to Geo it should be checked:
|
||||
|
||||
### Step 7. Verify proper functioning of the **secondary** node
|
||||
1. Navigate to **Admin Area > Settings** (`/admin/application_settings/general`) on the **primary** node.
|
||||
1. Expand "Visibility and access controls".
|
||||
1. Ensure "Enabled Git access protocols" is set to either "Both SSH and HTTP(S)" or "Only HTTP(S)".
|
||||
|
||||
### Step 6. Verify proper functioning of the **secondary** node
|
||||
|
||||
Your **secondary** node is now configured!
|
||||
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3,
|
|||
support is removed entirely. All installations will switch to the HTTP/HTTPS
|
||||
cloning method instead. Before updating, ensure that all your Geo nodes are
|
||||
configured to use this method and that it works for your installation. In
|
||||
particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps).
|
||||
particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-5-enable-git-access-over-httphttps).
|
||||
|
||||
Synchronizing repositories over the public Internet using HTTP is insecure, so
|
||||
you should ensure that you have HTTPS configured before updating. Note that
|
||||
|
|
|
|||
|
|
@ -7646,7 +7646,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
dueDate: Time
|
||||
|
||||
"""
|
||||
Indicates if a project has email notifications disabled
|
||||
Indicates if a project has email notifications disabled: `true` if email notifications are disabled
|
||||
"""
|
||||
emailsDisabled: Boolean!
|
||||
|
||||
|
|
@ -10185,7 +10185,7 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
dueDate: Time
|
||||
|
||||
"""
|
||||
Indicates if a project has email notifications disabled
|
||||
Indicates if a project has email notifications disabled: `true` if email notifications are disabled
|
||||
"""
|
||||
emailsDisabled: Boolean!
|
||||
|
||||
|
|
|
|||
|
|
@ -21191,7 +21191,7 @@
|
|||
},
|
||||
{
|
||||
"name": "emailsDisabled",
|
||||
"description": "Indicates if a project has email notifications disabled",
|
||||
"description": "Indicates if a project has email notifications disabled: `true` if email notifications are disabled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
@ -27886,7 +27886,7 @@
|
|||
},
|
||||
{
|
||||
"name": "emailsDisabled",
|
||||
"description": "Indicates if a project has email notifications disabled",
|
||||
"description": "Indicates if a project has email notifications disabled: `true` if email notifications are disabled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,7 @@ Relationship between an epic and an issue.
|
|||
| `discussions` | DiscussionConnection! | All discussions on this noteable |
|
||||
| `downvotes` | Int! | Number of downvotes the issue has received |
|
||||
| `dueDate` | Time | Due date of the issue |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
|
||||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `epicIssueId` | ID! | ID of the epic-issue relation |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
|
|
@ -1541,7 +1541,7 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `discussions` | DiscussionConnection! | All discussions on this noteable |
|
||||
| `downvotes` | Int! | Number of downvotes the issue has received |
|
||||
| `dueDate` | Time | Due date of the issue |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
|
||||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
|
||||
|
|
|
|||
|
|
@ -79,26 +79,28 @@ are certain use cases that you may need to work around. For more information:
|
|||
- [`needs` requirements and limitations](../yaml/README.md#requirements-and-limitations).
|
||||
- Related epic [tracking planned improvements](https://gitlab.com/groups/gitlab-org/-/epics/1716).
|
||||
|
||||
## DAG Visualization
|
||||
## Needs Visualization
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215517) in GitLab 13.1 as a [Beta feature](https://about.gitlab.com/handbook/product/#beta).
|
||||
> - It was deployed behind a feature flag, disabled by default.
|
||||
> - It became [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36802) in 13.2.
|
||||
> - It became a [standard feature](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38517) in 13.3.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-dag-visualization).
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-needs-visualization).
|
||||
|
||||
The DAG visualization makes it easier to visualize the relationships between dependent jobs in a DAG. This graph will display all the jobs in a pipeline that need or are needed by other jobs. Jobs with no relationships are not displayed in this view.
|
||||
The needs visualization makes it easier to visualize the relationships between dependent jobs in a DAG. This graph will display all the jobs in a pipeline that need or are needed by other jobs. Jobs with no relationships are not displayed in this view.
|
||||
|
||||

|
||||
To see the needs visualization, click on the **Needs** tab when viewing a pipeline that uses the `needs:` keyword.
|
||||
|
||||

|
||||
|
||||
Clicking a node will highlight all the job paths it depends on.
|
||||
|
||||

|
||||

|
||||
|
||||
### Enable or disable DAG Visualization **(CORE ONLY)**
|
||||
### Enable or disable Needs Visualization **(CORE ONLY)**
|
||||
|
||||
DAG Visualization is deployed behind a feature flag that is **enabled by default**.
|
||||
The needs visualization 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 for your instance:
|
||||
|
||||
|
|
|
|||
|
|
@ -134,17 +134,20 @@ from:
|
|||
- [Feature categorization](feature_categorization/index.md)
|
||||
- [Wikis development guide](wikis.md)
|
||||
- [Newlines style guide](newlines_styleguide.md)
|
||||
- [Image scaling guide](image_scaling.md)
|
||||
|
||||
## Performance guides
|
||||
|
||||
- [Instrumentation](instrumentation.md) for Ruby code running in production
|
||||
environments
|
||||
environments.
|
||||
- [Performance guidelines](performance.md) for writing code, benchmarks, and
|
||||
certain patterns to avoid
|
||||
certain patterns to avoid.
|
||||
- [Merge request performance guidelines](merge_request_performance_guidelines.md)
|
||||
for ensuring merge requests do not negatively impact GitLab performance
|
||||
- [Profiling](profiling.md) a URL, measuring performance using Sherlock, or
|
||||
tracking down N+1 queries using Bullet
|
||||
tracking down N+1 queries using Bullet.
|
||||
- [Cached queries guidelines](cached_queries.md), for tracking down N+1 queries masked by query caching, memory profiling and why should
|
||||
we avoid cached queries.
|
||||
|
||||
## Database guides
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
# Cached queries guidelines
|
||||
|
||||
Rails provides an [SQL query cache](https://guides.rubyonrails.org/caching_with_rails.html#sql-caching),
|
||||
used to cache the results of database queries for the duration of the request.
|
||||
|
||||
If Rails encounters the same query again for that request,
|
||||
it will use the cached result set as opposed to running the query against the database again.
|
||||
|
||||
The query results are only cached for the duration of that single request, it does not persist across multiple requests.
|
||||
|
||||
## Why cached queries are considered bad
|
||||
|
||||
The cached queries help with reducing DB load, but they still:
|
||||
|
||||
- Consume memory.
|
||||
- Require as to re-instantiate each `ActiveRecord` object.
|
||||
- Require as to re-instantiate each relation of the object.
|
||||
- Make us spend additional CPU-cycles to look into a list of cached queries.
|
||||
|
||||
The Cached SQL queries are cheaper, but they are not cheap at all from `memory` perspective.
|
||||
They could mask [N+1 query problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations),
|
||||
so we should threat them the same way we threat regular N+1 queries.
|
||||
|
||||
In case of N+1 queries, masked with cached queries, we are executing the same query N times.
|
||||
It will not hit the database N times, it will return the cached results instead.
|
||||
This is still expensive since we need to re-initialize objects each time, and this is CPU/Memory expensive.
|
||||
Instead, we should use the same in-memory objects, if possible.
|
||||
|
||||
When we introduce a new feature, we should avoid N+1 problems,
|
||||
minimize the [query count](merge_request_performance_guidelines.md#query-counts), and pay special attention that [cached
|
||||
queries](merge_request_performance_guidelines.md#cached-queries) are not masking N+1 problems.
|
||||
|
||||
## How to detect
|
||||
|
||||
### Detect potential offenders by using Kibana
|
||||
|
||||
On GitLab.com, we are logging entries with the number of executed cached queries in the
|
||||
`pubsub-redis-inf-gprd*` index with the [`db_cached_count`](https://log.gprd.gitlab.net/goto/77d18d80ad84c5df1bf1da5c2cd35b82).
|
||||
We can filter endpoints that have a large number of executed cached queries. For example, if we encounter an endpoint
|
||||
that has 100+ `db_cached_count`, this could indicate that there is an N+1 problem masked with cached queries.
|
||||
We should probably investigate this endpoint further, to check if we are executing duplicated cached queries.
|
||||
|
||||
For more cached queries Kibana visualizations see [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/259007).
|
||||
|
||||
### Inspect suspicious endpoint using Performance Bar
|
||||
|
||||
When building features, you could use the [performance bar](../administration/monitoring/performance/performance_bar.md)
|
||||
to list database queries, which will include cached queries as well. The performance bar will show a warning
|
||||
when threshold of total executed queries (including cached ones) has exceeded 100 queries.
|
||||
|
||||
## What to look for
|
||||
|
||||
Using [Kibana](cached_queries.md#detect-potential-offenders-by-using-kibana), you can look for a large number
|
||||
of executed cached queries. End-points with large number of `db_cached_count` could indicate that there
|
||||
are probably a lot of duplicated cached queries, which often indicates a masked N+1 problem.
|
||||
|
||||
When you investigate specific endpoint, you could use
|
||||
the [performance bar](cached_queries.md#inspect-suspicious-endpoint-using-performance-bar).
|
||||
If you see a lot of similar queries, this often indicates an N+1 query issue (or a similar kind of query batching problem).
|
||||
If you see same cached query executed multiple times, this often indicates a masked N+1 query problem.
|
||||
|
||||
For example, let's say you wanted to debug `GroupMembers` page.
|
||||
|
||||
In the left corner of the performance bar you could see **Database queries** showing the total number of database queries
|
||||
and the number of executed cached queries:
|
||||
|
||||

|
||||
|
||||
We can see that there are 55 cached queries. By clicking on the number, a modal window with more details is shown.
|
||||
Cached queries are marked with the `cached` label, so they are easy to spot. We can see that there are multiple duplicated
|
||||
cached queries:
|
||||
|
||||

|
||||
|
||||
If we click on `...` for one of them, it will expand the actual stack trace:
|
||||
|
||||
```shell
|
||||
[
|
||||
"app/models/group.rb:305:in `has_owner?'",
|
||||
"ee/app/views/shared/members/ee/_license_badge.html.haml:1",
|
||||
"app/helpers/application_helper.rb:19:in `render_if_exists'",
|
||||
"app/views/shared/members/_member.html.haml:31",
|
||||
"app/views/groups/group_members/index.html.haml:75",
|
||||
"app/controllers/application_controller.rb:134:in `render'",
|
||||
"ee/lib/gitlab/ip_address_state.rb:10:in `with'",
|
||||
"ee/app/controllers/ee/application_controller.rb:44:in `set_current_ip_address'",
|
||||
"app/controllers/application_controller.rb:493:in `set_current_admin'",
|
||||
"lib/gitlab/session.rb:11:in `with_session'",
|
||||
"app/controllers/application_controller.rb:484:in `set_session_storage'",
|
||||
"app/controllers/application_controller.rb:478:in `set_locale'",
|
||||
"lib/gitlab/error_tracking.rb:52:in `with_context'",
|
||||
"app/controllers/application_controller.rb:543:in `sentry_context'",
|
||||
"app/controllers/application_controller.rb:471:in `block in set_current_context'",
|
||||
"lib/gitlab/application_context.rb:54:in `block in use'",
|
||||
"lib/gitlab/application_context.rb:54:in `use'",
|
||||
"lib/gitlab/application_context.rb:21:in `with_context'",
|
||||
"app/controllers/application_controller.rb:463:in `set_current_context'",
|
||||
"lib/gitlab/jira/middleware.rb:19:in `call'"
|
||||
]
|
||||
```
|
||||
|
||||
The stack trace, shows us that we obviously have an N+1 problem, since we are repeatably executing for each group member:
|
||||
|
||||
```ruby
|
||||
group.has_owner?(current_user)
|
||||
```
|
||||
|
||||
This is easily solvable by extracting this check, above the loop.
|
||||
|
||||
After [the fix](https://gitlab.com/gitlab-org/gitlab/-/issues/231468), we now have:
|
||||
|
||||

|
||||
|
||||
## How to measure the impact of the change
|
||||
|
||||
We can use the [memory profiler](performance.md#using-memory-profiler) to profile our code.
|
||||
For the previous example, we could wrap the profiler around the `Groups::GroupMembersController#index` action.
|
||||
|
||||
We had:
|
||||
|
||||
- Total allocated: 7133601 bytes (84858 objects)
|
||||
- Total retained: 757595 bytes (6070 objects)
|
||||
- `db_count`: 144
|
||||
- `db_cached_count`: 55
|
||||
- `db_duration`: 303ms
|
||||
|
||||
After the fix, we can see that we have reduced the allocated memory as well as the number of cached queries and improved execution time:
|
||||
|
||||
- Total allocated: 5313899 bytes (65290 objects), 1810KB (25%) less
|
||||
- Total retained: 685593 bytes (5278 objects), 72KB (9%) less
|
||||
- `db_count`: 95 (34% less)
|
||||
- `db_cached_count`: 6 (89% less)
|
||||
- `db_duration`: 162ms (87% faster)
|
||||
|
||||
## See also
|
||||
|
||||
- [Metrics that would help us detect the potential N+1 Cached SQL calls](https://gitlab.com/gitlab-org/gitlab/-/issues/259007)
|
||||
- [Merge Request performance guidelines for cached queries](merge_request_performance_guidelines.md#cached-queries)
|
||||
- [Improvements for biggest offenders](https://gitlab.com/groups/gitlab-org/-/epics/4508)
|
||||
|
|
@ -63,3 +63,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
- [Database case study: Filtering by label](../filtering_by_label.md)
|
||||
- [Database case study: Namespaces storage statistics](../namespaces_storage_statistics.md)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- [Maintenance operations](maintenance_operations.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
stage: Enablement
|
||||
group: Database
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Maintenance operations
|
||||
|
||||
This page details various database related operations that may relate to development.
|
||||
|
||||
## Disabling an index
|
||||
|
||||
There are certain situations in which you might want to disable an index before removing it:
|
||||
|
||||
- The index is on a large table and rebuilding it in the case of a revert would take a long time.
|
||||
- It is uncertain whether or not the index is being used in ways that are not fully visible.
|
||||
|
||||
To disable an index before removing it:
|
||||
|
||||
1. Open a [production infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/new)
|
||||
and use the "Production Change" template.
|
||||
1. Inform the database team in the issue `@gl-database` or in Slack `#database`.
|
||||
1. Add a step to verify the index is used (this would likely be an `EXPLAIN` command known to use the index).
|
||||
1. Add the step to disable the index:
|
||||
|
||||
```sql
|
||||
UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'index_issues_on_foo'::regclass;
|
||||
```
|
||||
|
||||
1. Add a step to verify the index is invalid (this would likely be the same as used to verify before disabling the index).
|
||||
1. Verify the index is invalid on replicas:
|
||||
|
||||
```sql
|
||||
SELECT indisvalid FROM pg_index WHERE indexrelid = 'index_issues_on_foo'::regclass;
|
||||
```
|
||||
|
||||
1. Add steps for rolling back the invalidation:
|
||||
1. Rollback the index invalidation
|
||||
|
||||
```sql
|
||||
UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'index_issues_on_foo'::regclass;
|
||||
```
|
||||
|
||||
1. Verify the index is being used again.
|
||||
|
||||
See this [example infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/2795) for reference.
|
||||
|
|
@ -10,19 +10,20 @@ A generic dropdown for all of your custom dropdown needs.
|
|||
|
||||
## Usage
|
||||
|
||||
DropLab can be used by simply adding a `data-dropdown-trigger` HTML attribute.
|
||||
This attribute allows us to find the "trigger" _(toggle)_ for the dropdown,
|
||||
whether that is a button, link or input.
|
||||
DropLab can be used by adding a `data-dropdown-trigger` HTML attribute. This
|
||||
attribute allows us to find the "trigger" _(toggle)_ for the dropdown, whether
|
||||
it's a button, link or input.
|
||||
|
||||
The value of the `data-dropdown-trigger` should be a CSS selector that
|
||||
DropLab can use to find the trigger's dropdown list.
|
||||
The value of the `data-dropdown-trigger` should be a CSS selector that DropLab
|
||||
can use to find the trigger's dropdown list.
|
||||
|
||||
You should also add the `data-dropdown` attribute to declare the dropdown list.
|
||||
The value is irrelevant.
|
||||
|
||||
The DropLab class has no side effects, so you must always call `.init` when
|
||||
the DOM is ready. `DropLab.prototype.init` takes the same arguments as `DropLab.prototype.addHook`.
|
||||
If you do not provide any arguments, it will globally query and instantiate all droplab compatible dropdowns.
|
||||
The DropLab class has no side effects, so you must always call `.init` when the
|
||||
DOM is ready. `DropLab.prototype.init` takes the same arguments as `DropLab.prototype.addHook`.
|
||||
If you don't provide any arguments, it will globally query and instantiate all
|
||||
DropLab-compatible dropdowns.
|
||||
|
||||
```html
|
||||
<a href="#" data-dropdown-trigger="#list">Toggle</a>
|
||||
|
|
@ -37,8 +38,8 @@ const droplab = new DropLab();
|
|||
droplab.init();
|
||||
```
|
||||
|
||||
As you can see, we have a "Toggle" link, that is declared as a trigger.
|
||||
It provides a selector to find the dropdown list it should control.
|
||||
As noted, we have a "Toggle" link that's declared as a trigger. It provides a
|
||||
selector to find the dropdown list it should control.
|
||||
|
||||
### Static data
|
||||
|
||||
|
|
@ -60,8 +61,8 @@ droplab.init();
|
|||
|
||||
### Explicit instantiation
|
||||
|
||||
You can pass the trigger and list elements as constructor arguments to return
|
||||
a non-global instance of DropLab using the `DropLab.prototype.init` method.
|
||||
You can pass the trigger and list elements as constructor arguments to return a
|
||||
non-global instance of DropLab using the `DropLab.prototype.init` method.
|
||||
|
||||
```html
|
||||
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
|
||||
|
|
@ -102,12 +103,13 @@ droplab.addHook(trigger, list);
|
|||
|
||||
### Dynamic data
|
||||
|
||||
Adding `data-dynamic` to your dropdown element will enable dynamic list rendering.
|
||||
Adding `data-dynamic` to your dropdown element will enable dynamic list
|
||||
rendering.
|
||||
|
||||
You can template a list item using the keys of the data object provided.
|
||||
Use the handlebars syntax `{{ value }}` to HTML escape the value.
|
||||
Use the `<%= value %>` syntax to simply interpolate the value.
|
||||
Use the `<%= value %>` syntax to evaluate the value.
|
||||
You can template a list item using the keys of the data object provided. Use the
|
||||
handlebars syntax `{{ value }}` to HTML escape the value. Use the `<%= value %>`
|
||||
syntax to interpolate the value. Use the `<%= value %>` syntax to evaluate the
|
||||
value.
|
||||
|
||||
Passing an array of objects to `DropLab.prototype.addData` will render that data
|
||||
for all `data-dynamic` dropdown lists tracked by that DropLab instance.
|
||||
|
|
@ -132,8 +134,9 @@ droplab.init().addData([{
|
|||
}]);
|
||||
```
|
||||
|
||||
Alternatively, you can specify a specific dropdown to add this data to but passing
|
||||
the data as the second argument and the `id` of the trigger element as the first argument.
|
||||
Alternatively, you can specify a specific dropdown to add this data to by
|
||||
passing the data as the second argument and the `id` of the trigger element as
|
||||
the first argument.
|
||||
|
||||
```html
|
||||
<a href="#" data-dropdown-trigger="#list" id="trigger">Toggle</a>
|
||||
|
|
@ -155,7 +158,7 @@ droplab.init().addData('trigger', [{
|
|||
}]);
|
||||
```
|
||||
|
||||
This allows you to mix static and dynamic content with ease, even with one trigger.
|
||||
This allows you to mix static and dynamic content, even with one trigger.
|
||||
|
||||
Note the use of scoping regarding the `data-dropdown` attribute to capture both
|
||||
dropdown lists, one of which is dynamic.
|
||||
|
|
@ -191,12 +194,13 @@ DropLab adds some CSS classes to help lower the barrier to integration.
|
|||
|
||||
For example:
|
||||
|
||||
- The `droplab-item-selected` CSS class is added to items that have been selected
|
||||
either by a mouse click or by enter key selection.
|
||||
- The `droplab-item-selected` CSS class is added to items that have been
|
||||
selected either by a mouse click or by enter key selection.
|
||||
- The `droplab-item-active` CSS class is added to items that have been selected
|
||||
using arrow key navigation.
|
||||
- You can add the `droplab-item-ignore` CSS class to any item that you do not want to be selectable. For example,
|
||||
an `<li class="divider"></li>` list divider element that should not be interactive.
|
||||
- You can add the `droplab-item-ignore` CSS class to any item that you don't
|
||||
want to be selectable. For example, an `<li class="divider"></li>` list
|
||||
divider element that shouldn't be interactive.
|
||||
|
||||
## Internal events
|
||||
|
||||
|
|
@ -204,26 +208,33 @@ DropLab uses some custom events to help lower the barrier to integration.
|
|||
|
||||
For example:
|
||||
|
||||
- The `click.dl` event is fired when an `li` list item has been clicked. It is also
|
||||
fired when a list item has been selected with the keyboard. It is also fired when a
|
||||
`HookButton` button is clicked (a registered `button` tag or `a` tag trigger).
|
||||
- The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event.
|
||||
- The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event.
|
||||
- The `click.dl` event is fired when an `li` list item has been clicked. It's
|
||||
also fired when a list item has been selected with the keyboard. It's also
|
||||
fired when a `HookButton` button is clicked (a registered `button` tag or `a`
|
||||
tag trigger).
|
||||
- The `input.dl` event is fired when a `HookInput` (a registered `input` tag
|
||||
trigger) triggers an `input` event.
|
||||
- The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown`
|
||||
event.
|
||||
- The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event.
|
||||
- The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event.
|
||||
|
||||
These custom events add a `detail` object to the vanilla `Event` object that provides some potentially useful data.
|
||||
These custom events add a `detail` object to the vanilla `Event` object that
|
||||
provides some potentially useful data.
|
||||
|
||||
## Plugins
|
||||
|
||||
Plugins are objects that are registered to be executed when a hook is added (when a droplab trigger and dropdown are instantiated).
|
||||
Plugins are objects that are registered to be executed when a hook is added (when
|
||||
a DropLab trigger and dropdown are instantiated).
|
||||
|
||||
If no modules API is detected, the library will fall back as it does with `window.DropLab` and will add `window.DropLab.plugins.PluginName`.
|
||||
If no modules API is detected, the library will fall back as it does with
|
||||
`window.DropLab` and will add `window.DropLab.plugins.PluginName`.
|
||||
|
||||
### Usage
|
||||
|
||||
To use plugins, you can pass them in an array as the third argument of `DropLab.prototype.init` or `DropLab.prototype.addHook`.
|
||||
Some plugins require configuration values, the config object can be passed as the fourth argument.
|
||||
To use plugins, you can pass them in an array as the third argument of
|
||||
`DropLab.prototype.init` or `DropLab.prototype.addHook`. Some plugins require
|
||||
configuration values; the config object can be passed as the fourth argument.
|
||||
|
||||
```html
|
||||
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
|
||||
|
|
@ -246,14 +257,13 @@ droplab.init(trigger, list, [droplabAjax], {
|
|||
|
||||
### Documentation
|
||||
|
||||
- [Ajax plugin](plugins/ajax.md)
|
||||
- [Filter plugin](plugins/filter.md)
|
||||
- [InputSetter plugin](plugins/input_setter.md)
|
||||
Refer to the list of available [DropLab plugins](plugins/index.md) for
|
||||
information about their use.
|
||||
|
||||
### Development
|
||||
|
||||
When plugins are initialised for a droplab trigger+dropdown, DropLab will
|
||||
call the plugins `init` function, so this must be implemented in the plugin.
|
||||
When plugins are initialised for a DropLab trigger+dropdown, DropLab calls the
|
||||
plugins' `init` function, so this must be implemented in the plugin.
|
||||
|
||||
```javascript
|
||||
class MyPlugin {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@ group: unassigned
|
|||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Ajax
|
||||
# Ajax plugin
|
||||
|
||||
`Ajax` is a droplab plugin that allows for retrieving and rendering list data from a server.
|
||||
`Ajax` is a DropLab plugin that allows for retrieving and rendering list data
|
||||
from a server.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
|
||||
Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or
|
||||
`DropLab.prototype.addHook` call.
|
||||
|
||||
`Ajax` requires 2 configuration values, the `endpoint` and `method`.
|
||||
`Ajax` requires 2 configuration values: the `endpoint` and `method`.
|
||||
|
||||
- `endpoint` should be a URL to the request endpoint.
|
||||
- `method` should be `setData` or `addData`.
|
||||
- `setData` completely replaces the dropdown with the response data.
|
||||
- `addData` appends the response data to the current dropdown list.
|
||||
- `endpoint`: Should be a URL to the request endpoint.
|
||||
- `method`: Should be `setData` or `addData`.
|
||||
- `setData`: Completely replaces the dropdown with the response data.
|
||||
- `addData`: Appends the response data to the current dropdown list.
|
||||
|
||||
```html
|
||||
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
|
||||
|
|
@ -38,7 +40,7 @@ droplab.addHook(trigger, list, [Ajax], {
|
|||
});
|
||||
```
|
||||
|
||||
Optionally you can set `loadingTemplate` to a HTML string. This HTML string will
|
||||
replace the dropdown list while the request is pending.
|
||||
Optionally, you can set `loadingTemplate` to a HTML string. This HTML string
|
||||
replaces the dropdown list while the request is pending.
|
||||
|
||||
Additionally, you can set `onError` to a function to catch any XHR errors.
|
||||
|
|
|
|||
|
|
@ -4,18 +4,19 @@ group: unassigned
|
|||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Filter
|
||||
# Filter plugin
|
||||
|
||||
`Filter` is a plugin that allows for filtering data that has been added
|
||||
`Filter` is a DropLab plugin that allows for filtering data that has been added
|
||||
to the dropdown using a simple fuzzy string search of an input value.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
|
||||
Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or
|
||||
`DropLab.prototype.addHook` call.
|
||||
|
||||
- `Filter` requires a configuration value for `template`.
|
||||
- `template` should be the key of the objects within your data array that you want to compare
|
||||
to the user input string, for filtering.
|
||||
- `Filter`: Requires a configuration value for `template`.
|
||||
- `template`: Should be the key of the objects within your data array that you
|
||||
want to compare to the user input string, for filtering.
|
||||
|
||||
```html
|
||||
<input href="#" id="trigger" data-dropdown-trigger="#list">
|
||||
|
|
@ -45,8 +46,10 @@ droplab.addData('trigger', [{
|
|||
}]);
|
||||
```
|
||||
|
||||
Above, the input string will be compared against the `test` key of the passed data objects.
|
||||
In the previous code, the input string is compared against the `test` key of the
|
||||
passed data objects.
|
||||
|
||||
Optionally you can set `filterFunction` to a function. This function will be used instead
|
||||
of `Filter`'s built in string search. `filterFunction` is passed 2 arguments, the first
|
||||
is one of the data objects, the second is the current input value.
|
||||
Optionally you can set `filterFunction` to a function. This function will be
|
||||
used instead of `Filter`'s built-in string search. `filterFunction` is passed
|
||||
two arguments: the first is one of the data objects, and the second is the
|
||||
current input value.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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/#designated-technical-writers
|
||||
description: A list of DropLab plugins.
|
||||
---
|
||||
|
||||
# DropLab plugins
|
||||
|
||||
The following plugins are available for use with [DropLab](../droplab.md):
|
||||
|
||||
- [Ajax plugin](ajax.md)
|
||||
- [Filter plugin](filter.md)
|
||||
- [InputSetter plugin](input_setter.md)
|
||||
|
|
@ -4,20 +4,23 @@ group: unassigned
|
|||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
# InputSetter
|
||||
# InputSetter plugin
|
||||
|
||||
`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked.
|
||||
`InputSetter` is a DropLab plugin that allows for updating DOM out of the scope
|
||||
of DropLab when a list item is clicked.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
|
||||
Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init`
|
||||
or `DropLab.prototype.addHook` call.
|
||||
|
||||
- `InputSetter` requires a configuration value for `input` and `valueAttribute`.
|
||||
- `input` should be the DOM element that you want to manipulate.
|
||||
- `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
|
||||
to update the `input` element with.
|
||||
- `InputSetter`: Requires a configuration value for `input` and `valueAttribute`.
|
||||
- `input`: The DOM element that you want to manipulate.
|
||||
- `valueAttribute`: A string that's the name of an attribute on your list items
|
||||
that's used to get the value to update the `input` element with.
|
||||
|
||||
You can also set the `InputSetter` configuration to an array of objects, which will allow you to update multiple elements.
|
||||
You can also set the `InputSetter` configuration to an array of objects, which
|
||||
allows you to update multiple elements.
|
||||
|
||||
```html
|
||||
<input id="input" value="">
|
||||
|
|
@ -58,9 +61,12 @@ droplab.addData('trigger', [{
|
|||
}]);
|
||||
```
|
||||
|
||||
Above, if the second list item was clicked, it would update the `#input` element
|
||||
to have a `value` of `1`, it would also update the `#div` element's `data-selected-id` to `1`.
|
||||
In the previous code, if the second list item was clicked, it would update the
|
||||
`#input` element to have a `value` of `1`, it would also update the `#div`
|
||||
element's `data-selected-id` to `1`.
|
||||
|
||||
Optionally you can set `inputAttribute` to a string that is the name of an attribute on your `input` element that you want to update.
|
||||
If you do not provide an `inputAttribute`, `InputSetter` will update the `value` of the `input` element if it is an `INPUT` element,
|
||||
or the `textContent` of the `input` element if it is not an `INPUT` element.
|
||||
Optionally, you can set `inputAttribute` to a string that's the name of an
|
||||
attribute on your `input` element that you want to update. If you don't provide
|
||||
an `inputAttribute`, `InputSetter` will update the `value` of the `input`
|
||||
element if it's an `INPUT` element, or the `textContent` of the `input` element
|
||||
if it isn't an `INPUT` element.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Image scaling guide
|
||||
|
||||
This section contains a brief overview of GitLab's image scaler and how to work with it.
|
||||
|
||||
For a general introduction to the history of image scaling at GitLab, you might be interested in
|
||||
[this Unfiltered blog post](https://about.gitlab.com/blog/2020/11/02/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab/).
|
||||
|
||||
## Why image scaling?
|
||||
|
||||
Since version 13.6, GitLab scales down images on demand in order to reduce the page data footprint.
|
||||
This both reduces the amount of data "on the wire", but also helps with rendering performance,
|
||||
since the browser has less work to do.
|
||||
|
||||
## When do we scale images?
|
||||
|
||||
Generally, the image scaler is triggered whenever a client requests an image resource by adding
|
||||
the `width` parameter to the query string. However, we only scale images of certain kinds and formats.
|
||||
Whether we allow an image to be rescaled or not is decided by combination of hard-coded rules and configuration settings.
|
||||
|
||||
The hard-coded rules only permit:
|
||||
|
||||
- [Project, group and user avatars](https://gitlab.com/gitlab-org/gitlab/-/blob/fd08748862a5fe5c25b919079858146ea85843ae/app/controllers/concerns/send_file_upload.rb#L65-67)
|
||||
- [PNGs or JPEGs](https://gitlab.com/gitlab-org/gitlab/-/blob/5dff8fa3814f2a683d8884f468cba1ec06a60972/lib/gitlab/file_type_detection.rb#L23)
|
||||
- [Specific dimensions](https://gitlab.com/gitlab-org/gitlab/-/blob/5dff8fa3814f2a683d8884f468cba1ec06a60972/app/models/concerns/avatarable.rb#L6)
|
||||
|
||||
Furthermore, configuration in Workhorse can lead to the image scaler rejecting a request if:
|
||||
|
||||
- The image file is too large (controlled by [`max_filesize`](- we only rescale images that do not exceed a configured size in bytes (see [`max_filesize`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/67ab3a2985d2097392f93523ae1cffe0dbf01b31/config.toml.example#L17)))).
|
||||
- Too many image scalers are already running (controlled by [`max_scaler_procs`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/67ab3a2985d2097392f93523ae1cffe0dbf01b31/config.toml.example#L16)).
|
||||
|
||||
For instance, here are two different URLs that serve the GitLab project avatar both in its
|
||||
original size and scaled down to 64 pixels. Only the second request will trigger the image scaler:
|
||||
|
||||
- [`/uploads/-/system/project/avatar/278964/logo-extra-whitespace.png`](https://assets.gitlab-static.net/uploads/-/system/project/avatar/278964/logo-extra-whitespace.png)
|
||||
- [`/uploads/-/system/project/avatar/278964/logo-extra-whitespace.png?width=64`](https://assets.gitlab-static.net/uploads/-/system/project/avatar/278964/logo-extra-whitespace.png?width=64)
|
||||
|
||||
## Where do we scale images?
|
||||
|
||||
Rails and Workhorse currently collaborate to rescale images. This is a common implementation and performance
|
||||
pattern in GitLab: important business logic such as request authentication and validation
|
||||
happens in Rails, whereas the "heavy lifting", scaling and serving the binary data, happens in Workhorse.
|
||||
|
||||
The overall request flow is as follows:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Client->>+Workhorse: GET /uploads/-/system/project/avatar/278964/logo-extra-whitespace.png?width=64
|
||||
Workhorse->>+Rails: forward request
|
||||
Rails->>+Rails: validate request
|
||||
Rails->>+Rails: resolve image location
|
||||
Rails-->>-Workhorse: Gitlab-Workhorse-Send-Data: send-scaled-image
|
||||
Workhorse->>+Workhorse: invoke image scaler
|
||||
Workhorse-->>-Client: 200 OK
|
||||
```
|
||||
|
||||
### Rails
|
||||
|
||||
Currently, image scaling is limited to `Upload` entities, specifically avatars as mentioned above.
|
||||
Therefore, all image scaling related logic in Rails is currently found in the
|
||||
[`send_file_upload`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/concerns/send_file_upload.rb)
|
||||
controller mixin. Upon receiving a request coming from a client through Workhorse, we check whether
|
||||
it should trigger the image scaler as per the criteria mentioned above, and if so, render a special response
|
||||
header field (`Gitlab-Workhorse-Send-Data`) with the necessary parameters for Workhorse to carry
|
||||
out the scaling request. If Rails decides the request does not constitute a valid image scaling request,
|
||||
we simply follow the path we take to serve any ordinary upload.
|
||||
|
||||
### Workhorse
|
||||
|
||||
Assuming Rails decided the request to be valid, Workhorse will take over. Upon receiving the `send-scaled-image`
|
||||
instruction through the Rails response, a [special response injecter](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/master/internal/imageresizer/image_resizer.go)
|
||||
will be invoked that knows how to rescale images. The only inputs it requires are the location of the image
|
||||
(a path if the image resides in block storage, or a URL to remote storage otherwise) and the desired width.
|
||||
Workhorse will handle the location transparently so Rails does not need to be concerned with where the image
|
||||
actually resides.
|
||||
|
||||
Additionally, to request validation in Rails, Workhorse will run several pre-condition checks to ensure that
|
||||
we can actually rescale the image, such as making sure we wouldn't outgrow our scaler process budget but also
|
||||
if the file meets the configured maximum allowed size constraint (to keep memory consumption in check).
|
||||
|
||||
To actually scale the image, Workhorse will finally fork into a child process that performs the actual
|
||||
scaling work, and stream the result back to the client.
|
||||
|
||||
#### Caching rescaled images
|
||||
|
||||
We currently do not store rescaled images anywhere; the scaler runs every time a smaller version is requested.
|
||||
However, Workhorse implements standard conditional HTTP request strategies that allow us to skip the scaler
|
||||
if the image in the client cache is up-to-date.
|
||||
To that end we transmit a `Last-Modified` header field carrying the UTC
|
||||
timestamp of the original image file and match it against the `If-Modified-Since` header field in client requests.
|
||||
Only if the original image has changed and rescaling becomes necessary do we run the scaler again.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
|
|
@ -162,30 +162,11 @@ query. This in turn makes it much harder for this code to overload a database.
|
|||
|
||||
**Summary:** a merge request **should not** execute duplicated cached queries.
|
||||
|
||||
Rails provides an [SQL query cache](https://guides.rubyonrails.org/caching_with_rails.html#sql-caching),
|
||||
Rails provides an [SQL Query Cache](cached_queries.md#cached-queries-guidelines),
|
||||
used to cache the results of database queries for the duration of the request.
|
||||
If Rails encounters the same query again for that request,
|
||||
it will use the cached result set as opposed to running the query against the database again.
|
||||
The query results are only cached for the duration of that single request, it does not persist across multiple requests.
|
||||
|
||||
The cached queries help with reducing DB load, but they still:
|
||||
|
||||
- Consume memory.
|
||||
- Require as to re-instantiate each `ActiveRecord` object.
|
||||
- Require as to re-instantiate each relation of the object.
|
||||
- Make us spend additional CPU-cycles to look into a list of cached queries.
|
||||
|
||||
They are cheaper, but they are not cheap at all from `memory` perspective.
|
||||
|
||||
Cached SQL queries, could mask [N+1 query problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations).
|
||||
If those N queries are executing the same query, it will not hit the database N times, it will return the cached results instead,
|
||||
which is still expensive since we need to re-initialize objects each time, and this is CPU/Memory expensive.
|
||||
Instead, you should use the same in-memory objects, if possible.
|
||||
|
||||
When building features, you could use [Performance bar](../administration/monitoring/performance/performance_bar.md)
|
||||
in order to list Database queries, which will include cached queries as well. If you see a lot of similar queries,
|
||||
this often indicates an N+1 query issue (or a similar kind of query batching problem).
|
||||
If you see same cached query executed multiple times, this often indicates a masked N+1 query problem.
|
||||
See [why cached queries are considered bad](cached_queries.md#why-cached-queries-are-considered-bad) and
|
||||
[how to detect them](cached_queries.md#how-to-detect).
|
||||
|
||||
The code introduced by a merge request, should not execute multiple duplicated cached queries.
|
||||
|
||||
|
|
|
|||
|
|
@ -478,6 +478,12 @@ For a small table (such as an empty one or one with less than `1,000` records),
|
|||
it is recommended to use `remove_index` in a single-transaction migration,
|
||||
combining it with other operations that don't require `disable_ddl_transaction!`.
|
||||
|
||||
### Disabling an index
|
||||
|
||||
There are certain situations in which you might want to disable an index before removing it.
|
||||
See the [maintenance operations guide](database/maintenance_operations.md#disabling-an-index)
|
||||
for more details.
|
||||
|
||||
## Adding indexes
|
||||
|
||||
Before adding an index, consider if this one is necessary. There are situations in which an index
|
||||
|
|
|
|||
|
|
@ -349,6 +349,40 @@ issues in our code.
|
|||
|
||||
## Memory profiling
|
||||
|
||||
We can use two approaches, often in combination, to track down memory issues:
|
||||
|
||||
- Leaving the code intact and wrapping a profiler around it.
|
||||
- Monitor memory usage of the process while disabling/enabling different parts of the code we suspect could be problematic.
|
||||
|
||||
### Using Memory Profiler
|
||||
|
||||
We can use `memory_profiler` for profiling.
|
||||
|
||||
The [`memory_profiler`](https://github.com/SamSaffron/memory_profiler) gem is already present in GitLab's `Gemfile`,
|
||||
you just need to require it:
|
||||
|
||||
```ruby
|
||||
require 'sidekiq/testing'
|
||||
|
||||
report = MemoryProfiler.report do
|
||||
# Code you want to profile
|
||||
end
|
||||
|
||||
output = File.open('/tmp/profile.txt','w')
|
||||
report.pretty_print(output)
|
||||
```
|
||||
|
||||
The report breaks down 2 key concepts:
|
||||
|
||||
- Retained: long lived memory use and object count retained due to the execution of the code block.
|
||||
- Allocated: all object allocation and memory allocation during code block.
|
||||
|
||||
As a general rule, **retained** will always be smaller than or equal to allocated.
|
||||
|
||||
The actual RSS cost will always be slightly higher as MRI heaps are not squashed to size and memory fragments.
|
||||
|
||||
### Rbtrace
|
||||
|
||||
One of the reasons of the increased memory footprint could be Ruby memory fragmentation.
|
||||
|
||||
To diagnose it, you can visualize Ruby heap as described in [this post by Aaron Patterson](https://tenderlovemaking.com/2017/09/27/visualizing-your-ruby-heap.html).
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ After adding a new queue, run `bin/rake
|
|||
gitlab:sidekiq:all_queues_yml:generate` to regenerate
|
||||
`app/workers/all_queues.yml` or `ee/app/workers/all_queues.yml` so that
|
||||
it can be picked up by
|
||||
[`sidekiq-cluster`](../administration/operations/extra_sidekiq_processes.md).
|
||||
[`sidekiq-cluster`](../administration/operations/extra_sidekiq_processes.md).
|
||||
Additionally, run
|
||||
`bin/rake gitlab:sidekiq:sidekiq_queues_yml:generate` to regenerate
|
||||
`config/sidekiq_queues.yml`.
|
||||
|
||||
## Queue Namespaces
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Akismet
|
||||
|
||||
GitLab leverages [Akismet](https://akismet.com/) to protect against spam. Currently
|
||||
GitLab leverages [Akismet](https://akismet.com/) to protect against spam.
|
||||
GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues
|
||||
created via the web UI or the API can be submitted to Akismet for review.
|
||||
created through the web UI or the API can be submitted to Akismet for review.
|
||||
|
||||
Detected spam will be rejected, and an entry in the "Spam Log" section in the
|
||||
Admin page will be created.
|
||||
Detected spam is rejected, and an entry is added in the **Spam Log** section of the
|
||||
Admin page.
|
||||
|
||||
Privacy note: GitLab submits the user's IP and user agent to Akismet.
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ In earlier GitLab versions, this only applied to API and non-project members.
|
|||
|
||||
To use Akismet:
|
||||
|
||||
1. Go to the URL: <https://akismet.com/account/>
|
||||
1. Sign-in or create a new account.
|
||||
1. Click on **Show** to reveal the API key.
|
||||
1. Go to the [Akismet sign-in page](https://akismet.com/account/).
|
||||
1. Sign in or create a new account.
|
||||
1. Click **Show** to reveal the API key.
|
||||
1. Go to **Admin Area > Settings > Reporting** (`/admin/application_settings/reporting`).
|
||||
1. Check the **Enable Akismet** checkbox.
|
||||
1. Select the **Enable Akismet** checkbox.
|
||||
1. Fill in the API key from step 3.
|
||||
1. Save the configuration.
|
||||
|
||||
|
|
@ -35,23 +35,20 @@ To use Akismet:
|
|||
|
||||
## Training
|
||||
|
||||
NOTE: **Note:**
|
||||
Training the Akismet filter is only available in GitLab 8.11 and later.
|
||||
|
||||
As a way to better recognize between spam and ham, you can train the Akismet
|
||||
To better differentiate between spam and ham, you can train the Akismet
|
||||
filter whenever there is a false positive or false negative.
|
||||
|
||||
When an entry is recognized as spam, it is rejected and added to the Spam Logs.
|
||||
From here you can review if they are really spam. If one of them is not really
|
||||
From here you can review if entries are really spam. If one of them is not really
|
||||
spam, you can use the **Submit as ham** button to tell Akismet that it falsely
|
||||
recognized an entry as spam.
|
||||
|
||||

|
||||
|
||||
If an entry that is actually spam was not recognized as such, you will be able
|
||||
to also submit this to Akismet. The **Submit as spam** button will only appear
|
||||
to admin users.
|
||||
If an entry that is actually spam was not recognized as such, you can also submit
|
||||
this information to Akismet. The **Submit as spam** button is only displayed
|
||||
to administrator users.
|
||||
|
||||

|
||||
|
||||
Training Akismet will help it to recognize spam more accurately in the future.
|
||||
Training Akismet helps it to recognize spam more accurately in the future.
|
||||
|
|
|
|||
|
|
@ -12,31 +12,30 @@ application.
|
|||
1. Sign in to the [Auth0 Console](https://auth0.com/auth/login). If you need to
|
||||
create an account, you can do so at the same link.
|
||||
|
||||
1. Select "New App/API".
|
||||
1. Select **New App/API**.
|
||||
|
||||
1. Provide the Application Name ('GitLab' works fine).
|
||||
|
||||
1. Once created, you should see the Quick Start options. Disregard them and
|
||||
select 'Settings' above the Quick Start options.
|
||||
1. After creating, you should see the **Quick Start** options. Disregard them and
|
||||
select **Settings** above the **Quick Start** options.
|
||||
|
||||
1. At the top of the Settings screen, you should see your Domain, Client ID and
|
||||
Client Secret. Take note of these as you'll need to put them in the
|
||||
configuration file. For example:
|
||||
1. At the top of the Settings screen, you should see your **Domain**, **Client ID**, and
|
||||
**Client Secret**. These values are needed in the configuration file. For example:
|
||||
- Domain: `test1234.auth0.com`
|
||||
- Client ID: `t6X8L2465bNePWLOvt9yi41i`
|
||||
- Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2`
|
||||
|
||||
1. Fill in the Allowed Callback URLs:
|
||||
1. Fill in the **Allowed Callback URLs**:
|
||||
- `http://YOUR_GITLAB_URL/users/auth/auth0/callback` (or)
|
||||
- `https://YOUR_GITLAB_URL/users/auth/auth0/callback`
|
||||
|
||||
1. Fill in the Allowed Origins (CORS):
|
||||
1. Fill in the **Allowed Origins (CORS)**:
|
||||
- `http://YOUR_GITLAB_URL` (or)
|
||||
- `https://YOUR_GITLAB_URL`
|
||||
|
||||
1. On your GitLab server, open the configuration file.
|
||||
|
||||
For Omnibus package:
|
||||
For Omnibus GitLab:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
|
|
@ -49,12 +48,12 @@ application.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
1. Read [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Omnibus GitLab:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
|
|
@ -87,10 +86,14 @@ application.
|
|||
1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console
|
||||
page from step 5.
|
||||
|
||||
1. [Reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../administration/restart_gitlab.md#installations-from-source) for the changes to take effect if you
|
||||
installed GitLab via Omnibus or from source respectively.
|
||||
1. Reconfigure or restart GitLab, depending on your installation method:
|
||||
|
||||
On the sign in page there should now be an Auth0 icon below the regular sign in
|
||||
form. Click the icon to begin the authentication process. Auth0 will ask the
|
||||
user to sign in and authorize the GitLab application. If everything goes well
|
||||
the user will be returned to GitLab and will be signed in.
|
||||
- *If you installed from Omnibus GitLab,*
|
||||
[Reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab.
|
||||
- *If you installed from source,*
|
||||
[restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
||||
|
||||
On the sign-in page there should now be an Auth0 icon below the regular sign-in
|
||||
form. Click the icon to begin the authentication process. Auth0 asks the
|
||||
user to sign in and authorize the GitLab application. If the user authenticates
|
||||
successfully, the user is returned to GitLab and signed in.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Microsoft Azure OAuth2 OmniAuth Provider
|
||||
|
||||
To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your application with Azure. Azure will generate a client ID and secret key for you to use.
|
||||
To enable the Microsoft Azure OAuth2 OmniAuth provider, you must register your application with Azure. Azure generates a client ID and secret key for you to use.
|
||||
|
||||
Sign in to the [Azure Portal](https://portal.azure.com), and follow the instructions in
|
||||
the [Microsoft Quickstart documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).
|
||||
|
|
@ -15,15 +15,19 @@ As you go through the Microsoft procedure, keep the following in mind:
|
|||
|
||||
- If you have multiple instances of Azure Active Directory, you can switch to the desired tenant.
|
||||
- You're setting up a Web application.
|
||||
- For the redirect URI, you'll need the URL of the Azure OAuth callback of your GitLab installation (for example, `https://gitlab.mycompany.com/users/auth/azure_oauth2/callback`). The type dropdown should be set to "Web".
|
||||
- The redirect URI requires the URL of the Azure OAuth callback of your GitLab
|
||||
installation. For example, `https://gitlab.mycompany.com/users/auth/azure_oauth2/callback`.
|
||||
The type dropdown should be set to **Web**.
|
||||
- The `client ID` and `client secret` are terms associated with OAuth 2. In some Microsoft documentation,
|
||||
the terms may be listed as `Application ID` and `Application Secret`.
|
||||
- If you need to generate a new client secret, follow the Microsoft documentation on how to [Create a new application secret](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#create-a-new-application-secret).
|
||||
- Save the client ID and client secret for your new app. Once you leave the Azure portal, you won't be able to find the client secret again.
|
||||
- If you need to generate a new client secret, follow the Microsoft documentation
|
||||
for [creating a new application secret](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#create-a-new-application-secret).
|
||||
- Save the client ID and client secret for your new app, as the client secret is only
|
||||
displayed one time.
|
||||
|
||||
1. On your GitLab server, open the configuration file.
|
||||
|
||||
For Omnibus package:
|
||||
For Omnibus GitLab:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
|
|
@ -37,11 +41,12 @@ As you go through the Microsoft procedure, keep the following in mind:
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. Refer to [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Omnibus GitLab:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
|
|
@ -66,16 +71,22 @@ As you go through the Microsoft procedure, keep the following in mind:
|
|||
```
|
||||
|
||||
The `base_azure_url` is optional and can be added for different locales;
|
||||
e.g. `base_azure_url: "https://login.microsoftonline.de"`.
|
||||
such as `base_azure_url: "https://login.microsoftonline.de"`.
|
||||
|
||||
1. Replace 'CLIENT ID', 'CLIENT SECRET' and 'TENANT ID' with the values you got above.
|
||||
1. Replace `CLIENT ID`, `CLIENT SECRET` and `TENANT ID` with the values you got above.
|
||||
|
||||
1. Save the configuration file.
|
||||
|
||||
1. [Reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../administration/restart_gitlab.md#installations-from-source) for the changes to take effect if you
|
||||
installed GitLab via Omnibus or from source respectively.
|
||||
1. Reconfigure or restart GitLab, depending on your installation method:
|
||||
|
||||
On the sign-in page, you should now see a Microsoft icon below the regular sign in form. Click the icon
|
||||
to begin the authentication process. Microsoft then asks you to sign in and authorize the GitLab application. If everything goes well, you are returned to GitLab and signed in.
|
||||
See [Enable OmniAuth for an Existing User](omniauth.md#enable-omniauth-for-an-existing-user)
|
||||
- *If you installed from Omnibus GitLab,*
|
||||
[reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab.
|
||||
- *If you installed from source,*
|
||||
[restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
||||
|
||||
On the sign-in page, you should now see a Microsoft icon below the regular sign-in form.
|
||||
Click the icon to begin the authentication process. Microsoft then asks you to
|
||||
sign in and authorize the GitLab application. If successful, you are returned to GitLab and signed in.
|
||||
|
||||
Read [Enable OmniAuth for an Existing User](omniauth.md#enable-omniauth-for-an-existing-user)
|
||||
for information on how existing GitLab users can connect to their newly-available Azure AD accounts.
|
||||
|
|
|
|||
|
|
@ -8,33 +8,22 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
NOTE: **Note:**
|
||||
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
|
||||
earlier version, you'll need to explicitly enable it.
|
||||
|
||||
Import projects from Bitbucket.org and login to your GitLab instance with your
|
||||
Bitbucket.org account.
|
||||
|
||||
## Overview
|
||||
earlier version, you must explicitly enable it.
|
||||
|
||||
You can set up Bitbucket.org as an OAuth2 provider so that you can use your
|
||||
credentials to authenticate into GitLab or import your projects from
|
||||
Bitbucket.org account credentials to sign into GitLab or import your projects from
|
||||
Bitbucket.org.
|
||||
|
||||
- To use Bitbucket.org as an OmniAuth provider, follow the [Bitbucket OmniAuth
|
||||
provider](#bitbucket-omniauth-provider) section.
|
||||
- To use Bitbucket.org as an OmniAuth provider, follow the
|
||||
[Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) section.
|
||||
- To import projects from Bitbucket, follow both the
|
||||
[Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and
|
||||
[Bitbucket project import](#bitbucket-project-import) sections.
|
||||
|
||||
## Bitbucket OmniAuth provider
|
||||
|
||||
NOTE: **Note:**
|
||||
GitLab 8.15 significantly simplified the way to integrate Bitbucket.org with
|
||||
GitLab. You are encouraged to upgrade your GitLab instance if you haven't done so
|
||||
already. If you're using GitLab 8.14 or below, [use the previous integration
|
||||
docs](https://gitlab.com/gitlab-org/gitlab/blob/8-14-stable-ee/doc/integration/bitbucket.md).
|
||||
|
||||
To enable the Bitbucket OmniAuth provider you must register your application
|
||||
with Bitbucket.org. Bitbucket will generate an application ID and secret key for
|
||||
with Bitbucket.org. Bitbucket generates an application ID and secret key for
|
||||
you to use.
|
||||
|
||||
1. Sign in to [Bitbucket.org](https://bitbucket.org).
|
||||
|
|
@ -42,26 +31,23 @@ you to use.
|
|||
settings (**Manage team**), depending on how you want the application registered.
|
||||
It does not matter if the application is registered as an individual or a
|
||||
team, that is entirely up to you.
|
||||
1. Select **OAuth** in the left menu under "Access Management".
|
||||
1. In the left menu under **Access Management**, select **OAuth**.
|
||||
1. Select **Add consumer**.
|
||||
1. Provide the required details:
|
||||
|
||||
| Item | Description |
|
||||
| :--- | :---------- |
|
||||
| **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
|
||||
| **Application description** | Fill this in if you wish. |
|
||||
| **Callback URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com/users/auth`. |
|
||||
| **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
|
||||
- **Name:** This can be anything. Consider something like `<Organization>'s GitLab`
|
||||
or `<Your Name>'s GitLab` or something else descriptive.
|
||||
- **Application description:** *(Optional)* Fill this in if you wish.
|
||||
- **Callback URL:** (Required in GitLab versions 8.15 and greater)
|
||||
The URL to your GitLab installation, such as
|
||||
`https://gitlab.example.com/users/auth`. Be sure to append `/users/auth` to
|
||||
the end of the callback URL to prevent an
|
||||
[OAuth2 convert redirect](http://tetraph.com/covert_redirect/) vulnerability.
|
||||
Leaving this field empty
|
||||
[results in an `Invalid redirect_uri` message](https://confluence.atlassian.com/bitbucket/oauth-faq-338365710.html).
|
||||
- **URL:** The URL to your GitLab installation, such as `https://gitlab.example.com`.
|
||||
|
||||
NOTE: Be sure to append `/users/auth` to the end of the callback URL
|
||||
to prevent a [OAuth2 convert
|
||||
redirect](http://tetraph.com/covert_redirect/) vulnerability.
|
||||
|
||||
NOTE: Starting in GitLab 8.15, you MUST specify a callback URL, or you will
|
||||
see an "Invalid redirect_uri" message. For more details, see [the
|
||||
Bitbucket documentation](https://confluence.atlassian.com/bitbucket/oauth-faq-338365710.html).
|
||||
|
||||
And grant at least the following permissions:
|
||||
1. Grant at least the following permissions:
|
||||
|
||||
```plaintext
|
||||
Account: Email, Read
|
||||
|
|
@ -75,8 +61,8 @@ you to use.
|
|||

|
||||
|
||||
1. Select **Save**.
|
||||
1. Select your newly created OAuth consumer and you should now see a Key and
|
||||
Secret in the list of OAuth consumers. Keep this page open as you continue
|
||||
1. Select your newly created OAuth consumer, and you should now see a **Key** and
|
||||
**Secret** in the list of OAuth consumers. Keep this page open as you continue
|
||||
the configuration.
|
||||
|
||||

|
||||
|
|
@ -125,16 +111,16 @@ you to use.
|
|||
|
||||
1. Save the configuration file.
|
||||
1. For the changes to take effect, [reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) if you installed via
|
||||
Omnibus, or [restart](../administration/restart_gitlab.md#installations-from-source) if installed from source.
|
||||
Omnibus GitLab, or [restart](../administration/restart_gitlab.md#installations-from-source) if installed from source.
|
||||
|
||||
On the sign in page there should now be a Bitbucket icon below the regular sign
|
||||
in form. Click the icon to begin the authentication process. Bitbucket will ask
|
||||
the user to sign in and authorize the GitLab application. If everything goes
|
||||
well, the user will be returned to GitLab and will be signed in.
|
||||
On the sign-in page there should now be a Bitbucket icon below the regular
|
||||
sign-in form. Click the icon to begin the authentication process. Bitbucket asks
|
||||
the user to sign in and authorize the GitLab application. If successful, the user
|
||||
is returned to GitLab and signed in.
|
||||
|
||||
## Bitbucket project import
|
||||
|
||||
Once the above configuration is set up, you can use Bitbucket to sign into
|
||||
After the above configuration is set up, you can use Bitbucket to sign into
|
||||
GitLab and [start importing your projects](../user/project/import/bitbucket.md).
|
||||
|
||||
If you want to import projects from Bitbucket, but don't want to enable signing in,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ The v2 auto-deploy-image contains multiple dependency and architectural changes.
|
|||
If your Auto DevOps project has an active environment deployed with the v1 `auto-deploy-image`,
|
||||
please proceed with the following upgrade guide. Otherwise, you can skip this process.
|
||||
|
||||
#### Kubernetes 1.16+
|
||||
|
||||
The v2 auto-deploy-image also drops support for Kubernetes 1.15 and lower.
|
||||
|
||||
#### Helm 3
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228609) in GitLab 13.4.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Configure
|
||||
group: Configure
|
||||
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/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Instance Statistics **(CORE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235754) in GitLab 13.4.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235754) in GitLab 13.5 behind a feature flag, disabled by default.
|
||||
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46962) in GitLab 13.6.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
|
||||
CAUTION: **Warning:**
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
|
@ -36,3 +39,22 @@ in the categories shown in [Total counts](#total-counts).
|
|||
These charts help you visualize how rapidly these records are being created on your instance.
|
||||
|
||||

|
||||
|
||||
### Enable or disable Instance Statistics
|
||||
|
||||
In GitLab version 13.5 only, Instance Statistics was under development and not ready for production use.
|
||||
It was deployed behind a feature flag that was **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can opt to enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:instance_statistics)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:instance_statistics)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -638,6 +638,86 @@ variables:
|
|||
FUZZAPI_OVERRIDES_INTERVAL: 300
|
||||
```
|
||||
|
||||
### Header Fuzzing
|
||||
|
||||
Header fuzzing is disabled by default due to the high number of false positives that occur with many
|
||||
technology stacks. When header fuzzing is enabled, you must specify a list of headers to include in
|
||||
fuzzing.
|
||||
|
||||
Each profile in the default configuration file has an entry for `GeneralFuzzingCheck`. This check
|
||||
performs header fuzzing. Under the `Configuration` section, you must change the `HeaderFuzzing` and
|
||||
`Headers` settings to enable header fuzzing.
|
||||
|
||||
This snippet shows the `Quick-10` profile's default configuration with header fuzzing disabled:
|
||||
|
||||
```yaml
|
||||
- Name: Quick-10
|
||||
DefaultProfile: Empty
|
||||
Routes:
|
||||
- Route: *Route0
|
||||
Checks:
|
||||
- Name: FormBodyFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
- Name: GeneralFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
HeaderFuzzing: false
|
||||
Headers:
|
||||
- Name: JsonFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
- Name: XmlFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
```
|
||||
|
||||
`HeaderFuzzing` is a boolean that turns header fuzzing on and off. The default setting is `false`
|
||||
for off. To turn header fuzzing on, change this setting to `true`:
|
||||
|
||||
```yaml
|
||||
- Name: GeneralFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
HeaderFuzzing: true
|
||||
Headers:
|
||||
```
|
||||
|
||||
`Headers` is a list of headers to fuzz. Only headers listed are fuzzed. For example, to fuzz a
|
||||
custom header `X-Custom` used by your APIs, add an entry for it using the syntax
|
||||
`- Name: HeaderName`, substituting `HeaderName` with the header to fuzz:
|
||||
|
||||
```yaml
|
||||
- Name: GeneralFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
HeaderFuzzing: true
|
||||
Headers:
|
||||
- Name: X-Custom
|
||||
```
|
||||
|
||||
You now have a configuration to fuzz the header `X-Custom`. Use the same notation to list additional
|
||||
headers:
|
||||
|
||||
```yaml
|
||||
- Name: GeneralFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
HeaderFuzzing: true
|
||||
Headers:
|
||||
- Name: X-Custom
|
||||
- Name: X-AnotherHeader
|
||||
```
|
||||
|
||||
Repeat this configuration for each profile as needed.
|
||||
|
||||
## Running your first scan
|
||||
|
||||
When configured correctly, a CI/CD pipeline contains a `Fuzz` stage and a `apifuzzer_fuzz` job. The
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Based on https://community.developer.atlassian.com/t/get-rest-api-3-filter-search/29459/2,
|
||||
# it's enough at the moment to simply notice if the url is from `atlassian.net`
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Backfill the deployment_type in jira_tracker_data table
|
||||
class BackfillJiraTrackerDeploymentType2
|
||||
# Migration only version of jira_tracker_data table
|
||||
class JiraTrackerDataTemp < ApplicationRecord
|
||||
self.table_name = 'jira_tracker_data'
|
||||
|
||||
def self.encryption_options
|
||||
{
|
||||
key: Settings.attr_encrypted_db_key_base_32,
|
||||
encode: true,
|
||||
mode: :per_attribute_iv,
|
||||
algorithm: 'aes-256-gcm'
|
||||
}
|
||||
end
|
||||
|
||||
attr_encrypted :url, encryption_options
|
||||
attr_encrypted :api_url, encryption_options
|
||||
|
||||
enum deployment_type: { unknown: 0, server: 1, cloud: 2 }, _prefix: :deployment
|
||||
end
|
||||
|
||||
# Migration only version of services table
|
||||
class JiraServiceTemp < ApplicationRecord
|
||||
self.table_name = 'services'
|
||||
self.inheritance_column = :_type_disabled
|
||||
end
|
||||
|
||||
def perform(start_id, stop_id)
|
||||
@server_ids = []
|
||||
@cloud_ids = []
|
||||
|
||||
JiraTrackerDataTemp
|
||||
.where(id: start_id..stop_id, deployment_type: 0)
|
||||
.each do |jira_tracker_data|
|
||||
collect_deployment_type(jira_tracker_data)
|
||||
end
|
||||
|
||||
unless cloud_ids.empty?
|
||||
JiraTrackerDataTemp.where(id: cloud_ids)
|
||||
.update_all(deployment_type: JiraTrackerDataTemp.deployment_types[:cloud])
|
||||
end
|
||||
|
||||
unless server_ids.empty?
|
||||
JiraTrackerDataTemp.where(id: server_ids)
|
||||
.update_all(deployment_type: JiraTrackerDataTemp.deployment_types[:server])
|
||||
end
|
||||
|
||||
mark_jobs_as_succeeded(start_id, stop_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :server_ids, :cloud_ids
|
||||
|
||||
def client_url(jira_tracker_data)
|
||||
jira_tracker_data.api_url.presence || jira_tracker_data.url.presence
|
||||
end
|
||||
|
||||
def server_type(url)
|
||||
url.downcase.include?('.atlassian.net') ? :cloud : :server
|
||||
end
|
||||
|
||||
def collect_deployment_type(jira_tracker_data)
|
||||
url = client_url(jira_tracker_data)
|
||||
return unless url
|
||||
|
||||
case server_type(url)
|
||||
when :cloud
|
||||
cloud_ids << jira_tracker_data.id
|
||||
else
|
||||
server_ids << jira_tracker_data.id
|
||||
end
|
||||
end
|
||||
|
||||
def mark_jobs_as_succeeded(*arguments)
|
||||
Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(self.class.name, arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
|
||||
|
||||
ALLOWLISTED_GIT_ROUTES = {
|
||||
'repositories/git_http' => %w{git_upload_pack git_receive_pack}
|
||||
'repositories/git_http' => %w{git_upload_pack}
|
||||
}.freeze
|
||||
|
||||
ALLOWLISTED_GIT_LFS_ROUTES = {
|
||||
|
|
@ -96,7 +96,7 @@ module Gitlab
|
|||
def workhorse_passthrough_route?
|
||||
# Calling route_hash may be expensive. Only do it if we think there's a possible match
|
||||
return false unless request.post? &&
|
||||
request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack')
|
||||
request.path.end_with?('.git/git-upload-pack')
|
||||
|
||||
ALLOWLISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8356,9 +8356,6 @@ msgstr ""
|
|||
msgid "CycleAnalytics|stage dropdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "DAG"
|
||||
msgstr ""
|
||||
|
||||
msgid "DAG visualization requires at least 3 dependent jobs."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13985,6 +13982,9 @@ msgstr ""
|
|||
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "If you did not recently sign in, you should immediately %{password_link_start}change your password%{password_link_end}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15829,6 +15829,9 @@ msgstr ""
|
|||
msgid "Learn more about License-Check"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more about Needs relationships"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more about Vulnerability-Check"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15856,9 +15859,6 @@ msgstr ""
|
|||
msgid "Learn more about group-level project templates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more about job dependencies"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more about signing commits"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17930,6 +17930,9 @@ msgstr ""
|
|||
msgid "Need help?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Needs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Needs attention"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -22663,9 +22666,6 @@ msgstr ""
|
|||
msgid "Removes time estimate."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removing integrations is not supported for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Removing this group also removes all child projects, including archived projects, and their resources."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25644,6 +25644,9 @@ msgstr ""
|
|||
msgid "Specify the following URL during the Runner setup:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed up your pipelines with Needs relationships"
|
||||
msgstr ""
|
||||
|
||||
msgid "Squash commit message"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25761,9 +25764,6 @@ msgstr ""
|
|||
msgid "Start thread & reopen %{noteable_name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start using Directed Acyclic Graphs (DAG)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start your Free Gold Trial"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27826,9 +27826,6 @@ msgstr ""
|
|||
msgid "This page sends a payload. Go back to the events page to see a newly created event."
|
||||
msgstr ""
|
||||
|
||||
msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
|
||||
msgstr ""
|
||||
|
||||
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{b_open}Auto DevOps.%{b_close}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29582,6 +29579,9 @@ msgstr ""
|
|||
msgid "UserProfile|Report abuse"
|
||||
msgstr ""
|
||||
|
||||
msgid "UserProfile|Retry"
|
||||
msgstr ""
|
||||
|
||||
msgid "UserProfile|Snippets"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29693,15 +29693,15 @@ msgstr ""
|
|||
msgid "UsersSelect|Unassigned"
|
||||
msgstr ""
|
||||
|
||||
msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Using required encryption strategy when encrypted field is missing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Using the %{codeStart}needs%{codeEnd} keyword makes jobs run before their stage is reached. Jobs run as soon as their %{codeStart}needs%{codeEnd} relationships are met, which speeds up your pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Valid from"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -89,18 +89,6 @@ RSpec.describe Projects::Alerting::NotificationsController do
|
|||
make_request
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple endpoints are disabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_http_integrations: false)
|
||||
end
|
||||
|
||||
it 'does not find an integration' do
|
||||
expect(notify_service).to receive(:execute).with('some token', nil)
|
||||
|
||||
make_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without integration parameters specified' do
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::AvatarsController do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
controller.instance_variable_set(:@project, project)
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject { get :show, params: { namespace_id: project.namespace, project_id: project } }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
before do
|
||||
controller.instance_variable_set(:@project, project)
|
||||
end
|
||||
|
||||
subject { get :show, params: { namespace_id: project.namespace, project_id: project.path } }
|
||||
|
||||
context 'when repository has no avatar' do
|
||||
it 'shows 404' do
|
||||
|
|
@ -37,6 +37,15 @@ RSpec.describe Projects::AvatarsController do
|
|||
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
|
||||
end
|
||||
|
||||
it 'sets appropriate caching headers' do
|
||||
sign_in(project.owner)
|
||||
subject
|
||||
|
||||
expect(response.cache_control[:public]).to eq(true)
|
||||
expect(response.cache_control[:max_age]).to eq(60)
|
||||
expect(response.cache_control[:no_store]).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'project cache control headers'
|
||||
end
|
||||
|
||||
|
|
@ -51,9 +60,16 @@ RSpec.describe Projects::AvatarsController do
|
|||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
it 'removes avatar from DB by calling destroy' do
|
||||
delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id }
|
||||
let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
|
||||
|
||||
before do
|
||||
sign_in(project.owner)
|
||||
end
|
||||
|
||||
it 'removes avatar from DB by calling destroy' do
|
||||
delete :destroy, params: { namespace_id: project.namespace.path, project_id: project.path }
|
||||
|
||||
expect(response).to redirect_to(edit_project_path(project, anchor: 'js-general-project-settings'))
|
||||
expect(project.avatar.present?).to be_falsey
|
||||
expect(project).to be_valid
|
||||
end
|
||||
|
|
|
|||
|
|
@ -226,6 +226,15 @@ RSpec.describe Projects::RawController do
|
|||
get(:show, params: { namespace_id: project.namespace, project_id: project, id: 'master/README.md' })
|
||||
end
|
||||
|
||||
it 'sets appropriate caching headers' do
|
||||
sign_in create(:user)
|
||||
request_file
|
||||
|
||||
expect(response.cache_control[:public]).to eq(true)
|
||||
expect(response.cache_control[:max_age]).to eq(60)
|
||||
expect(response.cache_control[:no_store]).to be_nil
|
||||
end
|
||||
|
||||
context 'when If-None-Match header is set' do
|
||||
it 'returns a 304 status' do
|
||||
request_file
|
||||
|
|
|
|||
|
|
@ -122,7 +122,9 @@ RSpec.describe Projects::RepositoriesController do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.header['ETag']).to be_present
|
||||
expect(response.header['Cache-Control']).to include('max-age=60, private')
|
||||
expect(response.cache_control[:public]).to eq(false)
|
||||
expect(response.cache_control[:max_age]).to eq(60)
|
||||
expect(response.cache_control[:no_store]).to be_nil
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ RSpec.describe 'Pipeline', :js do
|
|||
it 'shows Pipeline, Jobs, DAG and Failed Jobs tabs with link' do
|
||||
expect(page).to have_link('Pipeline')
|
||||
expect(page).to have_link('Jobs')
|
||||
expect(page).to have_link('DAG')
|
||||
expect(page).to have_link('Needs')
|
||||
expect(page).to have_link('Failed Jobs')
|
||||
end
|
||||
|
||||
|
|
@ -893,7 +893,7 @@ RSpec.describe 'Pipeline', :js do
|
|||
it 'shows Pipeline, Jobs and DAG tabs with link' do
|
||||
expect(page).to have_link('Pipeline')
|
||||
expect(page).to have_link('Jobs')
|
||||
expect(page).to have_link('DAG')
|
||||
expect(page).to have_link('Needs')
|
||||
end
|
||||
|
||||
it 'shows counter in Jobs tab' do
|
||||
|
|
|
|||
|
|
@ -2,21 +2,26 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import Line from '~/jobs/components/log/line.vue';
|
||||
import LineNumber from '~/jobs/components/log/line_number.vue';
|
||||
|
||||
const httpUrl = 'http://example.com';
|
||||
const httpsUrl = 'https://example.com';
|
||||
|
||||
const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {}) => ({
|
||||
line: {
|
||||
content: [
|
||||
{
|
||||
text,
|
||||
style: 'term-fg-l-green',
|
||||
},
|
||||
],
|
||||
lineNumber: 0,
|
||||
},
|
||||
path: '/jashkenas/underscore/-/jobs/335',
|
||||
});
|
||||
|
||||
describe('Job Log Line', () => {
|
||||
let wrapper;
|
||||
|
||||
const data = {
|
||||
line: {
|
||||
content: [
|
||||
{
|
||||
text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
|
||||
style: 'term-fg-l-green',
|
||||
},
|
||||
],
|
||||
lineNumber: 0,
|
||||
},
|
||||
path: '/jashkenas/underscore/-/jobs/335',
|
||||
};
|
||||
let data;
|
||||
let originalGon;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(Line, {
|
||||
|
|
@ -26,12 +31,25 @@ describe('Job Log Line', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findLine = () => wrapper.find('span');
|
||||
const findLink = () => findLine().find('a');
|
||||
const findLinksAt = i =>
|
||||
findLine()
|
||||
.findAll('a')
|
||||
.at(i);
|
||||
|
||||
beforeEach(() => {
|
||||
originalGon = window.gon;
|
||||
window.gon.features = {
|
||||
ciJobLineLinks: false,
|
||||
};
|
||||
|
||||
data = mockProps();
|
||||
createComponent(data);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
window.gon = originalGon;
|
||||
});
|
||||
|
||||
it('renders the line number component', () => {
|
||||
|
|
@ -39,10 +57,103 @@ describe('Job Log Line', () => {
|
|||
});
|
||||
|
||||
it('renders a span the provided text', () => {
|
||||
expect(wrapper.find('span').text()).toBe(data.line.content[0].text);
|
||||
expect(findLine().text()).toBe(data.line.content[0].text);
|
||||
});
|
||||
|
||||
it('renders the provided style as a class attribute', () => {
|
||||
expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
|
||||
expect(findLine().classes()).toContain(data.line.content[0].style);
|
||||
});
|
||||
|
||||
describe.each([true, false])('when feature ci_job_line_links enabled = %p', ciJobLineLinks => {
|
||||
beforeEach(() => {
|
||||
window.gon.features = {
|
||||
ciJobLineLinks,
|
||||
};
|
||||
});
|
||||
|
||||
it('renders text with symbols', () => {
|
||||
const text = 'apt-get update < /dev/null > /dev/null';
|
||||
createComponent(mockProps({ text }));
|
||||
|
||||
expect(findLine().text()).toBe(text);
|
||||
});
|
||||
|
||||
it.each`
|
||||
tag | text
|
||||
${'a'} | ${'<a href="#">linked</a>'}
|
||||
${'script'} | ${'<script>doEvil();</script>'}
|
||||
${'strong'} | ${'<strong>highlighted</strong>'}
|
||||
`('escapes `<$tag>` tags in text', ({ tag, text }) => {
|
||||
createComponent(mockProps({ text }));
|
||||
|
||||
expect(
|
||||
findLine()
|
||||
.find(tag)
|
||||
.exists(),
|
||||
).toBe(false);
|
||||
expect(findLine().text()).toBe(text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ci_job_line_links is enabled', () => {
|
||||
beforeEach(() => {
|
||||
window.gon.features = {
|
||||
ciJobLineLinks: true,
|
||||
};
|
||||
});
|
||||
|
||||
it('renders an http link', () => {
|
||||
createComponent(mockProps({ text: httpUrl }));
|
||||
|
||||
expect(findLink().text()).toBe(httpUrl);
|
||||
expect(findLink().attributes().href).toBe(httpUrl);
|
||||
});
|
||||
|
||||
it('renders an https link', () => {
|
||||
createComponent(mockProps({ text: httpsUrl }));
|
||||
|
||||
expect(findLink().text()).toBe(httpsUrl);
|
||||
expect(findLink().attributes().href).toBe(httpsUrl);
|
||||
});
|
||||
|
||||
it('renders a multiple links surrounded by text', () => {
|
||||
createComponent(mockProps({ text: `My HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl}` }));
|
||||
expect(findLine().text()).toBe(
|
||||
'My HTTP url: http://example.com and my HTTPS url: https://example.com',
|
||||
);
|
||||
expect(findLinksAt(0).attributes().href).toBe(httpUrl);
|
||||
expect(findLinksAt(1).attributes().href).toBe(httpsUrl);
|
||||
});
|
||||
|
||||
it('renders a link with rel nofollow and noopener', () => {
|
||||
createComponent(mockProps({ text: httpsUrl }));
|
||||
|
||||
expect(findLink().attributes().rel).toBe('nofollow noopener noreferrer');
|
||||
});
|
||||
|
||||
it('render links surrounded by text', () => {
|
||||
createComponent(
|
||||
mockProps({ text: `My HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl} are here.` }),
|
||||
);
|
||||
expect(findLine().text()).toBe(
|
||||
'My HTTP url: http://example.com and my HTTPS url: https://example.com are here.',
|
||||
);
|
||||
expect(findLinksAt(0).attributes().href).toBe(httpUrl);
|
||||
expect(findLinksAt(1).attributes().href).toBe(httpsUrl);
|
||||
});
|
||||
|
||||
const jshref = 'javascript:doEvil();'; // eslint-disable-line no-script-url
|
||||
|
||||
test.each`
|
||||
type | text
|
||||
${'js'} | ${jshref}
|
||||
${'file'} | ${'file:///a-file'}
|
||||
${'ftp'} | ${'ftp://example.com/file'}
|
||||
${'email'} | ${'email@example.com'}
|
||||
${'no scheme'} | ${'example.com/page'}
|
||||
`('does not render a $type link', ({ text }) => {
|
||||
createComponent(mockProps({ text }));
|
||||
expect(findLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { shallowMount, createLocalVue, createWrapper } from '@vue/test-utils';
|
||||
import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import createStore from '~/notes/stores';
|
||||
|
|
@ -14,9 +14,9 @@ describe('noteActions', () => {
|
|||
let actions;
|
||||
let axiosMock;
|
||||
|
||||
const shallowMountNoteActions = (propsData, computed) => {
|
||||
const mountNoteActions = (propsData, computed) => {
|
||||
const localVue = createLocalVue();
|
||||
return shallowMount(localVue.extend(noteActions), {
|
||||
return mount(localVue.extend(noteActions), {
|
||||
store,
|
||||
propsData,
|
||||
localVue,
|
||||
|
|
@ -61,7 +61,7 @@ describe('noteActions', () => {
|
|||
beforeEach(() => {
|
||||
store.dispatch('setUserData', userDataMock);
|
||||
|
||||
wrapper = shallowMountNoteActions(props);
|
||||
wrapper = mountNoteActions(props);
|
||||
});
|
||||
|
||||
it('should render noteable author badge', () => {
|
||||
|
|
@ -178,7 +178,7 @@ describe('noteActions', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMountNoteActions(props, {
|
||||
wrapper = mountNoteActions(props, {
|
||||
targetType: () => 'issue',
|
||||
});
|
||||
store.state.noteableData = {
|
||||
|
|
@ -205,7 +205,7 @@ describe('noteActions', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMountNoteActions(props, {
|
||||
wrapper = mountNoteActions(props, {
|
||||
targetType: () => 'issue',
|
||||
});
|
||||
});
|
||||
|
|
@ -221,7 +221,7 @@ describe('noteActions', () => {
|
|||
describe('user is not logged in', () => {
|
||||
beforeEach(() => {
|
||||
store.dispatch('setUserData', {});
|
||||
wrapper = shallowMountNoteActions({
|
||||
wrapper = mountNoteActions({
|
||||
...props,
|
||||
canDelete: false,
|
||||
canEdit: false,
|
||||
|
|
@ -241,7 +241,7 @@ describe('noteActions', () => {
|
|||
|
||||
describe('for showReply = true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMountNoteActions({
|
||||
wrapper = mountNoteActions({
|
||||
...props,
|
||||
showReply: true,
|
||||
});
|
||||
|
|
@ -256,7 +256,7 @@ describe('noteActions', () => {
|
|||
|
||||
describe('for showReply = false', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMountNoteActions({
|
||||
wrapper = mountNoteActions({
|
||||
...props,
|
||||
showReply: false,
|
||||
});
|
||||
|
|
@ -273,7 +273,7 @@ describe('noteActions', () => {
|
|||
beforeEach(() => {
|
||||
store.dispatch('setUserData', userDataMock);
|
||||
|
||||
wrapper = shallowMountNoteActions({ ...props, canResolve: true, isDraft: true });
|
||||
wrapper = mountNoteActions({ ...props, canResolve: true, isDraft: true });
|
||||
});
|
||||
|
||||
it('should render the right resolve button title', () => {
|
||||
|
|
|
|||
|
|
@ -28,14 +28,6 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver do
|
|||
end
|
||||
|
||||
it { is_expected.to contain_exactly(active_http_integration, prometheus_integration) }
|
||||
|
||||
context 'feature flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_http_integrations: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillJiraTrackerDeploymentType2, :migration, schema: 20201028182809 do
|
||||
let_it_be(:jira_service_temp) { described_class::JiraServiceTemp }
|
||||
let_it_be(:jira_tracker_data_temp) { described_class::JiraTrackerDataTemp }
|
||||
let_it_be(:atlassian_host) { 'https://api.atlassian.net' }
|
||||
let_it_be(:mixedcase_host) { 'https://api.AtlassiaN.nEt' }
|
||||
let_it_be(:server_host) { 'https://my.server.net' }
|
||||
|
||||
let(:jira_service) { jira_service_temp.create!(type: 'JiraService', active: true, category: 'issue_tracker') }
|
||||
|
||||
subject { described_class.new }
|
||||
|
||||
def create_tracker_data(options = {})
|
||||
jira_tracker_data_temp.create!({ service_id: jira_service.id }.merge(options))
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context do
|
||||
it 'ignores if deployment already set' do
|
||||
tracker_data = create_tracker_data(url: atlassian_host, deployment_type: 'server')
|
||||
|
||||
expect(subject).not_to receive(:collect_deployment_type)
|
||||
|
||||
subject.perform(tracker_data.id, tracker_data.id)
|
||||
|
||||
expect(tracker_data.reload.deployment_type).to eq 'server'
|
||||
end
|
||||
|
||||
it 'ignores if no url is set' do
|
||||
tracker_data = create_tracker_data(deployment_type: 'unknown')
|
||||
|
||||
expect(subject).to receive(:collect_deployment_type)
|
||||
|
||||
subject.perform(tracker_data.id, tracker_data.id)
|
||||
|
||||
expect(tracker_data.reload.deployment_type).to eq 'unknown'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tracker is valid' do
|
||||
let!(:tracker_1) { create_tracker_data(url: atlassian_host, deployment_type: 0) }
|
||||
let!(:tracker_2) { create_tracker_data(url: mixedcase_host, deployment_type: 0) }
|
||||
let!(:tracker_3) { create_tracker_data(url: server_host, deployment_type: 0) }
|
||||
let!(:tracker_4) { create_tracker_data(api_url: server_host, deployment_type: 0) }
|
||||
let!(:tracker_nextbatch) { create_tracker_data(api_url: atlassian_host, deployment_type: 0) }
|
||||
|
||||
it 'sets the proper deployment_type', :aggregate_failures do
|
||||
subject.perform(tracker_1.id, tracker_4.id)
|
||||
|
||||
expect(tracker_1.reload.deployment_cloud?).to be_truthy
|
||||
expect(tracker_2.reload.deployment_cloud?).to be_truthy
|
||||
expect(tracker_3.reload.deployment_server?).to be_truthy
|
||||
expect(tracker_4.reload.deployment_server?).to be_truthy
|
||||
expect(tracker_nextbatch.reload.deployment_unknown?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'marks background migration job records' do
|
||||
let(:arguments) { [1, 4] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20201028182809_backfill_jira_tracker_deployment_type2.rb')
|
||||
|
||||
RSpec.describe BackfillJiraTrackerDeploymentType2, :sidekiq, schema: 20201028182809 do
|
||||
let(:services) { table(:services) }
|
||||
let(:jira_tracker_data) { table(:jira_tracker_data) }
|
||||
let(:migration) { described_class.new }
|
||||
let(:batch_interval) { described_class::DELAY_INTERVAL }
|
||||
|
||||
describe '#up' do
|
||||
before do
|
||||
stub_const("#{described_class}::BATCH_SIZE", 2)
|
||||
|
||||
active_service = services.create!(type: 'JiraService', active: true)
|
||||
inactive_service = services.create!(type: 'JiraService', active: false)
|
||||
|
||||
jira_tracker_data.create!(id: 1, service_id: active_service.id, deployment_type: 0)
|
||||
jira_tracker_data.create!(id: 2, service_id: active_service.id, deployment_type: 1)
|
||||
jira_tracker_data.create!(id: 3, service_id: inactive_service.id, deployment_type: 2)
|
||||
jira_tracker_data.create!(id: 4, service_id: inactive_service.id, deployment_type: 0)
|
||||
jira_tracker_data.create!(id: 5, service_id: active_service.id, deployment_type: 0)
|
||||
end
|
||||
|
||||
it 'schedules BackfillJiraTrackerDeploymentType2 background jobs' do
|
||||
Sidekiq::Testing.fake! do
|
||||
freeze_time do
|
||||
migration.up
|
||||
|
||||
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
|
||||
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(batch_interval, 1, 4)
|
||||
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(batch_interval * 2, 5, 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -38,14 +38,6 @@ RSpec.describe AlertManagement::HttpIntegrations::CreateService do
|
|||
it_behaves_like 'error response', 'You have insufficient permissions to create an HTTP integration for this project'
|
||||
end
|
||||
|
||||
context 'when feature flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_http_integrations: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project'
|
||||
end
|
||||
|
||||
context 'when an integration already exists' do
|
||||
let_it_be(:existing_integration) { create(:alert_management_http_integration, project: project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -38,14 +38,6 @@ RSpec.describe AlertManagement::HttpIntegrations::DestroyService do
|
|||
it_behaves_like 'error response', 'You have insufficient permissions to remove this HTTP integration'
|
||||
end
|
||||
|
||||
context 'when feature flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_http_integrations: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'error response', 'Removing integrations is not supported for this project'
|
||||
end
|
||||
|
||||
context 'when an error occurs during removal' do
|
||||
before do
|
||||
allow(integration).to receive(:destroy).and_return(false)
|
||||
|
|
|
|||
|
|
@ -39,14 +39,6 @@ RSpec.describe AlertManagement::HttpIntegrations::UpdateService do
|
|||
it_behaves_like 'error response', 'You have insufficient permissions to update this HTTP integration'
|
||||
end
|
||||
|
||||
context 'when feature flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_http_integrations: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project'
|
||||
end
|
||||
|
||||
context 'when an error occurs during update' do
|
||||
let(:params) { { name: '' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -253,23 +253,6 @@ RSpec.describe Projects::Alerting::NotifyService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with an Alerts Service' do
|
||||
let_it_be_with_reload(:integration) { create(:alerts_service, project: project) }
|
||||
|
||||
it_behaves_like 'notifcations are handled correctly' do
|
||||
let(:source) { 'Generic Alert Endpoint' }
|
||||
end
|
||||
|
||||
context 'with deactivated Alerts Service' do
|
||||
before do
|
||||
integration.update!(active: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not process incident issues due to error', http_status: :forbidden
|
||||
it_behaves_like 'does not an create alert management alert'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an HTTP Integration' do
|
||||
let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) }
|
||||
|
||||
|
|
@ -284,7 +267,7 @@ RSpec.describe Projects::Alerting::NotifyService do
|
|||
integration.update!(active: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not process incident issues due to error', http_status: :unauthorized
|
||||
it_behaves_like 'does not process incident issues due to error', http_status: :forbidden
|
||||
it_behaves_like 'does not an create alert management alert'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'marks background migration job records' do
|
||||
it 'marks each job record as succeeded after processing' do
|
||||
create(:background_migration_job, class_name: "::#{described_class.name}",
|
||||
arguments: arguments)
|
||||
|
||||
expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
|
||||
|
||||
expect do
|
||||
subject.perform(*arguments)
|
||||
end.to change { ::Gitlab::Database::BackgroundMigrationJob.succeeded.count }.from(0).to(1)
|
||||
end
|
||||
|
||||
it 'returns the number of job records marked as succeeded' do
|
||||
create(:background_migration_job, class_name: "::#{described_class.name}",
|
||||
arguments: arguments)
|
||||
|
||||
jobs_updated = subject.perform(*arguments)
|
||||
|
||||
expect(jobs_updated).to eq(1)
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue