Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-01-12 00:07:21 +00:00
parent 213f46f188
commit b53b2fbb6b
65 changed files with 694 additions and 272 deletions

View File

@ -16,7 +16,7 @@ Please read the below documentations for a workflow of triaging and resolving br
<!-- If the pipeline failure is reproducible, provide steps to recreate the issue locally. Please use an ordered list. -->
Please refer to [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) to
Please refer to [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) to
learn more about how to reproduce them.
### Proposed Resolution
@ -25,4 +25,4 @@ learn more about how to reproduce them.
Please refer to the [Resolution guidance](https://about.gitlab.com/handbook/engineering/workflow/#resolution-of-broken-master) to learn more about resolution of broken master.
/label ~"failure::flaky-test" ~"Engineering Productivity" ~"priority::2" ~"severity::3" ~"type::bug" ~"bug::transient"
/label ~"type::maintenance" ~"failure::flaky-test" ~"priority::3" ~"severity::3"

View File

@ -21,4 +21,4 @@ Please read the below documentations for a workflow of triaging and resolving br
Please refer to the [Resolution guidance](https://about.gitlab.com/handbook/engineering/workflow/#resolution-of-broken-master) to learn more about resolution of broken master.
/label ~"master:broken" ~"Engineering Productivity" ~"priority::1" ~"severity::1" ~"type::bug" ~"bug::transient"
/label ~"master:broken" ~"Engineering Productivity" ~"priority::1" ~"severity::1" ~"type::maintenance" ~"maintenance::pipelines"

View File

@ -0,0 +1,41 @@
<script>
import { GlBanner } from '@gitlab/ui';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import {
I18N_FEEDBACK_BANNER_TITLE,
I18N_FEEDBACK_BANNER_BODY,
I18N_FEEDBACK_BANNER_BUTTON,
FEEDBACK_URL,
} from '../constants';
export default {
components: {
GlBanner,
UserCalloutDismisser,
},
inject: ['artifactsManagementFeedbackImagePath'],
FEEDBACK_URL,
i18n: {
title: I18N_FEEDBACK_BANNER_TITLE,
body: I18N_FEEDBACK_BANNER_BODY,
button: I18N_FEEDBACK_BANNER_BUTTON,
},
};
</script>
<template>
<user-callout-dismisser feature-name="artifacts_management_page_feedback_banner">
<template #default="{ dismiss, shouldShowCallout }">
<gl-banner
v-if="shouldShowCallout"
class="gl-mb-6"
:title="$options.i18n.title"
:button-text="$options.i18n.button"
:button-link="$options.FEEDBACK_URL"
:svg-path="artifactsManagementFeedbackImagePath"
@close="dismiss"
>
<p>{{ $options.i18n.body }}</p>
</gl-banner>
</template>
</user-callout-dismisser>
</template>

View File

@ -35,6 +35,7 @@ import {
INITIAL_LAST_PAGE_SIZE,
} from '../constants';
import ArtifactsTableRowDetails from './artifacts_table_row_details.vue';
import FeedbackBanner from './feedback_banner.vue';
const INITIAL_PAGINATION_STATE = {
currentPage: INITIAL_CURRENT_PAGE,
@ -58,6 +59,7 @@ export default {
CiIcon,
TimeAgo,
ArtifactsTableRowDetails,
FeedbackBanner,
},
inject: ['projectPath'],
apollo: {
@ -214,6 +216,7 @@ export default {
</script>
<template>
<div>
<feedback-banner />
<gl-table
:items="jobArtifacts"
:fields="$options.fields"

View File

@ -43,6 +43,13 @@ export const I18N_MODAL_BODY = s__(
export const I18N_MODAL_PRIMARY = s__('Artifacts|Delete artifact');
export const I18N_MODAL_CANCEL = __('Cancel');
export const I18N_FEEDBACK_BANNER_TITLE = s__('Artifacts|Help us improve this page');
export const I18N_FEEDBACK_BANNER_BODY = s__(
'Artifacts|We want you to be able to use this page to easily manage your CI/CD job artifacts. We are working to improve this experience and would appreciate any feedback you have about the improvements we are making.',
);
export const I18N_FEEDBACK_BANNER_BUTTON = s__('Artifacts|Take a quick survey');
export const FEEDBACK_URL = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_cI9rAUI20Vo2St8';
export const INITIAL_CURRENT_PAGE = 1;
export const INITIAL_PREVIOUS_PAGE_CURSOR = '';
export const INITIAL_NEXT_PAGE_CURSOR = '';

View File

@ -16,13 +16,14 @@ export const initArtifactsTable = () => {
return false;
}
const { projectPath } = el.dataset;
const { projectPath, artifactsManagementFeedbackImagePath } = el.dataset;
return new Vue({
el,
apolloProvider,
provide: {
projectPath,
artifactsManagementFeedbackImagePath,
},
render: (createElement) => createElement(JobArtifactsTable),
});

View File

@ -3,6 +3,7 @@ import { GlBadge, GlToggle } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { createAlert } from '~/flash';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
export default {
i18n: {
@ -37,6 +38,12 @@ export default {
async toggleNav() {
try {
await axios.put(this.endpoint, { user: { use_new_navigation: !this.enabled } });
Tracking.event(undefined, 'click_toggle', {
label: this.enabled ? 'disable_new_nav_beta' : 'enable_new_nav_beta',
property: 'navigation',
});
window.location.reload();
} catch (error) {
createAlert({

View File

@ -1,7 +1,9 @@
import leaveByUrl from '~/namespaces/leave_by_url';
import { initGroupOverviewTabs } from '~/groups/init_overview_tabs';
import initReadMore from '~/read_more';
import initGroupDetails from '../shared/group_details';
leaveByUrl('group');
initGroupDetails();
initGroupOverviewTabs();
initReadMore();

View File

@ -31,9 +31,9 @@ export default function initReadMore(triggerSelector = '.js-read-more-trigger')
triggerEl.addEventListener(
'click',
(e) => {
() => {
targetEl.classList.add('is-expanded');
e.target.remove();
triggerEl.remove();
},
{ once: true },
);

View File

@ -10,11 +10,13 @@ import { initBlobRefSwitcher } from './under_topbar';
export const initSearchApp = () => {
syntaxHighlight(document.querySelectorAll('.js-search-results'));
const query = queryToObject(window.location.search, { gatherArrays: true });
const { navigationJsonParsed: navigation } = sidebarInitState() || {};
const query = queryToObject(window.location.search);
const navigation = sidebarInitState();
const store = createStore({ query, navigation });
const store = createStore({
query,
navigation,
});
initTopbar(store);
initSidebar(store);

View File

@ -21,6 +21,6 @@ export default {
<template>
<div>
<radio-filter :class="ffBasedXPadding" :filter-data="$options.confidentialFilterData" />
<hr class="gl-my-5 gl-border-gray-100" />
<hr class="gl-my-5 gl-mx-5 gl-border-gray-100" />
</div>
</template>

View File

@ -44,7 +44,7 @@ export default {
<form class="gl-pt-5 gl-md-pt-0" @submit.prevent="applyQuery">
<hr
v-if="searchPageVerticalNavFeatureFlag"
class="gl-my-5 gl-border-gray-100 gl-display-none gl-md-display-block"
class="gl-my-5 gl-mx-5 gl-border-gray-100 gl-display-none gl-md-display-block"
/>
<status-filter v-if="showStatusFilter" />
<confidentiality-filter v-if="showConfidentialityFilter" />

View File

@ -1,13 +1,10 @@
<script>
import { GlNav, GlNavItem, GlIcon } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { formatNumber, s__ } from '~/locale';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import {
NAV_LINK_DEFAULT_CLASSES,
NUMBER_FORMATING_OPTIONS,
NAV_LINK_COUNT_DEFAULT_CLASSES,
} from '../constants';
import { NAV_LINK_DEFAULT_CLASSES, NAV_LINK_COUNT_DEFAULT_CLASSES } from '../constants';
import { formatSearchResultCount } from '../../store/utils';
export default {
name: 'ScopeNavigation',
@ -29,11 +26,7 @@ export default {
methods: {
...mapActions(['fetchSidebarCount']),
showFormatedCount(count) {
if (!count) {
return '0';
}
const countNumber = parseInt(count.replace(/,/g, ''), 10);
return formatNumber(countNumber, NUMBER_FORMATING_OPTIONS);
return formatSearchResultCount(count);
},
isCountOverLimit(count) {
return count.includes('+');
@ -82,6 +75,6 @@ export default {
</span>
</gl-nav-item>
</gl-nav>
<hr class="gl-mt-5 gl-mb-0 gl-border-gray-100 gl-md-display-none" />
<hr class="gl-mt-5 gl-mx-5 gl-mb-0 gl-border-gray-100 gl-md-display-none" />
</nav>
</template>

View File

@ -21,6 +21,6 @@ export default {
<template>
<div>
<radio-filter :class="ffBasedXPadding" :filter-data="$options.stateFilterData" />
<hr class="gl-my-5 gl-border-gray-100" />
<hr class="gl-my-5 gl-mx-5 gl-border-gray-100" />
</div>
</template>

View File

@ -1,13 +1,16 @@
export const SCOPE_ISSUES = 'issues';
export const SCOPE_MERGE_REQUESTS = 'merge_requests';
export const NUMBER_FORMATING_OPTIONS = { notation: 'compact', compactDisplay: 'short' };
export const NAV_LINK_DEFAULT_CLASSES = [
export const SCOPE_BLOB = 'blobs';
export const LABEL_DEFAULT_CLASSES = [
'gl-display-flex',
'gl-flex-direction-row',
'gl-flex-wrap-nowrap',
'gl-justify-content-space-between',
'gl-text-gray-900',
];
export const NAV_LINK_DEFAULT_CLASSES = [
...LABEL_DEFAULT_CLASSES,
'gl-justify-content-space-between',
];
export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
export const HR_DEFAULT_CLASSES = ['gl-my-5', 'gl-mx-5', 'gl-border-gray-100'];
export const ONLY_SHOW_MD = ['gl-display-none', 'gl-md-display-block'];

View File

@ -6,11 +6,11 @@ Vue.use(Translate);
export const sidebarInitState = () => {
const el = document.getElementById('js-search-sidebar');
if (!el) return {};
const { navigation } = el.dataset;
return JSON.parse(navigation);
const { navigationJson } = el.dataset;
const navigationJsonParsed = JSON.parse(navigationJson);
return { navigationJsonParsed };
};
export const initSidebar = (store) => {

View File

@ -10,3 +10,5 @@ export const GROUPS_LOCAL_STORAGE_KEY = 'global-search-frequent-groups';
export const PROJECTS_LOCAL_STORAGE_KEY = 'global-search-frequent-projects';
export const SIDEBAR_PARAMS = [stateFilterData.filterParam, confidentialFilterData.filterParam];
export const NUMBER_FORMATING_OPTIONS = { notation: 'compact', compactDisplay: 'short' };

View File

@ -1,5 +1,12 @@
import AccessorUtilities from '~/lib/utils/accessor';
import { MAX_FREQUENT_ITEMS, MAX_FREQUENCY, SIDEBAR_PARAMS } from './constants';
import { formatNumber } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import {
MAX_FREQUENT_ITEMS,
MAX_FREQUENCY,
SIDEBAR_PARAMS,
NUMBER_FORMATING_OPTIONS,
} from './constants';
function extractKeys(object, keyList) {
return Object.fromEntries(keyList.map((key) => [key, object[key]]));
@ -90,3 +97,18 @@ export const isSidebarDirty = (currentQuery, urlQuery) => {
return userAddedParam || userChangedExistingParam;
});
};
export const formatSearchResultCount = (count) => {
if (!count) {
return '0';
}
const countNumber = typeof count === 'string' ? parseInt(count.replace(/,/g, ''), 10) : count;
return formatNumber(countNumber, NUMBER_FORMATING_OPTIONS);
};
export const getAggregationsUrl = () => {
const currentUrl = new URL(window.location.href);
currentUrl.pathname = joinPaths('/search', 'aggregations');
return currentUrl.toString();
};

View File

@ -1,12 +1,10 @@
<script>
import { GlButton, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlButton,
GlLoadingIcon,
GlIcon,
},
props: {
title: {
@ -32,7 +30,7 @@ export default {
computed: {
arrowIconName() {
return this.isCollapsed ? 'chevron-lg-right' : 'chevron-lg-down';
return this.isCollapsed ? 'chevron-right' : 'chevron-down';
},
ariaLabel() {
return this.isCollapsed ? __('Expand') : __('Collapse');
@ -47,7 +45,7 @@ export default {
</script>
<template>
<div class="mr-widget-extension">
<div class="d-flex align-items-center pl-3">
<div class="d-flex align-items-center pl-3 gl-py-3">
<div v-if="hasError" class="ci-widget media">
<div class="media-body">
<span class="gl-font-sm mr-widget-margin-left gl-line-height-24 js-error-state">
@ -57,16 +55,15 @@ export default {
</div>
<template v-else>
<button
class="btn-blank btn s32 square"
type="button"
<gl-button
class="gl-mr-3"
size="small"
:aria-label="ariaLabel"
:disabled="isLoading"
:loading="isLoading"
:icon="arrowIconName"
category="tertiary"
@click="toggleCollapsed"
>
<gl-loading-icon v-if="isLoading" size="sm" />
<gl-icon v-else :name="arrowIconName" class="js-icon" />
</button>
/>
<template v-if="isCollapsed">
<slot name="header"></slot>
<gl-button

View File

@ -46,7 +46,7 @@ module Ci
'artifacts', path
].join('/')
"#{project.pages_group_url}/#{artifact_path}"
"#{project.pages_namespace_url}/#{artifact_path}"
end
def external_link?(job)

View File

@ -19,7 +19,7 @@ class DeployKey < Key
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, namespace: :route] }) }
scope :including_projects_with_write_access, -> { includes(:projects_with_write_access) }
accepts_nested_attributes_for :deploy_keys_projects
accepts_nested_attributes_for :deploy_keys_projects, reject_if: :reject_deploy_keys_projects?
def private?
!public?
@ -72,4 +72,10 @@ class DeployKey < Key
def impersonated?
false
end
private
def reject_deploy_keys_projects?
!self.valid?
end
end

View File

@ -46,7 +46,7 @@ module Pages
strong_memoize_attr :source
def prefix
if project.pages_group_root?
if project.pages_namespace_url == project.pages_url
'/'
else
project.full_path.delete_prefix(trim_prefix) + '/'

View File

@ -2101,7 +2101,7 @@ class Project < ApplicationRecord
pages_metadatum&.deployed?
end
def pages_group_url
def pages_namespace_url
# The host in URL always needs to be downcased
Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
"#{prefix}#{pages_subdomain}."
@ -2109,19 +2109,23 @@ class Project < ApplicationRecord
end
def pages_url
url = pages_group_url
url = pages_namespace_url
url_path = full_path.partition('/').last
namespace_url = "#{Settings.pages.protocol}://#{url_path}".downcase
if Rails.env.development?
url_without_port = URI.parse(url)
url_without_port.port = nil
return url if url_without_port.to_s == namespace_url
end
# If the project path is the same as host, we serve it as group page
return url if url == "#{Settings.pages.protocol}://#{url_path}".downcase
return url if url == namespace_url
"#{url}/#{url_path}"
end
def pages_group_root?
pages_group_url == pages_url
end
def pages_subdomain
full_path.partition('/').first
end

View File

@ -24,25 +24,23 @@
- if current_user
.home-panel-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-center.gl-flex-wrap.gl-gap-3{ data: { testid: 'group-buttons' } }
- if current_user.admin?
= link_to [:admin, @group], class: 'btn btn-default gl-button btn-icon', title: _('View group in admin area'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('admin')
= render Pajamas::ButtonComponent.new(href: [:admin, @group], icon: 'admin', button_options: { title: _('View group in admin area'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } })
- if @notification_setting
.js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), group_id: @group.id, container_class: 'gl-vertical-align-top', no_flip: 'true' } }
- if can_create_subgroups
.gl-sm-w-auto.gl-w-full
= link_to _("New subgroup"),
new_group_path(parent_id: @group.id, anchor: 'create-group-pane'),
class: "btn btn-default gl-button gl-sm-w-auto gl-w-full",
data: { qa_selector: 'new_subgroup_button' }
= render Pajamas::ButtonComponent.new(href: new_group_path(parent_id: @group.id, anchor: 'create-group-pane'), button_options: { data: { qa_selector: 'new_subgroup_button' }, class: 'gl-sm-w-auto gl-w-full'}) do
= _("New subgroup")
- if can_create_projects
.gl-sm-w-auto.gl-w-full
= link_to _("New project"), new_project_path(namespace_id: @group.id), class: "btn btn-confirm gl-button gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_project_button' }
= render Pajamas::ButtonComponent.new(href: new_project_path(namespace_id: @group.id), variant: :confirm, button_options: { data: { qa_selector: 'new_project_button' }, class: 'gl-sm-w-auto gl-w-full' }) do
= _('New project')
- if @group.description.present?
.group-home-desc.mt-1
.home-panel-description
.home-panel-description-markdown.read-more-container{ itemprop: 'description' }
= markdown_field(@group, :description)
%button.gl-button.btn.btn-link.js-read-more-trigger.d-lg-none{ type: "button" }
= render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'js-read-more-trigger gl-lg-display-none' }) do
= _("Read more")

View File

@ -6,4 +6,5 @@
.gl-mb-6
%strong= s_('Artifacts|Total artifacts size')
= number_to_human_size(@total_size, precicion: 2)
#js-artifact-management{ data: { "project-path" => @project.full_path } }
#js-artifact-management{ data: { "project-path": @project.full_path,
"artifacts-management-feedback-image-path": image_path('illustrations/chat-bubble-sm.svg') } }

View File

@ -3,7 +3,7 @@
= render_if_exists 'shared/promotions/promote_advanced_search'
- if Feature.enabled?(:search_page_vertical_nav, current_user)
.results.gl-md-display-flex.gl-mt-0
#js-search-sidebar{ class: search_bar_classes, data: { navigation: search_navigation_json } }
#js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } }
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
= render partial: 'search/results_status' unless @search_objects.to_a.empty?
= render partial: 'search/results_list'
@ -12,7 +12,7 @@
.results.gl-md-display-flex.gl-mt-3
- if %w[issues merge_requests].include?(@scope)
#js-search-sidebar{ class: search_bar_classes, data: { navigation: search_navigation_json } }
#js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } }
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
= render partial: 'search/results_list'

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365350
milestone: '15.1'
type: development
group: group::release
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,14 @@
- title: 'Approvers and Approver Group fields in Merge Request Approval API'
announcement_milestone: '15.8'
announcement_date: '2023-01-22'
removal_milestone: '16.0'
removal_date: '2023-05-22'
breaking_change: true
body: |
The endpoint to get the configuration of approvals for a project returns empty arrays for `approvers` and `approval_groups`. These fields were deprecated in favor of the endpoint to [get project-level rules](https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules) for a merge request. API users are encouraged to switch to this endpoint instead. These fields will be removed from the `get configuration` endpoint in v5 of the GitLab REST API.
stage: create
tiers:
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353097
documentation_url: https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
image_url:
video_url:

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class PartitionPmPackageMetadataTables < Gitlab::Database::Migration[2.1]
def up
# no-op
# This migration was reverted as part of https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108644
# The migration file is re-added to ensure that all environments have the same list of migrations.
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
37df82f093bb81ff1bc36ea9ba29f4e70bcb96274e2dcc70438ce0710dd7e9d9

View File

@ -16,7 +16,8 @@ in the project. Must be authenticated for all endpoints.
### Get Configuration
> Moved to GitLab Premium in 13.9.
> - Moved to GitLab Premium in 13.9.
> - The `approvers` and `approver_groups` fields were deprecated in GitLab 12.3 and always return empty. Use the [project level approval rules](#get-project-level-rules) to access this information.
You can request information about a project's approval configuration using the
following endpoint:
@ -27,12 +28,14 @@ GET /projects/:id/approvals
Supported attributes:
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | ------------------- |
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| Attribute | Type | Required | Description |
|-----------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
```json
{
"approvers": [], // Deprecated in GitLab 12.3, always returns empty
"approver_groups": [], // Deprecated in GitLab 12.3, always returns empty
"approvals_before_merge": 2,
"reset_approvals_on_push": true,
"selective_code_owner_removals": false,
@ -56,16 +59,16 @@ POST /projects/:id/approvals
Supported attributes:
| Attribute | Type | Required | Description |
| ------------------------------------------------ | ------- | -------- | -- |
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_before_merge` (deprecated) | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. |
| `disable_overriding_approvers_per_merge_request` | boolean | **{dotted-circle}** No | Allow or prevent overriding approvers per merge request. |
| `merge_requests_author_approval` | boolean | **{dotted-circle}** No | Allow or prevent authors from self approving merge requests; `true` means authors can self approve. |
| `merge_requests_disable_committers_approval` | boolean | **{dotted-circle}** No | Allow or prevent committers from self approving merge requests. |
| `require_password_to_approve` | boolean | **{dotted-circle}** No | Require approver to enter a password to authenticate before adding the approval. |
| `reset_approvals_on_push` | boolean | **{dotted-circle}** No | Reset approvals on a new push. |
| `selective_code_owner_removals` | boolean | **{dotted-circle}** No | Reset approvals from Code Owners if their files changed. Can be enabled only if `reset_approvals_on_push` is disabled. |
| Attribute | Type | Required | Description |
|--------------------------------------------------|-------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_before_merge` (deprecated) | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. |
| `disable_overriding_approvers_per_merge_request` | boolean | **{dotted-circle}** No | Allow or prevent overriding approvers per merge request. |
| `merge_requests_author_approval` | boolean | **{dotted-circle}** No | Allow or prevent authors from self approving merge requests; `true` means authors can self approve. |
| `merge_requests_disable_committers_approval` | boolean | **{dotted-circle}** No | Allow or prevent committers from self approving merge requests. |
| `require_password_to_approve` | boolean | **{dotted-circle}** No | Require approver to enter a password to authenticate before adding the approval. |
| `reset_approvals_on_push` | boolean | **{dotted-circle}** No | Reset approvals on a new push. |
| `selective_code_owner_removals` | boolean | **{dotted-circle}** No | Reset approvals from Code Owners if their files changed. Can be enabled only if `reset_approvals_on_push` is disabled. |
```json
{
@ -97,9 +100,9 @@ Use the `page` and `per_page` [pagination](index.md#offset-based-pagination) par
Supported attributes:
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| Attribute | Type | Required | Description |
|-----------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
```json
[
@ -199,10 +202,10 @@ GET /projects/:id/approval_rules/:approval_rule_id
Supported attributes:
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
| Attribute | Type | Required | Description |
|--------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
```json
{
@ -301,18 +304,18 @@ POST /projects/:id/approval_rules
Supported attributes:
| Attribute | Type | Required | Description |
|-------------------------------------|-------------------|----------|------------ |
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `applies_to_all_protected_branches` | boolean | **{dotted-circle}** No | Whether the rule is applied to all protected branches. If set to `true`, the value of `protected_branch_ids` is ignored. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335316) in GitLab 15.3. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `protected_branch_ids` | Array | **{dotted-circle}** No | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `report_type` | string | **{dotted-circle}** No | The report type required when the rule type is `report_approver`. The supported report types are `license_scanning` and `code_coverage`. |
| `rule_type` | string | **{dotted-circle}** No | The type of rule. `any_approver` is a pre-configured default rule with `approvals_required` at `0`. Other rules are `regular`. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
| Attribute | Type | Required | Description |
|-------------------------------------|-------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `applies_to_all_protected_branches` | boolean | **{dotted-circle}** No | Whether the rule is applied to all protected branches. If set to `true`, the value of `protected_branch_ids` is ignored. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335316) in GitLab 15.3. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `protected_branch_ids` | Array | **{dotted-circle}** No | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `report_type` | string | **{dotted-circle}** No | The report type required when the rule type is `report_approver`. The supported report types are `license_scanning` and `code_coverage`. |
| `rule_type` | string | **{dotted-circle}** No | The type of rule. `any_approver` is a pre-configured default rule with `approvals_required` at `0`. Other rules are `regular`. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
```json
{
@ -430,18 +433,18 @@ PUT /projects/:id/approval_rules/:approval_rule_id
Supported attributes:
| Attribute | Type | Required | Description |
|-------------------------------------|-------------------|----------|-------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `applies_to_all_protected_branches` | boolean | **{dotted-circle}** No | Whether the rule is applied to all protected branches. If set to `true`, the value of `protected_branch_ids` is ignored. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335316) in GitLab 15.3. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `protected_branch_ids` | Array | **{dotted-circle}** No | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
| Attribute | Type | Required | Description |
|-------------------------------------|-------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `applies_to_all_protected_branches` | boolean | **{dotted-circle}** No | Whether the rule is applied to all protected branches. If set to `true`, the value of `protected_branch_ids` is ignored. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335316) in GitLab 15.3. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `protected_branch_ids` | Array | **{dotted-circle}** No | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
```json
{
@ -537,10 +540,10 @@ DELETE /projects/:id/approval_rules/:approval_rule_id
Supported attributes:
| Attribute | Type | Required | Description |
|--------------------|-------------------|----------|------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
| Attribute | Type | Required | Description |
|--------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of a approval rule. |
## Merge request-level MR approvals
@ -559,10 +562,10 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|-------------------|----------|------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
```json
{
@ -612,11 +615,11 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals
Supported attributes:
| Attribute | Type | Required | Description |
|----------------------|-------------------|----------|-------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| Attribute | Type | Required | Description |
|----------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
```json
{
@ -653,10 +656,10 @@ This includes additional information about the users who have already approved
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|-------------------|----------|------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
```json
{
@ -723,10 +726,10 @@ Use the `page` and `per_page` [pagination](index.md#offset-based-pagination) par
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
```json
[
@ -800,11 +803,11 @@ GET /projects/:id/merge_requests/:merge_request_iid/approval_rules/:approval_rul
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|---------|----------|------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |
```json
{
@ -876,16 +879,16 @@ POST /projects/:id/merge_requests/:merge_request_iid/approval_rules
Supported attributes:
| Attribute | Type | Required | Description |
|----------------------------|---------|----------|------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding) |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `approval_project_rule_id` | integer | **{dotted-circle}** No | The ID of a project-level approval rule. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
| Attribute | Type | Required | Description |
|----------------------------|-------------------|------------------------|------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding) |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `approval_project_rule_id` | integer | **{dotted-circle}** No | The ID of a project-level approval rule. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
**Important:** When `approval_project_rule_id` is set, the `name`, `users` and
`groups` of project-level rule are copied. The `approvals_required` specified
@ -966,17 +969,17 @@ These are system generated rules.
Supported attributes:
| Attribute | Type | Required | Description |
|----------------------|---------|----------|------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
| Attribute | Type | Required | Description |
|------------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | The number of required approvals for this rule. |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |
| `name` | string | **{check-circle}** Yes | The name of the approval rule. |
| `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. |
| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. |
| `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. |
| `usernames` | string array | **{dotted-circle}** No | The usernames for this rule. |
```json
{
@ -1051,11 +1054,11 @@ These are system generated rules.
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_rule_id` | integer | **{check-circle}** Yes | The ID of an approval rule. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
## Approve merge request
@ -1070,12 +1073,12 @@ POST /projects/:id/merge_requests/:merge_request_iid/approve
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|---------|----------|-------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_password` | string | **{dotted-circle}** No | Current user's password. Required if [**Require user password to approve**](../user/project/merge_requests/approvals/settings.md#require-user-password-to-approve) is enabled in the project settings. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| `sha` | string | **{dotted-circle}** No | The `HEAD` of the merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approval_password` | string | **{dotted-circle}** No | Current user's password. Required if [**Require user password to approve**](../user/project/merge_requests/approvals/settings.md#require-user-password-to-approve) is enabled in the project settings. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
| `sha` | string | **{dotted-circle}** No | The `HEAD` of the merge request. |
The `sha` parameter works in the same way as
when [accepting a merge request](merge_requests.md#merge-a-merge-request): if it is passed, then it must
@ -1133,7 +1136,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/unapprove
Supported attributes:
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |
| Attribute | Type | Required | Description |
|---------------------|-------------------|------------------------|-------------------------------------------------------------------------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. |

View File

@ -411,15 +411,13 @@ To configure markdownlint in your editor, install one of the following as approp
- Sublime Text [`SublimeLinter-contrib-markdownlint` package](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint).
- Visual Studio Code [`DavidAnson.vscode-markdownlint` extension](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint).
- Atom [`linter-node-markdownlint` package](https://atom.io/packages/linter-node-markdownlint).
- Vim [ALE plugin](https://github.com/dense-analysis/ale).
To configure Vale in your editor, install one of the following as appropriate:
- Sublime Text [`SublimeLinter-contrib-vale` package](https://packagecontrol.io/packages/SublimeLinter-contrib-vale).
- Sublime Text [`SublimeLinter-vale` package](https://packagecontrol.io/packages/SublimeLinter-vale).
- Visual Studio Code [`errata-ai.vale-server` extension](https://marketplace.visualstudio.com/items?itemName=errata-ai.vale-server).
You can configure the plugin to [display only a subset of alerts](#show-subset-of-vale-alerts).
- Atom [`atomic-vale` package](https://atom.io/packages/atomic-vale).
- Vim [ALE plugin](https://github.com/dense-analysis/ale).
- JetBrains IDEs - No plugin exists, but
[this issue comment](https://github.com/errata-ai/vale-server/issues/39#issuecomment-751714451)

View File

@ -57,11 +57,14 @@ values extracted from the `alerts` field in the
- Alert severity:
Extracted from the alert payload field `labels/severity`. Maps case-insensitive
value to [Alert's severity](../incident_management/alerts.md#alert-severity):
- **Critical**: `critical`, `s1`, `p1`, `emergency`, `fatal`, or any value not in this list
- **High**: `high`, `s2`, `p2`, `major`, `page`
- **Medium**: `medium`, `s3`, `p3`, `error`, `alert`
- **Low**: `low`, `s4`, `p4`, `warn`, `warning`
- **Info**: `info`, `s5`, `p5`, `debug`, `information`, `notice`
| Alert payload | Mapped to alert severity |
| ------------- | --------------------------------------------------------------------------- |
| Critical | `critical`, `s1`, `p1`, `emergency`, `fatal`, or any value not in this list |
| High | `high`, `s2`, `p2`, `major`, `page` |
| Medium | `medium`, `s3`, `p3`, `error`, `alert` |
| Low | `low`, `s4`, `p4`, `warn`, `warning` |
| Info | `info`, `s5`, `p5`, `debug`, `information`, `notice` |
To further customize the incident, you can add labels, mentions, or any other supported
[quick action](../../user/project/quick_actions.md) in the selected issue template,

View File

@ -52,6 +52,20 @@ sole discretion of GitLab Inc.
<div class="deprecation removal-160 breaking-change">
### Approvers and Approver Group fields in Merge Request Approval API
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
WARNING:
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
Review the details carefully before upgrading.
The endpoint to get the configuration of approvals for a project returns empty arrays for `approvers` and `approval_groups`. These fields were deprecated in favor of the endpoint to [get project-level rules](https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules) for a merge request. API users are encouraged to switch to this endpoint instead. These fields will be removed from the `get configuration` endpoint in v5 of the GitLab REST API.
</div>
<div class="deprecation removal-160 breaking-change">
### Azure Storage Driver defaults to the correct root prefix
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>

View File

@ -281,6 +281,9 @@ module Gitlab
# target_column - The name of the referenced column, defaults to "id".
# on_delete - The action to perform when associated data is removed,
# defaults to "CASCADE".
# on_update - The action to perform when associated data is updated,
# defaults to nil. This is useful for multi column FKs if
# it's desirable to update one of the columns.
# name - The name of the foreign key.
# validate - Flag that controls whether the new foreign key will be validated after creation.
# If the flag is not set, the constraint will only be enforced for new data.
@ -288,7 +291,8 @@ module Gitlab
# order of the ALTER TABLE. This can be useful in situations where the foreign
# key creation could deadlock with another process.
#
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, target_column: :id, name: nil, validate: true, reverse_lock_order: false)
# rubocop: disable Metrics/ParameterLists
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, on_update: nil, target_column: :id, name: nil, validate: true, reverse_lock_order: false)
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
if transaction_open?
@ -298,6 +302,7 @@ module Gitlab
options = {
column: column,
on_delete: on_delete,
on_update: on_update,
name: name.presence || concurrent_foreign_key_name(source, column),
primary_key: target_column
}
@ -306,7 +311,8 @@ module Gitlab
warning_message = "Foreign key not created because it exists already " \
"(this may be due to an aborted migration or similar): " \
"source: #{source}, target: #{target}, column: #{options[:column]}, "\
"name: #{options[:name]}, on_delete: #{options[:on_delete]}"
"name: #{options[:name]}, on_update: #{options[:on_update]}, "\
"on_delete: #{options[:on_delete]}"
Gitlab::AppLogger.warn warning_message
else
@ -322,6 +328,7 @@ module Gitlab
ADD CONSTRAINT #{options[:name]}
FOREIGN KEY (#{multiple_columns(options[:column])})
REFERENCES #{target} (#{multiple_columns(target_column)})
#{on_update_statement(options[:on_update])}
#{on_delete_statement(options[:on_delete])}
NOT VALID;
EOF
@ -343,6 +350,7 @@ module Gitlab
end
end
end
# rubocop: enable Metrics/ParameterLists
def validate_foreign_key(source, column, name: nil)
fk_name = name || concurrent_foreign_key_name(source, column)
@ -1296,6 +1304,13 @@ into similar problems in the future (e.g. when new tables are created).
"ON DELETE #{on_delete.upcase}"
end
def on_update_statement(on_update)
return '' if on_update.blank?
return 'ON UPDATE SET NULL' if on_update == :nullify
"ON UPDATE #{on_update.upcase}"
end
def create_column_from(table, old, new, type: nil, batch_column_name: :id, type_cast_function: nil, limit: nil)
old_col = column_for(table, old)
new_type = type || old_col.type

View File

@ -7,11 +7,11 @@ module Gitlab
class RunnerFleetPipelineSeeder
DEFAULT_JOB_COUNT = 400
MAX_QUEUE_TIME_IN_SECONDS = 5 * 60
PIPELINE_CREATION_RANGE_MIN_IN_MINUTES = 120
PIPELINE_CREATION_RANGE_MAX_IN_MINUTES = 30 * 24 * 60
PIPELINE_START_RANGE_MAX_IN_MINUTES = 60 * 60
PIPELINE_FINISH_RANGE_MAX_IN_MINUTES = 60
MAX_QUEUE_TIME_IN_SECONDS = 5.minutes.to_i
PIPELINE_CREATION_RANGE_MIN_IN_SECONDS = 2.hours.to_i
PIPELINE_CREATION_RANGE_MAX_IN_SECONDS = 30.days.to_i
PIPELINE_START_RANGE_MAX_IN_SECONDS = 5.minutes.to_i
PIPELINE_FINISH_RANGE_MAX_IN_SECONDS = 1.hour.to_i
PROJECT_JOB_DISTRIBUTION = [
{ allocation: 70, job_count_default: 10 },
@ -99,14 +99,14 @@ module Gitlab
sha = '00000000'
if ::Ci::HasStatus::ALIVE_STATUSES.include?(status) || ::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
created_at = Random.rand(PIPELINE_CREATION_RANGE_MIN_IN_MINUTES..PIPELINE_CREATION_RANGE_MAX_IN_MINUTES)
.minutes.ago
created_at = Random.rand(PIPELINE_CREATION_RANGE_MIN_IN_SECONDS..PIPELINE_CREATION_RANGE_MAX_IN_SECONDS)
.seconds.ago
if ::Ci::HasStatus::STARTED_STATUSES.include?(status) ||
::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
started_at = created_at + Random.rand(1..PIPELINE_START_RANGE_MAX_IN_MINUTES).minutes
started_at = created_at + Random.rand(1..PIPELINE_START_RANGE_MAX_IN_SECONDS)
if ::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
finished_at = started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_MINUTES).minutes
finished_at = started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_SECONDS)
end
end
end
@ -138,19 +138,15 @@ module Gitlab
started_at = pipeline.started_at
finished_at = pipeline.finished_at
max_job_duration =
if finished_at
[MAX_QUEUE_TIME_IN_SECONDS, finished_at - started_at].min
else
Random.rand(1..5).seconds
end
max_job_duration = [MAX_QUEUE_TIME_IN_SECONDS, 5, 2].sample
max_job_duration = (finished_at - started_at) if finished_at && max_job_duration > finished_at - started_at
job_created_at = pipeline.created_at
job_started_at = started_at + Random.rand(1..max_job_duration) if started_at
job_started_at = job_created_at + Random.rand(1..max_job_duration) if started_at
if finished_at
job_finished_at = Random.rand(job_started_at..finished_at)
elsif job_status == 'running'
job_finished_at = job_started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_MINUTES).minutes
job_finished_at = job_started_at + Random.rand(1 * 60..PIPELINE_FINISH_RANGE_MAX_IN_SECONDS)
end
# Do not use the first 2 runner tags ('runner-fleet', "#{registration_prefix}runner").

View File

@ -2726,12 +2726,21 @@ msgstr ""
msgid "AdminDashboard|Error loading the statistics. Please try again"
msgstr ""
msgid "AdminEmail|An error occurred fetching the groups and projects. Please refresh the page to try again."
msgstr ""
msgid "AdminEmail|Body"
msgstr ""
msgid "AdminEmail|Body is required."
msgstr ""
msgid "AdminEmail|Loading groups and projects."
msgstr ""
msgid "AdminEmail|No groups or projects found."
msgstr ""
msgid "AdminEmail|Recipient group or project"
msgstr ""
@ -5396,12 +5405,21 @@ msgstr ""
msgid "Artifacts|Delete artifact"
msgstr ""
msgid "Artifacts|Help us improve this page"
msgstr ""
msgid "Artifacts|Take a quick survey"
msgstr ""
msgid "Artifacts|This artifact will be permanently deleted. Any reports generated from this artifact will be empty."
msgstr ""
msgid "Artifacts|Total artifacts size"
msgstr ""
msgid "Artifacts|We want you to be able to use this page to easily manage your CI/CD job artifacts. We are working to improve this experience and would appreciate any feedback you have about the improvements we are making."
msgstr ""
msgid "As we continue to build more features for SAST, we'd love your feedback on the SAST configuration feature in %{linkStart}this issue%{linkEnd}."
msgstr ""

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'User', :requires_admin, product_group: :workspace do
describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:sub_group) do

View File

@ -4,7 +4,7 @@ require 'airborne'
module QA
RSpec.describe 'Manage' do
describe 'Users API', :reliable, product_group: :workspace do
describe 'Users API', :reliable, product_group: :organization do
let(:api_client) { Runtime::API::Client.new(:gitlab) }
let(:request) { Runtime::API::Request.new(api_client, '/users') }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Subgroup transfer', product_group: :workspace do
describe 'Subgroup transfer', product_group: :organization do
let(:source_group) do
Resource::Group.fabricate_via_api! do |group|
group.path = "source-group-for-transfer_#{SecureRandom.hex(8)}"

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Project transfer between groups', :reliable, product_group: :workspace do
describe 'Project transfer between groups', :reliable, product_group: :organization do
let(:source_group) do
Resource::Group.fabricate_via_api! do |group|
group.path = "source-group-#{SecureRandom.hex(8)}"

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', :reliable, product_group: :workspace do
RSpec.describe 'Manage', :reliable, product_group: :organization do
describe 'Add project member' do
it 'user adds project member', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347887' do
Flow::Login.sign_in

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Create project badge', :reliable, product_group: :workspace do
describe 'Create project badge', :reliable, product_group: :organization do
let(:badge_name) { "project-badge-#{SecureRandom.hex(8)}" }
let(:expected_badge_link_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}" }
let(:expected_badge_image_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}/badges/main/pipeline.svg" }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', :smoke, product_group: :workspace do
RSpec.describe 'Manage', :smoke, product_group: :organization do
describe 'Project' do
shared_examples 'successful project creation' do
it 'creates a new project' do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', product_group: :workspace do
RSpec.describe 'Manage', product_group: :organization do
shared_examples 'loads all images' do |admin|
let(:api_client) { Runtime::API::Client.as_admin }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Invite group', :reliable, product_group: :workspace do
describe 'Invite group', :reliable, product_group: :organization do
shared_examples 'invites group to project' do
it 'verifies group is added and members can access project with correct access level' do
Page::Project::Menu.perform(&:click_members)

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Project owner permissions', :reliable, product_group: :workspace do
describe 'Project owner permissions', :reliable, product_group: :organization do
let!(:owner) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Project activity', :reliable, product_group: :workspace do
describe 'Project activity', :reliable, product_group: :organization do
it 'user creates an event in the activity page upon Git push',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347879' do
Flow::Login.sign_in

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'User', :requires_admin, product_group: :workspace do
describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let(:followed_user_api_client) { Runtime::API::Client.new(:gitlab, user: followed_user) }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'User', :requires_admin, :reliable, product_group: :workspace do
describe 'User', :requires_admin, :reliable, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:user) do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'User', :requires_admin, product_group: :workspace do
describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:sub_group) do

View File

@ -102,7 +102,7 @@ RSpec.describe Projects::DeployKeysController do
it 'shows an alert with the validations errors' do
post :create, params: create_params(nil)
expect(flash[:alert]).to eq("Title can't be blank, Deploy keys projects deploy key title can't be blank")
expect(flash[:alert]).to eq("Title can't be blank")
end
end
@ -126,8 +126,7 @@ RSpec.describe Projects::DeployKeysController do
it 'shows an alert with the validations errors' do
post :create, params: create_params
expect(flash[:alert]).to eq("Fingerprint sha256 has already been taken, " \
"Deploy keys projects deploy key fingerprint sha256 has already been taken")
expect(flash[:alert]).to eq("Fingerprint sha256 has already been taken")
end
end
end

View File

@ -0,0 +1,63 @@
import { GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import FeedbackBanner from '~/artifacts/components/feedback_banner.vue';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import {
I18N_FEEDBACK_BANNER_TITLE,
I18N_FEEDBACK_BANNER_BUTTON,
FEEDBACK_URL,
} from '~/artifacts/constants';
const mockBannerImagePath = 'banner/image/path';
describe('Artifacts management feedback banner', () => {
let wrapper;
let userCalloutDismissSpy;
const findBanner = () => wrapper.findComponent(GlBanner);
const createComponent = ({ shouldShowCallout = true } = {}) => {
userCalloutDismissSpy = jest.fn();
wrapper = shallowMount(FeedbackBanner, {
provide: {
artifactsManagementFeedbackImagePath: mockBannerImagePath,
},
stubs: {
UserCalloutDismisser: makeMockUserCalloutDismisser({
dismiss: userCalloutDismissSpy,
shouldShowCallout,
}),
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('is displayed with the correct props', () => {
createComponent();
expect(findBanner().props()).toMatchObject({
title: I18N_FEEDBACK_BANNER_TITLE,
buttonText: I18N_FEEDBACK_BANNER_BUTTON,
buttonLink: FEEDBACK_URL,
svgPath: mockBannerImagePath,
});
});
it('dismisses the callout when closed', () => {
createComponent();
findBanner().vm.$emit('close');
expect(userCalloutDismissSpy).toHaveBeenCalled();
});
it('is not displayed once it has been dismissed', () => {
createComponent({ shouldShowCallout: false });
expect(findBanner().exists()).toBe(false);
});
});

View File

@ -5,6 +5,7 @@ import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/que
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import waitForPromises from 'helpers/wait_for_promises';
import JobArtifactsTable from '~/artifacts/components/job_artifacts_table.vue';
import FeedbackBanner from '~/artifacts/components/feedback_banner.vue';
import ArtifactsTableRowDetails from '~/artifacts/components/artifacts_table_row_details.vue';
import ArtifactDeleteModal from '~/artifacts/components/artifact_delete_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@ -23,6 +24,8 @@ describe('JobArtifactsTable component', () => {
let wrapper;
let requestHandlers;
const findBanner = () => wrapper.findComponent(FeedbackBanner);
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(GlTable);
const findDetailsRows = () => wrapper.findAllComponents(ArtifactsTableRowDetails);
@ -85,7 +88,10 @@ describe('JobArtifactsTable component', () => {
apolloProvider: createMockApollo([
[getJobArtifactsQuery, requestHandlers.getJobArtifactsQuery],
]),
provide: { projectPath: 'project/path' },
provide: {
projectPath: 'project/path',
artifactsManagementFeedbackImagePath: 'banner/image/path',
},
data() {
return data;
},
@ -96,6 +102,12 @@ describe('JobArtifactsTable component', () => {
wrapper.destroy();
});
it('renders feedback banner', () => {
createComponent();
expect(findBanner().exists()).toBe(true);
});
it('when loading, shows a loading state', () => {
createComponent();

View File

@ -1,4 +1,4 @@
import { mount } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import mockData from 'test_fixtures/issues/related_merge_requests.json';
import axios from '~/lib/utils/axios_utils';
@ -20,7 +20,7 @@ describe('RelatedMergeRequests', () => {
mock = new MockAdapter(axios);
mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });
wrapper = mount(RelatedMergeRequests, {
wrapper = shallowMount(RelatedMergeRequests, {
store: createStore(),
propsData: {
endpoint: API_ENDPOINT,
@ -42,18 +42,14 @@ describe('RelatedMergeRequests', () => {
const assignees = [{ name: 'foo' }, { name: 'bar' }];
describe('when there is assignees array', () => {
// https://gitlab.com/gitlab-org/gitlab/-/issues/387756
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should return assignees array', () => {
it('should return assignees array', () => {
const mr = { assignees };
expect(wrapper.vm.getAssignees(mr)).toEqual(assignees);
});
});
// https://gitlab.com/gitlab-org/gitlab/-/issues/387789
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should return an array with single assingee', () => {
it('should return an array with single assignee', () => {
const mr = { assignee: assignees[0] };
expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]);

View File

@ -1,21 +1,23 @@
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { loadHTMLFixture, resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
const fixtureName = 'projects/overview.html';
beforeEach(() => {
loadHTMLFixture(fixtureName);
});
const findTrigger = () => document.querySelector('.js-read-more-trigger');
afterEach(() => {
resetHTMLFixture();
});
describe('expands target element', () => {
beforeEach(() => {
loadHTMLFixture(fixtureName);
});
it('adds "is-expanded" class to target element', () => {
const target = document.querySelector('.read-more-container');
const trigger = document.querySelector('.js-read-more-trigger');
const trigger = findTrigger();
initReadMore();
trigger.click();
@ -23,4 +25,25 @@ describe('Read more click-to-expand functionality', () => {
expect(target.classList.contains('is-expanded')).toEqual(true);
});
});
describe('given click on nested element', () => {
beforeEach(() => {
setHTMLFixture(`
<p>Target</p>
<button type="button" class="js-read-more-trigger">
<span>Button text</span>
</button>
`);
const trigger = findTrigger();
const nestedElement = trigger.firstElementChild;
initReadMore();
nestedElement.click();
});
it('removes the trigger element', async () => {
expect(findTrigger()).toBe(null);
});
});
});

View File

@ -5,7 +5,10 @@ import {
setFrequentItemToLS,
mergeById,
isSidebarDirty,
formatSearchResultCount,
getAggregationsUrl,
} from '~/search/store/utils';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import {
MOCK_LS_KEY,
MOCK_GROUPS,
@ -241,4 +244,23 @@ describe('Global Search Store Utils', () => {
});
});
});
describe('formatSearchResultCount', () => {
it('returns zero as string if no count is provided', () => {
expect(formatSearchResultCount()).toStrictEqual('0');
});
it('returns 10K string for 10000 integer', () => {
expect(formatSearchResultCount(10000)).toStrictEqual('10K');
});
it('returns 23K string for "23,000+" string', () => {
expect(formatSearchResultCount('23,000+')).toStrictEqual('23K');
});
});
describe('getAggregationsUrl', () => {
useMockLocationHelper();
it('returns zero as string if no count is provided', () => {
const testURL = window.location.href;
expect(getAggregationsUrl()).toStrictEqual(`${testURL}search/aggregations`);
});
});
});

View File

@ -42,8 +42,8 @@ describe('Merge Request Collapsible Extension', () => {
expect(wrapper.find('[data-testid="collapsed-header"]').text()).toBe('hello there');
});
it('renders chevron-lg-right icon', () => {
expect(findIcon().props('name')).toBe('chevron-lg-right');
it('renders chevron-right icon', () => {
expect(findIcon().props('name')).toBe('chevron-right');
});
describe('onClick', () => {
@ -60,8 +60,8 @@ describe('Merge Request Collapsible Extension', () => {
expect(findTitle().text()).toBe('Collapse');
});
it('renders chevron-lg-down icon', () => {
expect(findIcon().props('name')).toBe('chevron-lg-down');
it('renders chevron-down icon', () => {
expect(findIcon().props('name')).toBe('chevron-down');
});
});
});

View File

@ -743,6 +743,75 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
context 'ON UPDATE statements' do
context 'on_update: :nullify' do
it 'appends ON UPDATE SET NULL statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
expect(model).to receive(:execute).with(/ON UPDATE SET NULL/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id,
on_update: :nullify)
end
end
context 'on_update: :cascade' do
it 'appends ON UPDATE CASCADE statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
expect(model).to receive(:execute).with(/ON UPDATE CASCADE/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id,
on_update: :cascade)
end
end
context 'on_update: nil' do
it 'appends no ON UPDATE statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
expect(model).not_to receive(:execute).with(/ON UPDATE/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id,
on_update: nil)
end
end
context 'when on_update is not provided' do
it 'appends no ON UPDATE statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
expect(model).not_to receive(:execute).with(/ON UPDATE/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id)
end
end
end
context 'when no custom key name is supplied' do
it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:with_lock_retries).and_call_original
@ -760,6 +829,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
name = model.concurrent_foreign_key_name(:projects, :user_id)
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
column: :user_id,
on_update: nil,
on_delete: :cascade,
name: name,
primary_key: :id).and_return(true)
@ -792,6 +862,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
name: :foo,
primary_key: :id,
on_update: nil,
on_delete: :cascade,
column: :user_id).and_return(true)
@ -861,6 +932,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
"ADD CONSTRAINT fk_multiple_columns\n" \
"FOREIGN KEY \(partition_number, user_id\)\n" \
"REFERENCES users \(partition_number, id\)\n" \
"ON UPDATE CASCADE\n" \
"ON DELETE CASCADE\n" \
"NOT VALID;\n"
)
@ -871,7 +943,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
column: [:partition_number, :user_id],
target_column: [:partition_number, :id],
validate: false,
name: :fk_multiple_columns
name: :fk_multiple_columns,
on_update: :cascade
)
end
@ -883,6 +956,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
{
column: [:partition_number, :user_id],
name: :fk_multiple_columns,
on_update: :cascade,
on_delete: :cascade,
primary_key: [:partition_number, :id]
}
@ -898,6 +972,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
:users,
column: [:partition_number, :user_id],
target_column: [:partition_number, :id],
on_update: :cascade,
validate: false,
name: :fk_multiple_columns
)

View File

@ -32,6 +32,9 @@ RSpec.describe ::Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder, feature
expect { seeder.seed }.to change { Ci::Build.count }.by(job_count)
.and change { Ci::Pipeline.count }.by(4)
expect(Ci::Pipeline.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes)
expect(Ci::Build.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes)
projects_to_runners.first(3).each do |project|
expect(Ci::Build.where(runner_id: project[:runner_ids])).not_to be_empty
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Pages::LookupPath do
RSpec.describe Pages::LookupPath, feature_category: :pages do
let(:project) { create(:project, :pages_private, pages_https_only: true) }
subject(:lookup_path) { described_class.new(project) }
@ -126,14 +126,18 @@ RSpec.describe Pages::LookupPath do
describe '#prefix' do
it 'returns "/" for pages group root projects' do
project = instance_double(Project, pages_group_root?: true)
project = instance_double(Project, pages_namespace_url: "namespace.test", pages_url: "namespace.test")
lookup_path = described_class.new(project, trim_prefix: 'mygroup')
expect(lookup_path.prefix).to eq('/')
end
it 'returns the project full path with the provided prefix removed' do
project = instance_double(Project, pages_group_root?: false, full_path: 'mygroup/myproject')
project = instance_double(
Project,
pages_namespace_url: "namespace.test",
pages_url: "namespace.other",
full_path: 'mygroup/myproject')
lookup_path = described_class.new(project, trim_prefix: 'mygroup')
expect(lookup_path.prefix).to eq('/myproject/')

View File

@ -2596,16 +2596,28 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#pages_url' do
describe '#pages_url', feature_category: :pages do
let(:group) { create(:group, name: group_name) }
let(:project) { create(:project, namespace: group, name: project_name) }
let(:project_path) { project_name.downcase }
let(:project) do
create(
:project,
namespace: group,
name: project_name,
path: project_path)
end
let(:domain) { 'Example.com' }
let(:port) { nil }
subject { project.pages_url }
before do
allow(Settings.pages).to receive(:host).and_return(domain)
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
allow(Gitlab.config.pages)
.to receive(:url)
.and_return(['http://example.com', port].compact.join(':'))
end
context 'group page' do
@ -2615,9 +2627,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to eq("http://group.example.com") }
context 'mixed case path' do
before do
project.update!(path: 'Group.example.com')
end
let(:project_path) { 'Group.example.com' }
it { is_expected.to eq("http://group.example.com") }
end
@ -2630,22 +2640,88 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to eq("http://group.example.com/project") }
context 'mixed case path' do
before do
project.update!(path: 'Project')
end
let(:project_path) { 'Project' }
it { is_expected.to eq("http://group.example.com/Project") }
end
end
context 'when there is an explicit port' do
let(:port) { 3000 }
context 'when not in dev mode' do
before do
stub_rails_env('production')
end
context 'group page' do
let(:group_name) { 'Group' }
let(:project_name) { 'group.example.com' }
it { is_expected.to eq('http://group.example.com:3000/group.example.com') }
context 'mixed case path' do
let(:project_path) { 'Group.example.com' }
it { is_expected.to eq('http://group.example.com:3000/Group.example.com') }
end
end
context 'project page' do
let(:group_name) { 'Group' }
let(:project_name) { 'Project' }
it { is_expected.to eq("http://group.example.com:3000/project") }
context 'mixed case path' do
let(:project_path) { 'Project' }
it { is_expected.to eq("http://group.example.com:3000/Project") }
end
end
end
context 'when in dev mode' do
before do
stub_rails_env('development')
end
context 'group page' do
let(:group_name) { 'Group' }
let(:project_name) { 'group.example.com' }
it { is_expected.to eq('http://group.example.com:3000') }
context 'mixed case path' do
let(:project_path) { 'Group.example.com' }
it { is_expected.to eq('http://group.example.com:3000') }
end
end
context 'project page' do
let(:group_name) { 'Group' }
let(:project_name) { 'Project' }
it { is_expected.to eq("http://group.example.com:3000/project") }
context 'mixed case path' do
let(:project_path) { 'Project' }
it { is_expected.to eq("http://group.example.com:3000/Project") }
end
end
end
end
end
describe '#pages_group_url' do
describe '#pages_namespace_url', feature_category: :pages do
let(:group) { create(:group, name: group_name) }
let(:project) { create(:project, namespace: group, name: project_name) }
let(:domain) { 'Example.com' }
let(:port) { 1234 }
subject { project.pages_group_url }
subject { project.pages_namespace_url }
before do
allow(Settings.pages).to receive(:host).and_return(domain)
@ -6992,21 +7068,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#pages_group_root?' do
it 'returns returns true if pages_url is same as pages_group_url' do
project = build(:project)
expect(project).to receive(:pages_url).and_return(project.pages_group_url)
expect(project.pages_group_root?).to eq(true)
end
it 'returns returns false if pages_url is different than pages_group_url' do
project = build(:project)
expect(project.pages_group_root?).to eq(false)
end
end
describe '#closest_setting' do
shared_examples_for 'fetching closest setting' do
let!(:namespace) { create(:namespace) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BulkImports::CreatePipelineTrackersService do
RSpec.describe BulkImports::CreatePipelineTrackersService, feature_category: :importers do
describe '#execute!' do
context 'when entity is group' do
it 'creates trackers for group entity' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Packages::Conan::SearchService do
RSpec.describe Packages::Conan::SearchService, feature_category: :package_registry do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Projects::CreateService, '#execute' do
RSpec.describe Projects::CreateService, '#execute', feature_category: :projects do
include ExternalAuthorizationServiceHelpers
let(:user) { create :user }