Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
91c2554bcf
commit
f0a387b4a5
|
@ -1 +1 @@
|
|||
42fab8fc526215f9426bc9f459f9e6da0951c574
|
||||
6b31501b13eae70aea5061edc8273c551ba4c349
|
||||
|
|
|
@ -342,20 +342,24 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
updateFormState(state) {
|
||||
setFormState(state) {
|
||||
this.store.setFormState(state);
|
||||
},
|
||||
|
||||
updateAndShowForm(templates = {}) {
|
||||
updateFormState(templates = {}) {
|
||||
this.setFormState({
|
||||
title: this.state.titleText,
|
||||
description: this.state.descriptionText,
|
||||
lock_version: this.state.lock_version,
|
||||
lockedWarningVisible: false,
|
||||
updateLoading: false,
|
||||
issuableTemplates: templates,
|
||||
});
|
||||
},
|
||||
|
||||
updateAndShowForm(templates) {
|
||||
if (!this.showForm) {
|
||||
this.store.setFormState({
|
||||
title: this.state.titleText,
|
||||
description: this.state.descriptionText,
|
||||
lock_version: this.state.lock_version,
|
||||
lockedWarningVisible: false,
|
||||
updateLoading: false,
|
||||
issuableTemplates: templates,
|
||||
});
|
||||
this.updateFormState(templates);
|
||||
this.showForm = true;
|
||||
}
|
||||
},
|
||||
|
@ -388,9 +392,7 @@ export default {
|
|||
},
|
||||
|
||||
updateIssuable() {
|
||||
this.store.setFormState({
|
||||
updateLoading: true,
|
||||
});
|
||||
this.setFormState({ updateLoading: true });
|
||||
|
||||
const {
|
||||
store: { formState },
|
||||
|
@ -428,10 +430,6 @@ export default {
|
|||
.catch((error = {}) => {
|
||||
const { message, response = {} } = error;
|
||||
|
||||
this.store.setFormState({
|
||||
updateLoading: false,
|
||||
});
|
||||
|
||||
let errMsg = this.defaultErrorMessage;
|
||||
|
||||
if (response.data && response.data.errors) {
|
||||
|
@ -443,6 +441,9 @@ export default {
|
|||
this.flashContainer = createFlash({
|
||||
message: errMsg,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.setFormState({ updateLoading: false });
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -461,6 +462,12 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
handleListItemReorder(description) {
|
||||
this.updateFormState();
|
||||
this.setFormState({ description });
|
||||
this.updateIssuable();
|
||||
},
|
||||
|
||||
taskListUpdateStarted() {
|
||||
this.poll.stop();
|
||||
},
|
||||
|
@ -498,7 +505,7 @@ export default {
|
|||
:can-attach-file="canAttachFile"
|
||||
:enable-autocomplete="enableAutocomplete"
|
||||
:issuable-type="issuableType"
|
||||
@updateForm="updateFormState"
|
||||
@updateForm="setFormState"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
@ -573,6 +580,8 @@ export default {
|
|||
:issuable-type="issuableType"
|
||||
:update-url="updateEndpoint"
|
||||
:lock-version="state.lock_version"
|
||||
:is-updating="formState.updateLoading"
|
||||
@listItemReorder="handleListItemReorder"
|
||||
@taskListUpdateStarted="taskListUpdateStarted"
|
||||
@taskListUpdateSucceeded="taskListUpdateSucceeded"
|
||||
@taskListUpdateFailed="taskListUpdateFailed"
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
GlModalDirective,
|
||||
} from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
import Vue from 'vue';
|
||||
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
||||
|
@ -14,6 +15,7 @@ import createFlash from '~/flash';
|
|||
import { isPositiveInteger } from '~/lib/utils/number_utils';
|
||||
import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { getSortableDefaultOptions, isDragging } from '~/sortable/utils';
|
||||
import TaskList from '~/task_list';
|
||||
import Tracking from '~/tracking';
|
||||
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
|
||||
|
@ -22,9 +24,14 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
|
||||
import animateMixin from '../mixins/animate';
|
||||
import { convertDescriptionWithNewSort } from '../utils';
|
||||
|
||||
Vue.use(GlToast);
|
||||
|
||||
const workItemTypes = {
|
||||
TASK: 'task',
|
||||
};
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
SafeHtml,
|
||||
|
@ -76,6 +83,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
isUpdating: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const workItemId = getParameterByName('work_item_id');
|
||||
|
@ -146,6 +158,9 @@ export default {
|
|||
this.openWorkItemDetailModal(taskLink);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeAllPointerEventListeners();
|
||||
},
|
||||
methods: {
|
||||
renderGFM() {
|
||||
$(this.$refs['gfm-content']).renderGFM();
|
||||
|
@ -161,9 +176,67 @@ export default {
|
|||
onSuccess: this.taskListUpdateSuccess.bind(this),
|
||||
onError: this.taskListUpdateError.bind(this),
|
||||
});
|
||||
|
||||
this.renderSortableLists();
|
||||
}
|
||||
},
|
||||
renderSortableLists() {
|
||||
this.removeAllPointerEventListeners();
|
||||
|
||||
const lists = document.querySelectorAll('.description ul, .description ol');
|
||||
lists.forEach((list) => {
|
||||
Array.from(list.children).forEach((listItem) => {
|
||||
listItem.prepend(this.createDragIconElement());
|
||||
this.addPointerEventListeners(listItem);
|
||||
});
|
||||
|
||||
Sortable.create(
|
||||
list,
|
||||
getSortableDefaultOptions({
|
||||
handle: '.drag-icon',
|
||||
onUpdate: (event) => {
|
||||
const description = convertDescriptionWithNewSort(this.descriptionText, event.to);
|
||||
this.$emit('listItemReorder', description);
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
createDragIconElement() {
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = `<svg class="drag-icon s14 gl-icon gl-cursor-grab gl-visibility-hidden" role="img" aria-hidden="true">
|
||||
<use href="${gon.sprite_icons}#drag-vertical"></use>
|
||||
</svg>`;
|
||||
return container.firstChild;
|
||||
},
|
||||
addPointerEventListeners(listItem) {
|
||||
const pointeroverListener = (event) => {
|
||||
if (isDragging() || this.isUpdating) {
|
||||
return;
|
||||
}
|
||||
event.target.closest('li').querySelector('.drag-icon').style.visibility = 'visible'; // eslint-disable-line no-param-reassign
|
||||
};
|
||||
const pointeroutListener = (event) => {
|
||||
event.target.closest('li').querySelector('.drag-icon').style.visibility = 'hidden'; // eslint-disable-line no-param-reassign
|
||||
};
|
||||
|
||||
// We use pointerover/pointerout instead of CSS so that when we hover over a
|
||||
// list item with children, the drag icons of its children do not become visible.
|
||||
listItem.addEventListener('pointerover', pointeroverListener);
|
||||
listItem.addEventListener('pointerout', pointeroutListener);
|
||||
|
||||
this.pointerEventListeners = this.pointerEventListeners || new Map();
|
||||
this.pointerEventListeners.set(listItem, [
|
||||
{ type: 'pointerover', listener: pointeroverListener },
|
||||
{ type: 'pointerout', listener: pointeroutListener },
|
||||
]);
|
||||
},
|
||||
removeAllPointerEventListeners() {
|
||||
this.pointerEventListeners?.forEach((events, listItem) => {
|
||||
events.forEach((event) => listItem.removeEventListener(event.type, event.listener));
|
||||
this.pointerEventListeners.delete(listItem);
|
||||
});
|
||||
},
|
||||
taskListUpdateStarted() {
|
||||
this.$emit('taskListUpdateStarted');
|
||||
},
|
||||
|
@ -214,7 +287,10 @@ export default {
|
|||
taskListFields.forEach((item, index) => {
|
||||
const taskLink = item.querySelector('.gfm-issue');
|
||||
if (taskLink) {
|
||||
const { issue, referenceType } = taskLink.dataset;
|
||||
const { issue, referenceType, issueType } = taskLink.dataset;
|
||||
if (issueType !== workItemTypes.TASK) {
|
||||
return;
|
||||
}
|
||||
const workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
|
||||
this.addHoverListeners(taskLink, workItemId);
|
||||
taskLink.addEventListener('click', (e) => {
|
||||
|
@ -237,10 +313,9 @@ export default {
|
|||
'btn-md',
|
||||
'gl-button',
|
||||
'btn-default-tertiary',
|
||||
'gl-left-0',
|
||||
'gl-p-0!',
|
||||
'gl-top-2',
|
||||
'gl-absolute',
|
||||
'gl-mt-n1',
|
||||
'gl-ml-3',
|
||||
'js-add-task',
|
||||
);
|
||||
button.id = `js-task-button-${index}`;
|
||||
|
@ -252,7 +327,7 @@ export default {
|
|||
`;
|
||||
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
|
||||
button.addEventListener('click', () => this.openCreateTaskModal(button));
|
||||
item.prepend(button);
|
||||
item.append(button);
|
||||
});
|
||||
},
|
||||
addHoverListeners(taskLink, id) {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import { COLON, HYPHEN, NEWLINE } from '~/lib/utils/text_utility';
|
||||
|
||||
/**
|
||||
* Get the index from sourcepos that represents the line of
|
||||
* the description when the description is split by newline.
|
||||
*
|
||||
* @param {String} sourcepos Source position in format `23:3-23:14`
|
||||
* @returns {Number} Index of description split by newline
|
||||
*/
|
||||
const getDescriptionIndex = (sourcepos) => {
|
||||
const [startRange] = sourcepos.split(HYPHEN);
|
||||
const [startRow] = startRange.split(COLON);
|
||||
return startRow - 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a `ul` or `ol` element containing a new sort order, this function performs
|
||||
* a depth-first search to get the new sort order in the form of sourcepos indices.
|
||||
*
|
||||
* @param {HTMLElement} list A `ul` or `ol` element containing a new sort order
|
||||
* @returns {Array<Number>} An array representing the new order of the list
|
||||
*/
|
||||
const getNewSourcePositions = (list) => {
|
||||
const newSourcePositions = [];
|
||||
|
||||
function pushPositionOfChildListItems(el) {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (el.tagName === 'LI') {
|
||||
newSourcePositions.push(getDescriptionIndex(el.dataset.sourcepos));
|
||||
}
|
||||
Array.from(el.children).forEach(pushPositionOfChildListItems);
|
||||
}
|
||||
|
||||
pushPositionOfChildListItems(list);
|
||||
|
||||
return newSourcePositions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a description to one with a new list sort order.
|
||||
*
|
||||
* Given a description like:
|
||||
*
|
||||
* <pre>
|
||||
* 1. I am text
|
||||
* 2.
|
||||
* 3. - Item 1
|
||||
* 4. - Item 2
|
||||
* 5. - Item 3
|
||||
* 6. - Item 4
|
||||
* 7. - Item 5
|
||||
* </pre>
|
||||
*
|
||||
* And a reordered list (due to dragging Item 2 into Item 1's position) like:
|
||||
*
|
||||
* <pre>
|
||||
* <ul data-sourcepos="3:1-8:0">
|
||||
* <li data-sourcepos="4:1-4:8">
|
||||
* Item 2
|
||||
* <ul data-sourcepos="5:1-6:10">
|
||||
* <li data-sourcepos="5:1-5:10">Item 3</li>
|
||||
* <li data-sourcepos="6:1-6:10">Item 4</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li data-sourcepos="3:1-3:8">Item 1</li>
|
||||
* <li data-sourcepos="7:1-8:0">Item 5</li>
|
||||
* <ul>
|
||||
* </pre>
|
||||
*
|
||||
* This function returns:
|
||||
*
|
||||
* <pre>
|
||||
* 1. I am text
|
||||
* 2.
|
||||
* 3. - Item 2
|
||||
* 4. - Item 3
|
||||
* 5. - Item 4
|
||||
* 6. - Item 1
|
||||
* 7. - Item 5
|
||||
* </pre>
|
||||
*
|
||||
* @param {String} description Description in markdown format
|
||||
* @param {HTMLElement} list A `ul` or `ol` element containing a new sort order
|
||||
* @returns {String} Markdown with a new list sort order
|
||||
*/
|
||||
export const convertDescriptionWithNewSort = (description, list) => {
|
||||
const descriptionLines = description.split(NEWLINE);
|
||||
const startIndexOfList = getDescriptionIndex(list.dataset.sourcepos);
|
||||
|
||||
getNewSourcePositions(list)
|
||||
.map((lineIndex) => descriptionLines[lineIndex])
|
||||
.forEach((line, index) => {
|
||||
descriptionLines[startIndexOfList + index] = line;
|
||||
});
|
||||
|
||||
return descriptionLines.join(NEWLINE);
|
||||
};
|
|
@ -94,7 +94,7 @@ export default {
|
|||
'emptyStateIllustration',
|
||||
'isScrollingDown',
|
||||
'emptyStateAction',
|
||||
'hasRunnersForProject',
|
||||
'hasOfflineRunnersForProject',
|
||||
]),
|
||||
|
||||
shouldRenderContent() {
|
||||
|
@ -220,7 +220,7 @@ export default {
|
|||
<!-- Body Section -->
|
||||
<stuck-block
|
||||
v-if="job.stuck"
|
||||
:has-no-runners-for-project="hasRunnersForProject"
|
||||
:has-offline-runners-for-project="hasOfflineRunnersForProject"
|
||||
:tags="job.tags"
|
||||
:runners-path="runnerSettingsUrl"
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
|||
GlLink,
|
||||
},
|
||||
props: {
|
||||
hasNoRunnersForProject: {
|
||||
hasOfflineRunnersForProject: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ export default {
|
|||
dataTestId: 'job-stuck-with-tags',
|
||||
showTags: true,
|
||||
};
|
||||
} else if (this.hasNoRunnersForProject) {
|
||||
} else if (this.hasOfflineRunnersForProject) {
|
||||
return {
|
||||
text: s__(`Job|This job is stuck because the project
|
||||
doesn't have any runners online assigned to it.`),
|
||||
|
|
|
@ -46,5 +46,5 @@ export const shouldRenderSharedRunnerLimitWarning = (state) =>
|
|||
|
||||
export const isScrollingDown = (state) => isScrolledToBottom() && !state.isJobLogComplete;
|
||||
|
||||
export const hasRunnersForProject = (state) =>
|
||||
export const hasOfflineRunnersForProject = (state) =>
|
||||
state?.job?.runners?.available && !state?.job?.runners?.online;
|
||||
|
|
|
@ -6,6 +6,10 @@ import {
|
|||
} from '~/lib/utils/constants';
|
||||
import { allSingleQuotes } from '~/lib/utils/regexp';
|
||||
|
||||
export const COLON = ':';
|
||||
export const HYPHEN = '-';
|
||||
export const NEWLINE = '\n';
|
||||
|
||||
/**
|
||||
* Adds a , to a string composed by numbers, at every 3 chars.
|
||||
*
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export const DRAG_CLASS = 'is-dragging';
|
||||
|
||||
/**
|
||||
* Default config options for sortablejs.
|
||||
* @type {object}
|
||||
|
@ -12,7 +14,7 @@
|
|||
export const defaultSortableOptions = {
|
||||
animation: 200,
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackClass: DRAG_CLASS,
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
fallbackTolerance: 1,
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
/* global DocumentTouch */
|
||||
|
||||
import { defaultSortableOptions } from './constants';
|
||||
import { defaultSortableOptions, DRAG_CLASS } from './constants';
|
||||
|
||||
export function sortableStart() {
|
||||
document.body.classList.add('is-dragging');
|
||||
document.body.classList.add(DRAG_CLASS);
|
||||
}
|
||||
|
||||
export function sortableEnd() {
|
||||
document.body.classList.remove('is-dragging');
|
||||
document.body.classList.remove(DRAG_CLASS);
|
||||
}
|
||||
|
||||
export function isDragging() {
|
||||
return document.body.classList.contains(DRAG_CLASS);
|
||||
}
|
||||
|
||||
export function getSortableDefaultOptions(options) {
|
||||
|
|
|
@ -42,21 +42,30 @@ export default {
|
|||
<div v-if="showCommentToolBar" class="comment-toolbar clearfix">
|
||||
<div class="toolbar-text">
|
||||
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
|
||||
<gl-link :href="markdownDocsPath" target="_blank">
|
||||
{{ __('Markdown is supported') }}
|
||||
</gl-link>
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}')
|
||||
"
|
||||
>
|
||||
<template #markdownDocsLink="{ content }">
|
||||
<gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
__(
|
||||
'%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported',
|
||||
s__(
|
||||
'NoteToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}. For %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd}, type %{keyboardStart}/%{keyboardEnd}.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #markdownDocsLink="{ content }">
|
||||
<gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
<template #keyboard="{ content }">
|
||||
<kbd>{{ content }}</kbd>
|
||||
</template>
|
||||
<template #quickActionsDocsLink="{ content }">
|
||||
<gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
|
|
|
@ -118,3 +118,11 @@ a {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tanuki-logo {
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
max-width: 40vw;
|
||||
display: block;
|
||||
margin: map-get($spacing-scale, 4) auto;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,46 @@
|
|||
@import 'mixins_and_variables_and_functions';
|
||||
|
||||
.description {
|
||||
ul,
|
||||
ol {
|
||||
/* We're changing list-style-position to inside because the default of outside
|
||||
* doesn't move the negative margin to the left of the bullet. */
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
/* In the browser, the li element comes after (to the right of) the bullet point, so hovering
|
||||
* over the left of the bullet point doesn't trigger a row hover. To trigger hovering on the
|
||||
* left, we're applying negative margin here to shift the li element left. */
|
||||
margin-inline-start: -1rem;
|
||||
padding-inline-start: 2.5rem;
|
||||
|
||||
.drag-icon {
|
||||
position: absolute;
|
||||
inset-block-start: 0.3rem;
|
||||
inset-inline-start: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul.task-list {
|
||||
> li.task-list-item {
|
||||
/* We're using !important to override the same selector in typography.scss */
|
||||
margin-inline-start: -1rem !important;
|
||||
padding-inline-start: 2.5rem;
|
||||
|
||||
> input.task-list-item-checkbox {
|
||||
position: static;
|
||||
vertical-align: middle;
|
||||
margin-block-start: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description.work-items-enabled {
|
||||
ul.task-list {
|
||||
> li.task-list-item {
|
||||
padding-inline-start: 2.5rem;
|
||||
|
||||
.js-add-task {
|
||||
svg {
|
||||
visibility: hidden;
|
||||
|
@ -15,10 +51,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
> input.task-list-item-checkbox {
|
||||
left: 1.25rem;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
.js-add-task svg {
|
||||
|
@ -28,3 +60,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-ghost {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PwaController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
|
||||
layout 'errors'
|
||||
|
||||
feature_category :navigation
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def offline
|
||||
end
|
||||
end
|
|
@ -246,13 +246,13 @@ module MergeRequestsHelper
|
|||
''
|
||||
end
|
||||
|
||||
link_to branch, branch_path, title: branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n3'
|
||||
link_to branch, branch_path, title: branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
|
||||
end
|
||||
|
||||
def merge_request_header(project, merge_request)
|
||||
link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold', avatar: false)
|
||||
copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy')
|
||||
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n3'
|
||||
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
|
||||
|
||||
_('%{author} requested to merge %{source_branch} %{copy_button} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
= link_to root_path do
|
||||
= render 'shared/logo.svg'
|
||||
%h1= _('Offline')
|
||||
.container
|
||||
%h3= _('You are currently offline, or the GitLab instance is not reachable.')
|
||||
%p= _("In the background, we're attempting to connect you again.")
|
||||
-# haml-lint:disable InlineJavaScript
|
||||
:javascript
|
||||
window.addEventListener('online', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
async function checkNetworkAndReload() {
|
||||
try {
|
||||
const response = await fetch('.');
|
||||
// Verify we get a valid response from the server
|
||||
if (response.status >= 200 && response.status < 500) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Unable to connect to the server, ignore.
|
||||
}
|
||||
window.setTimeout(checkNetworkAndReload, 2500);
|
||||
}
|
||||
|
||||
if (window.location.pathname.endsWith('/-/offline')) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkNetworkAndReload();
|
|
@ -2,15 +2,12 @@
|
|||
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
|
||||
.comment-toolbar.clearfix
|
||||
.toolbar-text
|
||||
= link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- markdownLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/markdown') }
|
||||
- quickActionsLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/quick_actions') }
|
||||
- if supports_quick_actions
|
||||
and
|
||||
= link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', rel: 'noopener noreferrer'
|
||||
are
|
||||
= html_escape(s_('NoteToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}. For %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd}, type %{keyboardStart}/%{keyboardEnd}.')) % { markdownDocsLinkStart: markdownLinkStart, markdownDocsLinkEnd: '</a>'.html_safe, quickActionsDocsLinkStart: quickActionsLinkStart, quickActionsDocsLinkEnd: '</a>'.html_safe, keyboardStart: '<kbd>'.html_safe, keyboardEnd: '</kbd>'.html_safe }
|
||||
- else
|
||||
is
|
||||
supported
|
||||
|
||||
= html_escape(s_('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}')) % { markdownDocsLinkStart: markdownLinkStart, markdownDocsLinkEnd: '</a>'.html_safe }
|
||||
- if supports_file_upload
|
||||
%span.uploading-container
|
||||
%span.uploading-progress-container.hide
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348317
|
|||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::license
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -116,6 +116,8 @@ Rails.application.routes.draw do
|
|||
|
||||
get '/whats_new' => 'whats_new#index'
|
||||
|
||||
get 'offline' => "pwa#offline"
|
||||
|
||||
# '/-/health' implemented by BasicHealthCheck middleware
|
||||
get 'liveness' => 'health#liveness'
|
||||
get 'readiness' => 'health#readiness'
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
- name: "Secret Detection configuration variables"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-05-22"
|
||||
breaking_change: false
|
||||
reporter: connorgilbert
|
||||
stage: Secure
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352565
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
To make it simpler and more reliable to [customize GitLab Secret Detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings), we've removed some of the variables that you could previously set in your CI/CD configuration.
|
||||
|
||||
The following variables previously allowed you to customize the options for historical scanning, but interacted poorly with the [GitLab-managed CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml) and are now removed:
|
||||
|
||||
- `SECRET_DETECTION_COMMIT_FROM`
|
||||
- `SECRET_DETECTION_COMMIT_TO`
|
||||
- `SECRET_DETECTION_COMMITS`
|
||||
- `SECRET_DETECTION_COMMITS_FILE`
|
||||
|
||||
The `SECRET_DETECTION_ENTROPY_LEVEL` previously allowed you to configure rules that only considered the entropy level of strings in your codebase, and is now removed.
|
||||
This type of entropy-only rule created an unacceptable number of incorrect results (false positives).
|
||||
|
||||
You can still customize the behavior of the Secret Detection analyzer using the [available CI/CD variables](https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-cicd-variables).
|
||||
|
||||
For further details, see [the deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352565).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
documentation_url: https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-cicd-variables # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
|
@ -48,3 +48,27 @@ Already conducted investigations:
|
|||
### Troubleshooting data warehouse layer
|
||||
|
||||
Reach out to [Data team](https://about.gitlab.com/handbook/business-technology/data-team/) to ask about current state of data warehouse. On their handbook page there is a [section with contact details](https://about.gitlab.com/handbook/business-technology/data-team/#how-to-connect-with-us)
|
||||
|
||||
## Delay in Snowplow Enrichers
|
||||
|
||||
If there is an alert for **Snowplow Raw Good Stream Backing Up**, we receive an email notification. This sometimes happens because Snowplow Enrichers don't scale well enough for the amount of Snowplow events.
|
||||
|
||||
If the delay goes over 48 hours, we lose data.
|
||||
|
||||
### Contact SRE on-call
|
||||
|
||||
Send a message in the [#infrastructure_lounge](https://gitlab.slack.com/archives/CB3LSMEJV) Slack channel using the following template:
|
||||
|
||||
```markdown
|
||||
Hello team!
|
||||
|
||||
We received an alert for [Snowplow Raw Good Stream Backing Up](https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#alarmsV2:alarm/SnowPlow+Raw+Good+Stream+Backing+Up?).
|
||||
|
||||
Enrichers are not scalling well for the amount of events we receive.
|
||||
|
||||
See the [dashboard](https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#dashboards:name=SnowPlow).
|
||||
|
||||
Could we get assistance in order to fix the delay?
|
||||
|
||||
Thank you!
|
||||
```
|
||||
|
|
|
@ -512,6 +512,24 @@ changes to your code, settings, or workflow.
|
|||
|
||||
Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12 SP2 [ended on March 31, 2021](https://www.suse.com/lifecycle/). The CA certificates on SP2 include the expired DST root certificate, and it's not getting new CA certificate package updates. We have implemented some [workarounds](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/-/merge_requests/191), but we will not be able to continue to keep the build running properly.
|
||||
|
||||
### Secret Detection configuration variables
|
||||
|
||||
To make it simpler and more reliable to [customize GitLab Secret Detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings), we've removed some of the variables that you could previously set in your CI/CD configuration.
|
||||
|
||||
The following variables previously allowed you to customize the options for historical scanning, but interacted poorly with the [GitLab-managed CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml) and are now removed:
|
||||
|
||||
- `SECRET_DETECTION_COMMIT_FROM`
|
||||
- `SECRET_DETECTION_COMMIT_TO`
|
||||
- `SECRET_DETECTION_COMMITS`
|
||||
- `SECRET_DETECTION_COMMITS_FILE`
|
||||
|
||||
The `SECRET_DETECTION_ENTROPY_LEVEL` previously allowed you to configure rules that only considered the entropy level of strings in your codebase, and is now removed.
|
||||
This type of entropy-only rule created an unacceptable number of incorrect results (false positives).
|
||||
|
||||
You can still customize the behavior of the Secret Detection analyzer using the [available CI/CD variables](https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-cicd-variables).
|
||||
|
||||
For further details, see [the deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352565).
|
||||
|
||||
### Sidekiq configuration for metrics and health checks
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -184,8 +184,8 @@ include:
|
|||
|
||||
The `CS_DISABLE_DEPENDENCY_LIST` CI/CD variable controls whether the scan creates a
|
||||
[Dependency List](../dependency_list/)
|
||||
report. The variable's default setting of `false` causes the scan to create the report. To disable
|
||||
the report, set the variable to `true`:
|
||||
report. This variable is currently only supported when the `trivy` analyzer is used. The variable's default setting of `"false"` causes the scan to create the report. To disable
|
||||
the report, set the variable to `"true"`:
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
@ -195,13 +195,18 @@ Secret Detection can be customized by defining available CI/CD variables:
|
|||
|
||||
| CI/CD variable | Default value | Description |
|
||||
|-----------------------------------|---------------|-------------|
|
||||
| `SECRET_DETECTION_COMMIT_FROM` | - | The commit a Gitleaks scan starts at. [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. Replaced with `SECRET_DETECTION_COMMITS`. |
|
||||
| `SECRET_DETECTION_COMMIT_TO` | - | The commit a Gitleaks scan ends at. [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. Replaced with `SECRET_DETECTION_COMMITS`. |
|
||||
| `SECRET_DETECTION_COMMITS` | - | The list of commits that Gitleaks should scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. |
|
||||
| `SECRET_DETECTION_EXCLUDED_PATHS` | "" | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec` ). Parent directories also match patterns. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225273) in GitLab 13.3. |
|
||||
| `SECRET_DETECTION_HISTORIC_SCAN` | false | Flag to enable a historic Gitleaks scan. |
|
||||
| `SECRET_DETECTION_IMAGE_SUFFIX` | Suffix added to the image name. If set to `-fips`, `FIPS-enabled` images are used for scan. See [FIPS-enabled images](#fips-enabled-images) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355519) in GitLab 14.10. |
|
||||
|
||||
In previous GitLab versions, the following variables were also available:
|
||||
|
||||
| CI/CD variable | Default value | Description |
|
||||
|-----------------------------------|---------------|-------------|
|
||||
| `SECRET_DETECTION_COMMIT_FROM` | - | The commit a Gitleaks scan starts at. [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. Replaced with `SECRET_DETECTION_COMMITS`. |
|
||||
| `SECRET_DETECTION_COMMIT_TO` | - | The commit a Gitleaks scan ends at. [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. Replaced with `SECRET_DETECTION_COMMITS`. |
|
||||
| `SECRET_DETECTION_COMMITS` | - | The list of commits that Gitleaks should scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/243564) in GitLab 13.5. [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/352565) in GitLab 15.0. |
|
||||
|
||||
### Custom rulesets **(ULTIMATE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211387) in GitLab 13.5.
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
stage: Growth
|
||||
group: Conversion
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Free user limit **(FREE SAAS)**
|
||||
|
||||
In GitLab 15.1 (June 22, 2022) and later, namespaces in GitLab.com on the Free tier
|
||||
will be limited to five (5) members per [namespace](group/index.md#namespaces).
|
||||
This limit applies to top-level groups and personal namespaces.
|
||||
|
||||
In a personal namespace, the limit applies across all projects in your personal
|
||||
namespace.
|
||||
|
||||
On the transition date, if your namespace has six or more unique members:
|
||||
|
||||
- Five members will keep a status of `Active`.
|
||||
- Remaining members will get a status of `Over limit` and lose access to the
|
||||
group.
|
||||
- Members invited through a group or project invitation outside of the namespace
|
||||
will be removed. You can add these members back by inviting them through their
|
||||
username or email address on the **Members** page for your group or project.
|
||||
|
||||
## How active members are determined
|
||||
|
||||
On the transition date, we'll automatically select the members who keep their `Active` status
|
||||
in the following order, until we reach a total of five:
|
||||
|
||||
1. Members with the Owner or Maintainer role.
|
||||
1. The most recently active members.
|
||||
|
||||
## Manage members in your namespace
|
||||
|
||||
To help manage your free user limit,
|
||||
you can view and manage the total number of members across all projects and groups
|
||||
in your namespace.
|
||||
|
||||
Prerequisite:
|
||||
|
||||
- You must have the Owner role for the group.
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Usage Quotas**.
|
||||
1. To view all members, select the **Seats** tab.
|
||||
1. To remove a member, select **Remove user**.
|
||||
|
||||
NOTE:
|
||||
The **Usage Quotas** page is not available for personal namespaces. You can
|
||||
view and [remove members](project/members/index.md#remove-a-member-from-a-project)
|
||||
in each project instead. The five user limit includes all
|
||||
unique members across all projects in your personal namespace.
|
||||
|
||||
If you need more time to manage your members, or to try GitLab features
|
||||
with a team of more than five members, you can [start a trial](https://about.gitlab.com/free-trial/).
|
||||
A trial lasts for 30 days and includes an unlimited number of members.
|
||||
|
||||
## Related topics
|
||||
|
||||
- [GitLab SaaS Free tier frequently asked questions](https://about.gitlab.com/pricing/faq-efficient-free-tier/)
|
|
@ -225,6 +225,8 @@ When you're creating a new issue, you can complete the following fields:
|
|||
|
||||
## Edit an issue
|
||||
|
||||
> Reordering list items [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15260) in GitLab 15.0.
|
||||
|
||||
You can edit an issue's title and description.
|
||||
|
||||
Prerequisites:
|
||||
|
@ -237,6 +239,14 @@ To edit an issue:
|
|||
1. Edit the available fields.
|
||||
1. Select **Save changes**.
|
||||
|
||||
You can also reorder list items, which include bullet, numerical, and task list items.
|
||||
To reorder list items:
|
||||
|
||||
1. Hover over the list item row to make the drag icon visible.
|
||||
1. Click and hold the drag icon.
|
||||
1. Drag the row to the new position in the list.
|
||||
1. Release the drag icon.
|
||||
|
||||
### Bulk edit issues from a project
|
||||
|
||||
> - Assigning epic [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210470) in GitLab 13.2.
|
||||
|
|
|
@ -154,7 +154,6 @@ module Gitlab
|
|||
def handle_unsupported_report_version(treat_as:)
|
||||
if report_version.nil?
|
||||
message = "Report version not provided, #{report_type} report type supports versions: #{supported_schema_versions}"
|
||||
add_message_as(level: treat_as, message: message)
|
||||
else
|
||||
message = "Version #{report_version} for report type #{report_type} is unsupported, supported versions for this report type are: #{supported_schema_versions}"
|
||||
end
|
||||
|
|
|
@ -772,9 +772,6 @@ msgstr ""
|
|||
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{mergeLength}/%{usersLength} can merge"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19357,6 +19354,9 @@ msgstr ""
|
|||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "In the background, we're attempting to connect you again."
|
||||
msgstr ""
|
||||
|
||||
msgid "In this page you will find information about the settings that are used in your current instance."
|
||||
msgstr ""
|
||||
|
||||
|
@ -23282,18 +23282,12 @@ msgstr ""
|
|||
msgid "Mark to do as done"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown Help"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown enabled."
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown is supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown supported."
|
||||
msgstr ""
|
||||
|
||||
|
@ -23321,6 +23315,9 @@ msgstr ""
|
|||
msgid "MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Marked For Deletion At - %{deletion_time}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25789,6 +25786,9 @@ msgstr ""
|
|||
msgid "NoteForm|Note"
|
||||
msgstr ""
|
||||
|
||||
msgid "NoteToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}. For %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd}, type %{keyboardStart}/%{keyboardEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26058,6 +26058,9 @@ msgstr ""
|
|||
msgid "Off"
|
||||
msgstr ""
|
||||
|
||||
msgid "Offline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oh no!"
|
||||
msgstr ""
|
||||
|
||||
|
@ -42992,6 +42995,9 @@ msgstr ""
|
|||
msgid "You are connected to the Prometheus server, but there is currently no data to display."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are currently offline, or the GitLab instance is not reachable."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -45566,9 +45572,6 @@ msgstr ""
|
|||
msgid "projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "quick actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "reCAPTCHA"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -63,6 +63,17 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def has_normalized_ws_text?(text, wait: Capybara.default_max_wait_time)
|
||||
if has_element?(:blob_viewer_file_content, wait: 1)
|
||||
# The blob viewer renders line numbers and whitespace in a way that doesn't match the source file
|
||||
# This isn't a visual validation test, so we ignore line numbers and whitespace
|
||||
find_element(:blob_viewer_file_content, wait: 0).text.gsub(/^\d+\s|\s*/, '')
|
||||
.start_with?(text.gsub(/\s*/, ''))
|
||||
else
|
||||
has_text?(text.gsub(/\s+/, " "), wait: wait)
|
||||
end
|
||||
end
|
||||
|
||||
def click_copy_file_contents(file_number = nil)
|
||||
within_file_by_number(:default_actions_container, file_number) { click_element(:copy_contents_button) }
|
||||
end
|
||||
|
|
|
@ -4,8 +4,9 @@ module QA
|
|||
module Page
|
||||
module File
|
||||
class Form < Page::Base
|
||||
include Shared::CommitMessage
|
||||
include Page::Component::DropdownFilter
|
||||
include Page::Component::BlobContent
|
||||
include Shared::CommitMessage
|
||||
include Shared::CommitButton
|
||||
include Shared::Editor
|
||||
|
||||
|
|
|
@ -133,6 +133,14 @@ module QA
|
|||
has_css?('[name="arkose_labs_token"][value]', visible: false)
|
||||
end
|
||||
|
||||
def has_accept_all_cookies_button?
|
||||
has_button?('Accept All Cookies')
|
||||
end
|
||||
|
||||
def click_accept_all_cookies
|
||||
click_button('Accept All Cookies')
|
||||
end
|
||||
|
||||
def switch_to_sign_in_tab
|
||||
click_element :sign_in_tab
|
||||
end
|
||||
|
@ -179,6 +187,7 @@ module QA
|
|||
fill_element :password_field, user.password
|
||||
|
||||
if Runtime::Env.running_on_dot_com?
|
||||
click_accept_all_cookies if has_accept_all_cookies_button?
|
||||
# Arkose only appears in staging.gitlab.com, gitlab.com, etc...
|
||||
|
||||
# Wait until the ArkoseLabs challenge has initialized
|
||||
|
|
|
@ -4,6 +4,7 @@ import { nextTick } from 'vue';
|
|||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import '~/behaviors/markdown/render_gfm';
|
||||
import { IssuableStatus, IssuableStatusText, IssuableType } from '~/issues/constants';
|
||||
import IssuableApp from '~/issues/show/components/app.vue';
|
||||
|
@ -647,4 +648,14 @@ describe('Issuable output', () => {
|
|||
expect(wrapper.vm.updateStoreState).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('listItemReorder event', () => {
|
||||
it('makes request to update issue', async () => {
|
||||
const description = 'I have been updated!';
|
||||
findDescription().vm.$emit('listItemReorder', description);
|
||||
await waitForPromises();
|
||||
|
||||
expect(mock.history.put[0].data).toContain(description);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,7 +77,22 @@ export const descriptionHtmlWithTask = `
|
|||
<ul data-sourcepos="1:1-3:7" class="task-list" dir="auto">
|
||||
<li data-sourcepos="1:1-1:10" class="task-list-item">
|
||||
<input type="checkbox" class="task-list-item-checkbox" disabled>
|
||||
<a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip">1 (#48)</a>
|
||||
<a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip" data-issue-type="task">1 (#48)</a>
|
||||
</li>
|
||||
<li data-sourcepos="2:1-2:7" class="task-list-item">
|
||||
<input type="checkbox" class="task-list-item-checkbox" disabled> 2
|
||||
</li>
|
||||
<li data-sourcepos="3:1-3:7" class="task-list-item">
|
||||
<input type="checkbox" class="task-list-item-checkbox" disabled> 3
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
export const descriptionHtmlWithIssue = `
|
||||
<ul data-sourcepos="1:1-3:7" class="task-list" dir="auto">
|
||||
<li data-sourcepos="1:1-1:10" class="task-list-item">
|
||||
<input type="checkbox" class="task-list-item-checkbox" disabled>
|
||||
<a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip" data-issue-type="issue">1 (#48)</a>
|
||||
</li>
|
||||
<li data-sourcepos="2:1-2:7" class="task-list-item">
|
||||
<input type="checkbox" class="task-list-item-checkbox" disabled> 2
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { convertDescriptionWithNewSort } from '~/issues/show/utils';
|
||||
|
||||
describe('app/assets/javascripts/issues/show/utils.js', () => {
|
||||
describe('convertDescriptionWithNewSort', () => {
|
||||
it('converts markdown description with new list sort order', () => {
|
||||
const description = `I am text
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
- Item 4
|
||||
- Item 5`;
|
||||
|
||||
// Drag Item 2 + children to Item 1's position
|
||||
const html = `<ul data-sourcepos="3:1-8:0">
|
||||
<li data-sourcepos="4:1-4:8">
|
||||
Item 2
|
||||
<ul data-sourcepos="5:1-6:10">
|
||||
<li data-sourcepos="5:1-5:10">Item 3</li>
|
||||
<li data-sourcepos="6:1-6:10">Item 4</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-sourcepos="3:1-3:8">Item 1</li>
|
||||
<li data-sourcepos="7:1-8:0">Item 5</li>
|
||||
<ul>`;
|
||||
const list = document.createElement('div');
|
||||
list.innerHTML = html;
|
||||
|
||||
const expected = `I am text
|
||||
|
||||
- Item 2
|
||||
- Item 3
|
||||
- Item 4
|
||||
- Item 1
|
||||
- Item 5`;
|
||||
|
||||
expect(convertDescriptionWithNewSort(description, list.firstChild)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -32,7 +32,7 @@ describe('Stuck Block Job component', () => {
|
|||
describe('with no runners for project', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
hasNoRunnersForProject: true,
|
||||
hasOfflineRunnersForProject: true,
|
||||
runnersPath: '/root/project/runners#js-runners-settings',
|
||||
});
|
||||
});
|
||||
|
@ -53,7 +53,7 @@ describe('Stuck Block Job component', () => {
|
|||
describe('with tags', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
hasNoRunnersForProject: false,
|
||||
hasOfflineRunnersForProject: false,
|
||||
tags,
|
||||
runnersPath: '/root/project/runners#js-runners-settings',
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ describe('Stuck Block Job component', () => {
|
|||
describe('without active runners', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
hasNoRunnersForProject: false,
|
||||
hasOfflineRunnersForProject: false,
|
||||
runnersPath: '/root/project/runners#js-runners-settings',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -208,7 +208,7 @@ describe('Job Store Getters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('hasRunnersForProject', () => {
|
||||
describe('hasOfflineRunnersForProject', () => {
|
||||
describe('with available and offline runners', () => {
|
||||
it('returns true', () => {
|
||||
localState.job.runners = {
|
||||
|
@ -216,7 +216,7 @@ describe('Job Store Getters', () => {
|
|||
online: false,
|
||||
};
|
||||
|
||||
expect(getters.hasRunnersForProject(localState)).toEqual(true);
|
||||
expect(getters.hasOfflineRunnersForProject(localState)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -227,7 +227,7 @@ describe('Job Store Getters', () => {
|
|||
online: false,
|
||||
};
|
||||
|
||||
expect(getters.hasRunnersForProject(localState)).toEqual(false);
|
||||
expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -238,7 +238,7 @@ describe('Job Store Getters', () => {
|
|||
online: true,
|
||||
};
|
||||
|
||||
expect(getters.hasRunnersForProject(localState)).toEqual(false);
|
||||
expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') }
|
||||
|
||||
let(:scanner) do
|
||||
{
|
||||
'id' => 'gemnasium',
|
||||
|
@ -22,7 +24,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
expect(described_class::SUPPORTED_VERSIONS.keys).to eq(described_class::DEPRECATED_VERSIONS.keys)
|
||||
end
|
||||
|
||||
context 'files under schema path are explicitly listed' do
|
||||
context 'when a schema JSON file exists for a particular report type version' do
|
||||
# We only care about the part that comes before report-format.json
|
||||
# https://rubular.com/r/N8Juz7r8hYDYgD
|
||||
filename_regex = /(?<report_type>[-\w]*)\-report-format.json/
|
||||
|
@ -36,14 +38,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
matches = filename_regex.match(file)
|
||||
report_type = matches[:report_type].tr("-", "_").to_sym
|
||||
|
||||
it "#{report_type} #{version}" do
|
||||
it "#{report_type} #{version} is in the constant" do
|
||||
expect(described_class::SUPPORTED_VERSIONS[report_type]).to include(version)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'every SUPPORTED_VERSION has a corresponding JSON file' do
|
||||
context 'when every SUPPORTED_VERSION has a corresponding JSON file' do
|
||||
described_class::SUPPORTED_VERSIONS.each_key do |report_type|
|
||||
# api_fuzzing is covered by DAST schema
|
||||
next if report_type == :api_fuzzing
|
||||
|
@ -66,7 +68,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -77,7 +79,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
|
@ -116,7 +118,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
|
||||
end
|
||||
|
||||
context 'and the report passes schema validation' do
|
||||
context 'when the report passes schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => '10.0.0',
|
||||
|
@ -141,8 +143,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'and the report does not pass schema validation' do
|
||||
context 'and enforce_security_report_validation is enabled' do
|
||||
context 'when the report does not pass schema validation' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
@ -156,7 +158,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'and enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
@ -176,12 +178,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { "12.37.0" }
|
||||
|
||||
context 'if enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -206,14 +208,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
context 'and scanner information is empty' do
|
||||
context 'when scanner information is empty' do
|
||||
let(:scanner) { {} }
|
||||
|
||||
it 'logs related information' do
|
||||
|
@ -245,12 +247,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -261,7 +263,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
|
@ -272,6 +274,30 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not given a schema version' do
|
||||
let(:report_type) { :dast }
|
||||
let(:report_version) { nil }
|
||||
let(:report_data) do
|
||||
{
|
||||
'vulnerabilities' => []
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
|
@ -281,7 +307,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -289,19 +315,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: project)
|
||||
end
|
||||
|
@ -315,14 +339,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to match_array(expected_errors) }
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -341,7 +363,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
|
||||
end
|
||||
|
||||
context 'and the report passes schema validation' do
|
||||
context 'when the report passes schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => '10.0.0',
|
||||
|
@ -349,13 +371,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report does not pass schema validation' do
|
||||
context 'and enforce_security_report_validation is enabled' do
|
||||
context 'when the report does not pass schema validation' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
@ -376,7 +396,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to match_array(expected_errors) }
|
||||
end
|
||||
|
||||
context 'and enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
@ -387,9 +407,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -398,12 +416,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { "12.37.0" }
|
||||
|
||||
context 'if enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -413,14 +431,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
|
||||
let(:expected_errors) do
|
||||
[
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: 14.0.0, 14.0.1, 14.0.2, 14.0.3, 14.0.4, 14.0.5, 14.0.6, 14.1.0, 14.1.1, 14.1.2"
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}"
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
|
@ -429,7 +447,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
|
||||
let(:expected_errors) do
|
||||
[
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: 14.0.0, 14.0.1, 14.0.2, 14.0.3, 14.0.4, 14.0.5, 14.0.6, 14.1.0, 14.1.1, 14.1.2",
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}",
|
||||
"root is missing required keys: vulnerabilities"
|
||||
]
|
||||
end
|
||||
|
@ -438,12 +456,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -451,24 +469,47 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not given a schema version' do
|
||||
let(:report_type) { :dast }
|
||||
let(:report_version) { nil }
|
||||
let(:report_data) do
|
||||
{
|
||||
'vulnerabilities' => []
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) do
|
||||
[
|
||||
"root is missing required keys: version",
|
||||
"Report version not provided, dast report type supports versions: #{supported_dast_versions}"
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_errors) }
|
||||
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deprecation_warnings' do
|
||||
|
@ -478,9 +519,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
|
||||
|
||||
let(:expected_deprecation_warnings) { [] }
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -488,17 +527,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_deprecation_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_deprecation_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -513,7 +552,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_version) { described_class::DEPRECATED_VERSIONS[report_type].last }
|
||||
let(:expected_deprecation_warnings) do
|
||||
[
|
||||
"Version V2.7.0 for report type dast has been deprecated, supported versions for this report type are: 14.0.0, 14.0.1, 14.0.2, 14.0.3, 14.0.4, 14.0.5, 14.0.6, 14.1.0, 14.1.1, 14.1.2"
|
||||
"Version V2.7.0 for report type dast has been deprecated, supported versions for this report type are: #{supported_dast_versions}"
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -521,7 +560,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
|
||||
end
|
||||
|
||||
context 'and the report passes schema validation' do
|
||||
context 'when the report passes schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -532,7 +571,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
it { is_expected.to match_array(expected_deprecation_warnings) }
|
||||
end
|
||||
|
||||
context 'and the report does not pass schema validation' do
|
||||
context 'when the report does not pass schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => 'V2.7.0'
|
||||
|
@ -565,7 +604,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -573,29 +612,25 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: project)
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
@ -625,36 +660,32 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
|
||||
end
|
||||
|
||||
context 'and the report passes schema validation' do
|
||||
context 'when the report passes schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'vulnerabilities' => []
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report does not pass schema validation' do
|
||||
context 'when the report does not pass schema validation' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => 'V2.7.0'
|
||||
}
|
||||
end
|
||||
|
||||
context 'and enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
@ -675,12 +706,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
let(:report_type) { :dast }
|
||||
let(:report_version) { "12.37.0" }
|
||||
|
||||
context 'if enforce_security_report_validation is enabled' do
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -688,30 +719,26 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_warnings) { [] }
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'if enforce_security_report_validation is disabled' do
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
context 'and the report is valid' do
|
||||
context 'when the report is valid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version,
|
||||
|
@ -721,14 +748,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
|
||||
let(:expected_warnings) do
|
||||
[
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: 14.0.0, 14.0.1, 14.0.2, 14.0.3, 14.0.4, 14.0.5, 14.0.6, 14.1.0, 14.1.1, 14.1.2"
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}"
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
end
|
||||
|
||||
context 'and the report is invalid' do
|
||||
context 'when the report is invalid' do
|
||||
let(:report_data) do
|
||||
{
|
||||
'version' => report_version
|
||||
|
@ -737,7 +764,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
|
||||
let(:expected_warnings) do
|
||||
[
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: 14.0.0, 14.0.1, 14.0.2, 14.0.3, 14.0.4, 14.0.5, 14.0.6, 14.1.0, 14.1.1, 14.1.2",
|
||||
"Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}",
|
||||
"root is missing required keys: vulnerabilities"
|
||||
]
|
||||
end
|
||||
|
@ -746,5 +773,32 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not given a schema version' do
|
||||
let(:report_type) { :dast }
|
||||
let(:report_version) { nil }
|
||||
let(:report_data) do
|
||||
{
|
||||
'vulnerabilities' => []
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
let(:expected_warnings) do
|
||||
[
|
||||
"root is missing required keys: version",
|
||||
"Report version not provided, dast report type supports versions: #{supported_dast_versions}"
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(expected_warnings) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PwaController do
|
||||
describe 'GET #offline' do
|
||||
it 'responds with static HTML page' do
|
||||
get offline_path
|
||||
|
||||
expect(response.body).to include('You are currently offline')
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,7 +23,7 @@ RSpec.describe 'shared/notes/_form' do
|
|||
let(:note) { build(:"note_on_#{noteable}", project: project) }
|
||||
|
||||
it 'says that markdown and quick actions are supported' do
|
||||
expect(rendered).to have_content('Markdown and quick actions are supported')
|
||||
expect(rendered).to have_content('Supports Markdown. For quick actions, type /.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -189,7 +190,9 @@ func WatchKey(key, value string, timeout time.Duration) (WatchKeyStatus, error)
|
|||
defer delKeyChan(kw)
|
||||
|
||||
currentValue, err := GetString(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.ErrNil) {
|
||||
currentValue = ""
|
||||
} else if err != nil {
|
||||
return WatchKeyStatusNoChange, fmt.Errorf("keywatcher: redis GET: %v", err)
|
||||
}
|
||||
if currentValue != value {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/rafaeljusto/redigomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -65,100 +67,191 @@ func processMessages(numWatchers int, value string) {
|
|||
processInner(psc)
|
||||
}
|
||||
|
||||
func TestWatchKeySeenChange(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
conn.Command("GET", runnerKey).Expect("something")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
val, err := WatchKey(runnerKey, "something", time.Second)
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, WatchKeyStatusSeenChange, val, "Expected value to change")
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
processMessages(1, "somethingelse")
|
||||
wg.Wait()
|
||||
type keyChangeTestCase struct {
|
||||
desc string
|
||||
returnValue string
|
||||
isKeyMissing bool
|
||||
watchValue string
|
||||
processedValue string
|
||||
expectedStatus WatchKeyStatus
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func TestWatchKeyNoChange(t *testing.T) {
|
||||
func TestKeyChangesBubblesUpError(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
conn.Command("GET", runnerKey).Expect("something")
|
||||
conn.Command("GET", runnerKey).ExpectError(errors.New("test error"))
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
_, err := WatchKey(runnerKey, "something", time.Second)
|
||||
require.Error(t, err, "Expected error")
|
||||
|
||||
go func() {
|
||||
val, err := WatchKey(runnerKey, "something", time.Second)
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, WatchKeyStatusNoChange, val, "Expected notification without change to value")
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
processMessages(1, "something")
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestWatchKeyTimeout(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
conn.Command("GET", runnerKey).Expect("something")
|
||||
|
||||
val, err := WatchKey(runnerKey, "something", time.Millisecond)
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, WatchKeyStatusTimeout, val, "Expected value to not change")
|
||||
|
||||
// Clean up watchers since Process isn't doing that for us (not running)
|
||||
deleteWatchers(runnerKey)
|
||||
}
|
||||
|
||||
func TestWatchKeyAlreadyChanged(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
conn.Command("GET", runnerKey).Expect("somethingelse")
|
||||
|
||||
val, err := WatchKey(runnerKey, "something", time.Second)
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, WatchKeyStatusAlreadyChanged, val, "Expected value to have already changed")
|
||||
|
||||
// Clean up watchers since Process isn't doing that for us (not running)
|
||||
deleteWatchers(runnerKey)
|
||||
}
|
||||
|
||||
func TestWatchKeyMassivelyParallel(t *testing.T) {
|
||||
runTimes := 100 // 100 parallel watchers
|
||||
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(runTimes)
|
||||
|
||||
getCmd := conn.Command("GET", runnerKey)
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
getCmd = getCmd.Expect("something")
|
||||
func TestKeyChangesInstantReturn(t *testing.T) {
|
||||
testCases := []keyChangeTestCase{
|
||||
// WatchKeyStatusAlreadyChanged
|
||||
{
|
||||
desc: "sees change with key existing and changed",
|
||||
returnValue: "somethingelse",
|
||||
watchValue: "something",
|
||||
expectedStatus: WatchKeyStatusAlreadyChanged,
|
||||
timeout: time.Second,
|
||||
},
|
||||
{
|
||||
desc: "sees change with key non-existing",
|
||||
isKeyMissing: true,
|
||||
watchValue: "something",
|
||||
processedValue: "somethingelse",
|
||||
expectedStatus: WatchKeyStatusAlreadyChanged,
|
||||
timeout: time.Second,
|
||||
},
|
||||
// WatchKeyStatusTimeout
|
||||
{
|
||||
desc: "sees timeout with key existing and unchanged",
|
||||
returnValue: "something",
|
||||
watchValue: "something",
|
||||
expectedStatus: WatchKeyStatusTimeout,
|
||||
timeout: time.Millisecond,
|
||||
},
|
||||
{
|
||||
desc: "sees timeout with key non-existing and unchanged",
|
||||
isKeyMissing: true,
|
||||
watchValue: "",
|
||||
expectedStatus: WatchKeyStatusTimeout,
|
||||
timeout: time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
go func() {
|
||||
val, err := WatchKey(runnerKey, "something", time.Second)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
if tc.isKeyMissing {
|
||||
conn.Command("GET", runnerKey).ExpectError(redis.ErrNil)
|
||||
} else {
|
||||
conn.Command("GET", runnerKey).Expect(tc.returnValue)
|
||||
}
|
||||
|
||||
val, err := WatchKey(runnerKey, tc.watchValue, tc.timeout)
|
||||
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, WatchKeyStatusSeenChange, val, "Expected value to change")
|
||||
wg.Done()
|
||||
}()
|
||||
require.Equal(t, tc.expectedStatus, val, "Expected value")
|
||||
|
||||
deleteWatchers(runnerKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyChangesWhenWatching(t *testing.T) {
|
||||
testCases := []keyChangeTestCase{
|
||||
// WatchKeyStatusSeenChange
|
||||
{
|
||||
desc: "sees change with key existing",
|
||||
returnValue: "something",
|
||||
watchValue: "something",
|
||||
processedValue: "somethingelse",
|
||||
expectedStatus: WatchKeyStatusSeenChange,
|
||||
},
|
||||
{
|
||||
desc: "sees change with key non-existing, when watching empty value",
|
||||
isKeyMissing: true,
|
||||
watchValue: "",
|
||||
processedValue: "something",
|
||||
expectedStatus: WatchKeyStatusSeenChange,
|
||||
},
|
||||
// WatchKeyStatusNoChange
|
||||
{
|
||||
desc: "sees no change with key existing",
|
||||
returnValue: "something",
|
||||
watchValue: "something",
|
||||
processedValue: "something",
|
||||
expectedStatus: WatchKeyStatusNoChange,
|
||||
},
|
||||
}
|
||||
|
||||
processMessages(runTimes, "somethingelse")
|
||||
wg.Wait()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
if tc.isKeyMissing {
|
||||
conn.Command("GET", runnerKey).ExpectError(redis.ErrNil)
|
||||
} else {
|
||||
conn.Command("GET", runnerKey).Expect(tc.returnValue)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
val, err := WatchKey(runnerKey, tc.watchValue, time.Second)
|
||||
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, tc.expectedStatus, val, "Expected value")
|
||||
}()
|
||||
|
||||
processMessages(1, tc.processedValue)
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyChangesParallel(t *testing.T) {
|
||||
testCases := []keyChangeTestCase{
|
||||
{
|
||||
desc: "massively parallel, sees change with key existing",
|
||||
returnValue: "something",
|
||||
watchValue: "something",
|
||||
processedValue: "somethingelse",
|
||||
expectedStatus: WatchKeyStatusSeenChange,
|
||||
},
|
||||
{
|
||||
desc: "massively parallel, sees change with key existing, watching missing keys",
|
||||
isKeyMissing: true,
|
||||
watchValue: "",
|
||||
processedValue: "somethingelse",
|
||||
expectedStatus: WatchKeyStatusSeenChange,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
runTimes := 100
|
||||
|
||||
conn, td := setupMockPool()
|
||||
defer td()
|
||||
|
||||
getCmd := conn.Command("GET", runnerKey)
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
if tc.isKeyMissing {
|
||||
getCmd = getCmd.ExpectError(redis.ErrNil)
|
||||
} else {
|
||||
getCmd = getCmd.Expect(tc.returnValue)
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(runTimes)
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
val, err := WatchKey(runnerKey, tc.watchValue, time.Second)
|
||||
|
||||
require.NoError(t, err, "Expected no error")
|
||||
require.Equal(t, tc.expectedStatus, val, "Expected value")
|
||||
}()
|
||||
}
|
||||
|
||||
processMessages(runTimes, tc.processedValue)
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutdown(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue