Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f5c9eb81b0
commit
8858979899
61 changed files with 849 additions and 99 deletions
|
@ -24,6 +24,7 @@ export default () => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'DesignRoot',
|
||||
router,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
|
|
|
@ -12,6 +12,7 @@ export default (el) => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'AwardsListRoot',
|
||||
store: createstore(),
|
||||
computed: {
|
||||
...mapState(['currentUserId', 'canAwardEmoji', 'awards']),
|
||||
|
|
|
@ -28,6 +28,7 @@ export default function initInviteMembersModal() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'InviteMembersModalRoot',
|
||||
provide: {
|
||||
newProjectPath: el.dataset.newProjectPath,
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function initInviteMembersTrigger() {
|
|||
return triggers.forEach((el) => {
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'InviteMembersTriggerRoot',
|
||||
render: (createElement) =>
|
||||
createElement(InviteMembersTrigger, {
|
||||
props: {
|
||||
|
|
|
@ -97,6 +97,7 @@ export function initIssuableHeaderWarnings(store) {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'IssuableHeaderWarningsRoot',
|
||||
store,
|
||||
provide: { hidden: parseBoolean(hidden) },
|
||||
render: (createElement) => createElement(IssuableHeaderWarnings),
|
||||
|
|
|
@ -13,6 +13,7 @@ export function initRelatedMergeRequests() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'RelatedMergeRequestsRoot',
|
||||
store: createStore(),
|
||||
render: (createElement) =>
|
||||
createElement(RelatedMergeRequests, {
|
||||
|
|
|
@ -44,6 +44,7 @@ export function initIncidentApp(issueData = {}) {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'DescriptionRoot',
|
||||
apolloProvider,
|
||||
provide: {
|
||||
issueType: INCIDENT_TYPE,
|
||||
|
@ -86,6 +87,7 @@ export function initIssueApp(issueData, store) {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'DescriptionRoot',
|
||||
apolloProvider,
|
||||
store,
|
||||
provide: {
|
||||
|
@ -123,6 +125,7 @@ export function initHeaderActions(store, type = '') {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'HeaderActionsRoot',
|
||||
apolloProvider,
|
||||
store,
|
||||
provide: {
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
cancelAction: { text: __('Cancel') },
|
||||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
primaryText: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -18,11 +26,27 @@ export default {
|
|||
required: false,
|
||||
default: 'confirm',
|
||||
},
|
||||
modalHtmlMessage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hideCancel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
primaryAction() {
|
||||
return { text: this.primaryText, attributes: { variant: this.primaryVariant } };
|
||||
},
|
||||
cancelAction() {
|
||||
return this.hideCancel ? null : this.$options.cancelAction;
|
||||
},
|
||||
shouldShowHeader() {
|
||||
return Boolean(this.title?.length);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.modal.show();
|
||||
|
@ -36,12 +60,14 @@ export default {
|
|||
size="sm"
|
||||
modal-id="confirmationModal"
|
||||
body-class="gl-display-flex"
|
||||
:title="title"
|
||||
:action-primary="primaryAction"
|
||||
:action-cancel="$options.cancelAction"
|
||||
hide-header
|
||||
:action-cancel="cancelAction"
|
||||
:hide-header="!shouldShowHeader"
|
||||
@primary="$emit('confirmed')"
|
||||
@hidden="$emit('closed')"
|
||||
>
|
||||
<div class="gl-align-self-center"><slot></slot></div>
|
||||
<div v-if="!modalHtmlMessage" class="gl-align-self-center"><slot></slot></div>
|
||||
<div v-else v-safe-html="modalHtmlMessage" class="gl-align-self-center"></div>
|
||||
</gl-modal>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {}) {
|
||||
export function confirmAction(
|
||||
message,
|
||||
{ primaryBtnVariant, primaryBtnText, modalHtmlMessage, title, hideCancel } = {},
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
let confirmed = false;
|
||||
|
||||
|
@ -15,6 +18,9 @@ export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {
|
|||
props: {
|
||||
primaryVariant: primaryBtnVariant,
|
||||
primaryText: primaryBtnText,
|
||||
title,
|
||||
modalHtmlMessage,
|
||||
hideCancel,
|
||||
},
|
||||
on: {
|
||||
confirmed() {
|
||||
|
|
|
@ -5,6 +5,12 @@ import { insertText } from '~/lib/utils/common_utils';
|
|||
|
||||
const LINK_TAG_PATTERN = '[{text}](url)';
|
||||
|
||||
// at the start of a line, find any amount of whitespace followed by
|
||||
// a bullet point character (*+-) and an optional checkbox ([ ] [x])
|
||||
// OR a number with a . after it and an optional checkbox ([ ] [x])
|
||||
// followed by one or more whitespace characters
|
||||
const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isOl>[*+-])|(?<isUl>\d+\.))( \[([x ])\])?\s)(?<content>.)?/;
|
||||
|
||||
function selectedText(text, textarea) {
|
||||
return text.substring(textarea.selectionStart, textarea.selectionEnd);
|
||||
}
|
||||
|
@ -13,8 +19,15 @@ function addBlockTags(blockTag, selected) {
|
|||
return `${blockTag}\n${selected}\n${blockTag}`;
|
||||
}
|
||||
|
||||
function lineBefore(text, textarea) {
|
||||
const split = text.substring(0, textarea.selectionStart).trim().split('\n');
|
||||
function lineBefore(text, textarea, trimNewlines = true) {
|
||||
let split = text.substring(0, textarea.selectionStart);
|
||||
|
||||
if (trimNewlines) {
|
||||
split = split.trim();
|
||||
}
|
||||
|
||||
split = split.split('\n');
|
||||
|
||||
return split[split.length - 1];
|
||||
}
|
||||
|
||||
|
@ -284,9 +297,9 @@ function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagCo
|
|||
}
|
||||
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
export function keypressNoteText(e) {
|
||||
function handleSurroundSelectedText(e, textArea) {
|
||||
if (!gon.markdown_surround_selection) return;
|
||||
if (this.selectionStart === this.selectionEnd) return;
|
||||
if (textArea.selectionStart === textArea.selectionEnd) return;
|
||||
|
||||
const keys = {
|
||||
'*': '**{text}**', // wraps with bold character
|
||||
|
@ -306,7 +319,7 @@ export function keypressNoteText(e) {
|
|||
|
||||
updateText({
|
||||
tag,
|
||||
textArea: this,
|
||||
textArea,
|
||||
blockTag: '',
|
||||
wrap: true,
|
||||
select: '',
|
||||
|
@ -316,6 +329,48 @@ export function keypressNoteText(e) {
|
|||
}
|
||||
/* eslint-enable @gitlab/require-i18n-strings */
|
||||
|
||||
function handleContinueList(e, textArea) {
|
||||
if (!gon.features?.markdownContinueLists) return;
|
||||
if (!(e.key === 'Enter')) return;
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
||||
if (textArea.selectionStart !== textArea.selectionEnd) return;
|
||||
|
||||
const currentLine = lineBefore(textArea.value, textArea, false);
|
||||
const result = currentLine.match(LIST_LINE_HEAD_PATTERN);
|
||||
|
||||
if (result) {
|
||||
const { indent, content, leader } = result.groups;
|
||||
const prevLineEmpty = !content;
|
||||
|
||||
if (prevLineEmpty) {
|
||||
// erase previous empty list item - select the text and allow the
|
||||
// natural line feed erase the text
|
||||
textArea.selectionStart = textArea.selectionStart - result[0].length;
|
||||
return;
|
||||
}
|
||||
|
||||
const itemInsert = `${indent}${leader}`;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
updateText({
|
||||
tag: itemInsert,
|
||||
textArea,
|
||||
blockTag: '',
|
||||
wrap: false,
|
||||
select: '',
|
||||
tagContent: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function keypressNoteText(e) {
|
||||
const textArea = this;
|
||||
|
||||
handleContinueList(e, textArea);
|
||||
handleSurroundSelectedText(e, textArea);
|
||||
}
|
||||
|
||||
export function updateTextForToolbarBtn($toolbarBtn) {
|
||||
return updateText({
|
||||
textArea: $toolbarBtn.closest('.md-area').find('textarea'),
|
||||
|
|
|
@ -12,6 +12,7 @@ const mount = (el, Component) => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'TopNavRoot',
|
||||
store,
|
||||
render(h) {
|
||||
return h(Component, {
|
||||
|
|
|
@ -19,7 +19,7 @@ export default (store) => {
|
|||
|
||||
return new Vue({
|
||||
el: discussionFilterEl,
|
||||
name: 'DiscussionFilter',
|
||||
name: 'DiscussionFilterRoot',
|
||||
components: {
|
||||
DiscussionFilter,
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ export default () => {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'NotesRoot',
|
||||
components: {
|
||||
notesApp,
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@ export default (store) => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'SortDiscussionRoot',
|
||||
store,
|
||||
render(createElement) {
|
||||
return createElement(SortDiscussion);
|
||||
|
|
|
@ -20,6 +20,7 @@ const initPerformanceBar = (el) => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'PerformanceBarRoot',
|
||||
components: {
|
||||
PerformanceBarApp: () => import('./components/performance_bar_app.vue'),
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ const getPopoversApp = () => {
|
|||
document.body.appendChild(container);
|
||||
|
||||
const Popovers = Vue.extend(PopoversComponent);
|
||||
app = new Popovers();
|
||||
app = new Popovers({ name: 'PopoversRoot' });
|
||||
app.$mount(`#${APP_ELEMENT_ID}`);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { debounce } from 'lodash';
|
||||
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
|
||||
import axios from '../lib/utils/axios_utils';
|
||||
import {
|
||||
|
@ -105,6 +106,21 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => {
|
|||
};
|
||||
|
||||
const bindHowToImport = () => {
|
||||
const importLinks = document.querySelectorAll('.js-how-to-import-link');
|
||||
|
||||
importLinks.forEach((link) => {
|
||||
const { modalTitle: title, modalMessage: modalHtmlMessage } = link.dataset;
|
||||
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
confirmAction('', {
|
||||
modalHtmlMessage,
|
||||
title,
|
||||
hideCancel: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.how_to_import_link').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
$(e.currentTarget).next('.modal').show();
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function initRelatedIssues() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: relatedIssuesRootElement,
|
||||
name: 'RelatedIssuesApp',
|
||||
name: 'RelatedIssuesRoot',
|
||||
components: {
|
||||
relatedIssuesRoot: RelatedIssuesRoot,
|
||||
},
|
||||
|
|
|
@ -57,6 +57,7 @@ function mountSidebarToDoWidget() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'SidebarTodoRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarTodoWidget,
|
||||
|
@ -103,6 +104,7 @@ function mountAssigneesComponentDeprecated(mediator) {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarAssigneesRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarAssignees,
|
||||
|
@ -135,6 +137,7 @@ function mountAssigneesComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarAssigneesRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarAssigneesWidget,
|
||||
|
@ -185,6 +188,7 @@ function mountReviewersComponent(mediator) {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarReviewersRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarReviewers,
|
||||
|
@ -218,6 +222,7 @@ function mountCrmContactsComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarCrmContactsRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
CrmContacts,
|
||||
|
@ -242,6 +247,7 @@ function mountMilestoneSelect() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'SidebarMilestoneRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarDropdownWidget,
|
||||
|
@ -274,6 +280,7 @@ export function mountSidebarLabels() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'SidebarLabelsRoot',
|
||||
apolloProvider,
|
||||
|
||||
components: {
|
||||
|
@ -328,6 +335,7 @@ function mountConfidentialComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarConfidentialRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarConfidentialityWidget,
|
||||
|
@ -362,6 +370,7 @@ function mountDueDateComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarDueDateRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarDueDateWidget,
|
||||
|
@ -392,6 +401,7 @@ function mountReferenceComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarReferenceRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarReferenceWidget,
|
||||
|
@ -428,6 +438,7 @@ function mountLockComponent(store) {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarLockRoot',
|
||||
store,
|
||||
provide: {
|
||||
fullPath,
|
||||
|
@ -451,6 +462,7 @@ function mountParticipantsComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarParticipantsRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarParticipantsWidget,
|
||||
|
@ -479,6 +491,7 @@ function mountSubscriptionsComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarSubscriptionsRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarSubscriptionsWidget,
|
||||
|
@ -509,6 +522,7 @@ function mountTimeTrackingComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarTimeTrackingRoot',
|
||||
apolloProvider,
|
||||
provide: { issuableType },
|
||||
render: (createElement) =>
|
||||
|
@ -534,6 +548,7 @@ function mountSeverityComponent() {
|
|||
|
||||
return new Vue({
|
||||
el: severityContainerEl,
|
||||
name: 'SidebarSeverityRoot',
|
||||
apolloProvider,
|
||||
components: {
|
||||
SidebarSeverity,
|
||||
|
@ -562,6 +577,7 @@ function mountCopyEmailComponent() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'SidebarCopyEmailRoot',
|
||||
render: (createElement) =>
|
||||
createElement(CopyEmailToClipboard, { props: { issueEmailAddress: createNoteEmail } }),
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ const tooltipsApp = () => {
|
|||
document.body.appendChild(container);
|
||||
|
||||
app = new Vue({
|
||||
name: 'TooltipsRoot',
|
||||
render(h) {
|
||||
return h(Tooltips, {
|
||||
props: {
|
||||
|
|
|
@ -200,6 +200,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.gl-xl-ml-3 {
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin-left: $gl-spacing-scale-3;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-mb-n3 {
|
||||
margin-bottom: -$gl-spacing-scale-3;
|
||||
}
|
||||
|
|
|
@ -46,14 +46,15 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
push_frontend_feature_flag(:vue_issues_list, project&.group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:contacts_autocomplete, project&.group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:markdown_continue_lists, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
before_action only: :show do
|
||||
push_frontend_feature_flag(:real_time_issue_sidebar, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:real_time_issue_sidebar, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:fix_comment_scroll, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:fix_comment_scroll, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:work_items, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
|
|
|
@ -36,20 +36,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
|
||||
before_action only: [:show] do
|
||||
push_frontend_feature_flag(:file_identifier_hash)
|
||||
push_frontend_feature_flag(:merge_request_widget_graphql, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
|
||||
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:merge_request_widget_graphql, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:default_merge_ref_for_diffs, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
|
||||
push_frontend_feature_flag(:paginated_notes, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:confidential_notes, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:refactor_mr_widgets_extensions, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:rebase_without_ci_ui, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:markdown_continue_lists, project, default_enabled: :yaml)
|
||||
# Usage data feature flags
|
||||
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:users_expanding_widgets_usage_data, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:usage_data_diff_searches, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:usage_data_diff_searches, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
before_action do
|
||||
|
|
|
@ -144,6 +144,7 @@ class SearchController < ApplicationController
|
|||
payload[:metadata]['meta.search.filters.state'] = params[:state]
|
||||
payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results]
|
||||
payload[:metadata]['meta.search.project_ids'] = params[:project_ids]
|
||||
payload[:metadata]['meta.search.search_level'] = params[:search_level]
|
||||
|
||||
if search_service.abuse_detected?
|
||||
payload[:metadata]['abuse.confidence'] = Gitlab::Abuse.confidence(:certain)
|
||||
|
|
|
@ -430,6 +430,18 @@ module ProjectsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def import_from_bitbucket_message
|
||||
link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("integration/bitbucket") }
|
||||
|
||||
str = if current_user.admin?
|
||||
'ImportProjects|To enable importing projects from Bitbucket, as administrator you need to configure %{link_start}OAuth integration%{link_end}'
|
||||
else
|
||||
'ImportProjects|To enable importing projects from Bitbucket, ask your GitLab administrator to configure %{link_start}OAuth integration%{link_end}'
|
||||
end
|
||||
|
||||
s_(str).html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tab_ability_map
|
||||
|
|
|
@ -50,8 +50,9 @@ class WebHook < ApplicationRecord
|
|||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def execute(data, hook_name)
|
||||
WebHookService.new(self, data, hook_name).execute if executable?
|
||||
def execute(data, hook_name, force: false)
|
||||
# hook.executable? is checked in WebHookService#execute
|
||||
WebHookService.new(self, data, hook_name, force: force).execute
|
||||
end
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ module TestHooks
|
|||
return error('Testing not available for this hook') if trigger_key.nil? || data.blank?
|
||||
return error(data[:error]) if data[:error].present?
|
||||
|
||||
hook.execute(data, trigger_key)
|
||||
hook.execute(data, trigger_key, force: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,11 +34,12 @@ class WebHookService
|
|||
hook_name.to_s.singularize.titleize
|
||||
end
|
||||
|
||||
def initialize(hook, data, hook_name, uniqueness_token = nil)
|
||||
def initialize(hook, data, hook_name, uniqueness_token = nil, force: false)
|
||||
@hook = hook
|
||||
@data = data
|
||||
@hook_name = hook_name.to_s
|
||||
@uniqueness_token = uniqueness_token
|
||||
@force = force
|
||||
@request_options = {
|
||||
timeout: Gitlab.config.gitlab.webhook_timeout,
|
||||
use_read_total_timeout: true,
|
||||
|
@ -46,8 +47,12 @@ class WebHookService
|
|||
}
|
||||
end
|
||||
|
||||
def disabled?
|
||||
!@force && !hook.executable?
|
||||
end
|
||||
|
||||
def execute
|
||||
return { status: :error, message: 'Hook disabled' } unless hook.executable?
|
||||
return { status: :error, message: 'Hook disabled' } if disabled?
|
||||
|
||||
if recursion_blocked?
|
||||
log_recursion_blocked
|
||||
|
@ -148,9 +153,13 @@ class WebHookService
|
|||
internal_error_message: error_message
|
||||
}
|
||||
|
||||
if @force # executed as part of test - run log-execution inline.
|
||||
::WebHooks::LogExecutionService.new(hook: hook, log_data: log_data, response_category: category).execute
|
||||
else
|
||||
::WebHooks::LogExecutionWorker
|
||||
.perform_async(hook.id, log_data, category, uniqueness_token)
|
||||
end
|
||||
end
|
||||
|
||||
def response_category(response)
|
||||
if response.success? || response.redirection?
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
#bitbucket_import_modal.modal
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
%h3.modal-title Import projects from Bitbucket
|
||||
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
|
||||
%span{ "aria-hidden": "true" } ×
|
||||
.modal-body
|
||||
To enable importing projects from Bitbucket,
|
||||
- if current_user.admin?
|
||||
as administrator you need to configure
|
||||
- else
|
||||
ask your GitLab administrator to configure
|
||||
= link_to 'OAuth integration', help_page_path("integration/bitbucket")
|
|
@ -22,13 +22,11 @@
|
|||
|
||||
- if bitbucket_import_enabled?
|
||||
%div
|
||||
= link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'how_to_import_link' unless bitbucket_import_configured?}",
|
||||
data: { platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
|
||||
= link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'js-how-to-import-link' unless bitbucket_import_configured?}",
|
||||
data: { modal_title: _("Import projects from Bitbucket"), modal_message: import_from_bitbucket_message, platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
|
||||
.gl-button-icon
|
||||
= sprite_icon('bitbucket')
|
||||
Bitbucket Cloud
|
||||
- unless bitbucket_import_configured?
|
||||
= render 'projects/bitbucket_import_modal'
|
||||
- if bitbucket_server_import_enabled?
|
||||
%div
|
||||
= link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn", data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } do
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: block_external_fork_network_mirrors
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60735
|
||||
rollout_issue_url:
|
||||
milestone: '14.0'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: true
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: markdown_continue_lists
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79161
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351386
|
||||
milestone: '14.8'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
66
data/deprecations/14-8-graphql-ids.yml
Normal file
66
data/deprecations/14-8-graphql-ids.yml
Normal file
|
@ -0,0 +1,66 @@
|
|||
- name: "GraphQL ID and GlobalID compatibility"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-04-22"
|
||||
breaking_change: true
|
||||
reporter: alexkalderimis
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
We are removing a non-standard extension to our GraphQL processor, which we added for backwards compatibility. This extension modifies the validation of GraphQL queries, allowing the use of the `ID` type for arguments where it would normally be rejected.
|
||||
Some arguments originally had the type `ID`. These were changed to specific
|
||||
kinds of `ID`. This change may be a breaking change if you:
|
||||
|
||||
- Use GraphQL.
|
||||
- Use the `ID` type for any argument in your query signatures.
|
||||
|
||||
Some field arguments still have the `ID` type. These are typically for
|
||||
IID values, or namespace paths. An example is `Query.project(fullPath: ID!)`.
|
||||
|
||||
For a list of affected and unaffected field arguments,
|
||||
see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352832).
|
||||
|
||||
You can test if this change affects you by validating
|
||||
your queries locally, using schema data fetched from a GitLab server.
|
||||
You can do this by using the GraphQL explorer tool for the relevant GitLab
|
||||
instance. For example: `https://gitlab.com/-/graphql-explorer`.
|
||||
|
||||
For example, the following query illustrates the breaking change:
|
||||
|
||||
```graphql
|
||||
# a query using the deprecated type of Query.issue(id:)
|
||||
# WARNING: This will not work after GitLab 15.0
|
||||
query($id: ID!) {
|
||||
deprecated: issue(id: $id) {
|
||||
title, description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The query above will not work after GitLab 15.0 is released, because the type
|
||||
of `Query.issue(id:)` is actually `IssueID!`.
|
||||
|
||||
Instead, you should use one of the following two forms:
|
||||
|
||||
```graphql
|
||||
# This will continue to work
|
||||
query($id: IssueID!) {
|
||||
a: issue(id: $id) {
|
||||
title, description
|
||||
}
|
||||
b: issue(id: "gid://gitlab/Issue/12345") {
|
||||
title, description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This query works now, and will continue to work after GitLab 15.0.
|
||||
You should convert any queries in the first form (using `ID` as a named type in the signature)
|
||||
to one of the other two forms (using the correct appropriate type in the signature, or using
|
||||
an inline argument expression).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257883'
|
||||
documentation_url: # (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
|
32
data/deprecations/14-8-sast-analyzer-removals.yml
Normal file
32
data/deprecations/14-8-sast-analyzer-removals.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
- name: "SAST analyzer consolidation and CI/CD template changes"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-05-22"
|
||||
breaking_change: true
|
||||
reporter: connorgilbert
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
GitLab SAST uses various [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) to scan code for vulnerabilities.
|
||||
|
||||
We are reducing the number of analyzers used in GitLab SAST as part of our long-term strategy to deliver a better and more consistent user experience.
|
||||
Streamlining the set of analyzers will also enable faster [iteration](https://about.gitlab.com/handbook/values/#iteration), better [results](https://about.gitlab.com/handbook/values/#results), and greater [efficiency](https://about.gitlab.com/handbook/values/#results) (including a reduction in CI runner usage in most cases).
|
||||
|
||||
In GitLab 15.0, GitLab SAST will no longer use the following analyzers:
|
||||
|
||||
- [ESLint](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (JavaScript, TypeScript, React)
|
||||
- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Go)
|
||||
- [Bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Python)
|
||||
|
||||
These analyzers will be removed from the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml) and replaced with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
|
||||
They will no longer receive routine updates, except for security issues.
|
||||
We will not delete container images previously published for these analyzers; any such change would be announced as a [deprecation, removal, or breaking change announcement](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes).
|
||||
|
||||
We will also remove Java from the scope of the [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) analyzer and replace it with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
|
||||
This change will make it simpler to scan Java code; compilation will no longer be required.
|
||||
This change will be reflected in the automatic language detection portion of the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml).
|
||||
|
||||
If you applied customizations to any of the affected analyzers, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352554#breaking-change).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: Secure
|
||||
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352554
|
31
data/deprecations/14-8-sast-dotnet-21.yml
Normal file
31
data/deprecations/14-8-sast-dotnet-21.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
- name: "SAST support for .NET 2.1"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-05-22"
|
||||
breaking_change: true
|
||||
reporter: connorgilbert
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
The GitLab SAST Security Code Scan analyzer scans .NET code for security vulnerabilities.
|
||||
For technical reasons, the analyzer must first build the code to scan it.
|
||||
|
||||
In GitLab versions prior to 15.0, the default analyzer image (version 2) includes support for:
|
||||
|
||||
- .NET 2.1
|
||||
- .NET 3.0 and .NET Core 3.0
|
||||
- .NET Core 3.1
|
||||
- .NET 5.0
|
||||
|
||||
In GitLab 15.0, we will change the default major version for this analyzer from version 2 to version 3. This change:
|
||||
|
||||
- Adds [severity values for vulnerabilities](https://gitlab.com/gitlab-org/gitlab/-/issues/350408) along with [other new features and improvements](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/blob/master/CHANGELOG.md).
|
||||
- Removes .NET 2.1 support.
|
||||
- Adds support for .NET 6.0, Visual Studio 2019, and Visual Studio 2022.
|
||||
|
||||
Version 3 was [announced in GitLab 14.6](https://about.gitlab.com/releases/2021/12/22/gitlab-14-6-released/#sast-support-for-net-6) and made available as an optional upgrade.
|
||||
|
||||
If you rely on .NET 2.1 support being present in the analyzer image by default, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352553#breaking-change).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: Secure
|
||||
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352553
|
28
data/deprecations/14-8-secret-detection-configurations.yml
Normal file
28
data/deprecations/14-8-secret-detection-configurations.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
- name: "Secret Detection configuration variables deprecated"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-05-22"
|
||||
breaking_change: false
|
||||
reporter: connorgilbert
|
||||
body: | # 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're deprecating some of the variables that you could previously set in your CI/CD configuration.
|
||||
|
||||
The following variables currently allow you to customize the options for historical scanning, but interact 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 deprecated:
|
||||
|
||||
- `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 deprecated.
|
||||
This type of entropy-only rule created an unacceptable number of incorrect results (false positives) and is no longer supported.
|
||||
|
||||
In GitLab 15.0, we'll update the Secret Detection [analyzer](https://docs.gitlab.com/ee/user/application_security/terminology/#analyzer) to ignore these deprecated options.
|
||||
You'll still be able to configure historical scanning of your commit history by setting the [`SECRET_DETECTION_HISTORIC_SCAN` CI/CD variable](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.
|
||||
stage: Secure
|
||||
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352565
|
|
@ -867,6 +867,68 @@ To align with this change, API calls to list external status checks will also re
|
|||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### GraphQL ID and GlobalID compatibility
|
||||
|
||||
WARNING:
|
||||
This feature will be changed or removed in 15.0
|
||||
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||
changes to your code, settings, or workflow.
|
||||
|
||||
We are removing a non-standard extension to our GraphQL processor, which we added for backwards compatibility. This extension modifies the validation of GraphQL queries, allowing the use of the `ID` type for arguments where it would normally be rejected.
|
||||
Some arguments originally had the type `ID`. These were changed to specific
|
||||
kinds of `ID`. This change may be a breaking change if you:
|
||||
|
||||
- Use GraphQL.
|
||||
- Use the `ID` type for any argument in your query signatures.
|
||||
|
||||
Some field arguments still have the `ID` type. These are typically for
|
||||
IID values, or namespace paths. An example is `Query.project(fullPath: ID!)`.
|
||||
|
||||
For a list of affected and unaffected field arguments,
|
||||
see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352832).
|
||||
|
||||
You can test if this change affects you by validating
|
||||
your queries locally, using schema data fetched from a GitLab server.
|
||||
You can do this by using the GraphQL explorer tool for the relevant GitLab
|
||||
instance. For example: `https://gitlab.com/-/graphql-explorer`.
|
||||
|
||||
For example, the following query illustrates the breaking change:
|
||||
|
||||
```graphql
|
||||
# a query using the deprecated type of Query.issue(id:)
|
||||
# WARNING: This will not work after GitLab 15.0
|
||||
query($id: ID!) {
|
||||
deprecated: issue(id: $id) {
|
||||
title, description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The query above will not work after GitLab 15.0 is released, because the type
|
||||
of `Query.issue(id:)` is actually `IssueID!`.
|
||||
|
||||
Instead, you should use one of the following two forms:
|
||||
|
||||
```graphql
|
||||
# This will continue to work
|
||||
query($id: IssueID!) {
|
||||
a: issue(id: $id) {
|
||||
title, description
|
||||
}
|
||||
b: issue(id: "gid://gitlab/Issue/12345") {
|
||||
title, description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This query works now, and will continue to work after GitLab 15.0.
|
||||
You should convert any queries in the first form (using `ID` as a named type in the signature)
|
||||
to one of the other two forms (using the correct appropriate type in the signature, or using
|
||||
an inline argument expression).
|
||||
|
||||
**Planned removal milestone: 15.0 (2022-04-22)**
|
||||
|
||||
### OAuth tokens without expiration
|
||||
|
||||
WARNING:
|
||||
|
@ -1082,6 +1144,88 @@ If you have explicitly excluded retire.js using DS_EXCLUDED_ANALYZERS you will n
|
|||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### SAST analyzer consolidation and CI/CD template changes
|
||||
|
||||
WARNING:
|
||||
This feature will be changed or removed in 15.0
|
||||
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||
changes to your code, settings, or workflow.
|
||||
|
||||
GitLab SAST uses various [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) to scan code for vulnerabilities.
|
||||
|
||||
We are reducing the number of analyzers used in GitLab SAST as part of our long-term strategy to deliver a better and more consistent user experience.
|
||||
Streamlining the set of analyzers will also enable faster [iteration](https://about.gitlab.com/handbook/values/#iteration), better [results](https://about.gitlab.com/handbook/values/#results), and greater [efficiency](https://about.gitlab.com/handbook/values/#results) (including a reduction in CI runner usage in most cases).
|
||||
|
||||
In GitLab 15.0, GitLab SAST will no longer use the following analyzers:
|
||||
|
||||
- [ESLint](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (JavaScript, TypeScript, React)
|
||||
- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Go)
|
||||
- [Bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Python)
|
||||
|
||||
These analyzers will be removed from the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml) and replaced with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
|
||||
They will no longer receive routine updates, except for security issues.
|
||||
We will not delete container images previously published for these analyzers; any such change would be announced as a [deprecation, removal, or breaking change announcement](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes).
|
||||
|
||||
We will also remove Java from the scope of the [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) analyzer and replace it with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
|
||||
This change will make it simpler to scan Java code; compilation will no longer be required.
|
||||
This change will be reflected in the automatic language detection portion of the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml).
|
||||
|
||||
If you applied customizations to any of the affected analyzers, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352554#breaking-change).
|
||||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### SAST support for .NET 2.1
|
||||
|
||||
WARNING:
|
||||
This feature will be changed or removed in 15.0
|
||||
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||
changes to your code, settings, or workflow.
|
||||
|
||||
The GitLab SAST Security Code Scan analyzer scans .NET code for security vulnerabilities.
|
||||
For technical reasons, the analyzer must first build the code to scan it.
|
||||
|
||||
In GitLab versions prior to 15.0, the default analyzer image (version 2) includes support for:
|
||||
|
||||
- .NET 2.1
|
||||
- .NET 3.0 and .NET Core 3.0
|
||||
- .NET Core 3.1
|
||||
- .NET 5.0
|
||||
|
||||
In GitLab 15.0, we will change the default major version for this analyzer from version 2 to version 3. This change:
|
||||
|
||||
- Adds [severity values for vulnerabilities](https://gitlab.com/gitlab-org/gitlab/-/issues/350408) along with [other new features and improvements](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/blob/master/CHANGELOG.md).
|
||||
- Removes .NET 2.1 support.
|
||||
- Adds support for .NET 6.0, Visual Studio 2019, and Visual Studio 2022.
|
||||
|
||||
Version 3 was [announced in GitLab 14.6](https://about.gitlab.com/releases/2021/12/22/gitlab-14-6-released/#sast-support-for-net-6) and made available as an optional upgrade.
|
||||
|
||||
If you rely on .NET 2.1 support being present in the analyzer image by default, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352553#breaking-change).
|
||||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### Secret Detection configuration variables deprecated
|
||||
|
||||
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're deprecating some of the variables that you could previously set in your CI/CD configuration.
|
||||
|
||||
The following variables currently allow you to customize the options for historical scanning, but interact 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 deprecated:
|
||||
|
||||
- `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 deprecated.
|
||||
This type of entropy-only rule created an unacceptable number of incorrect results (false positives) and is no longer supported.
|
||||
|
||||
In GitLab 15.0, we'll update the Secret Detection [analyzer](https://docs.gitlab.com/ee/user/application_security/terminology/#analyzer) to ignore these deprecated options.
|
||||
You'll still be able to configure historical scanning of your commit history by setting the [`SECRET_DETECTION_HISTORIC_SCAN` CI/CD variable](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).
|
||||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### Support for gRPC-aware proxy deployed between Gitaly and rest of GitLab
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -150,6 +150,7 @@ The following table lists project permissions available for each role:
|
|||
| [Projects](project/index.md):<br>Export project | | | | ✓ | ✓ |
|
||||
| [Projects](project/index.md):<br>Manage [project access tokens](project/settings/project_access_tokens.md) **(FREE SELF)** **(PREMIUM SAAS)** (*11*) | | | | ✓ | ✓ |
|
||||
| [Projects](project/index.md):<br>Manage [Project Operations](../operations/index.md) | | | | ✓ | ✓ |
|
||||
| [Projects](project/index.md):<br>Rename project | | | | ✓ | ✓ |
|
||||
| [Projects](project/index.md):<br>Share (invite) projects with groups | | | | ✓ (*7*) | ✓ (*7*) |
|
||||
| [Projects](project/index.md):<br>View 2FA status of members | | | | ✓ | ✓ |
|
||||
| [Projects](project/index.md):<br>Administer project compliance frameworks | | | | | ✓ |
|
||||
|
@ -157,7 +158,6 @@ The following table lists project permissions available for each role:
|
|||
| [Projects](project/index.md):<br>Change project visibility level | | | | | ✓ |
|
||||
| [Projects](project/index.md):<br>Delete project | | | | | ✓ |
|
||||
| [Projects](project/index.md):<br>Disable notification emails | | | | | ✓ |
|
||||
| [Projects](project/index.md):<br>Rename project | | | | | ✓ |
|
||||
| [Projects](project/index.md):<br>Transfer project to another namespace | | | | | ✓ |
|
||||
| [Repository](project/repository/index.md):<br>Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Repository](project/repository/index.md):<br>View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
|
|
@ -129,17 +129,7 @@ module Gitlab
|
|||
|
||||
def discussion_id
|
||||
strong_memoize(:discussion_id) do
|
||||
if in_reply_to_id.present?
|
||||
current_discussion_id
|
||||
else
|
||||
Discussion.discussion_id(
|
||||
Struct
|
||||
.new(:noteable_id, :noteable_type)
|
||||
.new(merge_request.id, NOTEABLE_TYPE)
|
||||
).tap do |discussion_id|
|
||||
cache_discussion_id(discussion_id)
|
||||
end
|
||||
end
|
||||
(in_reply_to_id.present? && current_discussion_id) || generate_discussion_id
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -160,6 +150,16 @@ module Gitlab
|
|||
side == 'RIGHT'
|
||||
end
|
||||
|
||||
def generate_discussion_id
|
||||
Discussion.discussion_id(
|
||||
Struct
|
||||
.new(:noteable_id, :noteable_type)
|
||||
.new(merge_request.id, NOTEABLE_TYPE)
|
||||
).tap do |discussion_id|
|
||||
cache_discussion_id(discussion_id)
|
||||
end
|
||||
end
|
||||
|
||||
def cache_discussion_id(discussion_id)
|
||||
Gitlab::Cache::Import::Caching.write(discussion_id_cache_key(note_id), discussion_id)
|
||||
end
|
||||
|
|
|
@ -12869,6 +12869,9 @@ msgstr ""
|
|||
msgid "Display name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Display progress of child issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Display rendered file"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ module QA
|
|||
# Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/
|
||||
Runtime::Scenario.define(:about_address, URI(-> { gitlab_address.host = "about.#{gitlab_address.host}"; gitlab_address }.call).to_s) # rubocop:disable Style/Semicolon
|
||||
|
||||
# Save the scenario class name
|
||||
Runtime::Scenario.define(:klass, self.class.name)
|
||||
|
||||
##
|
||||
# Setup knapsack and download latest report
|
||||
#
|
||||
|
|
|
@ -7,6 +7,7 @@ module QA
|
|||
module Specs
|
||||
class Runner < Scenario::Template
|
||||
attr_accessor :tty, :tags, :options
|
||||
RegexMismatchError = Class.new(StandardError)
|
||||
|
||||
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
|
||||
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
|
||||
|
@ -72,16 +73,48 @@ module QA
|
|||
elsif Runtime::Scenario.attributes[:count_examples_only]
|
||||
args.unshift('--dry-run')
|
||||
out = StringIO.new
|
||||
|
||||
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
|
||||
abort if status.nonzero?
|
||||
end
|
||||
$stdout.puts out.string.match(/(\d+) examples,/)[1]
|
||||
|
||||
begin
|
||||
total_examples = out.string.match(/(\d+) examples?,/)[1]
|
||||
rescue StandardError
|
||||
raise RegexMismatchError, 'Rspec output did not match regex'
|
||||
end
|
||||
|
||||
filename = build_filename
|
||||
|
||||
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
|
||||
|
||||
$stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''}"
|
||||
else
|
||||
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status|
|
||||
abort if status.nonzero?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_filename
|
||||
filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase
|
||||
|
||||
tags = []
|
||||
options.reduce do |before, after|
|
||||
tags << after if %w[--tag -t].include?(before)
|
||||
after
|
||||
end
|
||||
tags = tags.compact.join('_')
|
||||
|
||||
filename.concat("_#{tags}") unless tags.empty?
|
||||
|
||||
filename.concat('.txt')
|
||||
|
||||
FileUtils.mkdir_p('no_of_examples')
|
||||
File.join('no_of_examples', filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ RSpec.describe QA::Specs::Runner do
|
|||
allow(QA::Runtime::Browser).to receive(:configure!)
|
||||
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "http://gitlab.test")
|
||||
QA::Runtime::Scenario.define(:klass, "QA::Scenario::Test::Instance::All")
|
||||
end
|
||||
|
||||
it_behaves_like 'excludes orchestrated, transient, and geo'
|
||||
|
@ -43,6 +44,43 @@ RSpec.describe QA::Specs::Runner do
|
|||
subject.perform
|
||||
end
|
||||
|
||||
it 'writes to file when examples are more than zero' do
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect(File).to receive(:open).with('no_of_examples/test_instance_all.txt', 'w') { '22' }
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'does not write to file when zero examples' do
|
||||
out.string = '0 examples,'
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect(File).not_to receive(:open)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'raises error when Rspec output does not match regex' do
|
||||
out.string = '0'
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect { subject.perform }
|
||||
.to raise_error(QA::Specs::Runner::RegexMismatchError, 'Rspec output did not match regex')
|
||||
end
|
||||
|
||||
context 'when --tag is specified as an option' do
|
||||
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
|
||||
|
||||
it 'includes the option value in the file name' do
|
||||
expect_rspec_runner_arguments(['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
|
||||
|
||||
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
|
||||
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
QA::Runtime::Scenario.attributes.delete(:count_examples_only)
|
||||
end
|
||||
|
|
|
@ -397,9 +397,10 @@ RSpec.describe SearchController do
|
|||
expect(payload[:metadata]['meta.search.filters.confidential']).to eq('true')
|
||||
expect(payload[:metadata]['meta.search.filters.state']).to eq('true')
|
||||
expect(payload[:metadata]['meta.search.project_ids']).to eq(%w(456 789))
|
||||
expect(payload[:metadata]['meta.search.search_level']).to eq('multi-project')
|
||||
end
|
||||
|
||||
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', project_ids: %w(456 789), confidential: true, state: true, force_search_results: true }
|
||||
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', project_ids: %w(456 789), search_level: 'multi-project', confidential: true, state: true, force_search_results: true }
|
||||
end
|
||||
|
||||
it 'appends the default scope in meta.search.scope' do
|
||||
|
|
|
@ -404,4 +404,47 @@ RSpec.describe 'New project', :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'from Bitbucket', :js do
|
||||
shared_examples 'has a link to bitbucket cloud' do
|
||||
context 'when bitbucket is not configured' do
|
||||
before do
|
||||
allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).and_call_original
|
||||
allow(Gitlab::Auth::OAuth::Provider)
|
||||
.to receive(:enabled?).with(:bitbucket)
|
||||
.and_return(false)
|
||||
|
||||
visit new_project_path
|
||||
click_link 'Import project'
|
||||
click_link 'Bitbucket Cloud'
|
||||
end
|
||||
|
||||
it 'shows import instructions' do
|
||||
expect(find('.modal-body')).to have_content(bitbucket_link_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a user' do
|
||||
let(:user) { create(:user) }
|
||||
let(:bitbucket_link_content) { 'To enable importing projects from Bitbucket, ask your GitLab administrator to configure OAuth integration' }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'has a link to bitbucket cloud'
|
||||
end
|
||||
|
||||
context 'as an admin' do
|
||||
let(:user) { create(:admin) }
|
||||
let(:bitbucket_link_content) { 'To enable importing projects from Bitbucket, as administrator you need to configure OAuth integration' }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'has a link to bitbucket cloud'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,10 @@ describe('Design reply form component', () => {
|
|||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@ describe('Description field component', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -6,11 +6,13 @@ describe('Confirm Modal', () => {
|
|||
let wrapper;
|
||||
let modal;
|
||||
|
||||
const createComponent = ({ primaryText, primaryVariant } = {}) => {
|
||||
const createComponent = ({ primaryText, primaryVariant, title, hideCancel = false } = {}) => {
|
||||
wrapper = mount(ConfirmModal, {
|
||||
propsData: {
|
||||
primaryText,
|
||||
primaryVariant,
|
||||
hideCancel,
|
||||
title,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -55,5 +57,19 @@ describe('Confirm Modal', () => {
|
|||
expect(customProps.text).toBe('OK');
|
||||
expect(customProps.attributes.variant).toBe('confirm');
|
||||
});
|
||||
|
||||
it('should hide the cancel button if `hideCancel` is set', () => {
|
||||
createComponent({ hideCancel: true });
|
||||
const props = findGlModal().props();
|
||||
|
||||
expect(props.actionCancel).toBeNull();
|
||||
});
|
||||
|
||||
it('should set the modal title when the `title` prop is set', () => {
|
||||
const title = 'Modal title';
|
||||
createComponent({ title });
|
||||
|
||||
expect(findGlModal().props().title).toBe(title);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -165,6 +165,80 @@ describe('init markdown', () => {
|
|||
// cursor placement should be between tags
|
||||
expect(textArea.selectionStart).toBe(start.length + tag.length);
|
||||
});
|
||||
|
||||
describe('Continuing markdown lists', () => {
|
||||
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
|
||||
|
||||
beforeEach(() => {
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
it.each`
|
||||
text | expected
|
||||
${'- item'} | ${'- item\n- '}
|
||||
${'- [ ] item'} | ${'- [ ] item\n- [ ] '}
|
||||
${'- [x] item'} | ${'- [x] item\n- [x] '}
|
||||
${'- item\n - second'} | ${'- item\n - second\n - '}
|
||||
${'1. item'} | ${'1. item\n1. '}
|
||||
${'1. [ ] item'} | ${'1. [ ] item\n1. [ ] '}
|
||||
${'1. [x] item'} | ${'1. [x] item\n1. [x] '}
|
||||
${'108. item'} | ${'108. item\n108. '}
|
||||
${'108. item\n - second'} | ${'108. item\n - second\n - '}
|
||||
${'108. item\n 1. second'} | ${'108. item\n 1. second\n 1. '}
|
||||
`('adds correct list continuation characters', ({ text, expected }) => {
|
||||
textArea.value = text;
|
||||
textArea.setSelectionRange(text.length, text.length);
|
||||
|
||||
textArea.addEventListener('keydown', keypressNoteText);
|
||||
textArea.dispatchEvent(enterEvent);
|
||||
|
||||
expect(textArea.value).toEqual(expected);
|
||||
expect(textArea.selectionStart).toBe(expected.length);
|
||||
});
|
||||
|
||||
// test that when pressing Enter on an empty list item, the empty
|
||||
// list item text is selected, so that when the Enter propagates,
|
||||
// it's removed
|
||||
it.each`
|
||||
text | expected
|
||||
${'- item\n- '} | ${'- item\n'}
|
||||
${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'}
|
||||
${'- [x] item\n- [x] '} | ${'- [x] item\n'}
|
||||
${'- item\n - second\n - '} | ${'- item\n - second\n'}
|
||||
${'1. item\n1. '} | ${'1. item\n'}
|
||||
${'1. [ ] item\n1. [ ] '} | ${'1. [ ] item\n'}
|
||||
${'1. [x] item\n1. [x] '} | ${'1. [x] item\n'}
|
||||
${'108. item\n108. '} | ${'108. item\n'}
|
||||
${'108. item\n - second\n - '} | ${'108. item\n - second\n'}
|
||||
${'108. item\n 1. second\n 1. '} | ${'108. item\n 1. second\n'}
|
||||
`('adds correct list continuation characters', ({ text, expected }) => {
|
||||
textArea.value = text;
|
||||
textArea.setSelectionRange(text.length, text.length);
|
||||
|
||||
textArea.addEventListener('keydown', keypressNoteText);
|
||||
textArea.dispatchEvent(enterEvent);
|
||||
|
||||
expect(textArea.value.substr(0, textArea.selectionStart)).toEqual(expected);
|
||||
expect(textArea.selectionStart).toBe(expected.length);
|
||||
expect(textArea.selectionEnd).toBe(text.length);
|
||||
});
|
||||
|
||||
it('does nothing if feature flag disabled', () => {
|
||||
gon.features = { markdownContinueLists: false };
|
||||
|
||||
const text = '- item';
|
||||
const expected = '- item';
|
||||
|
||||
textArea.value = text;
|
||||
textArea.setSelectionRange(text.length, text.length);
|
||||
|
||||
textArea.addEventListener('keydown', keypressNoteText);
|
||||
textArea.dispatchEvent(enterEvent);
|
||||
|
||||
expect(textArea.value).toEqual(expected);
|
||||
expect(textArea.selectionStart).toBe(expected.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with selection', () => {
|
||||
|
|
|
@ -45,6 +45,8 @@ describe('issue_note_form component', () => {
|
|||
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
|
||||
noteId: '545',
|
||||
};
|
||||
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -36,6 +36,7 @@ describe('IssuableEditForm', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -45,6 +45,8 @@ describe('ZenMode', () => {
|
|||
|
||||
// Set this manually because we can't actually scroll the window
|
||||
zen.scroll_position = 456;
|
||||
|
||||
gon.features = { markdownContinueLists: true };
|
||||
});
|
||||
|
||||
describe('enabling dropzone', () => {
|
||||
|
|
|
@ -1026,4 +1026,26 @@ RSpec.describe ProjectsHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#import_from_bitbucket_message' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
end
|
||||
|
||||
context 'as a user' do
|
||||
it 'returns a link to contact an administrator' do
|
||||
allow(user).to receive(:admin?).and_return(false)
|
||||
|
||||
expect(helper.import_from_bitbucket_message).to have_text('To enable importing projects from Bitbucket, ask your GitLab administrator to configure OAuth integration')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as an administrator' do
|
||||
it 'returns a link to configure bitbucket' do
|
||||
allow(user).to receive(:admin?).and_return(true)
|
||||
|
||||
expect(helper.import_from_bitbucket_message).to have_text('To enable importing projects from Bitbucket, as administrator you need to configure OAuth integration')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_shared_state do
|
||||
RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_cache do
|
||||
let(:hunk) do
|
||||
'@@ -1 +1 @@
|
||||
-Hello
|
||||
|
@ -166,6 +166,23 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_red
|
|||
expect(new_discussion_note.discussion_id)
|
||||
.to eq('SECOND_DISCUSSION_ID')
|
||||
end
|
||||
|
||||
context 'when cached value does not exist' do
|
||||
it 'falls back to generating a new discussion_id' do
|
||||
expect(Discussion)
|
||||
.to receive(:discussion_id)
|
||||
.and_return('NEW_DISCUSSION_ID')
|
||||
|
||||
reply_note = described_class.from_json_hash(
|
||||
'note_id' => note.note_id + 1,
|
||||
'in_reply_to_id' => note.note_id
|
||||
)
|
||||
reply_note.project = project
|
||||
reply_note.merge_request = merge_request
|
||||
|
||||
expect(reply_note.discussion_id).to eq('NEW_DISCUSSION_ID')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe ServiceHook do
|
|||
let(:data) { { key: 'value' } }
|
||||
|
||||
it '#execute' do
|
||||
expect(WebHookService).to receive(:new).with(hook, data, 'service_hook').and_call_original
|
||||
expect(WebHookService).to receive(:new).with(hook, data, 'service_hook', force: false).and_call_original
|
||||
expect_any_instance_of(WebHookService).to receive(:execute)
|
||||
|
||||
hook.execute(data)
|
||||
|
|
|
@ -168,17 +168,17 @@ RSpec.describe SystemHook do
|
|||
let(:data) { { key: 'value' } }
|
||||
let(:hook_name) { 'system_hook' }
|
||||
|
||||
before do
|
||||
expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
|
||||
end
|
||||
|
||||
it '#execute' do
|
||||
expect(WebHookService).to receive(:new).with(hook, data, hook_name, force: false).and_call_original
|
||||
|
||||
expect_any_instance_of(WebHookService).to receive(:execute)
|
||||
|
||||
hook.execute(data, hook_name)
|
||||
end
|
||||
|
||||
it '#async_execute' do
|
||||
expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
|
||||
|
||||
expect_any_instance_of(WebHookService).to receive(:async_execute)
|
||||
|
||||
hook.async_execute(data, hook_name)
|
||||
|
|
|
@ -100,12 +100,18 @@ RSpec.describe WebHook do
|
|||
hook.execute(data, hook_name)
|
||||
end
|
||||
|
||||
it 'does not execute non-executable hooks' do
|
||||
hook.update!(disabled_until: 1.day.from_now)
|
||||
it 'passes force: false to the web hook service by default' do
|
||||
expect(WebHookService)
|
||||
.to receive(:new).with(hook, data, hook_name, force: false).and_return(double(execute: :done))
|
||||
|
||||
expect(WebHookService).not_to receive(:new)
|
||||
expect(hook.execute(data, hook_name)).to eq :done
|
||||
end
|
||||
|
||||
hook.execute(data, hook_name)
|
||||
it 'passes force: true to the web hook service if required' do
|
||||
expect(WebHookService)
|
||||
.to receive(:new).with(hook, data, hook_name, force: true).and_return(double(execute: :forced))
|
||||
|
||||
expect(hook.execute(data, hook_name, force: true)).to eq :forced
|
||||
end
|
||||
|
||||
it '#async_execute' do
|
||||
|
|
|
@ -37,7 +37,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
it 'executes hook' do
|
||||
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -49,7 +49,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
it 'executes hook' do
|
||||
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -69,7 +69,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data)
|
||||
allow_next(NotesFinder).to receive(:execute).and_return(Note.all)
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -86,7 +86,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(issue).to receive(:to_hook_data).and_return(sample_data)
|
||||
allow_next(IssuesFinder).to receive(:execute).and_return([issue])
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -119,7 +119,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(merge_request).to receive(:to_hook_data).and_return(sample_data)
|
||||
allow_next(MergeRequestsFinder).to receive(:execute).and_return([merge_request])
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -138,7 +138,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data)
|
||||
allow_next(Ci::JobsFinder).to receive(:execute).and_return([ci_job])
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -157,7 +157,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data)
|
||||
allow_next(Ci::PipelinesFinder).to receive(:execute).and_return([pipeline])
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -184,7 +184,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
create(:wiki_page, wiki: project.wiki)
|
||||
allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data)
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -203,7 +203,7 @@ RSpec.describe TestHooks::ProjectService do
|
|||
allow(release).to receive(:to_hook_data).and_return(sample_data)
|
||||
allow_next(ReleasesFinder).to receive(:execute).and_return([release])
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe TestHooks::SystemService do
|
|||
it 'executes hook' do
|
||||
expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
|
||||
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -45,7 +45,7 @@ RSpec.describe TestHooks::SystemService do
|
|||
allow(project.repository).to receive(:tags).and_return(['tag'])
|
||||
expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
|
||||
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -57,7 +57,7 @@ RSpec.describe TestHooks::SystemService do
|
|||
it 'executes hook' do
|
||||
expect(Gitlab::DataBuilder::Repository).to receive(:sample_data).and_call_original
|
||||
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
@ -76,7 +76,7 @@ RSpec.describe TestHooks::SystemService do
|
|||
it 'executes hook' do
|
||||
expect(MergeRequest).to receive(:of_projects).and_return([merge_request])
|
||||
expect(merge_request).to receive(:to_hook_data).and_return(sample_data)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,6 +52,25 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
|
|||
end
|
||||
end
|
||||
|
||||
describe '#disabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject { described_class.new(hook, data, :push_hooks, force: forced) }
|
||||
|
||||
let(:hook) { double(executable?: executable, allow_local_requests?: false) }
|
||||
|
||||
where(:forced, :executable, :disabled) do
|
||||
false | true | false
|
||||
false | false | true
|
||||
true | true | false
|
||||
true | false | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to have_attributes(disabled?: disabled) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let!(:uuid) { SecureRandom.uuid }
|
||||
let(:headers) do
|
||||
|
@ -129,7 +148,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
|
|||
end
|
||||
|
||||
it 'does not execute disabled hooks' do
|
||||
project_hook.update!(recent_failures: 4)
|
||||
allow(service_instance).to receive(:disabled?).and_return(true)
|
||||
|
||||
expect(service_instance.execute).to eq({ status: :error, message: 'Hook disabled' })
|
||||
end
|
||||
|
@ -251,6 +270,20 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
|
|||
stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
|
||||
end
|
||||
|
||||
context 'when forced' do
|
||||
let(:service_instance) { described_class.new(project_hook, data, :push_hooks, force: true) }
|
||||
|
||||
it 'logs execution inline' do
|
||||
expect(::WebHooks::LogExecutionWorker).not_to receive(:perform_async)
|
||||
expect(::WebHooks::LogExecutionService)
|
||||
.to receive(:new)
|
||||
.with(hook: project_hook, log_data: Hash, response_category: :ok)
|
||||
.and_return(double(execute: nil))
|
||||
|
||||
run_service
|
||||
end
|
||||
end
|
||||
|
||||
it 'log successful execution' do
|
||||
run_service
|
||||
|
||||
|
|
|
@ -85,7 +85,9 @@ RSpec.shared_examples 'User previews wiki changes' do
|
|||
end
|
||||
|
||||
it 'renders content with CommonMark' do
|
||||
fill_in :wiki_content, with: "1. one\n - sublist\n"
|
||||
# using two `\n` ensures we're sublist to it's own line due
|
||||
# to list auto-continue
|
||||
fill_in :wiki_content, with: "1. one\n\n - sublist\n"
|
||||
click_on "Preview"
|
||||
|
||||
# the above generates two separate lists (not embedded) in CommonMark
|
||||
|
|
Loading…
Reference in a new issue