Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-16 09:12:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-16 09:12:24 +0300
commit885897989971931fcb139969b49d8b06f907d7d0 (patch)
tree7c2873a6690b73d0036b56bf379634c235dfb438
parentf5c9eb81b000010cf8abc91cbcfd1d6537f66c64 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/design_management/index.js1
-rw-r--r--app/assets/javascripts/emoji/awards_app/index.js1
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js1
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_trigger.js1
-rw-r--r--app/assets/javascripts/issuable/index.js1
-rw-r--r--app/assets/javascripts/issues/related_merge_requests/index.js1
-rw-r--r--app/assets/javascripts/issues/show/index.js3
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue34
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js8
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js65
-rw-r--r--app/assets/javascripts/nav/mount.js1
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js2
-rw-r--r--app/assets/javascripts/notes/index.js1
-rw-r--r--app/assets/javascripts/notes/sort_discussions.js1
-rw-r--r--app/assets/javascripts/performance_bar/index.js1
-rw-r--r--app/assets/javascripts/popovers/index.js2
-rw-r--r--app/assets/javascripts/projects/project_new.js16
-rw-r--r--app/assets/javascripts/related_issues/index.js2
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js16
-rw-r--r--app/assets/javascripts/tooltips/index.js1
-rw-r--r--app/assets/stylesheets/utilities.scss6
-rw-r--r--app/controllers/projects/issues_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb19
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/helpers/projects_helper.rb12
-rw-r--r--app/models/hooks/web_hook.rb5
-rw-r--r--app/services/test_hooks/base_service.rb2
-rw-r--r--app/services/web_hook_service.rb17
-rw-r--r--app/views/projects/_bitbucket_import_modal.html.haml14
-rw-r--r--app/views/projects/_import_project_pane.html.haml6
-rw-r--r--config/feature_flags/development/block_external_fork_network_mirrors.yml8
-rw-r--r--config/feature_flags/development/markdown_continue_lists.yml8
-rw-r--r--data/deprecations/14-8-graphql-ids.yml66
-rw-r--r--data/deprecations/14-8-sast-analyzer-removals.yml32
-rw-r--r--data/deprecations/14-8-sast-dotnet-21.yml31
-rw-r--r--data/deprecations/14-8-secret-detection-configurations.yml28
-rw-r--r--doc/update/deprecations.md144
-rw-r--r--doc/user/permissions.md2
-rw-r--r--lib/gitlab/github_import/representation/diff_note.rb22
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/scenario/template.rb3
-rw-r--r--qa/qa/specs/runner.rb35
-rw-r--r--qa/spec/specs/runner_spec.rb38
-rw-r--r--spec/controllers/search_controller_spec.rb3
-rw-r--r--spec/features/projects/new_project_spec.rb43
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js4
-rw-r--r--spec/frontend/issues/show/components/fields/description_spec.js1
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js18
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js74
-rw-r--r--spec/frontend/notes/components/note_form_spec.js2
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js1
-rw-r--r--spec/frontend/zen_mode_spec.js2
-rw-r--r--spec/helpers/projects_helper_spec.rb22
-rw-r--r--spec/lib/gitlab/github_import/representation/diff_note_spec.rb19
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb8
-rw-r--r--spec/models/hooks/web_hook_spec.rb14
-rw-r--r--spec/services/test_hooks/project_service_spec.rb18
-rw-r--r--spec/services/test_hooks/system_service_spec.rb8
-rw-r--r--spec/services/web_hook_service_spec.rb35
-rw-r--r--spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb4
61 files changed, 849 insertions, 99 deletions
diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js
index 4ae76050aa5..b856ac6c627 100644
--- a/app/assets/javascripts/design_management/index.js
+++ b/app/assets/javascripts/design_management/index.js
@@ -24,6 +24,7 @@ export default () => {
return new Vue({
el,
+ name: 'DesignRoot',
router,
apolloProvider,
provide: {
diff --git a/app/assets/javascripts/emoji/awards_app/index.js b/app/assets/javascripts/emoji/awards_app/index.js
index 1a084d37762..0986533dcd1 100644
--- a/app/assets/javascripts/emoji/awards_app/index.js
+++ b/app/assets/javascripts/emoji/awards_app/index.js
@@ -12,6 +12,7 @@ export default (el) => {
return new Vue({
el,
+ name: 'AwardsListRoot',
store: createstore(),
computed: {
...mapState(['currentUserId', 'canAwardEmoji', 'awards']),
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index 588b1c9ef52..e9d620cedf0 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -28,6 +28,7 @@ export default function initInviteMembersModal() {
return new Vue({
el,
+ name: 'InviteMembersModalRoot',
provide: {
newProjectPath: el.dataset.newProjectPath,
},
diff --git a/app/assets/javascripts/invite_members/init_invite_members_trigger.js b/app/assets/javascripts/invite_members/init_invite_members_trigger.js
index 935edb35349..54a5eab2e4b 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_trigger.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_trigger.js
@@ -11,6 +11,7 @@ export default function initInviteMembersTrigger() {
return triggers.forEach((el) => {
return new Vue({
el,
+ name: 'InviteMembersTriggerRoot',
render: (createElement) =>
createElement(InviteMembersTrigger, {
props: {
diff --git a/app/assets/javascripts/issuable/index.js b/app/assets/javascripts/issuable/index.js
index 57bad5182e7..00b027523e2 100644
--- a/app/assets/javascripts/issuable/index.js
+++ b/app/assets/javascripts/issuable/index.js
@@ -97,6 +97,7 @@ export function initIssuableHeaderWarnings(store) {
return new Vue({
el,
+ name: 'IssuableHeaderWarningsRoot',
store,
provide: { hidden: parseBoolean(hidden) },
render: (createElement) => createElement(IssuableHeaderWarnings),
diff --git a/app/assets/javascripts/issues/related_merge_requests/index.js b/app/assets/javascripts/issues/related_merge_requests/index.js
index 5045f7e1a2a..196084093c8 100644
--- a/app/assets/javascripts/issues/related_merge_requests/index.js
+++ b/app/assets/javascripts/issues/related_merge_requests/index.js
@@ -13,6 +13,7 @@ export function initRelatedMergeRequests() {
return new Vue({
el,
+ name: 'RelatedMergeRequestsRoot',
store: createStore(),
render: (createElement) =>
createElement(RelatedMergeRequests, {
diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js
index bf4debcdfa9..732bdc09aea 100644
--- a/app/assets/javascripts/issues/show/index.js
+++ b/app/assets/javascripts/issues/show/index.js
@@ -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: {
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
index 733d0f69f5d..f3380b7b4ba 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
@@ -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>
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
index fdd0e045d07..a8a89d0644a 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
@@ -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() {
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 40dd29bea76..ec6789d81ec 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -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'),
diff --git a/app/assets/javascripts/nav/mount.js b/app/assets/javascripts/nav/mount.js
index 51b6a31b8cb..7b0cc977107 100644
--- a/app/assets/javascripts/nav/mount.js
+++ b/app/assets/javascripts/nav/mount.js
@@ -12,6 +12,7 @@ const mount = (el, Component) => {
return new Vue({
el,
+ name: 'TopNavRoot',
store,
render(h) {
return h(Component, {
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
index 7c9e7703d59..104e9d4183a 100644
--- a/app/assets/javascripts/notes/discussion_filters.js
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -19,7 +19,7 @@ export default (store) => {
return new Vue({
el: discussionFilterEl,
- name: 'DiscussionFilter',
+ name: 'DiscussionFilterRoot',
components: {
DiscussionFilter,
},
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 2ce60976adb..19fa484d659 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -14,6 +14,7 @@ export default () => {
// eslint-disable-next-line no-new
new Vue({
el,
+ name: 'NotesRoot',
components: {
notesApp,
},
diff --git a/app/assets/javascripts/notes/sort_discussions.js b/app/assets/javascripts/notes/sort_discussions.js
index ecfa3223039..ca8df880fe4 100644
--- a/app/assets/javascripts/notes/sort_discussions.js
+++ b/app/assets/javascripts/notes/sort_discussions.js
@@ -8,6 +8,7 @@ export default (store) => {
return new Vue({
el,
+ name: 'SortDiscussionRoot',
store,
render(createElement) {
return createElement(SortDiscussion);
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
index 66e999ca43b..eb5b50dd1ec 100644
--- a/app/assets/javascripts/performance_bar/index.js
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -20,6 +20,7 @@ const initPerformanceBar = (el) => {
return new Vue({
el,
+ name: 'PerformanceBarRoot',
components: {
PerformanceBarApp: () => import('./components/performance_bar_app.vue'),
},
diff --git a/app/assets/javascripts/popovers/index.js b/app/assets/javascripts/popovers/index.js
index 7db669b8c52..94340ae16a0 100644
--- a/app/assets/javascripts/popovers/index.js
+++ b/app/assets/javascripts/popovers/index.js
@@ -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}`);
}
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 55913394ca3..62e2cec874a 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -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();
diff --git a/app/assets/javascripts/related_issues/index.js b/app/assets/javascripts/related_issues/index.js
index bd9845e616b..35858be90b2 100644
--- a/app/assets/javascripts/related_issues/index.js
+++ b/app/assets/javascripts/related_issues/index.js
@@ -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,
},
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 6363422259e..c29784aa328 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -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 } }),
});
diff --git a/app/assets/javascripts/tooltips/index.js b/app/assets/javascripts/tooltips/index.js
index 49a43b120e0..4639671984a 100644
--- a/app/assets/javascripts/tooltips/index.js
+++ b/app/assets/javascripts/tooltips/index.js
@@ -21,6 +21,7 @@ const tooltipsApp = () => {
document.body.appendChild(container);
app = new Vue({
+ name: 'TooltipsRoot',
render(h) {
return h(Tooltips, {
props: {
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index d6aa63be996..57c9bcde024 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -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;
}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 281e21b3ab0..1b98810b09b 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -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
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 05452ef6578..6445f920db5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -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
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index d58ed252a36..e38eeaed367 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -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)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index b39cd485fe8..6098ef63ec3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -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
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 3320c13e87b..7e538238cbd 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -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
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index 0fda6fb1ed0..b41a9959c13 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -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
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index a4342aa5b90..b1d8872aa5e 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -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,8 +153,12 @@ class WebHookService
internal_error_message: error_message
}
- ::WebHooks::LogExecutionWorker
- .perform_async(hook.id, log_data, category, uniqueness_token)
+ 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)
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
deleted file mode 100644
index 1379a339feb..00000000000
--- a/app/views/projects/_bitbucket_import_modal.html.haml
+++ /dev/null
@@ -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" } &times;
- .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")
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index f8f96dd77c4..aca7b73267b 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -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
diff --git a/config/feature_flags/development/block_external_fork_network_mirrors.yml b/config/feature_flags/development/block_external_fork_network_mirrors.yml
deleted file mode 100644
index 8c313bc9273..00000000000
--- a/config/feature_flags/development/block_external_fork_network_mirrors.yml
+++ /dev/null
@@ -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
diff --git a/config/feature_flags/development/markdown_continue_lists.yml b/config/feature_flags/development/markdown_continue_lists.yml
new file mode 100644
index 00000000000..8be9a3008da
--- /dev/null
+++ b/config/feature_flags/development/markdown_continue_lists.yml
@@ -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
diff --git a/data/deprecations/14-8-graphql-ids.yml b/data/deprecations/14-8-graphql-ids.yml
new file mode 100644
index 00000000000..dd73f310335
--- /dev/null
+++ b/data/deprecations/14-8-graphql-ids.yml
@@ -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
diff --git a/data/deprecations/14-8-sast-analyzer-removals.yml b/data/deprecations/14-8-sast-analyzer-removals.yml
new file mode 100644
index 00000000000..6bbeee0cd6b
--- /dev/null
+++ b/data/deprecations/14-8-sast-analyzer-removals.yml
@@ -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
diff --git a/data/deprecations/14-8-sast-dotnet-21.yml b/data/deprecations/14-8-sast-dotnet-21.yml
new file mode 100644
index 00000000000..ab1b3c16b23
--- /dev/null
+++ b/data/deprecations/14-8-sast-dotnet-21.yml
@@ -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
diff --git a/data/deprecations/14-8-secret-detection-configurations.yml b/data/deprecations/14-8-secret-detection-configurations.yml
new file mode 100644
index 00000000000..f9103c1cea3
--- /dev/null
+++ b/data/deprecations/14-8-secret-detection-configurations.yml
@@ -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
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 037890b93bf..49eba783e21 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -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:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index a00ac13c87b..e45bce4b24f 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -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*) | ✓ | ✓ | ✓ | ✓ |
diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb
index 04f53accfeb..883abef9bdb 100644
--- a/lib/gitlab/github_import/representation/diff_note.rb
+++ b/lib/gitlab/github_import/representation/diff_note.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2f3fad4e703..8769f0fcede 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -12869,6 +12869,9 @@ msgstr ""
msgid "Display name"
msgstr ""
+msgid "Display progress of child issues"
+msgstr ""
+
msgid "Display rendered file"
msgstr ""
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index ef634d3ccda..8cf1fa0705f 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -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
#
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 2c9e302fc56..a861c13a44c 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -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
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 5cc9ff403cd..e52ca1fb17c 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -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
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 58d34a5e5c1..0f1501d4c3c 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -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
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 51e91b76d3f..b3fbf5d356e 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -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
diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
index d2d1fe6b2d8..0cef18c60de 100644
--- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
@@ -31,6 +31,10 @@ describe('Design reply form component', () => {
});
}
+ beforeEach(() => {
+ gon.features = { markdownContinueLists: true };
+ });
+
afterEach(() => {
wrapper.destroy();
});
diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js
index 3043c4c3673..dd511c3945c 100644
--- a/spec/frontend/issues/show/components/fields/description_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_spec.js
@@ -25,6 +25,7 @@ describe('Description field component', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit');
+ gon.features = { markdownContinueLists: true };
});
afterEach(() => {
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
index d19f9352bbc..e06d1384610 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
@@ -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);
+ });
});
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index ab81ec47b64..dded32cc890 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -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', () => {
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index d3b5ab02f24..3e80b24f128 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -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(() => {
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
index d3e484cf913..b79dc0bf976 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
@@ -36,6 +36,7 @@ describe('IssuableEditForm', () => {
beforeEach(() => {
wrapper = createComponent();
+ gon.features = { markdownContinueLists: true };
});
afterEach(() => {
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 13f221fd9d9..44684619fae 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -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', () => {
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 1d78778c757..604ce0fe0c1 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
index 63834cfdb94..fe3040c102b 100644
--- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
@@ -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
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 85f433f5f81..0d65fe302e1 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -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)
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 89bfb742f5d..a3d36058b74 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -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)
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index c292e78b32d..482e372543c 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -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
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index cd6284b4a87..d97a6f15270 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -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
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index 48c8c24212a..66a1218d123 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -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
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 448d00076c1..64371f97908 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
index 1a981f42086..2285d9a17e2 100644
--- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
@@ -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