Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-08 21:10:06 +00:00
parent 0612ffef12
commit cb36ae7dd5
65 changed files with 516 additions and 95 deletions

View File

@ -42,6 +42,7 @@ rules:
no-jquery/no-serialize: error
promise/always-return: off
promise/no-callback-in-promise: off
"@gitlab/no-global-event-off": error
overrides:
- files:
- '**/spec/**/*'

View File

@ -74,6 +74,7 @@ export default class Autosave {
}
dispose() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.field.off('input');
}
}

View File

@ -596,6 +596,7 @@ export class AwardsHandler {
hideMenuElement($emojiMenu) {
$emojiMenu.on(transitionEndEventString, e => {
if (e.currentTarget === e.target) {
// eslint-disable-next-line @gitlab/no-global-event-off
$emojiMenu.removeClass(IS_RENDERED).off(transitionEndEventString);
}
});

View File

@ -97,6 +97,7 @@ export default class Shortcuts {
e.preventDefault();
});
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-shortcuts-modal-trigger')
.off('click')
.on('click', this.onToggleHelp);

View File

@ -15,6 +15,7 @@ function shouldCreateListGraphQL(label) {
return store.getters.shouldUseGraphQL && !store.getters.getListByLabelId(fullLabelId(label));
}
// eslint-disable-next-line @gitlab/no-global-event-off
$(document)
.off('created.label')
.on('created.label', (e, label, addNewList) => {

View File

@ -265,13 +265,21 @@ export default class Clusters {
removeListeners() {
eventHub.$off('installApplication', this.installApplication);
eventHub.$off('updateApplication', this.updateApplication);
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('saveKnativeDomain');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setKnativeDomain');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setCrossplaneProviderStack');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('uninstallApplication');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setIngressModSecurityEnabled');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setIngressModSecurityMode');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('resetIngressModSecurityChanges');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setFluentdSettings');
}

View File

@ -72,12 +72,14 @@ export default class ImageFile {
callback(e, left);
};
// eslint-disable-next-line @gitlab/no-global-event-off
$el
.off('mousedown')
.off('touchstart')
.on('mousedown', dragStart)
.on('touchstart', dragStart);
// eslint-disable-next-line @gitlab/no-global-event-off
$body
.off('mouseup')
.off('mousemove')

View File

@ -14,6 +14,7 @@ function openConfirmDangerModal($form, $modal, text) {
$submit.disable();
$input.focus();
// eslint-disable-next-line @gitlab/no-global-event-off
$input.off('input').on('input', function handleInput() {
const confirmText = rstrip($(this).val());
if (confirmText === confirmTextMatch) {
@ -23,6 +24,7 @@ function openConfirmDangerModal($form, $modal, text) {
}
});
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-confirm-danger-submit', $modal)
.off('click')
.on('click', () => {

View File

@ -154,6 +154,7 @@ export default {
});
},
beforeDestroy() {
// eslint-disable-next-line @gitlab/no-global-event-off
$(this.$refs.dropdown).off();
},
methods: {

View File

@ -29,11 +29,17 @@ export default class CreateLabelDropdown {
}
cleanBinding() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.$colorSuggestions.off('click');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$newLabelField.off('keyup change');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$newColorField.off('keyup change');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$dropdownBack.off('click');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$cancelButton.off('click');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$newLabelCreateButton.off('click');
}

View File

@ -74,6 +74,7 @@ export default () => {
const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label');
// eslint-disable-next-line @gitlab/no-global-event-off
$dropdown
.find('li a')
.off('click')

View File

@ -622,6 +622,7 @@ export class GitLabDropdown {
// eslint-disable-next-line class-methods-use-this
removeArrowKeyEvent() {
// eslint-disable-next-line @gitlab/no-global-event-off
return $('body').off('keydown');
}

View File

@ -93,7 +93,9 @@ export default {
},
beforeDestroy() {
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('toggleFolder');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('toggleDeployBoard');
},

View File

@ -78,6 +78,7 @@ class GfmAutoComplete {
this.input.each((i, input) => {
const $input = $(input);
if (!$input.hasClass('js-gfm-input-initialized')) {
// eslint-disable-next-line @gitlab/no-global-event-off
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
$input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
// This triggers at.js again

View File

@ -80,6 +80,7 @@ export default class GlFieldError {
// hidden when injected into DOM
errorAnchor.after(this.fieldErrorElement);
// eslint-disable-next-line @gitlab/no-global-event-off
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
@ -117,6 +118,7 @@ export default class GlFieldError {
this.form.focusInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
// eslint-disable-next-line @gitlab/no-global-event-off
this.inputElement
.off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this));

View File

@ -70,8 +70,10 @@ export default class GLForm {
}
setupAutosize() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.textarea.off('autosize:resized').on('autosize:resized', this.setHeightData.bind(this));
// eslint-disable-next-line @gitlab/no-global-event-off
this.textarea.off('mouseup.autosize').on('mouseup.autosize', this.destroyAutosize.bind(this));
setTimeout(() => {
@ -97,7 +99,9 @@ export default class GLForm {
}
clearEventListeners() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.textarea.off('focus');
// eslint-disable-next-line @gitlab/no-global-event-off
this.textarea.off('blur');
removeMarkdownListeners(this.form);
}

View File

@ -30,6 +30,7 @@ export default {
.on('hide.bs.dropdown', () => this.hideDropdown());
},
removeDropdownListeners() {
// eslint-disable-next-line @gitlab/no-global-event-off
$(this.$refs.dropdown)
.off('show.bs.dropdown')
.off('hide.bs.dropdown');

View File

@ -15,6 +15,7 @@ export default {
},
bindEvents() {
// eslint-disable-next-line @gitlab/no-global-event-off
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
},

View File

@ -3,7 +3,6 @@ import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import Visibility from 'visibilityjs';
import { __, s__, sprintf } from '~/locale';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { sanitize } from '~/lib/dompurify';
import { visitUrl } from '~/lib/utils/url_utility';
import Poll from '~/lib/utils/poll';
import eventHub from '../event_hub';
@ -179,7 +178,7 @@ export default {
const store = new Store({
titleHtml: this.initialTitleHtml,
titleText: this.initialTitleText,
descriptionHtml: sanitize(this.initialDescriptionHtml),
descriptionHtml: this.initialDescriptionHtml,
descriptionText: this.initialDescriptionText,
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,

View File

@ -4,13 +4,11 @@ import { sanitize } from '~/lib/dompurify';
// We currently load + parse the data from the issue app and related merge request
let cachedParsedData;
export const parseIssuableData = () => {
export const parseIssuableData = el => {
try {
if (cachedParsedData) return cachedParsedData;
const initialDataEl = document.getElementById('js-issuable-app');
const parsedData = JSON.parse(initialDataEl.dataset.initial);
const parsedData = JSON.parse(el.dataset.initial);
parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml);
parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml);

View File

@ -215,6 +215,7 @@ export default {
this.fetchIssuables();
},
beforeDestroy() {
// eslint-disable-next-line @gitlab/no-global-event-off
issueableEventHub.$off('issuables:toggleBulkEdit');
},
methods: {

View File

@ -339,6 +339,7 @@ export function addMarkdownListeners(form) {
Shortcuts.initMarkdownEditorShortcuts($(this), updateTextForToolbarBtn);
});
// eslint-disable-next-line @gitlab/no-global-event-off
const $allToolbarBtns = $('.js-md', form)
.off('click')
.on('click', function() {
@ -351,6 +352,7 @@ export function addMarkdownListeners(form) {
}
export function addEditorMarkdownListeners(editor) {
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-md')
.off('click')
.on('click', e => {
@ -376,5 +378,6 @@ export function removeMarkdownListeners(form) {
Shortcuts.removeMarkdownEditorShortcuts($(this));
});
// eslint-disable-next-line @gitlab/no-global-event-off
return $('.js-md', form).off('click');
}

View File

@ -77,6 +77,7 @@ if (process.env.NODE_ENV !== 'production' && gon?.test_env) {
document.addEventListener('beforeunload', () => {
// Unbind scroll events
// eslint-disable-next-line @gitlab/no-global-event-off
$(document).off('scroll');
// Close any open tooltips
tooltips.dispose(document.querySelectorAll('.has-tooltip, [data-toggle="tooltip"]'));

View File

@ -11,9 +11,11 @@ export default class Members {
}
addListeners() {
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-member-update-control')
.off('change')
.on('change', this.formSubmit.bind(this));
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-edit-member-form')
.off('ajax:success')
.on('ajax:success', this.formSuccess.bind(this));

View File

@ -39,6 +39,7 @@ export default class MirrorRepos {
initMirrorSSH() {
if (this.$password) {
// eslint-disable-next-line @gitlab/no-global-event-off
this.$password.off('input.updateUrl');
}
this.$password = undefined;

View File

@ -185,10 +185,15 @@ export default class SSHMirror {
}
destroy() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.$repositoryUrl.off('keyup');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$form.find('.js-known-hosts').off('keyup');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$dropdownAuthType.off('change');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$btnDetectHostKeys.off('click');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$btnSSHHostsShowAdvanced.off('click');
}
}

View File

@ -367,6 +367,7 @@ export default {
},
);
// eslint-disable-next-line @gitlab/no-global-event-off
eChart.off('datazoom');
eChart.on('datazoom', this.throttledDatazoom);
},

View File

@ -187,6 +187,7 @@ export default class Notes {
this.$wrapperEl.off('click', '.js-discussion-reply-button');
this.$wrapperEl.off('click', '.js-add-diff-note-button');
this.$wrapperEl.off('click', '.js-add-image-diff-note-button');
// eslint-disable-next-line @gitlab/no-global-event-off
this.$wrapperEl.off('visibilitychange');
this.$wrapperEl.off('keyup input', '.js-note-text');
this.$wrapperEl.off('click', '.js-note-target-reopen');

View File

@ -72,6 +72,7 @@ export default {
},
initLoadMore() {
// eslint-disable-next-line @gitlab/no-global-event-off
$(document).off('scroll');
$(document).endlessScroll({
bottomPixels: ENDLESS_SCROLL_BOTTOM_PX,

View File

@ -17,7 +17,8 @@ import initInviteMemberModal from '~/invite_member/init_invite_member_modal';
import { IssuableType } from '~/issuable_show/constants';
export default function() {
const { issueType, ...issuableData } = parseIssuableData();
const initialDataEl = document.getElementById('js-issuable-app');
const { issueType, ...issuableData } = parseIssuableData(initialDataEl);
switch (issueType) {
case IssuableType.Incident:

View File

@ -0,0 +1,11 @@
#import "~/pipelines/graphql/queries/pipeline_stages.fragment.graphql"
query getCiConfigData($content: String!) {
ciConfig(content: $content) {
errors
status
stages {
...PipelineStagesData
}
}
}

View File

@ -1,7 +1,7 @@
<script>
import { GlAlert, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { redirectTo, mergeUrlParams, refreshCurrentPage } from '~/lib/utils/url_utility';
import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import CommitForm from './components/commit/commit_form.vue';
@ -9,24 +9,25 @@ import TextEditor from './components/text_editor.vue';
import commitCiFileMutation from './graphql/mutations/commit_ci_file.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
const LOAD_FAILURE_NO_REF = 'LOAD_FAILURE_NO_REF';
const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
const COMMIT_FAILURE = 'COMMIT_FAILURE';
const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
const LOAD_FAILURE_NO_REF = 'LOAD_FAILURE_NO_REF';
const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export default {
components: {
CommitForm,
GlAlert,
GlLoadingIcon,
GlTab,
GlTabs,
PipelineGraph,
CommitForm,
TextEditor,
},
props: {
@ -55,14 +56,15 @@ export default {
},
data() {
return {
showFailureAlert: false,
failureType: null,
failureReasons: [],
isSaving: false,
editorIsReady: false,
ciConfigData: {},
content: '',
contentModel: '',
currentTabIndex: 0,
editorIsReady: false,
failureType: null,
failureReasons: [],
isSaving: false,
showFailureAlert: false,
};
},
apollo: {
@ -85,18 +87,35 @@ export default {
this.handleBlobContentError(error);
},
},
ciConfigData: {
query: getCiConfigData,
// If content is not loaded, we can't lint the data
skip: ({ contentModel }) => {
return !contentModel;
},
variables() {
return {
content: this.contentModel,
};
},
update(data) {
return data?.ciConfig ?? {};
},
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
},
},
},
computed: {
isLoading() {
isBlobContentLoading() {
return this.$apollo.queries.content.loading;
},
isVisualizeTabActive() {
return this.currentTabIndex === 1;
},
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
pipelineData() {
// Note data will loaded as part of https://gitlab.com/gitlab-org/gitlab/-/issues/263141
return {};
},
failure() {
switch (this.failureType) {
case LOAD_FAILURE_NO_REF:
@ -233,17 +252,17 @@ export default {
</ul>
</gl-alert>
<div class="gl-mt-4">
<gl-loading-icon v-if="isLoading" size="lg" class="gl-m-3" />
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<div v-else class="file-editor gl-mb-3">
<gl-tabs>
<gl-tabs v-model="currentTabIndex">
<!-- editor should be mounted when its tab is visible, so the container has a size -->
<gl-tab :title="$options.i18n.tabEdit" :lazy="!editorIsReady">
<!-- editor should be mounted only once, when the tab is displayed -->
<text-editor v-model="contentModel" @editor-ready="editorIsReady = true" />
</gl-tab>
<gl-tab :title="$options.i18n.tabGraph">
<pipeline-graph :pipeline-data="pipelineData" />
<gl-tab :title="$options.i18n.tabGraph" :lazy="!isVisualizeTabActive">
<pipeline-graph :pipeline-data="ciConfigData" />
</gl-tab>
</gl-tabs>
</div>

View File

@ -0,0 +1,12 @@
fragment PipelineStagesData on CiConfigStage {
name
groups {
name
jobs {
name
needs {
name
}
}
}
}

View File

@ -55,6 +55,7 @@ export default class ProjectFindFile {
}
initEvent() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.inputElement.off('keyup');
this.inputElement.on('keyup', event => {
const target = $(event.target);

View File

@ -26,12 +26,14 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
};
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
// eslint-disable-next-line @gitlab/no-global-event-off
$projectNameInput.off('keyup change').on('keyup change', () => {
onProjectNameChange($projectNameInput, $projectPathInput);
hasUserDefinedProjectName = $projectNameInput.val().trim().length > 0;
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
});
// eslint-disable-next-line @gitlab/no-global-event-off
$projectPathInput.off('keyup change').on('keyup change', () => {
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
@ -137,6 +139,7 @@ const bindEvents = () => {
target.focus();
})
.on('hide.bs.popover', () => {
// eslint-disable-next-line @gitlab/no-global-event-off
$(document).off('click.popover touchstart.popover');
});
}

View File

@ -97,7 +97,9 @@ export default {
},
beforeDestroy() {
const $input = $(this.$refs.input);
// eslint-disable-next-line @gitlab/no-global-event-off
$input.off('shown-issues.atwho');
// eslint-disable-next-line @gitlab/no-global-event-off
$input.off('hidden-issues.atwho');
$input.off('inserted-issues.atwho', this.onInput);
},

View File

@ -23,8 +23,11 @@ Sidebar.initialize = function() {
Sidebar.prototype.removeListeners = function() {
this.sidebar.off('click', '.sidebar-collapsed-icon');
// eslint-disable-next-line @gitlab/no-global-event-off
this.sidebar.off('hidden.gl.dropdown');
// eslint-disable-next-line @gitlab/no-global-event-off
$('.dropdown').off('loading.gl.dropdown');
// eslint-disable-next-line @gitlab/no-global-event-off
$('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle');
};

View File

@ -3,6 +3,7 @@ import { __ } from './locale';
function expandSection($section) {
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
// eslint-disable-next-line @gitlab/no-global-event-off
$section
.find('.settings-content')
.off('scroll.expandSection')

View File

@ -95,6 +95,7 @@ export default class SmartInterval {
window.removeEventListener('blur', this.onWindowVisibilityChange);
window.removeEventListener('focus', this.onWindowVisibilityChange);
this.cancel();
// eslint-disable-next-line @gitlab/no-global-event-off
$(document)
.off('visibilitychange')
.off('beforeunload');

View File

@ -25,6 +25,7 @@ export default class GLTerminal {
this.setSocketUrl();
this.createTerminal();
// eslint-disable-next-line @gitlab/no-global-event-off
$(window)
.off('resize.terminal')
.on('resize.terminal', () => {
@ -104,6 +105,7 @@ export default class GLTerminal {
}
dispose() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.terminal.off('data');
this.terminal.dispose();
this.socket.close();

View File

@ -1,5 +1,6 @@
export default class VersionCheckImage {
static bindErrorEvent(imageElement) {
// eslint-disable-next-line @gitlab/no-global-event-off
imageElement.off('error').on('error', () => imageElement.hide());
}
}

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Enums
# These color palettes are part of the Pajamas Design System.
# See https://design.gitlab.com/data-visualization/color/#categorical-data
module DataVisualizationPalette
def self.colors
{
blue: 0,
orange: 1,
aqua: 2,
green: 3,
magenta: 4
}
end
def self.weights
{
'50' => 0,
'100' => 1,
'200' => 2,
'300' => 3,
'400' => 4,
'500' => 5,
'600' => 6,
'700' => 7,
'800' => 8,
'900' => 9,
'950' => 10
}
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Add oncall rotations and participants tables
merge_request: 49058
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add member_events column to web_hooks table
merge_request: 49273
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix division by error when upload max size is set to 0
merge_request: 49482
author:
type: fixed

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class CreateIncidentManagementOnCallRotations < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless table_exists?(:incident_management_oncall_rotations)
with_lock_retries do
create_table :incident_management_oncall_rotations do |t|
t.timestamps_with_timezone
t.references :oncall_schedule, index: false, null: false, foreign_key: { to_table: :incident_management_oncall_schedules, on_delete: :cascade }
t.integer :length, null: false
t.integer :length_unit, limit: 2, null: false
t.datetime_with_timezone :starts_at, null: false
t.text :name, null: false
t.index %w(oncall_schedule_id id), name: 'index_inc_mgmnt_oncall_rotations_on_oncall_schedule_id_and_id', unique: true, using: :btree
t.index %w(oncall_schedule_id name), name: 'index_inc_mgmnt_oncall_rotations_on_oncall_schedule_id_and_name', unique: true, using: :btree
end
end
end
add_text_limit :incident_management_oncall_rotations, :name, 200
end
def down
with_lock_retries do
drop_table :incident_management_oncall_rotations
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
class AddIncidentManagementOnCallParticipants < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PARTICIPANT_ROTATION_INDEX_NAME = 'index_inc_mgmnt_oncall_participants_on_oncall_rotation_id'
PARTICIPANT_USER_INDEX_NAME = 'index_inc_mgmnt_oncall_participants_on_oncall_user_id'
UNIQUE_INDEX_NAME = 'index_inc_mgmnt_oncall_participants_on_user_id_and_rotation_id'
disable_ddl_transaction!
def up
unless table_exists?(:incident_management_oncall_participants)
with_lock_retries do
create_table :incident_management_oncall_participants do |t|
t.references :oncall_rotation, index: false, null: false, foreign_key: { to_table: :incident_management_oncall_rotations, on_delete: :cascade }
t.references :user, index: false, null: false, foreign_key: { on_delete: :cascade }
t.integer :color_palette, limit: 2, null: false
t.integer :color_weight, limit: 2, null: false
t.index :user_id, name: PARTICIPANT_USER_INDEX_NAME
t.index :oncall_rotation_id, name: PARTICIPANT_ROTATION_INDEX_NAME
t.index [:user_id, :oncall_rotation_id], unique: true, name: UNIQUE_INDEX_NAME
end
end
end
end
def down
drop_table :incident_management_oncall_participants
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddMemberEventsToWebHooks < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :web_hooks, :member_events, :boolean, null: false, default: false
end
end

View File

@ -0,0 +1 @@
2929b74d9b9d6e205c0e1fb2aaaffe323394058f6e583c56035a2c83b4d4ff03

View File

@ -0,0 +1 @@
451d7f29392f965467f364c7b119d269551e2dc1485e8cb15ebd14753fdb6e6a

View File

@ -0,0 +1 @@
8178b8a9acf7d2d8990bb6f7d984eb9e3b77d45cb2a8b54b56500ef6f93772ad

View File

@ -12989,6 +12989,44 @@ CREATE SEQUENCE import_failures_id_seq
ALTER SEQUENCE import_failures_id_seq OWNED BY import_failures.id;
CREATE TABLE incident_management_oncall_participants (
id bigint NOT NULL,
oncall_rotation_id bigint NOT NULL,
user_id bigint NOT NULL,
color_palette smallint NOT NULL,
color_weight smallint NOT NULL
);
CREATE SEQUENCE incident_management_oncall_participants_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE incident_management_oncall_participants_id_seq OWNED BY incident_management_oncall_participants.id;
CREATE TABLE incident_management_oncall_rotations (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
oncall_schedule_id bigint NOT NULL,
length integer NOT NULL,
length_unit smallint NOT NULL,
starts_at timestamp with time zone NOT NULL,
name text NOT NULL,
CONSTRAINT check_5209fb5d02 CHECK ((char_length(name) <= 200))
);
CREATE SEQUENCE incident_management_oncall_rotations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE incident_management_oncall_rotations_id_seq OWNED BY incident_management_oncall_rotations.id;
CREATE TABLE incident_management_oncall_schedules (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -17713,7 +17751,8 @@ CREATE TABLE web_hooks (
encrypted_url_iv character varying,
deployment_events boolean DEFAULT false NOT NULL,
releases_events boolean DEFAULT false NOT NULL,
feature_flag_events boolean DEFAULT false NOT NULL
feature_flag_events boolean DEFAULT false NOT NULL,
member_events boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE web_hooks_id_seq
@ -18230,6 +18269,10 @@ ALTER TABLE ONLY import_export_uploads ALTER COLUMN id SET DEFAULT nextval('impo
ALTER TABLE ONLY import_failures ALTER COLUMN id SET DEFAULT nextval('import_failures_id_seq'::regclass);
ALTER TABLE ONLY incident_management_oncall_participants ALTER COLUMN id SET DEFAULT nextval('incident_management_oncall_participants_id_seq'::regclass);
ALTER TABLE ONLY incident_management_oncall_rotations ALTER COLUMN id SET DEFAULT nextval('incident_management_oncall_rotations_id_seq'::regclass);
ALTER TABLE ONLY incident_management_oncall_schedules ALTER COLUMN id SET DEFAULT nextval('incident_management_oncall_schedules_id_seq'::regclass);
ALTER TABLE ONLY index_statuses ALTER COLUMN id SET DEFAULT nextval('index_statuses_id_seq'::regclass);
@ -19439,6 +19482,12 @@ ALTER TABLE ONLY import_export_uploads
ALTER TABLE ONLY import_failures
ADD CONSTRAINT import_failures_pkey PRIMARY KEY (id);
ALTER TABLE ONLY incident_management_oncall_participants
ADD CONSTRAINT incident_management_oncall_participants_pkey PRIMARY KEY (id);
ALTER TABLE ONLY incident_management_oncall_rotations
ADD CONSTRAINT incident_management_oncall_rotations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY incident_management_oncall_schedules
ADD CONSTRAINT incident_management_oncall_schedules_pkey PRIMARY KEY (id);
@ -21372,6 +21421,16 @@ CREATE INDEX index_import_failures_on_project_id_not_null ON import_failures USI
CREATE INDEX index_imported_projects_on_import_type_creator_id_created_at ON projects USING btree (import_type, creator_id, created_at) WHERE (import_type IS NOT NULL);
CREATE INDEX index_inc_mgmnt_oncall_participants_on_oncall_rotation_id ON incident_management_oncall_participants USING btree (oncall_rotation_id);
CREATE INDEX index_inc_mgmnt_oncall_participants_on_oncall_user_id ON incident_management_oncall_participants USING btree (user_id);
CREATE UNIQUE INDEX index_inc_mgmnt_oncall_participants_on_user_id_and_rotation_id ON incident_management_oncall_participants USING btree (user_id, oncall_rotation_id);
CREATE UNIQUE INDEX index_inc_mgmnt_oncall_rotations_on_oncall_schedule_id_and_id ON incident_management_oncall_rotations USING btree (oncall_schedule_id, id);
CREATE UNIQUE INDEX index_inc_mgmnt_oncall_rotations_on_oncall_schedule_id_and_name ON incident_management_oncall_rotations USING btree (oncall_schedule_id, name);
CREATE INDEX index_incident_management_oncall_schedules_on_project_id ON incident_management_oncall_schedules USING btree (project_id);
CREATE UNIQUE INDEX index_index_statuses_on_project_id ON index_statuses USING btree (project_id);
@ -23744,6 +23803,9 @@ ALTER TABLE ONLY namespace_statistics
ALTER TABLE ONLY clusters_applications_elastic_stacks
ADD CONSTRAINT fk_rails_026f219f46 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE;
ALTER TABLE ONLY incident_management_oncall_participants
ADD CONSTRAINT fk_rails_032b12996a FOREIGN KEY (oncall_rotation_id) REFERENCES incident_management_oncall_rotations(id) ON DELETE CASCADE;
ALTER TABLE ONLY events
ADD CONSTRAINT fk_rails_0434b48643 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@ -23921,6 +23983,9 @@ ALTER TABLE ONLY saml_group_links
ALTER TABLE ONLY group_custom_attributes
ADD CONSTRAINT fk_rails_246e0db83a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY incident_management_oncall_rotations
ADD CONSTRAINT fk_rails_256e0bc604 FOREIGN KEY (oncall_schedule_id) REFERENCES incident_management_oncall_schedules(id) ON DELETE CASCADE;
ALTER TABLE ONLY analytics_devops_adoption_snapshots
ADD CONSTRAINT fk_rails_25da9a92c0 FOREIGN KEY (segment_id) REFERENCES analytics_devops_adoption_segments(id) ON DELETE CASCADE;
@ -24251,6 +24316,9 @@ ALTER TABLE ONLY resource_weight_events
ALTER TABLE ONLY approval_project_rules
ADD CONSTRAINT fk_rails_5fb4dd100b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY incident_management_oncall_participants
ADD CONSTRAINT fk_rails_5fe86ea341 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_highest_roles
ADD CONSTRAINT fk_rails_60f6c325a6 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -210,7 +210,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `external_https` | Configure Pages to bind to one or more secondary IP addresses, serving HTTPS requests. Multiple addresses can be given as an array, along with exact ports, for example `['1.2.3.4', '1.2.3.5:8063']`. Sets value for `listen_https`.
| `gitlab_client_http_timeout` | GitLab API HTTP client connection timeout in seconds (default: 10s).
| `gitlab_client_jwt_expiry` | JWT Token expiry time in seconds (default: 30s).
| `domain_config_source` | Domain configuration source (default: `disk`)
| `domain_config_source` | Domain configuration source (default: `auto`)
| `gitlab_id` | The OAuth application public ID. Leave blank to automatically fill when Pages authenticates with GitLab.
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab.
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`.
@ -668,13 +668,14 @@ Pages server.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217912) in GitLab 13.3.
GitLab Pages can use different sources to get domain configuration.
The default value is `nil`; however, GitLab Pages will default to `disk`.
The default value is `nil`; however, GitLab Pages will default to `auto`.
```ruby
gitlab_pages['domain_config_source'] = nil
```
You can specify `gitlab` to enable [API-based configuration](#gitlab-api-based-configuration).
If left unchanged, GitLab Pages tries to use any available source (either `gitlab` or `disk`). The
preferred source is `gitlab`, which uses [API-based configuration](#gitlab-api-based-configuration).
For more details see this [blog post](https://about.gitlab.com/blog/2020/08/03/how-gitlab-pages-uses-the-gitlab-api-to-serve-content/).
@ -691,10 +692,10 @@ was used prior to GitLab 13.0. Follow these steps to enable it:
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
If you encounter an issue, you can disable it by choosing `disk` or `nil`:
If you encounter an issue, you can disable it by choosing `disk`:
```ruby
gitlab_pages['domain_config_source'] = nil
gitlab_pages['domain_config_source'] = "disk"
```
For other common issues, see the [troubleshooting section](#failed-to-connect-to-the-internal-gitlab-api)

View File

@ -9,45 +9,45 @@ info: To determine the technical writer assigned to the Stage/Group associated w
You can run `kas` and `agentk` locally to test the [Kubernetes Agent](index.md) yourself.
1. Create a `cfg.yaml` file from the contents of
[`config_example.yaml`](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/pkg/kascfg/config_example.yaml), or this example:
[`config_example.yaml`](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/pkg/kascfg/config_example.yaml), or this example:
```yaml
agent:
listen:
network: tcp
address: 127.0.0.1:8150
websocket: false
gitops:
poll_period: "10s"
gitlab:
address: http://localhost:3000
authentication_secret_file: /Users/tkuah/code/ee-gdk/gitlab/.gitlab_kas_secret
```
```yaml
agent:
listen:
network: tcp
address: 127.0.0.1:8150
websocket: false
gitops:
poll_period: "10s"
gitlab:
address: http://localhost:3000
authentication_secret_file: /Users/tkuah/code/ee-gdk/gitlab/.gitlab_kas_secret
```
1. Create a `token.txt`. This is the token for
[the agent you created](../../user/clusters/agent/index.md#create-an-agent-record-in-gitlab). This file must not contain a newline character. You can create the file with this command:
[the agent you created](../../user/clusters/agent/index.md#create-an-agent-record-in-gitlab). This file must not contain a newline character. You can create the file with this command:
```shell
echo -n "<TOKEN>" > token.txt
```
```shell
echo -n "<TOKEN>" > token.txt
```
1. Start the binaries with the following commands:
```shell
# Need GitLab to start
gdk start
# Stop GDK's version of kas
gdk stop gitlab-k8s-agent
```shell
# Need GitLab to start
gdk start
# Stop GDK's version of kas
gdk stop gitlab-k8s-agent
# Start kas
bazel run //cmd/kas -- --configuration-file="$(pwd)/cfg.yaml"
```
# Start kas
bazel run //cmd/kas -- --configuration-file="$(pwd)/cfg.yaml"
```
1. In a new terminal window, run this command to start agentk:
1. In a new terminal window, run this command to start `agentk`:
```shell
bazel run //cmd/agentk -- --kas-address=grpc://127.0.0.1:8150 --token-file="$(pwd)/token.txt"
```
```shell
bazel run //cmd/agentk -- --kas-address=grpc://127.0.0.1:8150 --token-file="$(pwd)/token.txt"
```
You can also inspect the
[Makefile](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/Makefile)

View File

@ -112,21 +112,43 @@ Here `params[:ip]` should not contain anything else but numbers and dots. Howeve
In most cases the anchors `\A` for beginning of text and `\z` for end of text should be used instead of `^` and `$`.
## Denial of Service (ReDoS)
## Denial of Service (ReDoS) / Catastrophic Backtracking
[ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) is a possible attack if the attacker knows
or controls the regular expression (regex) used, and is able to enter user input to match against the bad regular expression.
When a regular expression (regex) is used to search for a string and can't find a match,
it may then backtrack to try other possibilities.
For example when the regex `.*!$` matches the string `hello!`, the `.*` first matches
the entire string but then the `!` from the regex is unable to match because the
character has already been used. In that case, the Ruby regex engine _backtracks_
one character to allow the `!` to match.
[ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)
is an attack in which the attacker knows or controls the regular expression used.
The attacker may be able to enter user input that triggers this backtracking behavior in a
way that increases execution time by several orders of magnitude.
### Impact
The resource, for example Unicorn, Puma, or Sidekiq, can be made to hang as it takes a long time to evaluate the bad regex match.
The resource, for example Unicorn, Puma, or Sidekiq, can be made to hang as it takes
a long time to evaluate the bad regex match. The evaluation time may require manual
termination of the resource.
### Examples
GitLab-specific examples can be found in the following merge requests:
Here are some GitLab-specific examples.
- [MR25314](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25314)
- [MR25122](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25122#note_289087459)
User inputs used to create regular expressions:
- [User-controlled filename](https://gitlab.com/gitlab-org/gitlab/-/issues/257497)
- [User-controlled domain name](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25314)
- [User-controlled email address](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25122#note_289087459)
Hardcoded regular expressions with backtracking issues:
- [Repository name validation](https://gitlab.com/gitlab-org/gitlab/-/issues/220019)
- [Link validation](https://gitlab.com/gitlab-org/gitlab/-/issues/218753), and [a bypass](https://gitlab.com/gitlab-org/gitlab/-/issues/273771)
- [Entity name validation](https://gitlab.com/gitlab-org/gitlab/-/issues/289934)
- [Validating color codes](https://gitlab.com/gitlab-org/gitlab/commit/717824144f8181bef524592eab882dd7525a60ef)
Consider the following example application, which defines a check using a regular expression. A user entering `user@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!.com` as the email on a form will hang the web server.
@ -141,22 +163,32 @@ class Email < ApplicationRecord
def domain_matches
errors.add(:email, 'does not match') if email =~ DOMAIN_MATCH
end
end
```
### Mitigation
GitLab has `Gitlab::UntrustedRegexp` which internally uses the [`re2`](https://github.com/google/re2/wiki/Syntax) library.
By utilizing `re2`, we get a strict limit on total execution time, and a smaller subset of available regex features.
#### Ruby
GitLab has [`Gitlab::UntrustedRegexp`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/untrusted_regexp.rb)
which internally uses the [`re2`](https://github.com/google/re2/wiki/Syntax) library.
`re2` does not support backtracking so we get constant execution time, and a smaller subset of available regex features.
All user-provided regular expressions should use `Gitlab::UntrustedRegexp`.
For other regular expressions, here are a few guidelines:
- Remove unnecessary backtracking.
- Avoid nested quantifiers if possible.
- Try to be as precise as possible in your regex and avoid the `.` if something else can be used (e.g.: Use `_[^_]+_` instead of `_.*_` to match `_text here_`).
- If there's a clean non-regex solution, such as `String#start_with?`, consider using it
- Ruby supports some advanced regex features like [atomic groups](https://www.regular-expressions.info/atomic.html)
and [possessive quantifiers](https://www.regular-expressions.info/possessive.html) that eleminate backtracking
- Avoid nested quantifiers if possible (for example `(a+)+`)
- Try to be as precise as possible in your regex and avoid the `.` if there's an alternative
- For example, Use `_[^_]+_` instead of `_.*_` to match `_text here_`
- If in doubt, don't hesitate to ping `@gitlab-com/gl-security/appsec`
An example can be found [in this commit](https://gitlab.com/gitlab-org/gitlab/commit/717824144f8181bef524592eab882dd7525a60ef).
#### Go
Go's [`regexp`](https://golang.org/pkg/regexp/) package uses `re2` and isn't vulnerable to backtracking issues.
## Further Links
@ -466,7 +498,7 @@ where you can't avoid this:
characters, for example).
- Always use `--` to separate options from arguments.
### Ruby
#### Ruby
Consider using `system("command", "arg0", "arg1", ...)` whenever you can. This prevents an attacker
from concatenating commands.
@ -475,7 +507,7 @@ For more examples on how to use shell commands securely, consult
[Guidelines for shell commands in the GitLab codebase](shell_commands.md).
It contains various examples on how to securely call OS commands.
### Go
#### Go
Go has built-in protections that usually prevent an attacker from successfully injecting OS commands.

View File

@ -56,6 +56,12 @@ There are several components that work in concert for the Agent to accomplish Gi
These repositories might be the same GitLab project or separate projects.
NOTE:
GitLab recommends you use the same GitLab project for the agent configuration
and manifest repositories. Our backlog contains issues for adding support for
[private manifest repositories outside of the configuration project](https://gitlab.com/gitlab-org/gitlab/-/issues/220912) and
[group level agents](https://gitlab.com/gitlab-org/gitlab/-/issues/283885).
For more details, please refer to our [full architecture documentation](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/architecture.md#high-level-architecture) in the Agent project.
## Get started with GitOps and the GitLab Agent

View File

@ -45,17 +45,17 @@ versions at any given time. We regularly review the versions we support, and
provide a three-month deprecation period before we remove support of a specific
version. The range of supported versions is based on the evaluation of:
- Our own needs.
- The versions supported by major managed Kubernetes providers.
- The versions [supported by the Kubernetes community](https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions).
GitLab supports the following Kubernetes versions, and you can upgrade your
Kubernetes version to any supported version at any time:
- 1.18
- 1.17
- 1.16
- 1.15
- 1.19 (support ends on February 22, 2022)
- 1.18 (support ends on November 22, 2021)
- 1.17 (support ends on September 22, 2021)
- 1.16 (support ends on July 22, 2021)
- 1.15 (support ends on May 22, 2021)
- 1.14 (deprecated, support ends on December 22, 2020)
Some GitLab features may support versions outside the range provided here.

View File

@ -115,4 +115,10 @@ module Gitlab
'web'
end
def self.maintenance_mode?
return false unless ::Feature.enabled?(:maintenance_mode)
::Gitlab::CurrentSettings.maintenance_mode
end
end

View File

@ -184,15 +184,20 @@ module ObjectStorage
private
def rounded_multipart_part_size
# round multipart_part_size up to minimum_mulitpart_size
# round multipart_part_size up to minimum_multipart_size
(multipart_part_size + MINIMUM_MULTIPART_SIZE - 1) / MINIMUM_MULTIPART_SIZE * MINIMUM_MULTIPART_SIZE
end
def multipart_part_size
return MINIMUM_MULTIPART_SIZE if maximum_size == 0
maximum_size / number_of_multipart_parts
end
def number_of_multipart_parts
# If we don't have max length, we can only assume the file is as large as possible.
return MAXIMUM_MULTIPART_PARTS if maximum_size == 0
[
# round maximum_size up to minimum_mulitpart_size
(maximum_size + MINIMUM_MULTIPART_SIZE - 1) / MINIMUM_MULTIPART_SIZE,
@ -201,7 +206,7 @@ module ObjectStorage
end
def requires_multipart_upload?
config.aws? && !has_length
config.aws? && !has_length && !use_workhorse_s3_client?
end
def upload_id

View File

@ -25,3 +25,6 @@ rules:
- 'testAction'
jest/no-test-callback:
- off
"@gitlab/no-global-event-off":
- off

View File

@ -30,7 +30,8 @@ describe('Issue show index', () => {
initialDescriptionHtml: '<svg onload=window.alert(1)>',
});
const issuableData = parseData.parseIssuableData();
const initialDataEl = document.getElementById('js-issuable-app');
const issuableData = parseData.parseIssuableData(initialDataEl);
initIssuableApp(issuableData, createStore());
await waitForPromises();

View File

@ -12,6 +12,16 @@ job1:
- echo 'test'
`;
export const mockCiConfigQueryResponse = {
data: {
ciConfig: {
errors: [],
stages: [],
status: '',
},
},
};
export const mockLintResponse = {
valid: true,
errors: [],

View File

@ -13,9 +13,10 @@ import waitForPromises from 'helpers/wait_for_promises';
import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import { redirectTo, refreshCurrentPage, objectToQuery } from '~/lib/utils/url_utility';
import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
import {
mockCiConfigPath,
mockCiConfigQueryResponse,
mockCiYml,
mockCommitId,
mockCommitMessage,
@ -24,10 +25,11 @@ import {
mockNewMergeRequestPath,
} from './mock_data';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import getCiConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
const localVue = createLocalVue();
localVue.use(VueApollo);
@ -42,9 +44,10 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
let wrapper;
let mockMutate;
let mockApollo;
let mockBlobContentData;
let mockCiConfigData;
let mockMutate;
const createComponent = ({
props = {},
@ -96,7 +99,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
};
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
mockApollo = createMockApollo([], {
const handlers = [[getCiConfig, mockCiConfigData]];
const resolvers = {
Query: {
blobContent() {
return {
@ -105,7 +109,9 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
};
},
},
});
};
mockApollo = createMockApollo(handlers, resolvers);
const options = {
localVue,
@ -125,10 +131,12 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
beforeEach(() => {
mockBlobContentData = jest.fn();
mockCiConfigData = jest.fn().mockResolvedValue(mockCiConfigQueryResponse);
});
afterEach(() => {
mockBlobContentData.mockReset();
mockCiConfigData.mockReset();
refreshCurrentPage.mockReset();
redirectTo.mockReset();
mockMutate.mockReset();
@ -177,12 +185,10 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
beforeEach(async () => {
createComponent({ mountFn: mount });
wrapper.setData({
await wrapper.setData({
content: mockCiYml,
contentModel: mockCiYml,
});
await nextTick();
});
it('displays content after the query loads', () => {
@ -347,7 +353,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
describe('displays fetch content errors', () => {
it('no error is show when data is set', async () => {
it('no error is shown when data is set', async () => {
mockBlobContentData.mockResolvedValue(mockCiYml);
createComponentWithApollo();

View File

@ -329,4 +329,24 @@ RSpec.describe Gitlab do
expect(described_class.http_proxy_env?).to eq(false)
end
end
describe '.maintenance_mode?' do
it 'returns true when maintenance mode is enabled' do
stub_application_setting(maintenance_mode: true)
expect(described_class.maintenance_mode?).to eq(true)
end
it 'returns false when maintenance mode is disabled' do
stub_application_setting(maintenance_mode: false)
expect(described_class.maintenance_mode?).to eq(false)
end
it 'returns false when maintenance mode feature flag is disabled' do
stub_feature_flags(maintenance_mode: false)
expect(described_class.maintenance_mode?).to eq(false)
end
end
end

View File

@ -162,6 +162,10 @@ RSpec.describe ObjectStorage::DirectUpload do
it 'enables the Workhorse client' do
expect(subject[:UseWorkhorseClient]).to be true
end
it 'omits the multipart upload URLs' do
expect(subject).not_to include(:MultipartUpload)
end
end
context 'when only server side encryption is used' do
@ -340,6 +344,30 @@ RSpec.describe ObjectStorage::DirectUpload do
stub_object_storage_multipart_init(storage_url, "myUpload")
end
context 'when maximum upload size is 0' do
let(:maximum_size) { 0 }
it 'returns maximum number of parts' do
expect(subject[:MultipartUpload][:PartURLs].length).to eq(100)
end
it 'part size is minimum, 5MB' do
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
end
end
context 'when maximum upload size is < 5 MB' do
let(:maximum_size) { 1024 }
it 'returns only 1 part' do
expect(subject[:MultipartUpload][:PartURLs].length).to eq(1)
end
it 'part size is minimum, 5MB' do
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
end
end
context 'when maximum upload size is 10MB' do
let(:maximum_size) { 10.megabyte }