Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-20 06:09:59 +00:00
parent e83144f0ee
commit ba2e4183d9
40 changed files with 418 additions and 276 deletions

View File

@ -15,9 +15,14 @@ export default class GlFieldErrors {
initValidators() { initValidators() {
// register selectors here as needed // register selectors here as needed
const validateSelectors = [':text', ':password', '[type=email]', '[type=url]', '[type=number]'] const validateSelectors = [
.map((selector) => `input${selector}`) 'input:text',
.join(','); 'input:password',
'input[type=email]',
'input[type=url]',
'input[type=number]',
'textarea',
].join(',');
this.state.inputs = this.form this.state.inputs = this.form
.find(validateSelectors) .find(validateSelectors)

View File

@ -0,0 +1,11 @@
import { s__, __ } from '~/locale';
export const STOP_JOBS_MODAL_ID = 'stop-jobs-modal';
export const STOP_JOBS_MODAL_TITLE = s__('AdminArea|Stop all jobs?');
export const STOP_JOBS_BUTTON_TEXT = s__('AdminArea|Stop all jobs');
export const CANCEL_TEXT = __('Cancel');
export const STOP_JOBS_FAILED_TEXT = s__('AdminArea|Stopping jobs failed');
export const PRIMARY_ACTION_TEXT = s__('AdminArea|Stop jobs');
export const STOP_JOBS_WARNING = s__(
'AdminArea|Youre about to stop all jobs. This will halt all current jobs that are running.',
);

View File

@ -3,7 +3,14 @@ import { GlModal } from '@gitlab/ui';
import { createAlert } from '~/flash'; import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import {
CANCEL_TEXT,
STOP_JOBS_MODAL_ID,
STOP_JOBS_FAILED_TEXT,
STOP_JOBS_MODAL_TITLE,
STOP_JOBS_WARNING,
PRIMARY_ACTION_TEXT,
} from './constants';
export default { export default {
components: { components: {
@ -15,13 +22,6 @@ export default {
required: true, required: true,
}, },
}, },
computed: {
text() {
return s__(
'AdminArea|Youre about to stop all jobs. This will halt all current jobs that are running.',
);
},
},
methods: { methods: {
onSubmit() { onSubmit() {
return axios return axios
@ -32,30 +32,33 @@ export default {
}) })
.catch((error) => { .catch((error) => {
createAlert({ createAlert({
message: s__('AdminArea|Stopping jobs failed'), message: STOP_JOBS_FAILED_TEXT,
}); });
throw error; throw error;
}); });
}, },
}, },
primaryAction: { primaryAction: {
text: s__('AdminArea|Stop jobs'), text: PRIMARY_ACTION_TEXT,
attributes: [{ variant: 'danger' }], attributes: [{ variant: 'danger' }],
}, },
cancelAction: { cancelAction: {
text: __('Cancel'), text: CANCEL_TEXT,
}, },
STOP_JOBS_WARNING,
STOP_JOBS_MODAL_ID,
STOP_JOBS_MODAL_TITLE,
}; };
</script> </script>
<template> <template>
<gl-modal <gl-modal
modal-id="stop-jobs-modal" :modal-id="$options.STOP_JOBS_MODAL_ID"
:action-primary="$options.primaryAction" :action-primary="$options.primaryAction"
:action-cancel="$options.cancelAction" :action-cancel="$options.cancelAction"
@primary="onSubmit" @primary="onSubmit"
> >
<template #modal-title>{{ s__('AdminArea|Stop all jobs?') }}</template> <template #modal-title>{{ $options.STOP_JOBS_MODAL_TITLE }}</template>
{{ text }} {{ $options.STOP_JOBS_WARNING }}
</gl-modal> </gl-modal>
</template> </template>

View File

