Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-22 12:11:52 +00:00
parent 3f7e1004e7
commit 219623e82f
37 changed files with 393 additions and 217 deletions

View File

@ -262,12 +262,6 @@ export default {
'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue',
'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue',
'app/assets/javascripts/vue_merge_request_widget/components/state_container.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue',
'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue',
'app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue',
'app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue',
'app/assets/javascripts/vue_merge_request_widget/widgets/accessibility/index.vue',

View File

@ -1 +1 @@
da219155e5f13896b0427c536e5ff169b95ec558
fb8fb7e475bc3693f1e45875d266c73d450fe58f

View File

@ -1,17 +1,13 @@
import ClipboardJS from 'clipboard';
import toast from '~/vue_shared/plugins/global_toast';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils';
import { s__ } from '~/locale';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/sidebar/components/labels/labels_select_widget/constants';
import { CopyAsGFM } from '../markdown/copy_as_gfm';
import {
ISSUE_MR_CHANGE_ASSIGNEE,
ISSUE_MR_CHANGE_MILESTONE,
ISSUABLE_CHANGE_LABEL,
ISSUABLE_EDIT_DESCRIPTION,
ISSUABLE_COPY_REF,
ISSUABLE_COMMENT_OR_REPLY,
WORK_ITEM_TOGGLE_SIDEBAR,
} from './keybindings';
@ -33,7 +29,6 @@ export default class ShortcutsWorkItem {
[ISSUABLE_EDIT_DESCRIPTION, ShortcutsWorkItem.editDescription],
[WORK_ITEM_TOGGLE_SIDEBAR, ShortcutsWorkItem.toggleSidebar],
[ISSUABLE_COPY_REF, () => this.copyReference()],
[ISSUABLE_COMMENT_OR_REPLY, ShortcutsWorkItem.replyWithSelectedText],
]);
/**
@ -106,84 +101,4 @@ export default class ShortcutsWorkItem {
this.refInMemoryButton.dispatchEvent(new CustomEvent('click'));
}
}
static replyWithSelectedText() {
const gfmSelector = '.js-vue-markdown-field .js-gfm-input';
let replyField =
document.querySelector(`.gl-drawer ${gfmSelector}`) ||
document.querySelector(`.modal ${gfmSelector}`) ||
document.querySelector(gfmSelector);
// Ensure that markdown input is still present in the DOM
// otherwise fall back to main comment input field.
if (
ShortcutsWorkItem.lastFocusedReplyField &&
isElementVisible(ShortcutsWorkItem.lastFocusedReplyField)
) {
replyField = ShortcutsWorkItem.lastFocusedReplyField;
}
if (!replyField || !isElementVisible(replyField)) {
return false;
}
const documentFragment = getSelectedFragment(document.querySelector('#content-body'));
if (!documentFragment) {
replyField.focus();
return false;
}
// Sanity check: Make sure the selected text comes from a discussion : it can either contain a message...
let foundMessage = Boolean(documentFragment.querySelector('.md'));
// ... Or come from a message
if (!foundMessage) {
if (documentFragment.originalNodes) {
documentFragment.originalNodes.forEach((e) => {
let node = e;
do {
// Text nodes don't define the `matches` method
if (node.matches && node.matches('.md')) {
foundMessage = true;
}
node = node.parentNode;
} while (node && !foundMessage);
});
}
// If there is no message, just select the reply field
if (!foundMessage) {
replyField.focus();
return false;
}
}
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const blockquoteEl = document.createElement('blockquote');
blockquoteEl.appendChild(el);
CopyAsGFM.nodeToGFM(blockquoteEl)
.then((text) => {
if (text.trim() === '') {
return false;
}
// If replyField already has some content, add a newline before our quote
const separator = (replyField.value.trim() !== '' && '\n\n') || '';
replyField.value = `${replyField.value}${separator}${text}\n\n`;
// Trigger autosize
const event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
replyField.dispatchEvent(event);
// Focus the input field
replyField.focus();
return false;
})
.catch(() => {});
return false;
}
}

View File

@ -14,12 +14,6 @@ export default {
BoldText,
StateContainer,
},
props: {
mr: {
type: Object,
required: true,
},
},
};
</script>

View File

@ -3,10 +3,8 @@ import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import MrWidgetAuthor from '../mr_widget_author.vue';
@ -45,30 +43,12 @@ export default {
return {
state: null,
isCancellingAutoMerge: false,
isRemovingSourceBranch: false,
};
},
computed: {
loading() {
return this.$apollo.queries.state.loading || !this.state;
},
stateRemoveSourceBranch() {
if (!this.state.mergeRequest.shouldRemoveSourceBranch) return false;
return (
this.state.mergeRequest.shouldRemoveSourceBranch ||
this.state.mergeRequest.forceRemoveSourceBranch
);
},
canRemoveSourceBranch() {
const { currentUserId } = this.mr;
const mergeUserId = getIdFromGraphQLId(this.state.mergeRequest.mergeUser?.id);
const canRemoveSourceBranch = this.state.mergeRequest.userPermissions.removeSourceBranch;
return (
!this.stateRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId
);
},
actions() {
const actions = [];
@ -105,32 +85,6 @@ export default {
});
});
},
removeSourceBranch() {
const options = {
sha: this.mr.sha,
auto_merge_strategy: this.state.mergeRequest.autoMergeStrategy,
should_remove_source_branch: true,
};
this.isRemovingSourceBranch = true;
this.service
.merge(options)
.then((res) => res.data)
.then((data) => {
if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
.then(() => {
this.$apollo.queries.state.refetch();
})
.catch(() => {
this.isRemovingSourceBranch = false;
createAlert({
message: __('Something went wrong. Please try again.'),
});
});
},
},
};
</script>

View File

@ -21,6 +21,7 @@ export default {
},
},
props: {
// eslint-disable-next-line vue/no-unused-properties -- `mr` is used in the `mergeRequestQueryVariables` computed property
mr: {
type: Object,
required: true,

View File

@ -6,12 +6,6 @@ export default {
components: {
StateContainer,
},
props: {
mr: {
type: Object,
required: true,
},
},
};
</script>
<template>

View File

@ -44,10 +44,6 @@ export default {
!isRemovingSourceBranch
);
},
shouldShowSourceBranchRemoving() {
const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr;
return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest);
},
revertTitle() {
return s__('mrWidget|Revert this merge request in a new merge request');
},

View File

@ -23,6 +23,7 @@ export default {
},
mixins: [mergeRequestQueryVariablesMixin],
apollo: {
// `state` is used here to manage Apollo query result,
state: {
query: missingBranchQuery,
variables() {
@ -38,6 +39,7 @@ export default {
},
},
data() {
// eslint-disable-next-line vue/no-unused-properties -- `state` is tied to an Apollo query
return { state: {} };
},
computed: {

View File

@ -1,5 +1,5 @@
<template>
<li class="timeline-entry note-container-query-wrapper">
<li class="js-timeline-entry timeline-entry note-container-query-wrapper">
<div class="timeline-entry-inner"><slot></slot></div>
</li>
</template>

View File

@ -241,6 +241,12 @@ export default {
// used to rerender work-item-comment-form so the text in the textarea is cleared
return uniqueId(`work-item-add-note-${this.workItemId}-`);
},
// This method is called from parent components so we need
// ignore linter here.
// eslint-disable-next-line vue/no-unused-properties
appendText(text) {
this.$refs.commentForm.$refs.markdownEditor.append(text);
},
async updateWorkItem({ commentText, isNoteInternal = false }) {
this.isSubmitting = true;
this.$emit('replying', commentText);
@ -378,6 +384,7 @@ export default {
<work-item-comment-form
v-if="isEditing"
:key="addNoteKey"
ref="commentForm"
:work-item-type="workItemType"
:aria-label="__('Add a reply')"
:is-submitting="isSubmitting"

View File

@ -365,6 +365,7 @@ export default {
:noteable-type="workItemTypeKey"
>
<markdown-editor
ref="markdownEditor"
class="js-gfm-wrapper"
:value="commentText"
:render-markdown-path="markdownPreviewPath"

View File

@ -2,6 +2,7 @@
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { ASC } from '~/notes/constants';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import toggleWorkItemNoteResolveDiscussion from '~/work_items/graphql/notes/toggle_work_item_note_resolve_discussion.mutation.graphql';
import DiscussionNotesRepliesWrapper from '~/notes/components/discussion_notes_replies_wrapper.vue';
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
@ -153,7 +154,31 @@ export default {
deep: true,
},
},
mounted() {
gfmEventHub.$on('quote-reply', this.handleQuoteReply);
},
beforeDestroy() {
gfmEventHub.$off('quote-reply', this.handleQuoteReply);
},
methods: {
handleQuoteReply({ event, discussionId, text }) {
// Ensure we're using correct discussion block for reply form.
if (this.note.discussion.id === discussionId) {
// Prevent 'r' being written.
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
// Show reply form if not open already.
if (!this.showForm) this.showReplyForm();
// Wait for form rendering.
this.$nextTick(() => {
// We're using `append` method from ~/vue_shared/components/markdown/markdown_editor.vue
this.$refs.addNote.appendText(text);
});
}
},
showReplyForm() {
this.showForm = true;
this.isExpanded = true;
@ -340,6 +365,7 @@ export default {
/>
<work-item-add-note
v-if="shouldShowReplyForm"
ref="addNote"
:notes-form="false"
:autofocus="autofocus"
:full-path="fullPath"

View File

@ -347,7 +347,7 @@ export default {
<div
v-else
ref="description"
class="work-item-description description md gl-relative gl-clearfix"
class="js-work-item-description work-item-description description md gl-relative gl-clearfix"
>
<div
ref="gfm-content"

View File

@ -350,6 +350,9 @@ export default {
hasBlockedWorkItemsFeature() {
return this.workItem.userPermissions?.blockedWorkItems;
},
canCreateNote() {
return this.workItem.userPermissions?.createNote;
},
isDiscussionLocked() {
return this.workItemNotes?.discussionLocked;
},
@ -1255,6 +1258,7 @@ export default {
:assignees="workItemAssignees && workItemAssignees.assignees.nodes"
:can-set-work-item-metadata="canAssignUnassignUser"
:can-summarize-comments="canSummarizeComments"
:can-create-note="canCreateNote"
:is-discussion-locked="isDiscussionLocked"
:is-work-item-confidential="workItem.confidential"
:new-comment-template-paths="newCommentTemplatePaths"

View File

@ -8,7 +8,11 @@ import {
TYPENAME_NOTE,
TYPENAME_GROUP,
} from '~/graphql_shared/constants';
import { Mousetrap } from '~/lib/mousetrap';
import { ISSUABLE_COMMENT_OR_REPLY, keysFor } from '~/behaviors/shortcuts/keybindings';
import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import SystemNote from '~/work_items/components/notes/system_note.vue';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import WorkItemNotesLoading from '~/work_items/components/notes/work_item_notes_loading.vue';
import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue';
import {
@ -92,6 +96,11 @@ export default {
required: false,
default: false,
},
canCreateNote: {
type: Boolean,
required: false,
default: false,
},
isDiscussionLocked: {
type: Boolean,
required: false,
@ -267,6 +276,14 @@ export default {
if (this.shouldLoadPreviewNote) {
this.cleanupScrollListener = scrollToTargetOnResize();
}
if (this.canCreateNote) {
Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), (e) => this.quoteReply(e));
}
},
beforeDestroy() {
if (this.canCreateNote) {
Mousetrap.unbind(keysFor(ISSUABLE_COMMENT_OR_REPLY), this.quoteReply);
}
},
apollo: {
previewNote: {
@ -368,6 +385,39 @@ export default {
},
},
methods: {
getDiscussionIdFromSelection() {
const selection = window.getSelection();
if (selection.rangeCount <= 0) return null;
// Return early if selection is from description, we need to use the top-level comment field.
if (selection.anchorNode?.parentElement?.closest('.js-work-item-description')) return null;
const el = selection.getRangeAt(0).startContainer;
const node = el.nodeType === Node.TEXT_NODE ? el.parentNode : el;
return node.closest('.js-timeline-entry').getAttribute('discussion-id');
},
async quoteReply(e) {
const discussionId = this.getDiscussionIdFromSelection();
const text = await CopyAsGFM.selectionToGfm();
// Check if selection is coming from an existing discussion
if (discussionId) {
gfmEventHub.$emit('quote-reply', {
discussionId,
text,
event: e,
});
} else {
// Selection is from description, append it to top-level comment form,
this.appendText(text);
}
},
appendText(text) {
// Based on selected sort order of discussion timeline,
// we have to choose correct <work-item-add-note/> reference.
// We're using `append` method from ~/vue_shared/components/markdown/markdown_editor.vue
this.$refs[this.formAtTop ? 'addNoteTop' : 'addNoteBottom'].appendText(text);
},
getDiscussionKey(discussion) {
// discussion key is important like this since after first comment changes
const discussionId = discussion.notes.nodes[0].id;
@ -474,6 +524,7 @@ export default {
<div v-if="formAtTop && !commentsDisabled" class="js-comment-form">
<ul class="notes notes-form timeline">
<work-item-add-note
ref="addNoteTop"
v-bind="workItemCommentFormProps"
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
:is-group-work-item="isGroupWorkItem"
@ -495,6 +546,7 @@ export default {
<template v-else>
<work-item-discussion
:key="getDiscussionKey(discussion)"
ref="workItemDiscussion"
:discussion="discussion.notes.nodes"
:full-path="fullPath"
:work-item-id="workItemId"
@ -526,6 +578,7 @@ export default {
<div v-if="!formAtTop && !commentsDisabled" class="js-comment-form">
<ul class="notes notes-form timeline">
<work-item-add-note
ref="addNoteBottom"
v-bind="workItemCommentFormProps"
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
:is-group-work-item="isGroupWorkItem"

View File

@ -49,9 +49,10 @@ module MergeRequests
# Deleted branch
next if Gitlab::Git.blank_ref?(changes[:newrev])
# Default branch
branch_name = Gitlab::Git.branch_name(changes[:ref])
next if branch_name == target_project.default_branch
# Skip default branch only if no target branch is specified
next if branch_name == target_project.default_branch && !push_options[:target]
result[branch_name] = changes
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class RemoveAdjustIndicesReservedStorageBytesEventWorker < Gitlab::Database::Migration[2.2]
milestone '18.0'
disable_ddl_transaction!
DEPRECATED_JOB_CLASSES = %w[Search::Zoekt::AdjustIndicesReservedStorageBytesEventWorker]
def up
sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES)
end
def down; end
end

View File

@ -0,0 +1 @@
21084a617043b364cdea06a6d60690a9334c07f35af948ed392ae24d9e17ae4e

View File

@ -237,7 +237,7 @@ repeatedly reports that some items never reach 100%,
the following command helps to focus on the most common errors.
```shell
jq --raw-output 'select(.severity == "ERROR") | [.project_path, .class, .message, .error] | @tsv' geo.log | sort | uniq -c | sort | tail
jq --raw-output 'select(.severity == "ERROR") | [.class, .id, .message, .error] | @tsv' geo.log | sort | uniq -c | sort -n | tail
```
Refer to our [Geo troubleshooting page](../geo/replication/troubleshooting/_index.md)

View File

@ -436,10 +436,10 @@ Then customize the configuration:
```ruby
# Lower minimum index size to 100 MB (default is 1GB)
ApplicationSetting.current.update!(database_reindexing: { reindexing_minimum_index_size: 100.megabytes })
Gitlab::Database::Reindexing.minimum_index_size!(100.megabytes)
# Change minimum bloat threshold to 30% (default is 20%, there is no benefit from setting it lower)
ApplicationSetting.current.update!(database_reindexing: { reindexing_minimum_relative_bloat_size: 0.3 })
Gitlab::Database::Reindexing.minimum_relative_bloat_size!(0.3)
```
### Automated reindexing

View File

@ -3366,8 +3366,8 @@ build_job:
#### `needs:pipeline:job`
A [child pipeline](../pipelines/downstream_pipelines.md#parent-child-pipelines) can download artifacts from a job in
its parent pipeline or another child pipeline in the same parent-child pipeline hierarchy.
A [child pipeline](../pipelines/downstream_pipelines.md#parent-child-pipelines) can download artifacts from a
successfully finished job in its parent pipeline or another child pipeline in the same parent-child pipeline hierarchy.
**Keyword type**: Job keyword. You can use it only as part of a job.
@ -3381,6 +3381,10 @@ its parent pipeline or another child pipeline in the same parent-child pipeline
- Parent pipeline (`.gitlab-ci.yml`):
```yaml
stages:
- build
- test
create-artifact:
stage: build
script: echo "sample artifact" > artifact.txt
@ -3410,6 +3414,8 @@ In this example, the `create-artifact` job in the parent pipeline creates some a
The `child-pipeline` job triggers a child pipeline, and passes the `CI_PIPELINE_ID`
variable to the child pipeline as a new `PARENT_PIPELINE_ID` variable. The child pipeline
can use that variable in `needs:pipeline` to download artifacts from the parent pipeline.
Having the `create-artifact` and `child-pipeline` jobs in subsequent stages ensures that
the `use-artifact` job only executes when `create-artifact` has successfully finished.
**Additional details**:
@ -3418,6 +3424,9 @@ can use that variable in `needs:pipeline` to download artifacts from the parent
- You cannot use `needs:pipeline:job` in a [trigger job](#trigger), or to fetch artifacts
from a [multi-project pipeline](../pipelines/downstream_pipelines.md#multi-project-pipelines).
To fetch artifacts from a multi-project pipeline use [`needs:project`](#needsproject).
- The job listed in `needs:pipeline:job` must complete with a status of `success`
or the artifacts can't be fetched. [Issue 367229](https://gitlab.com/gitlab-org/gitlab/-/issues/367229)
proposes to allow fetching artifacts from any job with artifacts.
#### `needs:optional`

View File

@ -135,8 +135,8 @@ Git push options can perform actions for merge requests while pushing changes:
| Push option | Description |
|----------------------------------------------|-------------|
| `merge_request.create` | Create a new merge request for the pushed branch. |
| `merge_request.target=<branch_name>` | Set the target of the merge request to a particular branch, such as: `git push -o merge_request.target=branch_name`. |
| `merge_request.create` | Create a new merge request for the pushed branch. When pushing from the default branch, you must specify a target branch using the `merge_request.target` option to create a merge request. |
| `merge_request.target=<branch_name>` | Set the target of the merge request to a particular branch, such as: `git push -o merge_request.target=branch_name`. Required when creating a merge request from the default branch. |
| `merge_request.target_project=<project>` | Set the target of the merge request to a particular upstream project, such as: `git push -o merge_request.target_project=path/to/project`. Introduced in [GitLab 16.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132475). |
| `merge_request.merge_when_pipeline_succeeds` | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185368) in GitLab 17.11 favor of the `auto_merge` option. |
| `merge_request.auto_merge` | Set the merge request to [auto merge](../../user/project/merge_requests/auto_merge.md). |
@ -186,6 +186,12 @@ new merge request, targets a branch (`my-target-branch`), and sets auto-merge:
git push -o merge_request.create -o merge_request.target=my-target-branch -o merge_request.auto_merge
```
To create a new merge request from the default branch targeting a different branch:
```shell
git push -o merge_request.create -o merge_request.target=feature-branch
```
### Create Git aliases for pushing
Adding push options to Git commands can create very long commands. If

View File

@ -626,7 +626,7 @@ SSO enforcement for web activity has the following effects when enabled:
group, even if the project is forked.
- Git activity originating from CI/CD jobs do not have the SSO check enforced.
- Credentials that are not tied to regular users (for example, project and group
access tokens, and deploy keys) do not have the SSO check enforced.
access tokens, service accounts, and deploy keys) do not have the SSO check enforced.
- Users must be signed-in through SSO before they can pull images using the
[Dependency Proxy](../../packages/dependency_proxy/_index.md).
- When the **Enforce SSO-only authentication for Git and Dependency Proxy

View File

@ -89,6 +89,22 @@ module Gitlab
Coordinator.new(index).drop
end
end
def self.minimum_index_size!(bytes)
update_reindexing_setting!(:reindexing_minimum_index_size, bytes)
end
def self.minimum_relative_bloat_size!(threshold)
update_reindexing_setting!(:reindexing_minimum_relative_bloat_size, threshold)
end
def self.update_reindexing_setting!(key, value)
application_settings = Gitlab::CurrentSettings.current_application_settings
current_settings = application_settings.database_reindexing || {}
updated_settings = current_settings.merge(key => value)
application_settings.update!(database_reindexing: updated_settings)
end
private_class_method :update_reindexing_setting!
end
end
end

View File

@ -23367,9 +23367,6 @@ msgstr ""
msgid "Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "Dynamic application security testing (DAST)"
msgstr ""
msgid "E-mail:"
msgstr ""
@ -32259,9 +32256,6 @@ msgstr ""
msgid "Infrastructure as Code (IaC) Scanning"
msgstr ""
msgid "Infrastructure as code scanning (IaC)"
msgstr ""
msgid "InfrastructureRegistry|Copy Terraform Command"
msgstr ""
@ -54160,27 +54154,54 @@ msgstr ""
msgid "SecurityInventory|Add projects to this group to start tracking their security posture."
msgstr ""
msgid "SecurityInventory|Basic SAST"
msgstr ""
msgid "SecurityInventory|CS"
msgstr ""
msgid "SecurityInventory|Container scanning"
msgstr ""
msgid "SecurityInventory|Container scanning (standard)"
msgstr ""
msgid "SecurityInventory|Container scanning for registry"
msgstr ""
msgid "SecurityInventory|DAST"
msgstr ""
msgid "SecurityInventory|DS"
msgstr ""
msgid "SecurityInventory|Dependency scanning"
msgstr ""
msgid "SecurityInventory|Dynamic application security testing (DAST)"
msgstr ""
msgid "SecurityInventory|GitLab Advanced SAST"
msgstr ""
msgid "SecurityInventory|Group vulnerabilities"
msgstr ""
msgid "SecurityInventory|IaC"
msgstr ""
msgid "SecurityInventory|Infrastructure as code scanning (IaC)"
msgstr ""
msgid "SecurityInventory|Manage security configuration"
msgstr ""
msgid "SecurityInventory|No projects found"
msgstr ""
msgid "SecurityInventory|Pipeline secret detection"
msgstr ""
msgid "SecurityInventory|Project vulnerabilities"
msgstr ""
@ -54190,9 +54211,18 @@ msgstr ""
msgid "SecurityInventory|SD"
msgstr ""
msgid "SecurityInventory|Secret detection"
msgstr ""
msgid "SecurityInventory|Secret push protection"
msgstr ""
msgid "SecurityInventory|Security inventory"
msgstr ""
msgid "SecurityInventory|Static application security testing (SAST)"
msgstr ""
msgid "SecurityInventory|Tool coverage: %{coverage}%%"
msgstr ""
@ -58533,9 +58563,6 @@ msgstr ""
msgid "Static Application Security Testing (SAST)"
msgstr ""
msgid "Static application security testing (SAST)"
msgstr ""
msgid "Statistics"
msgstr ""
@ -63197,6 +63224,9 @@ msgstr ""
msgid "Tool Coverage"
msgstr ""
msgid "ToolCoverageDetails|Manage configuration"
msgstr ""
msgid "Topic %{source_topic} was successfully merged into topic %{target_topic}."
msgstr ""

View File

@ -73,7 +73,7 @@
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.0.807",
"@rails/ujs": "7.0.807",
"@sentry/browser": "9.12.0",
"@sentry/browser": "9.13.0",
"@snowplow/browser-plugin-client-hints": "^3.24.2",
"@snowplow/browser-plugin-form-tracking": "^3.24.2",
"@snowplow/browser-plugin-ga-cookies": "^3.24.2",

View File

@ -7,7 +7,6 @@ RSpec.describe Organizations::GroupsFinder, feature_category: :groups_and_projec
let_it_be(:organization_user) { create(:organization_user) }
let_it_be(:organization) { organization_user.organization }
let_it_be(:user) { organization_user.user }
let_it_be(:default_organization) { create(:organization, :default) }
let_it_be(:other_organization) { create(:organization) }
let_it_be_with_reload(:public_group) { create(:group, name: 'public-group', organization: organization) }
let_it_be_with_reload(:outside_organization_group) do

View File

@ -7,7 +7,7 @@ describe('MRWidgetArchived', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(archivedComponent, { propsData: { mr: {} } });
wrapper = shallowMount(archivedComponent);
});
it('renders error icon', () => {

View File

@ -6,7 +6,7 @@ describe('MRWidgetChecking', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(CheckingComponent, { propsData: { mr: {} } });
wrapper = shallowMount(CheckingComponent);
});
it('renders loading icon', () => {

View File

@ -2,7 +2,7 @@
exports[`History Item renders the correct markup 1`] = `
<li
class="note-container-query-wrapper note-wrapper system-note timeline-entry"
class="js-timeline-entry note-container-query-wrapper note-wrapper system-note timeline-entry"
>
<div
class="timeline-entry-inner"

View File

@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
import WorkItemNote from '~/work_items/components/notes/work_item_note.vue';
@ -54,10 +55,14 @@ describe('Work Item Discussion', () => {
workItemIid: '1',
workItemType,
markdownPreviewPath: '/group/project/preview_markdown?target_type=WorkItem',
uploadsPath: '/group/project/uploads',
autocompleteDataSources: {},
isExpandedOnLoad,
hideFullscreenMarkdownButton,
},
stubs: {
WorkItemAddNote,
},
});
};
@ -267,4 +272,37 @@ describe('Work Item Discussion', () => {
expect(findToggleRepliesWidget().props('collapsed')).toBe(false);
});
});
describe('quote-reply event', () => {
const mockDiscussions =
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes;
let mockAppendTextSpy;
beforeEach(async () => {
window.gon.current_user_id = 1;
createComponent({
discussion: mockDiscussions,
});
mockAppendTextSpy = jest
.spyOn(wrapper.vm.$refs.addNote, 'appendText')
.mockImplementation(() => {});
await gfmEventHub.$emit('quote-reply', {
event: {
preventDefault: jest.fn(),
},
discussionId: mockDiscussions[0].discussion.id,
text: 'quoted text',
});
await nextTick();
});
it('shows reply input form for the discussion note', () => {
expect(findWorkItemAddNote().exists()).toBe(true);
});
it('calls appendText on work-item-add-note', () => {
expect(mockAppendTextSpy).toHaveBeenCalledWith('quoted text');
});
});
});

View File

@ -853,6 +853,7 @@ describe('WorkItemDetail component', () => {
expect(findNotesWidget().exists()).toBe(true);
expect(findNotesWidget().props('isWorkItemConfidential')).toBe(confidential);
expect(findNotesWidget().props('canCreateNote')).toBeDefined();
});
});

View File

@ -6,7 +6,12 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import { Mousetrap } from '~/lib/mousetrap';
import { ISSUABLE_COMMENT_OR_REPLY, keysFor } from '~/behaviors/shortcuts/keybindings';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import SystemNote from '~/work_items/components/notes/system_note.vue';
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
@ -109,6 +114,7 @@ describe('WorkItemNotes component', () => {
workItemIid = mockWorkItemIid,
defaultWorkItemNotesQueryHandler = workItemNotesQueryHandler,
deleteWINoteMutationHandler = deleteWorkItemNoteMutationSuccessHandler,
canCreateNote = false,
isGroup = false,
isDrawer = false,
isModal = false,
@ -116,7 +122,16 @@ describe('WorkItemNotes component', () => {
parentId = null,
propsData = {},
} = {}) => {
setHTMLFixture(`
<div class="work-item-overview">
<div class="js-work-item-description">
<p>Work Item Description</p>
</div>
<div id="root"></div>
</div>
`);
wrapper = shallowMount(WorkItemNotes, {
attachTo: '#root',
apolloProvider: createMockApollo([
[workItemNoteQuery, workItemNoteQueryHandler],
[workItemNotesByIidQuery, defaultWorkItemNotesQueryHandler],
@ -130,6 +145,7 @@ describe('WorkItemNotes component', () => {
},
propsData: {
fullPath: 'test-path',
uploadsPath: '/group/project/uploads',
workItemId,
workItemIid,
workItemType: 'task',
@ -137,6 +153,7 @@ describe('WorkItemNotes component', () => {
isModal,
isWorkItemConfidential,
parentId,
canCreateNote,
...propsData,
},
stubs: {
@ -149,6 +166,10 @@ describe('WorkItemNotes component', () => {
createComponent();
});
afterEach(() => {
resetHTMLFixture();
});
it('has the work item note activity header', () => {
expect(findActivityHeader().exists()).toBe(true);
});
@ -596,4 +617,57 @@ describe('WorkItemNotes component', () => {
expect(findAllWorkItemCommentNotes().at(0).props('hideFullscreenMarkdownButton')).toBe(true);
});
});
describe('reply shortcut', () => {
const triggerReplyShortcut = async () => {
Mousetrap.trigger(keysFor(ISSUABLE_COMMENT_OR_REPLY)[0]);
await nextTick();
};
beforeEach(async () => {
window.gon.current_user_id = 1;
createComponent({
defaultWorkItemNotesQueryHandler: jest
.fn()
.mockResolvedValue(mockWorkItemNotesResponseWithComments()),
canCreateNote: true,
});
jest.spyOn(gfmEventHub, '$emit');
jest.spyOn(wrapper.vm, 'appendText').mockImplementation(() => {});
await waitForPromises();
});
it('emits `quote-reply` event on $root when reply quotes an existing discussion', async () => {
jest.spyOn(CopyAsGFM, 'selectionToGfm').mockReturnValueOnce('foo');
jest.spyOn(wrapper.vm, 'getDiscussionIdFromSelection').mockReturnValue('discussion-1');
await triggerReplyShortcut();
expect(gfmEventHub.$emit).toHaveBeenCalledWith('quote-reply', {
discussionId: 'discussion-1',
text: 'foo',
event: expect.any(Object),
});
});
it.each`
sortDirection | description
${ASC} | ${'oldest-first'}
${DESC} | ${'newest-first'}
`(
'calls on appendText on work-item-add-note form with discussion sort direction set to $description',
async ({ sortDirection }) => {
jest.spyOn(CopyAsGFM, 'selectionToGfm').mockReturnValueOnce('foo');
findActivityHeader().vm.$emit('changeSort', sortDirection);
await nextTick();
await triggerReplyShortcut();
expect(gfmEventHub.$emit).not.toHaveBeenCalledWith();
expect(wrapper.vm.appendText).toHaveBeenCalledWith('foo');
},
);
});
});

View File

@ -279,4 +279,30 @@ RSpec.describe Gitlab::Database::Reindexing, feature_category: :database, time_t
expect(expected_queries).to eq(actual_queries)
end
end
describe '.minimum_index_size!' do
let(:current_application_settings) { Gitlab::CurrentSettings.current_application_settings }
let(:bytes) { 2.gigabytes }
subject(:minimum_index_size) { described_class.minimum_index_size!(bytes) }
it 'updates reindexing minimun index size' do
minimum_index_size
expect(current_application_settings.database_reindexing['reindexing_minimum_index_size']).to be(bytes)
end
end
describe '.minimum_relative_bloat_size!' do
let(:current_application_settings) { Gitlab::CurrentSettings.current_application_settings }
let(:threshold) { 0.3 }
subject(:minimum_relative_bloat_size) { described_class.minimum_relative_bloat_size!(threshold) }
it 'updates minimum relative bloat size' do
minimum_relative_bloat_size
expect(current_application_settings.database_reindexing['reindexing_minimum_relative_bloat_size']).to be(threshold)
end
end
end

View File

@ -358,6 +358,23 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
it_behaves_like 'with the project default branch'
end
describe 'pushing from default branch' do
let(:changes) { default_branch_changes }
context 'with target branch specified' do
let(:push_options) { { create: true, target: target_branch } }
it_behaves_like 'a service that can create a merge request'
it_behaves_like 'a service that can set the target of a merge request'
end
context 'without target branch specified' do
let(:push_options) { { create: true } }
it_behaves_like 'a service that does nothing'
end
end
describe '`target` push option' do
let(:push_options) { { target: target_branch } }

View File

@ -1417,8 +1417,7 @@
resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.3.0.tgz#df89c1bb6714e4a8a5d3272568aa4de7fb337267"
integrity sha512-DoMUIN3DqjEn7wvcxBg/b7Ite5fTdF5EmuOZoBRo2j0UBGweDXmNBi+9HrTZs4cBU660dOxcf1hATFcG3npbPg==
"@gitlab/noop@^1.0.1", jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
name jackspeak
"@gitlab/noop@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
@ -2531,51 +2530,51 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
"@sentry-internal/browser-utils@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.12.0.tgz#6efef1489cbe7cb19b5227ab4c37b015fde1fbc4"
integrity sha512-GXuDEG2Ix8DmVtTkjsItWdusk2CvJ6EPWKYVqFKifxt+IAT3ZbhGZd99Rg3wdRmt9xhCNuS4QrDzDTPMPgfdCw==
"@sentry-internal/browser-utils@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.13.0.tgz#886e6c1dcf706624d98166c389aadcbf9681bd71"
integrity sha512-uZcbwcGI49oPC/YDEConJ+3xi2mu0TsVsDiMQKb6JoSc33KH37wq2IwXJb9nakzKJXxyMNemb44r8irAswjItw==
dependencies:
"@sentry/core" "9.12.0"
"@sentry/core" "9.13.0"
"@sentry-internal/feedback@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.12.0.tgz#fc26bf1cd0abfeb249b941c1c943469775d056ad"
integrity sha512-3+UxoT97QIXNSUQS4ATL1FFws0RkUb6PeaQN8CPndI6mFlqTW5tuVVLNg9Eo1seNg7R/dfk6WHCWrYN1NbFFKQ==
"@sentry-internal/feedback@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.13.0.tgz#235bea4cd8c7dd4151725467a64b1c0f05b9788a"
integrity sha512-fOhMnhEbOR5QVPtn5Gc5+UKQHjvAN/LmtYE6Qya3w2FDh3ZlnIXNFJWqwOneuICV3kCWjN4lLckwmzzwychr7A==
dependencies:
"@sentry/core" "9.12.0"
"@sentry/core" "9.13.0"
"@sentry-internal/replay-canvas@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.12.0.tgz#41d4b0e85bad8f5491f0168a0396ae54d8128142"
integrity sha512-p8LuKZgWT/CoQBbDOXkSGjWWnc8WsnAayWgna8M/ZFWNITCNEM2rCuqZOyWOElIlrni+M7qoEA3jS7MZe8Ejxw==
"@sentry-internal/replay-canvas@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.13.0.tgz#1821377b9587b61f080c8a21617ebdeef26b2580"
integrity sha512-5muW2BmEfWP1fpVWDNcIsph/WgqOqpHaXC1QMr4hk8/BWgt1/S2KPy85YiGVtM5lJJr0VhASKK8rBXG+9zm9IQ==
dependencies:
"@sentry-internal/replay" "9.12.0"
"@sentry/core" "9.12.0"
"@sentry-internal/replay" "9.13.0"
"@sentry/core" "9.13.0"
"@sentry-internal/replay@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.12.0.tgz#fa20b509dc534d57a19500690c2141e9cec4c767"
integrity sha512-njEQosFeO/UX+gG+DMRANkPUuz6OIJLb+A1GVylhq9adUgFQydQ9Ay3v7/x1gMhdfHVP6Jeb27qkti0BWYbzBQ==
"@sentry-internal/replay@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.13.0.tgz#0266a5ff990f371f25154e85950d6a6e6da77c93"
integrity sha512-l+Atwab/bqI1N8+PSG1WWTCVmiOl7swL85Z9ntwS39QBnd66CTyzt/+j/n/UbAs8GienJK6FIfX1dvG1WmvUhA==
dependencies:
"@sentry-internal/browser-utils" "9.12.0"
"@sentry/core" "9.12.0"
"@sentry-internal/browser-utils" "9.13.0"
"@sentry/core" "9.13.0"
"@sentry/browser@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.12.0.tgz#f39c5e05ea9dd31b54ccf27a9d49f8de9d9ff7f2"
integrity sha512-4xQYoZqi+VVhNvlhWiwRd57+SMr3Og4sLjuayAA+zIp1Wx/bDcIld697cugLwml/BR+mVJI2eokkgh1CBl6zag==
"@sentry/browser@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.13.0.tgz#76cf662656c7c7ddfd36081b6e8ba05c385fe823"
integrity sha512-KiC8s9/6HvdlfCRqA420YbiBiXMBif7GYESJ8VQqOKUmlPczn8V2CRrEZjMqxhlHdIGiR0PS6jb2VSgeJBchJQ==
dependencies:
"@sentry-internal/browser-utils" "9.12.0"
"@sentry-internal/feedback" "9.12.0"
"@sentry-internal/replay" "9.12.0"
"@sentry-internal/replay-canvas" "9.12.0"
"@sentry/core" "9.12.0"
"@sentry-internal/browser-utils" "9.13.0"
"@sentry-internal/feedback" "9.13.0"
"@sentry-internal/replay" "9.13.0"
"@sentry-internal/replay-canvas" "9.13.0"
"@sentry/core" "9.13.0"
"@sentry/core@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.12.0.tgz#164a88af070cd77191ca14c88914e008c7ce7716"
integrity sha512-jOqQK/90uzHmsBvkPTj/DAEFvA5poX4ZRyC7LE1zjg4F5jdOp3+M4W3qCy0CkSTu88Zu5VWBoppCU2Bs34XEqg==
"@sentry/core@9.13.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.13.0.tgz#3269cab4ba34fa0928f04936ddb82182069cb568"
integrity sha512-Zn1Qec5XNkNRE/M5QjL6YJLghETg6P188G/v2OzdHdHIRf0Y58/SnJilu3louF+ogos6kaSqqdMgzqKgZ8tCdg==
"@sinclair/typebox@^0.27.8":
version "0.27.8"
@ -9447,6 +9446,11 @@ iterall@^1.2.1:
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
jed@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"