Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-16 18:09:15 +00:00
parent 01a8b31afe
commit 6d9c4dc2ef
101 changed files with 1357 additions and 534 deletions

View File

@ -1 +1 @@
40d58655a42f71b6180a3cbaf369cc20b60e695a
2ecaa5afbef22c84b4ce31fcda2a246753a966a3

View File

@ -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, {

View File

@ -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 };

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -1,5 +1,3 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});
initNewCluster();

View File

@ -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();

View File

@ -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) {

View File

@ -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>

View File

@ -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', {});

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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,

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.")

View File

@ -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.')

View File

@ -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'

View File

@ -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),

View File

@ -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)

View File

@ -0,0 +1,5 @@
---
title: Support fuzzing HTTP headers with API Fuzzing
merge_request: 47727
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Enable `vue_group_members_list` feature flag by default
merge_request: 47427
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Background migration for setting Jira tracker data deployment type
merge_request: 46368
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add new text and tab name for DAG
merge_request: 47415
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Refactor and UI-polish around activity calendar on user profile
merge_request: 47797
author: Takuya Noguchi
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Enable HTTP caching of repository raw, archive, and avatar endpoints
merge_request: 47430
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Expose GraphQL API for managing HTTP alerting intergations
merge_request: 47687
author:
type: added

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
c51bf825045ef80714f3903f25321785883da3d612725f6fa67ec3ddd12d5808

View File

@ -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!

View File

@ -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

View File

@ -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!

View File

@ -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": [
],

View File

@ -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 |

View File

@ -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.
![DAG visualization example](img/dag_graph_example_v13_1.png)
To see the needs visualization, click on the **Needs** tab when viewing a pipeline that uses the `needs:` keyword.
![Needs visualization example](img/dag_graph_example_v13_1.png)
Clicking a node will highlight all the job paths it depends on.
![DAG visualization with path highlight](img/dag_graph_example_clicked_v13_1.png)
![Needs visualization with path highlight](img/dag_graph_example_clicked_v13_1.png)
### 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:

View File

@ -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

View File

@ -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:
![Performance Bar Database Queries](img/performance_bar_members_page.png)
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:
![Performance Bar Cached Queries Modal](img/performance_bar_cached_queries.png)
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:
![Performance Bar Fixed Cached Queries](img/performance_bar_fixed_cached_queries.png)
## 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)

View File

@ -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)

View File

@ -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.

View File

@ -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 {

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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.
![Screenshot of Spam Logs](img/spam_log.png)
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.
![Screenshot of Issue](img/submit_issue.png)
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.

View File

@ -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.

View File

@ -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.

View File

@ -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.
![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
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.
![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
@ -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,

View File

@ -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
---

View File

@ -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
---

View File

@ -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
---

View File

@ -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
---

View File

@ -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
---

View File

@ -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.

View File

@ -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
---

View File

@ -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.
![Instance Activity Pipelines chart](img/instance_activity_pipelines_chart_v13_6.png)
### 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)
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, youll 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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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);
});
});
});

View File

@ -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', () => {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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)

View File

@ -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: '' } }

View File

@ -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

View File

@ -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