@ -1,29 +1,29 @@
import Vue from 'vue'; import Vue from 'vue';
import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import { STOP_JOBS_MODAL_ID } from './components/constants';
import StopJobsModal from './components/stop_jobs_modal.vue'; import StopJobsModal from './components/stop_jobs_modal.vue';
Vue.use(Translate); Vue.use(Translate);
function initJobs() { function initJobs() {
const buttonId = 'js-stop-jobs-button'; const buttonId = 'js-stop-jobs-button';
const modalId = 'stop-jobs-modal';
const stopJobsButton = document.getElementById(buttonId); const stopJobsButton = document.getElementById(buttonId);
if (stopJobsButton) { if (stopJobsButton) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: `#js-${modalId}`, el: `#js-${STOP_JOBS_MODAL_ID}`,
components: { components: {
StopJobsModal, StopJobsModal,
}, },
mounted() { mounted() {
stopJobsButton.classList.remove('disabled'); stopJobsButton.classList.remove('disabled');
stopJobsButton.addEventListener('click', () => { stopJobsButton.addEventListener('click', () => {
this.$root.$emit(BV_SHOW_MODAL, modalId, `#${buttonId}`); this.$root.$emit(BV_SHOW_MODAL, STOP_JOBS_MODAL_ID, `#${buttonId}`);
}); });
}, },
render(createElement) { render(createElement) {
return createElement(modalId, { return createElement(STOP_JOBS_MODAL_ID, {
props: { props: {
url: stopJobsButton.dataset.url, url: stopJobsButton.dataset.url,
}, },

View File

@ -5,7 +5,7 @@ module Types
graphql_name 'DeploymentDetails' graphql_name 'DeploymentDetails'
description 'The details of the deployment' description 'The details of the deployment'
authorize :read_deployment authorize :read_deployment
present_using Deployments::DeploymentPresenter present_using ::Deployments::DeploymentPresenter
field :tags, field :tags,
[Types::DeploymentTagType], [Types::DeploymentTagType],
@ -13,3 +13,5 @@ module Types
calls_gitaly: true calls_gitaly: true
end end
end end
Types::DeploymentDetailsType.prepend_mod_with('Types::DeploymentDetailsType')

View File

@ -11,7 +11,7 @@ module Types
graphql_name 'Deployment' graphql_name 'Deployment'
description 'The deployment of an environment' description 'The deployment of an environment'
present_using Deployments::DeploymentPresenter present_using ::Deployments::DeploymentPresenter
authorize :read_deployment authorize :read_deployment

View File

@ -281,76 +281,28 @@ module Nav
end end
def projects_submenu_items(builder:) def projects_submenu_items(builder:)
if Feature.enabled?(:remove_extra_primary_submenu_options) title = _('View all projects')
title = _('View all projects')
builder.add_primary_menu_item( builder.add_primary_menu_item(
id: 'your', id: 'your',
title: title, title: title,
href: dashboard_projects_path, href: dashboard_projects_path,
data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) }
) )
else
# These project links come from `app/views/layouts/nav/projects_dropdown/_show.html.haml`
[
{ id: 'your', title: _('Your projects'), href: dashboard_projects_path },
{ id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path },
{ id: 'explore', title: _('Explore projects'), href: explore_root_path },
{ id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path }
].each do |item|
builder.add_primary_menu_item(
**item,
data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) }
)
end
title = _('Create new project')
builder.add_secondary_menu_item(
id: 'create',
title: title,
href: new_project_path,
data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) }
)
end
end end
def groups_submenu def groups_submenu
# These group links come from `app/views/layouts/nav/groups_dropdown/_show.html.haml` # These group links come from `app/views/layouts/nav/groups_dropdown/_show.html.haml`
builder = ::Gitlab::Nav::TopNavMenuBuilder.new builder = ::Gitlab::Nav::TopNavMenuBuilder.new
if Feature.enabled?(:remove_extra_primary_submenu_options) title = _('View all groups')
title = _('View all groups')
builder.add_primary_menu_item(
id: 'your',
title: title,
href: dashboard_groups_path,
data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) }
)
else
[
{ id: 'your', title: _('Your groups'), href: dashboard_groups_path },
{ id: 'explore', title: _('Explore groups'), href: explore_groups_path }
].each do |item|
builder.add_primary_menu_item(
**item,
data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) }
)
end
if current_user.can_create_group?
title = _('Create group')
builder.add_secondary_menu_item(
id: 'create',
title: title,
href: new_group_path,
data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) }
)
end
end
builder.add_primary_menu_item(
id: 'your',
title: title,
href: dashboard_groups_path,
data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) }
)
builder.build builder.build
end end
end end

View File

@ -136,7 +136,7 @@ class MergeRequest < ApplicationRecord
before_validation :set_draft_status before_validation :set_draft_status
after_create :ensure_merge_request_diff after_create :ensure_merge_request_diff, unless: :skip_ensure_merge_request_diff
after_update :clear_memoized_shas after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
after_commit :ensure_metrics, on: [:create, :update], unless: :importing? after_commit :ensure_metrics, on: [:create, :update], unless: :importing?
@ -146,6 +146,10 @@ class MergeRequest < ApplicationRecord
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
attr_accessor :allow_broken attr_accessor :allow_broken
# Temporary flag to skip merge_request_diff creation on create.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100390
attr_accessor :skip_ensure_merge_request_diff
# Temporary fields to store compare vars # Temporary fields to store compare vars
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare attr_accessor :can_be_created, :compare_commits, :diff_options, :compare

View File

@ -5,6 +5,8 @@ module MergeRequests
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def execute(merge_request) def execute(merge_request)
merge_request.ensure_merge_request_diff
prepare_for_mergeability(merge_request) prepare_for_mergeability(merge_request)
prepare_merge_request(merge_request) prepare_merge_request(merge_request)
end end

View File

@ -34,7 +34,12 @@ module MergeRequests
# callback (e.g. after_create), a database transaction will be # callback (e.g. after_create), a database transaction will be
# open while the Gitaly RPC waits. To avoid an idle in transaction # open while the Gitaly RPC waits. To avoid an idle in transaction
# timeout, we do this before we attempt to save the merge request. # timeout, we do this before we attempt to save the merge request.
merge_request.eager_fetch_ref!
if Feature.enabled?(:async_merge_request_diff_creation, current_user)
merge_request.skip_ensure_merge_request_diff = true
else
merge_request.eager_fetch_ref!
end
end end
def set_projects! def set_projects!

View File

@ -28,7 +28,7 @@
.gl-display-none.gl-sm-display-block .gl-display-none.gl-sm-display-block
= render "layouts/nav/top_nav" = render "layouts/nav/top_nav"
- if top_nav_show_search && Feature.enabled?(:new_navbar_layout) - if top_nav_show_search
.navbar-collapse.gl-transition-medium.collapse.gl-mr-auto.global-search-container.hide-when-top-nav-responsive-open .navbar-collapse.gl-transition-medium.collapse.gl-mr-auto.global-search-container.hide-when-top-nav-responsive-open
- search_menu_item = top_nav_search_menu_item_attrs - search_menu_item = top_nav_search_menu_item_attrs
%ul.nav.navbar-nav.gl-w-full.gl-align-items-center %ul.nav.navbar-nav.gl-w-full.gl-align-items-center
@ -42,21 +42,10 @@
= link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon(search_menu_item.fetch(:icon)) = sprite_icon(search_menu_item.fetch(:icon))
.navbar-collapse.gl-transition-medium.collapse{ class: ('global-search-container' unless Feature.enabled?(:new_navbar_layout)) } .navbar-collapse.gl-transition-medium.collapse
%ul.nav.navbar-nav.gl-w-full.gl-align-items-center.gl-justify-content-end %ul.nav.navbar-nav.gl-w-full.gl-align-items-center.gl-justify-content-end
- if current_user - if current_user
= render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right' = render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right'
- if top_nav_show_search && Feature.disabled?(:new_navbar_layout)
- search_menu_item = top_nav_search_menu_item_attrs
%li.nav-item.header-search-new.gl-display-none.gl-lg-display-block.gl-w-full
- unless current_controller?(:search)
- if Feature.enabled?(:new_header_search)
= render 'layouts/header_search'
- else
= render 'layouts/search'
%li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' }
= link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon(search_menu_item.fetch(:icon))
- if header_link?(:issues) - if header_link?(:issues)
= nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues js-prefetch-document', aria: { label: _('Issues') }, = link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues js-prefetch-document', aria: { label: _('Issues') },

View File

@ -0,0 +1,8 @@
---
name: async_merge_request_diff_creation
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100390"
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/376759
milestone: '15.6'
type: development
group: group::code review
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: new_navbar_layout
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96853
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373078
milestone: '15.4'
type: development
group: group::foundations
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: remove_extra_primary_submenu_options
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96931
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373078
milestone: '15.4'
type: development
group: group::foundations
default_enabled: true

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddIndexAuthorIdTargetProjectIdOnMergeRequests < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_merge_requests_on_author_id_and_target_project_id'
disable_ddl_transaction!
def up
add_concurrent_index :merge_requests, %i[author_id target_project_id], name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :merge_requests, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
250ec3ff701dacd333d669f128762e9f035a626f2f7720c6e7e1dc61499d431d

View File

@ -29481,6 +29481,8 @@ CREATE INDEX index_merge_requests_on_assignee_id ON merge_requests USING btree (
CREATE INDEX index_merge_requests_on_author_id ON merge_requests USING btree (author_id); CREATE INDEX index_merge_requests_on_author_id ON merge_requests USING btree (author_id);
CREATE INDEX index_merge_requests_on_author_id_and_target_project_id ON merge_requests USING btree (author_id, target_project_id);
CREATE INDEX index_merge_requests_on_created_at ON merge_requests USING btree (created_at); CREATE INDEX index_merge_requests_on_created_at ON merge_requests USING btree (created_at);
CREATE INDEX index_merge_requests_on_description_trigram ON merge_requests USING gin (description gin_trgm_ops); CREATE INDEX index_merge_requests_on_description_trigram ON merge_requests USING gin (description gin_trgm_ops);

View File

@ -11442,6 +11442,33 @@ The deployment of an environment.
| <a id="deploymenttriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. | | <a id="deploymenttriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. |
| <a id="deploymentupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. | | <a id="deploymentupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. |
### `DeploymentApproval`
Approval of the deployment.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="deploymentapprovalcomment"></a>`comment` | [`String`](#string) | Additional comment. |
| <a id="deploymentapprovalcreatedat"></a>`createdAt` | [`Time`](#time) | When the user approved/rejected first time. |
| <a id="deploymentapprovalstatus"></a>`status` | [`DeploymentsApprovalStatus`](#deploymentsapprovalstatus) | Whether the deployment was approved/rejected. |
| <a id="deploymentapprovalupdatedat"></a>`updatedAt` | [`Time`](#time) | When the user updated the approval. |
| <a id="deploymentapprovaluser"></a>`user` | [`UserCore`](#usercore) | User who approved or rejected the deployment. |
### `DeploymentApprovalSummary`
Approval summary of the deployment.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="deploymentapprovalsummaryrules"></a>`rules` | [`[ProtectedEnvironmentApprovalRuleForSummary!]`](#protectedenvironmentapprovalruleforsummary) | Approval Rules for the deployment. |
| <a id="deploymentapprovalsummarystatus"></a>`status` | [`DeploymentApprovalSummaryStatus`](#deploymentapprovalsummarystatus) | Status of the approvals. |
| <a id="deploymentapprovalsummarytotalpendingapprovalcount"></a>`totalPendingApprovalCount` | [`Int`](#int) | Total pending approval count. |
| <a id="deploymentapprovalsummarytotalrequiredapprovals"></a>`totalRequiredApprovals` | [`Int`](#int) | Total number of required approvals. |
### `DeploymentDetails` ### `DeploymentDetails`
The details of the deployment. The details of the deployment.
@ -11450,6 +11477,7 @@ The details of the deployment.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="deploymentdetailsapprovalsummary"></a>`approvalSummary` | [`DeploymentApprovalSummary`](#deploymentapprovalsummary) | Approval summary of the deployment. |
| <a id="deploymentdetailscommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. | | <a id="deploymentdetailscommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. |
| <a id="deploymentdetailscreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. | | <a id="deploymentdetailscreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. |
| <a id="deploymentdetailsfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. | | <a id="deploymentdetailsfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. |
@ -17605,6 +17633,23 @@ Which group, user or role is allowed to approve deployments to the environment.
| <a id="protectedenvironmentapprovalrulerequiredapprovals"></a>`requiredApprovals` | [`Int`](#int) | Number of required approvals. | | <a id="protectedenvironmentapprovalrulerequiredapprovals"></a>`requiredApprovals` | [`Int`](#int) | Number of required approvals. |
| <a id="protectedenvironmentapprovalruleuser"></a>`user` | [`UserCore`](#usercore) | User details. Present if it's user specific access control. | | <a id="protectedenvironmentapprovalruleuser"></a>`user` | [`UserCore`](#usercore) | User details. Present if it's user specific access control. |
### `ProtectedEnvironmentApprovalRuleForSummary`
Which group, user or role is allowed to approve deployments to the environment.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="protectedenvironmentapprovalruleforsummaryaccesslevel"></a>`accessLevel` | [`AccessLevel`](#accesslevel) | Role details. Present if it's role specific access control. |
| <a id="protectedenvironmentapprovalruleforsummaryapprovals"></a>`approvals` | [`[DeploymentApproval!]`](#deploymentapproval) | Current approvals of the deployment. |
| <a id="protectedenvironmentapprovalruleforsummaryapprovedcount"></a>`approvedCount` | [`Int`](#int) | Approved count. |
| <a id="protectedenvironmentapprovalruleforsummarygroup"></a>`group` | [`Group`](#group) | Group details. Present if it's group specific access control. |
| <a id="protectedenvironmentapprovalruleforsummarypendingapprovalcount"></a>`pendingApprovalCount` | [`Int`](#int) | Pending approval count. |
| <a id="protectedenvironmentapprovalruleforsummaryrequiredapprovals"></a>`requiredApprovals` | [`Int`](#int) | Number of required approvals. |
| <a id="protectedenvironmentapprovalruleforsummarystatus"></a>`status` | [`DeploymentApprovalSummaryStatus`](#deploymentapprovalsummarystatus) | Status of the approval summary. |
| <a id="protectedenvironmentapprovalruleforsummaryuser"></a>`user` | [`UserCore`](#usercore) | User details. Present if it's user specific access control. |
### `ProtectedEnvironmentDeployAccessLevel` ### `ProtectedEnvironmentDeployAccessLevel`
Which group, user or role is allowed to execute deployments to the environment. Which group, user or role is allowed to execute deployments to the environment.
@ -20479,6 +20524,16 @@ Weight of the data visualization palette.
| <a id="dependencyproxymanifeststatuspending_destruction"></a>`PENDING_DESTRUCTION` | Dependency proxy manifest has a status of pending_destruction. | | <a id="dependencyproxymanifeststatuspending_destruction"></a>`PENDING_DESTRUCTION` | Dependency proxy manifest has a status of pending_destruction. |
| <a id="dependencyproxymanifeststatusprocessing"></a>`PROCESSING` | Dependency proxy manifest has a status of processing. | | <a id="dependencyproxymanifeststatusprocessing"></a>`PROCESSING` | Dependency proxy manifest has a status of processing. |
### `DeploymentApprovalSummaryStatus`
Status of the deployment approval summary.
| Value | Description |
| ----- | ----------- |
| <a id="deploymentapprovalsummarystatusapproved"></a>`APPROVED` | Summarized deployment approval status that is approved. |
| <a id="deploymentapprovalsummarystatuspending_approval"></a>`PENDING_APPROVAL` | Summarized deployment approval status that is pending approval. |
| <a id="deploymentapprovalsummarystatusrejected"></a>`REJECTED` | Summarized deployment approval status that is rejected. |
### `DeploymentStatus` ### `DeploymentStatus`
All deployment statuses. All deployment statuses.
@ -20505,6 +20560,15 @@ All environment deployment tiers.
| <a id="deploymenttierstaging"></a>`STAGING` | Staging. | | <a id="deploymenttierstaging"></a>`STAGING` | Staging. |
| <a id="deploymenttiertesting"></a>`TESTING` | Testing. | | <a id="deploymenttiertesting"></a>`TESTING` | Testing. |
### `DeploymentsApprovalStatus`
Status of the deployment approval.
| Value | Description |
| ----- | ----------- |
| <a id="deploymentsapprovalstatusapproved"></a>`APPROVED` | A deployment approval that is approved. |
| <a id="deploymentsapprovalstatusrejected"></a>`REJECTED` | A deployment approval that is rejected. |
### `DesignCollectionCopyState` ### `DesignCollectionCopyState`
Copy state of a DesignCollection. Copy state of a DesignCollection.

View File

@ -2830,6 +2830,42 @@ Read more in the [Project members](members.md) documentation.
Read more in the [Project vulnerabilities](project_vulnerabilities.md) documentation. Read more in the [Project vulnerabilities](project_vulnerabilities.md) documentation.
## Get a project's pull mirror details **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354506) in GitLab 15.5.
Returns the details of the project's pull mirror.
```plaintext
GET /projects/:id/mirror/pull
```
Supported attributes:
| Attribute | Type | Required | Description |
|:----------|:------|:------------|:------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
Example request:
```shell
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/mirror/pull"
```
Example response:
```json
{
"id": 101486,
"last_error": null,
"last_successful_update_at": "2020-01-06T17:32:02.823Z",
"last_update_at": "2020-01-06T17:32:02.823Z",
"last_update_started_at": "2020-01-06T17:31:55.864Z",
"update_status": "finished",
"url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git"
}
```
## Configure pull mirroring for a project **(PREMIUM)** ## Configure pull mirroring for a project **(PREMIUM)**
> Moved to GitLab Premium in 13.9. > Moved to GitLab Premium in 13.9.

View File

@ -1580,7 +1580,7 @@ Returns:
- `404 User Not Found` if user cannot be found. - `404 User Not Found` if user cannot be found.
- `403 Forbidden` when trying to deactivate a user: - `403 Forbidden` when trying to deactivate a user:
- Blocked by administrator or by LDAP synchronization. - Blocked by administrator or by LDAP synchronization.
- That has any activity in past 90 days. These users cannot be deactivated. - That is not [dormant](../user/admin_area/moderate_users.md#automatically-deactivate-dormant-users).
- That is internal. - That is internal.
## Activate user **(FREE SELF)** ## Activate user **(FREE SELF)**

View File

@ -162,7 +162,7 @@ A user can be deactivated from the Admin Area. To do this:
For the deactivation option to be visible to an administrator, the user: For the deactivation option to be visible to an administrator, the user:
- Must be currently active. - Must be currently active.
- Must not have signed in, or have any activity, in the last 90 days. - Must not be [dormant](#automatically-deactivate-dormant-users).
NOTE: NOTE:
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user). Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).

View File

@ -201,6 +201,13 @@ To remove a member from a group:
- To unassign the user from linked issues and merge requests, select the **Also unassign this user from linked issues and merge requests** checkbox. - To unassign the user from linked issues and merge requests, select the **Also unassign this user from linked issues and merge requests** checkbox.
1. Select **Remove member**. 1. Select **Remove member**.
## Ensure removed users cannot invite themselves back
Malicious users with the Maintainer or Owner role could exploit a race condition that allows
them to invite themselves back to a group or project that a GitLab administrator has removed them from.
To avoid this problem, GitLab administrators can [ensure removed users cannot invite themselves back](../project/members/index.md#ensure-removed-users-cannot-invite-themselves-back).
## Add projects to a group ## Add projects to a group
There are two different ways to add a new project to a group: There are two different ways to add a new project to a group:

View File

@ -89,8 +89,10 @@ At any time, you can revoke a personal access token.
## View the last time a token was used ## View the last time a token was used
Token usage is updated once every 24 hours. It is updated each time the token is used to request Token usage information is updated every 24 hours. GitLab considers a token used when the token is used to:
[API resources](../../api/api_resources.md) and the [GraphQL API](../../api/graphql/index.md).
- Authenticate with the [REST](../../api/index.md) or [GraphQL](../../api/graphql/index.md) APIs.
- Perform a Git operation.
To view the last time a token was used: To view the last time a token was used:

View File

@ -187,6 +187,21 @@ To remove a member from a project:
[from being forked outside their group](../../group/access_and_permissions.md#prevent-project-forking-outside-group). [from being forked outside their group](../../group/access_and_permissions.md#prevent-project-forking-outside-group).
1. Select **Remove member**. 1. Select **Remove member**.
## Ensure removed users cannot invite themselves back
Malicious users with the Maintainer or Owner role could exploit a race condition that allows
them to invite themselves back to a group or project that a GitLab administrator has removed them from.
To avoid this problem, GitLab administrators can:
- Remove the malicious user session from the [GitLab Rails console](../../../administration/operations/rails_console.md).
- Impersonate the malicious user to:
- Remove the user from the project.
- Log the user out of GitLab.
- Block the malicious user account.
- Remove the malicious user account.
- Change the password for the malicious user account.
## Filter and sort members ## Filter and sort members
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6.

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module API
module Entities
class PullMirror < Grape::Entity
expose :id
expose :status, as: :update_status
expose :url do |import_state|
import_state.project.safe_import_url
end
expose :last_error
expose :last_update_at
expose :last_update_started_at
expose :last_successful_update_at
end
end
end

View File

@ -42,13 +42,10 @@ module Gitlab
def build def build
menu = @menu_builder.build menu = @menu_builder.build
hide_menu_text = Feature.enabled?(:new_navbar_layout)
menu.merge({ menu.merge({
views: @views, views: @views,
shortcuts: @shortcuts, shortcuts: @shortcuts,
menuTitle: (_('Menu') unless hide_menu_text), menuTooltip: _('Main menu')
menuTooltip: (_('Main menu') if hide_menu_text)
}.compact) }.compact)
end end
end end

View File

@ -2689,6 +2689,24 @@ msgstr ""
msgid "AdminDashboard|Error loading the statistics. Please try again" msgid "AdminDashboard|Error loading the statistics. Please try again"
msgstr "" msgstr ""
msgid "AdminEmail|Body"
msgstr ""
msgid "AdminEmail|Body is required."
msgstr ""
msgid "AdminEmail|Recipient group or project"
msgstr ""
msgid "AdminEmail|Recipient group or project is required."
msgstr ""
msgid "AdminEmail|Subject"
msgstr ""
msgid "AdminEmail|Subject is required."
msgstr ""
msgid "AdminLabels|Define your default set of project labels" msgid "AdminLabels|Define your default set of project labels"
msgstr "" msgstr ""
@ -11226,9 +11244,6 @@ msgstr ""
msgid "Create new label" msgid "Create new label"
msgstr "" msgstr ""
msgid "Create new project"
msgstr ""
msgid "Create new..." msgid "Create new..."
msgstr "" msgstr ""

View File

@ -64,12 +64,7 @@ module QA
def go_to_groups def go_to_groups
within_groups_menu do within_groups_menu do
# Remove if statement once :remove_extra_primary_submenu_options ff is enabled by default click_element(:menu_item_link, title: 'View all groups')
if has_element?(:menu_item_link, title: 'Your groups')
click_element(:menu_item_link, title: 'Your groups')
else
click_element(:menu_item_link, title: 'View all groups')
end
end end
end end
@ -80,12 +75,7 @@ module QA
def go_to_projects def go_to_projects
within_projects_menu do within_projects_menu do
# Remove if statement once :remove_extra_primary_submenu_options ff is enabled by default click_element(:menu_item_link, title: 'View all projects')
if has_element?(:menu_item_link, title: 'Your projects')
click_element(:menu_item_link, title: 'Your projects')
else
click_element(:menu_item_link, title: 'View all projects')
end
end end
end end

View File

@ -39,6 +39,7 @@ module QA
add_failure_issues_link(example) add_failure_issues_link(example)
add_ci_job_link(example) add_ci_job_link(example)
set_flaky_status(example) set_flaky_status(example)
set_behaviour_categories(example)
end end
private private
@ -97,6 +98,19 @@ module QA
log(:error, "Failed to add spec pass rate data for example '#{example.description}', error: #{e}") log(:error, "Failed to add spec pass rate data for example '#{example.description}', error: #{e}")
end end
# Add behaviour categories to report
#
# @param [RSpec::Core::Example] example
# @return [void]
def set_behaviour_categories(example)
file_path = example.file_path.gsub('./qa/specs/features', '')
devops_stage = file_path.match(%r{\d{1,2}_(\w+)/})&.captures&.first
product_group = example.metadata[:product_group]
example.epic(devops_stage) if devops_stage
example.feature(product_group) if product_group
end
# Flaky specs with pass rate below 98% # Flaky specs with pass rate below 98%
# #
# @return [Array] # @return [Array]
@ -107,7 +121,7 @@ module QA
runs = records.count runs = records.count
failed = records.count { |r| r.values["status"] == "failed" } failed = records.count { |r| r.values["status"] == "failed" }
pass_rate = 100 - ((failed.to_f / runs.to_f) * 100) pass_rate = 100 - ((failed.to_f / runs) * 100)
# Consider spec with a pass rate less than 98% as flaky # Consider spec with a pass rate less than 98% as flaky
result[records.last.values["testcase"]] = pass_rate if pass_rate < 98 result[records.last.values["testcase"]] = pass_rate if pass_rate < 98

View File

@ -72,6 +72,7 @@ module QA
merge_request: merge_request, merge_request: merge_request,
run_type: run_type, run_type: run_type,
stage: devops_stage(file_path), stage: devops_stage(file_path),
product_group: example.metadata[:product_group],
testcase: example.metadata[:testcase] testcase: example.metadata[:testcase]
}, },
fields: { fields: {

View File

@ -28,6 +28,7 @@ describe QA::Support::Formatters::TestStatsFormatter do
let(:api_fabrication) { 0 } let(:api_fabrication) { 0 }
let(:fabrication_resources) { {} } let(:fabrication_resources) { {} }
let(:testcase) { 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234' } let(:testcase) { 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234' }
let(:product_group) { nil }
let(:influx_client_args) do let(:influx_client_args) do
{ {
@ -53,6 +54,7 @@ describe QA::Support::Formatters::TestStatsFormatter do
merge_request: 'false', merge_request: 'false',
run_type: run_type, run_type: run_type,
stage: stage.match(%r{\d{1,2}_(\w+)}).captures.first, stage: stage.match(%r{\d{1,2}_(\w+)}).captures.first,
product_group: product_group,
testcase: testcase testcase: testcase
}, },
fields: { fields: {
@ -146,6 +148,19 @@ describe QA::Support::Formatters::TestStatsFormatter do
end end
end end
context 'with product group tag' do
let(:product_group) { :import }
it 'exports data to influxdb with correct reliable tag' do
run_spec do
it('spec', product_group: :import, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234') {}
end
expect(influx_write_api).to have_received(:write).once
expect(influx_write_api).to have_received(:write).with(data: [data])
end
end
context 'with smoke spec' do context 'with smoke spec' do
let(:smoke) { 'true' } let(:smoke) { 'true' }

View File

@ -56,7 +56,7 @@ RSpec.describe 'Value Stream Analytics', :js do
end end
end end
context "when there's value stream analytics data" do context "when there's value stream analytics data", :sidekiq_inline do
# NOTE: in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68595 travel back # NOTE: in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68595 travel back
# 5 days in time before we create data for these specs, to mitigate some flakiness # 5 days in time before we create data for these specs, to mitigate some flakiness
# So setting the date range to be the last 2 days should skip past the existing data # So setting the date range to be the last 2 days should skip past the existing data
@ -103,7 +103,7 @@ RSpec.describe 'Value Stream Analytics', :js do
end end
end end
it 'shows data on each stage', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338332' do it 'shows data on each stage', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338332' do
expect_issue_to_be_present expect_issue_to_be_present
click_stage('Plan') click_stage('Plan')
@ -207,11 +207,11 @@ RSpec.describe 'Value Stream Analytics', :js do
wait_for_requests wait_for_requests
end end
it 'does not show the commit stats' do it 'does not show the commit stats', :sidekiq_inline do
expect(page.find(metrics_selector)).not_to have_selector("#commits") expect(page.find(metrics_selector)).not_to have_selector("#commits")
end end
it 'does not show restricted stages', :aggregate_failures do it 'does not show restricted stages', :aggregate_failures, :sidekiq_inline do
expect(find(stage_table_selector)).to have_content(issue.title) expect(find(stage_table_selector)).to have_content(issue.title)
expect(page).to have_selector('.gl-path-nav-list-item', text: 'Issue') expect(page).to have_selector('.gl-path-nav-list-item', text: 'Issue')

View File

@ -0,0 +1,48 @@
{
"type": "object",
"required": [
"id",
"url",
"update_status",
"last_update_at",
"last_update_started_at",
"last_successful_update_at",
"last_error"
],
"properties": {
"id": {
"type": "integer"
},
"url": {
"type": "string"
},
"update_status": {
"type": "string"
},
"last_update_at": {
"type": [
"string",
"null"
]
},
"last_update_started_at": {
"type": [
"string",
"null"
]
},
"last_successful_update_at": {
"type": [
"string",
"null"
]
},
"last_error": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}

View File

@ -17,6 +17,9 @@
<div class="form-group"> <div class="form-group">
<input class="custom gl-field-error-ignore" type="text">Custom, do not validate</input> <input class="custom gl-field-error-ignore" type="text">Custom, do not validate</input>
</div> </div>
<div class="form-group">
<textarea required title="Textarea is required">Textarea</textarea>
</div>
<div class="form-group"></div> <div class="form-group"></div>
<input class="submit" type="submit">Submit</input> <input class="submit" type="submit">Submit</input>
</form> </form>

View File

@ -27,7 +27,7 @@ describe('GL Style Field Errors', () => {
expect(testContext.fieldErrors).toBeDefined(); expect(testContext.fieldErrors).toBeDefined();
const { inputs } = testContext.fieldErrors.state; const { inputs } = testContext.fieldErrors.state;
expect(inputs.length).toBe(4); expect(inputs.length).toBe(5);
}); });
it('should ignore elements with custom error handling', () => { it('should ignore elements with custom error handling', () => {

View File

@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['DeploymentDetails'] do
id iid ref tag tags sha created_at updated_at finished_at status commit job triggerer id iid ref tag tags sha created_at updated_at finished_at status commit job triggerer
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
end end
specify { expect(described_class).to require_graphql_authorizations(:read_deployment) } specify { expect(described_class).to require_graphql_authorizations(:read_deployment) }

View File

@ -27,11 +27,9 @@ RSpec.describe Nav::TopNavHelper do
let(:subject) { helper.top_nav_view_model(project: current_project, group: current_group) } let(:subject) { helper.top_nav_view_model(project: current_project, group: current_group) }
let(:menu_title) { 'Menu' } let(:menu_tooltip) { 'Main menu' }
before do before do
stub_feature_flags(new_navbar_layout: false)
allow(Gitlab::CurrentSettings).to receive(:admin_mode) { with_current_settings_admin_mode } allow(Gitlab::CurrentSettings).to receive(:admin_mode) { with_current_settings_admin_mode }
allow(helper).to receive(:header_link?).with(:admin_mode) { with_header_link_admin_mode } allow(helper).to receive(:header_link?).with(:admin_mode) { with_header_link_admin_mode }
@ -46,8 +44,8 @@ RSpec.describe Nav::TopNavHelper do
allow(helper).to receive(:dashboard_nav_link?).with(:activity) { with_activity } allow(helper).to receive(:dashboard_nav_link?).with(:activity) { with_activity }
end end
it 'has :menuTitle' do it 'has :menuTooltip' do
expect(subject[:menuTitle]).to eq(menu_title) expect(subject[:menuTooltip]).to eq(menu_tooltip)
end end
context 'when current_user is nil (anonymous)' do context 'when current_user is nil (anonymous)' do
@ -108,7 +106,7 @@ RSpec.describe Nav::TopNavHelper do
let(:current_user) { user } let(:current_user) { user }
it 'has no menu items or views by default' do it 'has no menu items or views by default' do
expect(subject).to eq({ menuTitle: menu_title, expect(subject).to eq({ menuTooltip: menu_tooltip,
primary: [], primary: [],
secondary: [], secondary: [],
shortcuts: [], shortcuts: [],
@ -176,74 +174,6 @@ RSpec.describe Nav::TopNavHelper do
expect(projects_view[:linksSecondary]).to eq([]) expect(projects_view[:linksSecondary]).to eq([])
end end
context 'when extra submenu options are not hidden' do
before do
stub_feature_flags(remove_extra_primary_submenu_options: false)
end
it 'has expected :linksPrimary' do
expected_links_primary = [
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Your projects',
**menu_data_tracking_attrs('your_projects')
},
href: '/dashboard/projects',
id: 'your',
title: 'Your projects'
),
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Starred projects',
**menu_data_tracking_attrs('starred_projects')
},
href: '/dashboard/projects/starred',
id: 'starred',
title: 'Starred projects'
),
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Explore projects',
**menu_data_tracking_attrs('explore_projects')
},
href: '/explore',
id: 'explore',
title: 'Explore projects'
),
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Explore topics',
**menu_data_tracking_attrs('explore_topics')
},
href: '/explore/projects/topics',
id: 'topics',
title: 'Explore topics'
)
]
expect(projects_view[:linksPrimary]).to eq(expected_links_primary)
end
it 'has expected :linksSecondary' do
expected_links_secondary = [
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Create new project',
**menu_data_tracking_attrs('create_new_project')
},
href: '/projects/new',
id: 'create',
title: 'Create new project'
)
]
expect(projects_view[:linksSecondary]).to eq(expected_links_secondary)
end
end
context 'with current nav as project' do context 'with current nav as project' do
before do before do
helper.nav('project') helper.nav('project')
@ -341,54 +271,6 @@ RSpec.describe Nav::TopNavHelper do
expect(groups_view[:linksSecondary]).to eq([]) expect(groups_view[:linksSecondary]).to eq([])
end end
context 'when extra submenu options are not hidden' do
before do
stub_feature_flags(remove_extra_primary_submenu_options: false)
end
it 'has expected :linksPrimary' do
expected_links_primary = [
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Your groups',
**menu_data_tracking_attrs('your_groups')
},
href: '/dashboard/groups',
id: 'your',
title: 'Your groups'
),
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Explore groups',
**menu_data_tracking_attrs('explore_groups')
},
href: '/explore/groups',
id: 'explore',
title: 'Explore groups'
)
]
expect(groups_view[:linksPrimary]).to eq(expected_links_primary)
end
it 'has expected :linksSecondary' do
expected_links_secondary = [
::Gitlab::Nav::TopNavMenuItem.build(
data: {
qa_selector: 'menu_item_link',
qa_title: 'Create group',
**menu_data_tracking_attrs('create_group')
},
href: '/groups/new',
id: 'create',
title: 'Create group'
)
]
expect(groups_view[:linksSecondary]).to eq(expected_links_secondary)
end
end
context 'with external user' do context 'with external user' do
let(:current_user) { external_user } let(:current_user) { external_user }

View File

@ -278,6 +278,34 @@ RSpec.describe MergeRequest, factory_default: :keep do
end end
describe 'callbacks' do describe 'callbacks' do
describe '#ensure_merge_request_diff' do
let(:merge_request) { build(:merge_request) }
context 'when async_merge_request_diff_creation is true' do
before do
merge_request.skip_ensure_merge_request_diff = true
end
it 'does not create a merge_request_diff after create' do
merge_request.save!
expect(merge_request.merge_request_diff).to be_empty
end
end
context 'when async_merge_request_diff_creation is false' do
before do
merge_request.skip_ensure_merge_request_diff = false
end
it 'creates merge_request_diff after create' do
merge_request.save!
expect(merge_request.merge_request_diff).not_to be_empty
end
end
end
describe '#ensure_merge_request_metrics' do describe '#ensure_merge_request_metrics' do
let(:merge_request) { create(:merge_request) } let(:merge_request) { create(:merge_request) }

View File

@ -9,7 +9,7 @@ RSpec.describe 'value stream analytics events' do
let(:project) { create(:project, :repository, public_builds: false) } let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do describe 'GET /:namespace/:project/value_stream_analytics/events/issues', :sidekiq_inline do
let(:first_issue_iid) { project.issues.sort_by_attribute(:created_desc).pick(:iid).to_s } let(:first_issue_iid) { project.issues.sort_by_attribute(:created_desc).pick(:iid).to_s }
let(:first_mr_iid) { project.merge_requests.sort_by_attribute(:created_desc).pick(:iid).to_s } let(:first_mr_iid) { project.merge_requests.sort_by_attribute(:created_desc).pick(:iid).to_s }
@ -65,7 +65,7 @@ RSpec.describe 'value stream analytics events' do
expect(json_response['events'].first['iid']).to eq(first_mr_iid) expect(json_response['events'].first['iid']).to eq(first_mr_iid)
end end
it 'lists the staging events', :sidekiq_inline do it 'lists the staging events' do
get project_cycle_analytics_staging_path(project, format: :json) get project_cycle_analytics_staging_path(project, format: :json)
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty

View File

@ -495,15 +495,40 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
project.add_developer(user) project.add_developer(user)
end end
it 'creates the merge request', :sidekiq_might_not_need_inline do context 'when async_merge_request_diff_creation is enabled' do
expect_next_instance_of(MergeRequest) do |instance| before do
expect(instance).to receive(:eager_fetch_ref!).and_call_original stub_feature_flags(async_merge_request_diff_creation: true)
end end
merge_request = described_class.new(project: project, current_user: user, params: opts).execute it 'creates the merge request', :sidekiq_inline do
expect_next_instance_of(MergeRequest) do |instance|
expect(instance).not_to receive(:eager_fetch_ref!)
end
expect(merge_request).to be_persisted merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.iid).to be > 0
expect(merge_request).to be_persisted
expect(merge_request.iid).to be > 0
expect(merge_request.merge_request_diff).not_to be_empty
end
end
context 'when async_merge_request_diff_creation is disabled' do
before do
stub_feature_flags(async_merge_request_diff_creation: false)
end
it 'creates the merge request' do
expect_next_instance_of(MergeRequest) do |instance|
expect(instance).to receive(:eager_fetch_ref!).and_call_original
end
merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request).to be_persisted
expect(merge_request.iid).to be > 0
expect(merge_request.merge_request_diff).not_to be_empty
end
end end
it 'does not create the merge request when the target project is archived' do it 'does not create the merge request when the target project is archived' do