Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-05 18:10:06 +00:00
parent 6659634b2b
commit c8419b0227
96 changed files with 1127 additions and 546 deletions

View File

@ -1,6 +1,6 @@
include:
- project: gitlab-org/quality/pipeline-common
ref: 7.13.2
ref: 7.13.3
file:
- /ci/danger-review.yml

View File

@ -2113,7 +2113,6 @@ Style/InlineDisableAnnotation:
- 'ee/spec/models/vulnerabilities/read_spec.rb'
- 'ee/spec/policies/group_policy_spec.rb'
- 'ee/spec/presenters/approval_rule_presenter_spec.rb'
- 'ee/spec/presenters/ee/project_presenter_spec.rb'
- 'ee/spec/presenters/ee/projects/import_export/project_export_presenter_spec.rb'
- 'ee/spec/presenters/member_presenter_spec.rb'
- 'ee/spec/requests/api/conan_project_packages_spec.rb'

View File

@ -0,0 +1,154 @@
<script>
import {
GlButton,
GlDisclosureDropdownItem,
GlDisclosureDropdown,
GlIcon,
GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
export default {
components: {
GlButton,
GlDisclosureDropdownItem,
GlDisclosureDropdown,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: [
'isGroup',
'id',
'leavePath',
'leaveConfirmMessage',
'withdrawPath',
'withdrawConfirmMessage',
'requestAccessPath',
],
computed: {
namespaceType() {
return this.isGroup ? WORKSPACE_GROUP : WORKSPACE_PROJECT;
},
leaveTitle() {
return this.isGroup
? this.$options.i18n.groupLeaveTitle
: this.$options.i18n.projectLeaveTitle;
},
copyTitle() {
return this.isGroup ? this.$options.i18n.groupCopyTitle : this.$options.i18n.projectCopyTitle;
},
copiedToClipboard() {
return this.isGroup
? this.$options.i18n.groupCopiedToClipboard
: this.$options.i18n.projectCopiedToClipboard;
},
leaveItem() {
return {
text: this.leaveTitle,
href: this.leavePath,
extraAttrs: {
'aria-label': this.leaveTitle,
'data-method': 'delete',
'data-confirm': this.leaveConfirmMessage,
'data-confirm-btn-variant': 'danger',
'data-testid': `leave-${this.namespaceType}-link`,
rel: 'nofollow',
class: 'gl-text-red-500! js-leave-link',
},
};
},
withdrawItem() {
return {
text: this.$options.i18n.withdrawAccessTitle,
href: this.withdrawPath,
extraAttrs: {
'data-method': 'delete',
'data-confirm': this.withdrawConfirmMessage,
'data-testid': 'withdraw-access-link',
rel: 'nofollow',
},
};
},
requestAccessItem() {
return {
text: this.$options.i18n.requestAccessTitle,
href: this.requestAccessPath,
extraAttrs: {
'data-method': 'post',
'data-testid': 'request-access-link',
rel: 'nofollow',
},
};
},
copyIdItem() {
return {
text: sprintf(this.copyTitle, { id: this.id }),
action: () => {
this.$toast.show(this.copiedToClipboard);
},
extraAttrs: {
'data-testid': `copy-${this.namespaceType}-id`,
},
};
},
},
i18n: {
actionsLabel: __('Actions'),
groupCopiedToClipboard: s__('GroupPage|Group ID copied to clipboard.'),
projectCopiedToClipboard: s__('ProjectPage|Project ID copied to clipboard.'),
groupLeaveTitle: __('Leave group'),
projectLeaveTitle: __('Leave project'),
withdrawAccessTitle: __('Withdraw Access Request'),
requestAccessTitle: __('Request Access'),
groupCopyTitle: s__('GroupPage|Copy group ID: %{id}'),
projectCopyTitle: s__('ProjectPage|Copy project ID: %{id}'),
},
};
</script>
<template>
<gl-disclosure-dropdown
v-gl-tooltip.hover="$options.i18n.actionsLabel"
category="tertiary"
icon="ellipsis_v"
no-caret
:toggle-text="$options.i18n.actionsLabel"
text-sr-only
data-testid="groups-projects-more-actions-dropdown"
class="gl-relative gl-w-full gl-sm-w-auto"
>
<template #toggle>
<div class="gl-min-h-7">
<gl-button
class="gl-md-display-none! gl-new-dropdown-toggle gl-absolute gl-top-0 gl-left-0 gl-w-full"
button-text-classes="gl-w-full"
category="secondary"
:aria-label="$options.i18n.actionsLabel"
:title="$options.i18n.actionsLabel"
>
<span class="gl-new-dropdown-button-text">{{ $options.i18n.actionsLabel }}</span>
<gl-icon class="dropdown-chevron" name="chevron-down" />
</gl-button>
<gl-button
ref="moreActionsDropdown"
class="gl-display-none gl-md-display-flex! gl-new-dropdown-toggle gl-new-dropdown-icon-only gl-new-dropdown-toggle-no-caret"
category="tertiary"
icon="ellipsis_v"
:aria-label="$options.i18n.actionsLabel"
:title="$options.i18n.actionsLabel"
/>
</div>
</template>
<gl-disclosure-dropdown-item v-if="leavePath" ref="leaveItem" :item="leaveItem" />
<gl-disclosure-dropdown-item v-else-if="withdrawPath" :item="withdrawItem" />
<gl-disclosure-dropdown-item v-else-if="requestAccessPath" :item="requestAccessItem" />
<gl-disclosure-dropdown-item v-if="id" :item="copyIdItem" :data-clipboard-text="id" />
</gl-disclosure-dropdown>
</template>

View File

@ -0,0 +1,36 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
export default function InitMoreActionsDropdown() {
const el = document.querySelector('.js-groups-projects-more-actions-dropdown');
if (!el) {
return false;
}
const {
isGroup,
id,
leavePath,
leaveConfirmMessage,
withdrawPath,
withdrawConfirmMessage,
requestAccessPath,
} = el.dataset;
return new Vue({
el,
name: 'MoreActionsDropdownRoot',
provide: {
isGroup: parseBoolean(isGroup),
id,
leavePath,
leaveConfirmMessage,
withdrawPath,
withdrawConfirmMessage,
requestAccessPath,
},
render: (createElement) => createElement(MoreActionsDropdown),
});
}

View File

@ -281,62 +281,31 @@ async function fetchMetrics(metricsUrl, { filters = {}, limit } = {}) {
}
}
async function fetchMetric() {
// TODO https://gitlab.com/gitlab-org/opstrace/opstrace/-/work_items/2545 Load metric from API
/* eslint-disable @gitlab/require-i18n-strings */
return [
{
name: 'container_cpu_usage_seconds_total',
description: 'System disk operations',
type: 'Gauge',
unit: 'gb',
attributes: {
beta_kubernetes_io_arch: 'amd64',
beta_kubernetes_io_instance_type: 'n1-standard-4',
beta_kubernetes_io_os: 'linux',
env: 'production',
},
values: [
[1700118610000 * 1e6, 0.25595267476015443],
[1700118660000 * 1e6, 0.1881374588830907],
[1700118720000 * 1e6, 0.28915416028993485],
[1700118780000 * 1e6, 0.29304883966696416],
[1700118840000 * 1e6, 0.2657727031708884],
[1700118900000 * 1e6, 0.24415948639572538],
[1700118960000 * 1e6, 0.32778875228243076],
[1700119020000 * 1e6, 0.9658100987444416],
[1700119080000 * 1e6, 1.0604918827864345],
[1700119140000 * 1e6, 1.0205790879854122],
[1700119200000 * 1e6, 0.868291210099945],
],
},
{
name: 'container_cpu_usage_seconds_total',
description: 'System disk operations',
type: 'Gauge',
unit: 'gb',
attributes: {
beta_kubernetes_io_arch: 'amd64',
beta_kubernetes_io_instance_type: 'n1-standard-4',
beta_kubernetes_io_os: 'linux',
env: 'staging',
},
values: [
[1700118600000 * 1e6, 0.3559526747601544],
[1700118660000 * 1e6, 0.1881374588830907],
[1700118720000 * 1e6, 0.7891541602899349],
[1700118780000 * 1e6, 0.6930488396669642],
[1700118840000 * 1e6, 0.2959927031708884],
[1700118900000 * 1e6, 0.34415948639572536],
[1700118960000 * 1e6, 0.39778875228243077],
[1700119020000 * 1e6, 1.2658100987444416],
[1700119080000 * 1e6, 3.0604918827864345],
[1700119140000 * 1e6, 3.0205790879854124],
[1700119200000 * 1e6, 0.888291210099945],
],
},
];
/* eslint-enable @gitlab/require-i18n-strings */
async function fetchMetric(searchUrl, name, type) {
try {
if (!name) {
throw new Error('fetchMetric() - metric name is required.');
}
if (!type) {
throw new Error('fetchMetric() - metric type is required.');
}
const params = new URLSearchParams({
mname: name,
mtype: type,
});
const { data } = await axios.get(searchUrl, {
params,
withCredentials: true,
});
if (!Array.isArray(data.results)) {
throw new Error('metrics are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
}
return data.results;
} catch (e) {
return reportErrorAndThrow(e);
}
}
export function buildClient(config) {
@ -344,7 +313,14 @@ export function buildClient(config) {
throw new Error('No options object provided'); // eslint-disable-line @gitlab/require-i18n-strings
}
const { provisioningUrl, tracingUrl, servicesUrl, operationsUrl, metricsUrl } = config;
const {
provisioningUrl,
tracingUrl,
servicesUrl,
operationsUrl,
metricsUrl,
metricsSearchUrl,
} = config;
if (typeof provisioningUrl !== 'string') {
throw new Error('provisioningUrl param must be a string');
@ -366,6 +342,10 @@ export function buildClient(config) {
throw new Error('metricsUrl param must be a string');
}
if (typeof metricsSearchUrl !== 'string') {
throw new Error('metricsSearchUrl param must be a string');
}
return {
enableObservability: () => enableObservability(provisioningUrl),
isObservabilityEnabled: () => isObservabilityEnabled(provisioningUrl),
@ -374,6 +354,6 @@ export function buildClient(config) {
fetchServices: () => fetchServices(servicesUrl),
fetchOperations: (serviceName) => fetchOperations(operationsUrl, serviceName),
fetchMetrics: (options) => fetchMetrics(metricsUrl, options),
fetchMetric: (metricId) => fetchMetric(metricId),
fetchMetric: (metricName, metricType) => fetchMetric(metricsSearchUrl, metricName, metricType),
};
}

View File

@ -2,10 +2,12 @@ import leaveByUrl from '~/namespaces/leave_by_url';
import { initGroupOverviewTabs } from '~/groups/init_overview_tabs';
import { initGroupReadme } from '~/groups/init_group_readme';
import initReadMore from '~/read_more';
import InitMoreActionsDropdown from '~/groups_projects/init_more_actions_dropdown';
import initGroupDetails from '../shared/group_details';
leaveByUrl('group');
initGroupDetails();
initGroupOverviewTabs();
initReadMore();
initGroupReadme();
InitMoreActionsDropdown();
leaveByUrl('group');

View File

@ -9,6 +9,7 @@ import { initUploadFileTrigger } from '~/projects/upload_file';
import initReadMore from '~/read_more';
import initForksButton from '~/forks/init_forks_button';
import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
import InitMoreActionsDropdown from '~/groups_projects/init_more_actions_dropdown';
// Project show page loads different overview content based on user preferences
if (document.getElementById('js-tree-list')) {
@ -35,8 +36,6 @@ if (document.querySelector('.project-show-activity')) {
.catch(() => {});
}
leaveByUrl('project');
initVueNotificationsDropdown();
addShortcutsExtension(ShortcutsNavigation);
@ -62,3 +61,5 @@ if (document.querySelector('.js-autodevops-banner')) {
}
initForksButton();
InitMoreActionsDropdown();
leaveByUrl('project');

View File

@ -92,7 +92,7 @@ export default {
</script>
<template>
<div class="gl-ml-5">
<gl-loading-icon v-if="isLoading" size="lg" label="Loading pipeline status" />
<gl-loading-icon v-if="isLoading" size="sm" label="Loading pipeline status" />
<a v-else :href="ciStatus.details_path">
<ci-icon :status="ciStatus" :title="statusTitle" :aria-label="statusTitle" />
</a>

View File

@ -106,7 +106,7 @@ export default {
</script>
<template>
<gl-loading-icon v-if="isLoading" size="lg" color="dark" class="m-auto" />
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
<commit-info v-else-if="commit" :commit="commit">
<div
class="commit-actions gl-display-flex gl-flex-align gl-align-items-center gl-flex-direction-row"

View File

@ -226,7 +226,7 @@ $system-footer-height: $system-header-height;
$mr-sticky-header-height: 72px;
$mr-review-bar-height: calc(2rem + 16px);
$top-bar-height: 48px;
$home-panel-title-row-height: 64px;
$home-panel-title-row-height: 48px;
$home-panel-avatar-mobile-size: 24px;
$issuable-title-max-width: 350px;
$milestone-title-max-width: 75px;

View File

@ -2,41 +2,12 @@
.group-home-panel {
.home-panel-avatar {
width: $home-panel-title-row-height;
height: $home-panel-title-row-height;
flex-basis: $home-panel-title-row-height;
}
.home-panel-title {
.icon {
vertical-align: -1px;
}
}
.home-panel-title-row {
@include media-breakpoint-down(sm) {
.home-panel-avatar {
width: $home-panel-avatar-mobile-size;
height: $home-panel-avatar-mobile-size;
flex-basis: $home-panel-avatar-mobile-size;
.avatar {
font-size: 20px;
line-height: 46px;
}
}
.home-panel-title {
margin-top: 4px;
margin-bottom: 2px;
font-size: $gl-font-size;
line-height: $gl-font-size-large;
}
.home-panel-metadata {
font-size: $gl-font-size-small;
}
vertical-align: 1px;
}
}

View File

@ -7,7 +7,7 @@
.home-panel-title {
.icon {
vertical-align: -1px;
vertical-align: 1px;
}
.home-panel-topic-list {
@ -17,28 +17,6 @@
}
}
.home-panel-title-row {
@include media-breakpoint-down(sm) {
.home-panel-avatar {
width: $home-panel-avatar-mobile-size;
height: $home-panel-avatar-mobile-size;
flex-basis: $home-panel-avatar-mobile-size;
.avatar {
font-size: 20px;
line-height: 46px;
}
}
.home-panel-title {
margin-top: 4px;
margin-bottom: 2px;
font-size: $gl-font-size;
line-height: $gl-font-size-large;
}
}
}
.home-panel-description {
@include media-breakpoint-up(md) {
font-size: $gl-font-size-large;

View File

@ -405,18 +405,6 @@
}
}
@include media-breakpoint-down(md) {
.avatar-container {
@include avatar-size(40px, 10px);
min-height: 40px;
min-width: 40px;
.identicon.s64 {
font-size: 16px;
}
}
}
@include media-breakpoint-down(md) {
.updated-note {
@include gl-mt-3;

View File

@ -78,10 +78,6 @@
.btn-group {
width: 100%;
}
.btn {
margin-top: 10px;
}
}
}

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Types
module Ci
module Catalog
module Resources
# rubocop: disable Graphql/AuthorizeTypes -- Authorization is handled by VersionsType
class ComponentType < BaseObject
graphql_name 'CiCatalogResourcesComponent'
field :id, ::Types::GlobalIDType[::Ci::Catalog::Resources::Component], null: false,
description: 'ID of the component.',
alpha: { milestone: '16.7' }
field :name, GraphQL::Types::String, null: true,
description: 'Name of the component.',
alpha: { milestone: '16.7' }
field :path, GraphQL::Types::String, null: true,
description: 'Path used to include the component.',
alpha: { milestone: '16.7' }
field :inputs, [Types::Ci::Catalog::Resources::Components::InputType], null: true,
description: 'Inputs for the component.',
alpha: { milestone: '16.7' }
def inputs
object.inputs.map do |key, value|
{
name: key,
required: !value&.key?('default'),
default: value&.dig('default')
}
end
end
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Types
module Ci
module Catalog
module Resources
module Components
# rubocop: disable Graphql/AuthorizeTypes -- Authorization hanlded by ComponentType -> VersionType
class InputType < BaseObject
graphql_name 'CiCatalogResourcesComponentsInput'
field :name, GraphQL::Types::String, null: true,
description: 'Name of the input.',
alpha: { milestone: '16.7' }
field :default, GraphQL::Types::String, null: true,
description: 'Default value for the input.',
alpha: { milestone: '16.7' }
field :required, GraphQL::Types::Boolean, null: true,
description: 'Indicates if an input is required.',
alpha: { milestone: '16.7' }
end
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end

View File

@ -35,6 +35,10 @@ module Types
description: 'Commit associated with the version.',
alpha: { milestone: '16.7' }
field :components, Types::Ci::Catalog::Resources::ComponentType.connection_type, null: true,
description: 'Components belonging to the catalog resource.',
alpha: { milestone: '16.7' }
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end

View File

@ -218,6 +218,31 @@ module GroupsHelper
GroupMember.access_level_roles.select { |_k, v| v <= max_access_level }
end
def groups_projects_more_actions_dropdown_data(source)
model_name = source.model_name.to_s.downcase
dropdown_data = {
is_group: source.is_a?(Group).to_s,
id: source.id
}
return dropdown_data unless current_user
if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) # rubocop: disable CodeReuse/ActiveRecord -- we need to fetch it
dropdown_data[:leave_path] = polymorphic_path([:leave, source, :members])
dropdown_data[:leave_confirm_message] = leave_confirmation_message(source)
elsif source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord -- we need to fetch it
requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord -- we need to fetch it
if can?(current_user, :withdraw_member_access_request, requester)
dropdown_data[:withdraw_path] = polymorphic_path([:leave, source, :members])
dropdown_data[:withdraw_confirm_message] = remove_member_message(requester)
end
elsif source.request_access_enabled && can?(current_user, :request_access, source)
dropdown_data[:request_access_path] = polymorphic_path([:request_access, source, :members])
end
dropdown_data
end
private
def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false)

View File

@ -171,12 +171,12 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def storage_anchor_data
can_show_quota = can?(current_user, :admin_project, project) && !empty_repo?
return unless can?(current_user, :admin_project, project) && !empty_repo?
AnchorData.new(
true,
statistic_icon('disk') + storage_anchor_text,
can_show_quota ? project_usage_quotas_path(project) : nil
project_usage_quotas_path(project)
)
end

View File

@ -42,7 +42,7 @@ module QuickActions
@updates = {}
@execution_message = {}
content, commands = extractor.extract_commands(content, only: only, target: quick_action_target)
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
[content, @updates, execution_messages_for(commands), command_names(commands)]
@ -56,7 +56,7 @@ module QuickActions
@quick_action_target = quick_action_target
content, commands = extractor(keep_actions).extract_commands(content, target: quick_action_target)
content, commands = extractor(keep_actions).extract_commands(content)
commands = explain_commands(commands)
[content, commands]
end

View File

@ -3,24 +3,16 @@
- emails_disabled = @group.emails_disabled?
.group-home-panel
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-md-flex-direction-row.gl-gap-3.gl-my-5
.home-panel-title-row.gl-display-flex.gl-align-items-center
.avatar-container.rect-avatar.s64.home-panel-avatar.gl-flex-shrink-0.float-none{ class: 'gl-mr-3!' }
= render Pajamas::AvatarComponent.new(@group, alt: @group.name, size: 64, avatar_options: { itemprop: 'logo' })
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-sm-flex-direction-column.gl-md-flex-direction-row.gl-gap-3.gl-my-5
.home-panel-title-row.gl-display-flex
.avatar-container.rect-avatar.s48.home-panel-avatar.gl-flex-shrink-0.float-none{ class: 'gl-mr-3!' }
= render Pajamas::AvatarComponent.new(@group, alt: @group.name, size: 48, avatar_options: { itemprop: 'logo' })
%div
%h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex.gl-word-break-word{ itemprop: 'name' }
%h1.home-panel-title.gl-font-size-h-display.gl-mt-3.gl-mb-2.gl-ml-2.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ itemprop: 'name' }
= @group.name
%span.visibility-icon.gl-text-secondary.has-tooltip.gl-ml-2{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, options: {class: 'icon'})
%span.visibility-icon.gl-text-secondary.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, options: { class: 'icon' })
= render_if_exists 'shared/tier_badge', source: @group, namespace_to_track: @group
.home-panel-metadata.gl-text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { qa_selector: 'group_id_content' }, itemprop: 'identifier' }
- if can?(current_user, :read_group, @group)
%span.gl-display-inline-block.gl-vertical-align-middle
= s_("GroupPage|Group ID: %{group_id}") % { group_id: @group.id }
= clipboard_button(title: s_('GroupPage|Copy group ID'), text: @group.id)
- if current_user
%span.gl-ml-3.gl-mb-3
= render 'shared/members/access_request_links', source: @group
- 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' } }
@ -38,6 +30,8 @@
= render Pajamas::ButtonComponent.new(href: new_project_path(namespace_id: @group.id), variant: :confirm, button_options: { data: { testid: 'new-project-button' }, class: 'gl-sm-w-auto gl-w-full' }) do
= _('New project')
= render 'shared/groups_projects_more_actions_dropdown', source: @group
- if @group.description.present?
.group-home-desc.mt-1
.home-panel-description

View File

@ -7,7 +7,7 @@
- if readme_path = @project.repository.readme_path
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
#tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } }
#tree-holder.tree-holder.clearfix.js-per-page.gl-mt-5{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } }
- if Feature.enabled?(:project_overview_reorg)
.nav-block.gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-align-items-stretch
= render 'projects/tree/tree_header', tree: @tree

View File

@ -5,24 +5,18 @@
%header.project-home-panel.js-show-on-project-root.gl-mt-5{ class: [("empty-project" if empty_repo)] }
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-md-flex-direction-row.gl-gap-5
.home-panel-title-row.gl-display-flex.gl-align-items-center
= render Pajamas::AvatarComponent.new(@project, alt: @project.name, class: 'gl-flex-shrink-0 gl-mr-3', size: 64, avatar_options: { itemprop: 'image' })
%div
%h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex.gl-word-break-word{ data: { testid: 'project-name-content' }, itemprop: 'name' }
= render Pajamas::AvatarComponent.new(@project, alt: @project.name, class: 'gl-flex-shrink-0 gl-mr-3', size: 48, avatar_options: { itemprop: 'image' })
.gl-ml-2
%h1.home-panel-title.gl-font-size-h-display.gl-mt-3.gl-mb-2.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ data: { testid: 'project-name-content' }, itemprop: 'name' }
= @project.name
= visibility_level_content(@project, css_class: 'visibility-icon gl-text-secondary gl-mx-2', icon_css_class: 'icon')
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center gl-mx-2'
= visibility_level_content(@project, css_class: 'visibility-icon gl-text-secondary', icon_css_class: 'icon')
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center'
- if @project.catalog_resource
= render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(@project.catalog_resource), css_class: 'gl-mx-2' }
= render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(@project.catalog_resource), css_class: 'gl-mx-0' }
- if @project.group
= render_if_exists 'shared/tier_badge', source: @project, namespace_to_track: @project.namespace
.home-panel-metadata.gl-font-sm.gl-text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { testid: 'project-id-content' }, itemprop: 'identifier' }
- if can?(current_user, :read_project, @project)
%span.gl-display-inline-block.gl-vertical-align-middle
= s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
= clipboard_button(title: s_('ProjectPage|Copy project ID'), text: @project.id)
- if current_user
%span.gl-ml-3.gl-mb-3
= render 'shared/members/access_request_links', source: @project
.gl-text-secondary
= render_if_exists "projects/home_mirror"
.project-repo-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-center.gl-flex-wrap.gl-gap-3
- if current_user
@ -33,6 +27,7 @@
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
= render 'shared/groups_projects_more_actions_dropdown', source: @project
- if ff_reorg_disabled
- if can?(current_user, :read_code, @project)

View File

@ -1,5 +1,6 @@
- anchors = local_assigns.fetch(:anchors, [])
- project_buttons = local_assigns.fetch(:project_buttons, false)
- ff_reorg_enabled = Feature.enabled?(:project_overview_reorg)
- return unless anchors.any?
@ -7,4 +8,4 @@
- anchors.each do |anchor|
%li.nav-item
= link_to_if(anchor.link, anchor.label, anchor.link, stat_anchor_attrs(anchor)) do
.stat-text.d-flex.align-items-center{ class: ('btn gl-button btn-default gl-px-0! disabled' if project_buttons) }= anchor.label
.stat-text.d-flex.align-items-center{ class: "#{'btn gl-button btn-default disabled' if project_buttons} #{'gl-px-0! gl-pb-2!' if ff_reorg_enabled}" }= anchor.label

View File

@ -17,7 +17,7 @@
- else
= link_to title, project_tree_path(@project, tree_join(@ref, path), ref_type: @ref_type)
.tree-controls.gl-children-ml-sm-3<
.tree-controls.gl-display-flex.gl-flex-wrap.gl-sm-flex-nowrap.gl-align-items-baseline.gl-gap-3
= render 'projects/find_file_link'
-# only show normal/blame view links for text files
- if blob.readable_text?

View File

@ -17,10 +17,10 @@
.project-page-layout
.project-page-layout-content.gl-mt-5
.project-buttons.gl-mb-5{ data: { testid: 'quick-actions-container' } }
.project-clone-holder.d-block.d-md-none
.project-clone-holder.d-block.d-sm-none
= render "shared/mobile_clone_panel"
.project-clone-holder.gl-display-none.gl-md-display-flex.gl-justify-content-end.gl-w-full.gl-mt-2
.project-clone-holder.gl-display-none.gl-sm-display-flex.gl-justify-content-end.gl-w-full.gl-mt-2
= render "projects/buttons/code", ref: @ref
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-new-card-body gl-bg-gray-10 gl-p-5' }) do |c|
@ -93,10 +93,10 @@
= _('You can get started by cloning the repository or start adding files to it with one of the following options.')
.project-buttons{ data: { testid: 'quick-actions-container' } }
.project-clone-holder.d-block.d-md-none.gl-mt-3.gl-mr-3
.project-clone-holder.d-block.d-sm-none.gl-mt-3.gl-mr-3
= render "shared/mobile_clone_panel"
.project-clone-holder.d-none.d-md-inline-block.gl-mb-3.gl-mr-3.float-left
.project-clone-holder.d-none.d-sm-inline-block.gl-mb-3.gl-mr-3.float-left
= render "projects/buttons/code", ref: @ref
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons, project_buttons: true

View File

@ -22,7 +22,7 @@
.project-page-layout-sidebar.js-show-on-project-root.gl-mt-5
= render "sidebar"
.project-page-layout-content.gl-mt-5
.project-page-layout-content
- if can?(current_user, :read_code, @project) && @project.repository_languages.present?
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })

View File

@ -17,9 +17,9 @@
= render 'projects/find_file_link'
= render 'shared/web_ide_button', blob: nil
.project-code-holder.d-none.d-md-inline-block>
.project-code-holder.d-none.d-sm-inline-block>
= render "projects/buttons/code", dropdown_class: 'dropdown-menu-right', ref: @ref
.project-code-holder.d-block.d-md-none.mt-sm-2.mt-md-0.ml-md-2>
.project-code-holder.d-block.d-sm-none.mt-sm-2.mt-md-0.ml-md-2>
= render 'projects/buttons/download', project: @project, ref: @ref
= render "shared/mobile_clone_panel", ref: @ref

View File

@ -0,0 +1,16 @@
- dropdown_data = groups_projects_more_actions_dropdown_data(source)
- if dropdown_data[:is_group] && can?(current_user, :read_group, @group)
- id = @group.id
%span.gl-sr-only{ itemprop: 'identifier', data: { testid: 'group-id-content' } }
= s_('GroupPage|Group ID: %{id}') % { id: id }
- elsif can?(current_user, :read_project, @project)
- id = @project.id
%span.gl-sr-only{ itemprop: 'identifier', data: { testid: 'project-id-content' } }
= s_('ProjectPage|Project ID: %{id}') % { id: id }
- if id || current_user
.js-groups-projects-more-actions-dropdown{ data: dropdown_data }

View File

@ -17,11 +17,11 @@
= markdown_field(group, :description)
.stats.gl-text-gray-500.gl-flex-shrink-0
%span.gl-ml-5
= sprite_icon('bookmark', css_class: 'gl-vertical-align-text-bottom')
%span.gl-ml-5.has-tooltip{ title: _('Projects') }
= sprite_icon('project', css_class: 'gl-vertical-align-text-bottom')
= number_with_delimiter(group.projects.non_archived.count)
%span.gl-ml-5
%span.gl-ml-5.has-tooltip{ title: _('Users') }
= sprite_icon('users', css_class: 'gl-vertical-align-text-bottom')
= number_with_delimiter(group.users.count)

View File

@ -1,18 +0,0 @@
- model_name = source.model_name.to_s.downcase
- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) # rubocop: disable CodeReuse/ActiveRecord
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
= link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete,
aria: { label: link_text },
data: { confirm: leave_confirmation_message(source), confirm_btn_variant: 'danger', qa_selector: 'leave_group_link' },
class: 'js-leave-link'
- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
- if can?(current_user, :withdraw_member_access_request, requester)
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
method: :delete,
data: { confirm: remove_member_message(requester) }
- elsif source.request_access_enabled && can?(current_user, :request_access, source)
= link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
method: :post,
data: { testid: 'request-access-link' }

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/406559
milestone: '16.2'
type: development
group: group::import and integrate
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: quick_action_refactor
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130782
rollout_issue_url:
milestone: '16.7'
type: development
group: group::project management
default_enabled: false

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class PrepareIndexesForPartitioningCiPipelineVariables < Gitlab::Database::Migration[2.2]
milestone '16.7'
disable_ddl_transaction!
TABLE_NAME = :ci_pipeline_variables
PK_INDEX_NAME = :index_ci_pipeline_variables_on_id_partition_id_unique
UNIQUE_INDEX_NAME = :index_pipeline_variables_on_pipeline_id_key_partition_id_unique
def up
add_concurrent_index(TABLE_NAME, %i[id partition_id], unique: true, name: PK_INDEX_NAME)
add_concurrent_index(TABLE_NAME, %i[pipeline_id key partition_id], unique: true, name: UNIQUE_INDEX_NAME)
end
def down
remove_concurrent_index_by_name(TABLE_NAME, PK_INDEX_NAME)
remove_concurrent_index_by_name(TABLE_NAME, UNIQUE_INDEX_NAME)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class RemoveIndexesWithoutPartitionIdFromCiPipelineVariables < Gitlab::Database::Migration[2.2]
milestone '16.7'
disable_ddl_transaction!
TABLE_NAME = :ci_pipeline_variables
OLD_UNIQUE_INDEX_NAME = :index_ci_pipeline_variables_on_pipeline_id_and_key
def up
remove_concurrent_index_by_name(TABLE_NAME, OLD_UNIQUE_INDEX_NAME)
end
def down
add_concurrent_index(TABLE_NAME, %i[pipeline_id key], unique: true, name: OLD_UNIQUE_INDEX_NAME)
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AsyncCreateSupportingIndexForFindingIdBackfill < Gitlab::Database::Migration[2.2]
milestone '16.7'
INDEX_NAME = "tmp_index_vulnerabilities_on_id_finding_id_empty"
# TODO: Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/433253
def up
prepare_async_index(
:vulnerabilities,
:id,
where: "finding_id IS NULL",
name: INDEX_NAME
)
end
def down
unprepare_async_index(
:vulnerabilities,
:id,
name: INDEX_NAME
)
end
end

View File

@ -0,0 +1 @@
1bdc6128604324a7bebec587ed935dfd2e91838f36e3ca68fadf695a48b32d24

View File

@ -0,0 +1 @@
20c7fd677cfa00821b67761f9d406d1bf4cfdf65831c3c96910ccb43986b9926

View File

@ -0,0 +1 @@
2269867e97f1194f376979f964912f386aa5248966601a46e27ebb1b72d9e96a

View File

@ -32174,7 +32174,7 @@ CREATE INDEX index_ci_pipeline_schedules_on_owner_id_and_id_and_active ON ci_pip
CREATE INDEX index_ci_pipeline_schedules_on_project_id ON ci_pipeline_schedules USING btree (project_id);
CREATE UNIQUE INDEX index_ci_pipeline_variables_on_pipeline_id_and_key ON ci_pipeline_variables USING btree (pipeline_id, key);
CREATE UNIQUE INDEX index_ci_pipeline_variables_on_id_partition_id_unique ON ci_pipeline_variables USING btree (id, partition_id);
CREATE INDEX index_ci_pipelines_config_on_pipeline_id ON ci_pipelines_config USING btree (pipeline_id);
@ -33940,6 +33940,8 @@ CREATE INDEX index_personal_access_tokens_on_user_id ON personal_access_tokens U
CREATE INDEX index_pipeline_metadata_on_pipeline_id_name_text_pattern ON ci_pipeline_metadata USING btree (pipeline_id, name text_pattern_ops);
CREATE UNIQUE INDEX index_pipeline_variables_on_pipeline_id_key_partition_id_unique ON ci_pipeline_variables USING btree (pipeline_id, key, partition_id);
CREATE UNIQUE INDEX index_plan_limits_on_plan_id ON plan_limits USING btree (plan_id);
CREATE UNIQUE INDEX index_plans_on_name ON plans USING btree (name);

View File

@ -291,7 +291,7 @@ Deleting old backups... [SKIPPING]
### Backup timestamp
The backup archive is saved in `backup_path`, which is specified in the
`config/gitlab.yml` file. The default path is `/var/opt/gitlab/backups`. The filename is `[TIMESTAMP]_gitlab_backup.tar`,
`config/gitlab.yml` file. The default path is `/var/opt/gitlab/backups`. The file name is `[TIMESTAMP]_gitlab_backup.tar`,
where `TIMESTAMP` identifies the time at which each backup was created, plus
the GitLab version. The timestamp is needed if you need to restore GitLab and
multiple backups are available.
@ -327,15 +327,15 @@ To use the `copy` strategy instead of the default streaming strategy, specify
sudo gitlab-backup create STRATEGY=copy
```
#### Backup filename
#### Backup file name
WARNING:
If you use a custom backup filename, you can't
If you use a custom backup file name, you can't
[limit the lifetime of the backups](#limit-backup-lifetime-for-local-files-prune-old-backups).
By default, a backup file is created according to the specification in the
previous [Backup timestamp](#backup-timestamp) section. You can, however,
override the `[TIMESTAMP]` portion of the filename by setting the `BACKUP`
override the `[TIMESTAMP]` portion of the file name by setting the `BACKUP`
environment variable. For example:
```shell
@ -359,7 +359,7 @@ Caveats:
- The compression command is used in a pipeline, so your custom command must output to `stdout`.
- If you specify a command that is not packaged with GitLab, then you must install it yourself.
- The resultant filenames will still end in `.gz`.
- The resultant file names will still end in `.gz`.
- The default decompression command, used during restore, is `gzip -cd`. Therefore if you override the compression command to use a format that cannot be decompressed by `gzip -cd`, you must override the decompression command during restore.
##### Default compression: Gzip with fastest method
@ -440,7 +440,7 @@ sudo gitlab-backup restore DECOMPRESS_CMD="zstd --decompress --stdout"
To ensure the generated archive is transferable by rsync, you can set the `GZIP_RSYNCABLE=yes`
option. This sets the `--rsyncable` option to `gzip`, which is useful only in
combination with setting [the Backup filename option](#backup-filename).
combination with setting [the Backup file name option](#backup-file-name).
The `--rsyncable` option in `gzip` isn't guaranteed to be available
on all distributions. To verify that it's available in your distribution, run
@ -615,8 +615,8 @@ to create an incremental backup from:
- In GitLab 14.9 and 14.10, use the `BACKUP=<timestamp_of_backup>` option to choose the backup to use. The chosen previous backup is overwritten.
- In GitLab 15.0 and later, use the `PREVIOUS_BACKUP=<timestamp_of_backup>` option to choose the backup to use. By default, a backup file is created
as documented in the [Backup timestamp](#backup-timestamp) section. You can override the `[TIMESTAMP]` portion of the filename by setting the
[`BACKUP` environment variable](#backup-filename).
as documented in the [Backup timestamp](#backup-timestamp) section. You can override the `[TIMESTAMP]` portion of the file name by setting the
[`BACKUP` environment variable](#backup-file-name).
To create an incremental backup, run:
@ -1208,7 +1208,7 @@ When troubleshooting backup problems, however, replace `CRON=1` with `--trace` t
#### Limit backup lifetime for local files (prune old backups)
WARNING:
The process described in this section doesn't work if you used a [custom filename](#backup-filename)
The process described in this section doesn't work if you used a [custom file name](#backup-file-name)
for your backups.
To prevent regular backups from using all your disk space, you may want to set a limited lifetime
@ -1770,16 +1770,16 @@ During backup, you can get the `File name too long` error ([issue #354984](https
Problem: <class 'OSError: [Errno 36] File name too long:
```
This problem stops the backup script from completing. To fix this problem, you must truncate the filenames causing the problem. A maximum of 246 characters, including the file extension, is permitted.
This problem stops the backup script from completing. To fix this problem, you must truncate the file names causing the problem. A maximum of 246 characters, including the file extension, is permitted.
WARNING:
The steps in this section can potentially lead to **data loss**. All steps must be followed strictly in the order given.
Consider opening a [Support Request](https://support.gitlab.com/hc/en-us/requests/new) if you're a Premium or Ultimate customer.
Truncating filenames to resolve the error involves:
Truncating file names to resolve the error involves:
- Cleaning up remote uploaded files that aren't tracked in the database.
- Truncating the filenames in the database.
- Truncating the file names in the database.
- Rerunning the backup task.
#### Clean up remote uploaded files
@ -1803,15 +1803,15 @@ To fix these files, you must clean up all remote uploaded files that are in the
bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production DRY_RUN=false
```
#### Truncate the filenames referenced by the database
#### Truncate the file names referenced by the database
You must truncate the files referenced by the database that are causing the problem. The filenames referenced by the database are stored:
You must truncate the files referenced by the database that are causing the problem. The file names referenced by the database are stored:
- In the `uploads` table.
- In the references found. Any reference found from other database tables and columns.
- On the file system.
Truncate the filenames in the `uploads` table:
Truncate the file names in the `uploads` table:
1. Enter the database console:
@ -1839,9 +1839,9 @@ Truncate the filenames in the `uploads` table:
sudo -u git -H bundle exec rails dbconsole -e production
```
1. Search the `uploads` table for filenames longer than 246 characters:
1. Search the `uploads` table for file names longer than 246 characters:
The following query selects the `uploads` records with filenames longer than 246 characters in batches of 0 to 10000. This improves the performance on large GitLab instances with tables having thousand of records.
The following query selects the `uploads` records with file names longer than 246 characters in batches of 0 to 10000. This improves the performance on large GitLab instances with tables having thousand of records.
```sql
CREATE TEMP TABLE uploads_with_long_filenames AS
@ -1892,7 +1892,7 @@ Truncate the filenames in the `uploads` table:
After you validate the batch results, you must change the batch size (`row_id`) using the following sequence of numbers (10000 to 20000). Repeat this process until you reach the last record in the `uploads` table.
1. Rename the files found in the `uploads` table from long filenames to new truncated filenames. The following query rolls back the update so you can check the results safely in a transaction wrapper:
1. Rename the files found in the `uploads` table from long file names to new truncated file names. The following query rolls back the update so you can check the results safely in a transaction wrapper:
```sql
CREATE TEMP TABLE uploads_with_long_filenames AS
@ -1927,7 +1927,7 @@ Truncate the filenames in the `uploads` table:
After you validate the batch update results, you must change the batch size (`row_id`) using the following sequence of numbers (10000 to 20000). Repeat this process until you reach the last record in the `uploads` table.
1. Validate that the new filenames from the previous query are the expected ones. If you are sure you want to truncate the records found in the previous step to 246 characters, run the following:
1. Validate that the new file names from the previous query are the expected ones. If you are sure you want to truncate the records found in the previous step to 246 characters, run the following:
WARNING:
The following action is **irreversible**.
@ -1959,9 +1959,9 @@ Truncate the filenames in the `uploads` table:
After you finish the batch update, you must change the batch size (`updatable_uploads.row_id`) using the following sequence of numbers (10000 to 20000). Repeat this process until you reach the last record in the `uploads` table.
Truncate the filenames in the references found:
Truncate the file names in the references found:
1. Check if those records are referenced somewhere. One way to do this is to dump the database and search for the parent directory name and filename:
1. Check if those records are referenced somewhere. One way to do this is to dump the database and search for the parent directory name and file name:
1. To dump your database, you can use the following command as an example:
@ -1969,15 +1969,15 @@ Truncate the filenames in the references found:
pg_dump -h /var/opt/gitlab/postgresql/ -d gitlabhq_production > gitlab-dump.tmp
```
1. Then you can search for the references using the `grep` command. Combining the parent directory and the filename can be a good idea. For example:
1. Then you can search for the references using the `grep` command. Combining the parent directory and the file name can be a good idea. For example:
```shell
grep public/alongfilenamehere.txt gitlab-dump.tmp
```
1. Replace those long filenames using the new filenames obtained from querying the `uploads` table.
1. Replace those long file names using the new file names obtained from querying the `uploads` table.
Truncate the filenames on the file system. You must manually rename the files in your file system to the new filenames obtained from querying the `uploads` table.
Truncate the file names on the file system. You must manually rename the files in your file system to the new file names obtained from querying the `uploads` table.
#### Re-run the backup task

View File

@ -96,7 +96,7 @@ To back up the Git repositories:
sudo gitlab-backup create SKIP=db
```
The resulting tar file will include only the Git repositories and some metadata. Blobs such as uploads, artifacts, and LFS do not need to be explicitly skipped, because the command does not back up object storage by default. The tar file will be created in the [`/var/opt/gitlab/backups` directory](https://docs.gitlab.com/omnibus/settings/backups.html#creating-an-application-backup) and [the filename will end in `_gitlab_backup.tar`](backup_gitlab.md#backup-timestamp).
The resulting tar file will include only the Git repositories and some metadata. Blobs such as uploads, artifacts, and LFS do not need to be explicitly skipped, because the command does not back up object storage by default. The tar file will be created in the [`/var/opt/gitlab/backups` directory](https://docs.gitlab.com/omnibus/settings/backups.html#creating-an-application-backup) and [the file name will end in `_gitlab_backup.tar`](backup_gitlab.md#backup-timestamp).
Since we configured uploading backups to remote cloud storage, the tar file will be uploaded to the remote region and deleted from disk.

View File

@ -214,12 +214,26 @@ Make sure the AWS KMS keys are replicated to your desired primary, secondary and
## Configuration changes
### Configuration change policy
Configuration changes are batched up and applied during your environment's weekly four-hour maintenance
window.
To have a change considered for an upcoming weekly maintenance window, all required information
must be submitted in full two business days before the start of the window.
If there is insufficient time to complete a configuration change during the weekly maintenance
window, it will postponed to the following week.
Changes cannot be applied outside of a weekly maintenance window unless it qualifies for
[emergency support](https://about.gitlab.com/support/#how-to-engage-emergency-support).
### Making configuration changes
Switchboard empowers the user to make limited configuration changes to their Dedicated Tenant Instance. As Switchboard matures further configuration changes will be made available.
To change or update the configuration of your GitLab Dedicated instance, use Switchboard following the instructions in the relevant section or open a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) with your request. You can request configuration changes for the options originally specified during onboarding, or for any of the following optional features.
The turnaround time to process configuration change requests is [documented in the GitLab handbook](https://about.gitlab.com/handbook/engineering/infrastructure/team/gitlab-dedicated/#handling-configuration-changes-for-tenant-environments).
### Inbound Private Link
[AWS Private Link](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html) allows users and applications in your VPC on AWS to securely connect to the GitLab Dedicated endpoint without network traffic going over the public internet.

View File

@ -971,7 +971,7 @@ By default, the cache storage directory is set to a subdirectory of the first Gi
defined in the configuration file.
Multiple Gitaly processes can use the same directory for cache storage. Each Gitaly process
uses a unique random string as part of the cache filenames it creates. This means:
uses a unique random string as part of the cache file names it creates. This means:
- They do not collide.
- They do not reuse another process's files.

View File

@ -50,7 +50,7 @@ default value. The default value depends on the GitLab version.
Network latency for Gitaly Cluster should ideally be measurable in single-digit milliseconds. Latency is particularly
important for:
- Gitaly node health checks. Nodes must be able to respond 1 second or faster.
- Gitaly node health checks. Nodes must be able to respond within 1 second.
- Reference transactions that enforce [strong consistency](index.md#strong-consistency). Lower latencies mean Gitaly
nodes can agree on changes faster.

View File

@ -576,7 +576,7 @@ To determine the primary node of a repository:
Praefect node:
```shell
curl localhost:9652/metrics | grep gitaly_praefect_primaries`
curl localhost:9652/metrics | grep gitaly_praefect_primaries
```
### View repository metadata

View File

@ -961,7 +961,7 @@ Reports that go over the 20 MB limit aren't loaded. Affected reports:
### Maximum file size indexed
You can set a limit on the content of repository files that are indexed in
Elasticsearch. Any files larger than this limit only index the filename.
Elasticsearch. Any files larger than this limit only index the file name.
The file content is neither indexed nor searchable.
Setting a limit helps reduce the memory usage of the indexing processes and

View File

@ -44,7 +44,7 @@ If you have a license, you can also import it when you install GitLab.
- For self-compiled installations:
- Place the `Gitlab.gitlab-license` file in the `config/` directory.
- To specify a custom location and filename for the license, set the
- To specify a custom location and file name for the license, set the
`GITLAB_LICENSE_FILE` environment variable with the path to the file:
```shell
@ -53,7 +53,7 @@ If you have a license, you can also import it when you install GitLab.
- For Linux package installations:
- Place the `Gitlab.gitlab-license` file in the `/etc/gitlab/` directory.
- To specify a custom location and filename for the license, add this entry to `gitlab.rb`:
- To specify a custom location and file name for the license, add this entry to `gitlab.rb`:
```ruby
gitlab_rails['initial_license_file'] = "/path/to/license/file"

View File

@ -24,7 +24,7 @@ include use cases targeted for parsing GitLab log files.
## Parsing Logs
The examples listed below address their respective log files by
their relative Linux package installation paths and default filenames.
their relative Linux package installation paths and default file names.
Find the respective full paths in the [GitLab logs sections](../logs/index.md#production_jsonlog).
### General Commands

View File

@ -707,7 +707,7 @@ irb(#<Project>)> web_url
The `gitlab-rails` command executes Rails Runner using a non-root account and group, by default: `git:git`.
If the non-root account cannot find the Ruby script filename passed to `gitlab-rails runner`
If the non-root account cannot find the Ruby script file name passed to `gitlab-rails runner`
you may get a syntax error, not an error that the file couldn't be accessed.
A common reason for this is that the script has been put in the root account's home directory.

View File

@ -179,7 +179,7 @@ Files stored in an S3-compatible endpoint can have the same advantages as
#### Avatars
Each file is stored in a directory that matches the `id` assigned to it in the database. The
filename is always `avatar.png` for user avatars. When an avatar is replaced, the `Upload` model is
file name is always `avatar.png` for user avatars. When an avatar is replaced, the `Upload` model is
destroyed and a new one takes place with a different `id`.
#### CI/CD artifacts

View File

@ -49,7 +49,7 @@ To set server hooks for a repository:
example, if the script is in Ruby the shebang is probably `#!/usr/bin/env ruby`.
- To create a single server hook, create a file with a name that matches the hook type. For example, for a
`pre-receive` server hook, the filename should be `pre-receive` with no extension.
`pre-receive` server hook, the file name should be `pre-receive` with no extension.
- To create many server hooks, create a directory for the hooks that matches the hook type. For example, for a
`pre-receive` server hook, the directory name should be `pre-receive.d`. Put the files for the hook in that
directory.
@ -84,7 +84,7 @@ To create server hooks for a repository:
1. On the file system, create a new directory in the correct location called `custom_hooks`.
1. In the new `custom_hooks` directory:
- To create a single server hook, create a file with a name that matches the hook type. For example, for a
`pre-receive` server hook, the filename should be `pre-receive` with no extension.
`pre-receive` server hook, the file name should be `pre-receive` with no extension.
- To create many server hooks, create a directory for the hooks that matches the hook type. For example, for a
`pre-receive` server hook, the directory name should be `pre-receive.d`. Put the files for the hook in that directory.
1. **Make the server hook files executable** and ensure that they are owned by the Git user.
@ -155,7 +155,7 @@ To create a global server hook for all repositories:
1. Make the hook file executable, ensure that it's owned by the Git user, and ensure it does not match the backup file
pattern (`*~`).
If the server hook code is properly implemented, it should execute when the Git hook is next triggered. Hooks are executed in alphabetical order by filename in the hook type
If the server hook code is properly implemented, it should execute when the Git hook is next triggered. Hooks are executed in alphabetical order by file name in the hook type
subdirectories.
## Remove server hooks for a repository

View File

@ -982,7 +982,7 @@ Parameters for multiline comments only:
A line code is of the form `<SHA>_<old>_<new>`, like this: `adc83b19e793491b1c6ea0fd8b46cd9f32e292fc_5_5`
- `<SHA>` is the SHA1 hash of the filename.
- `<SHA>` is the SHA1 hash of the file name.
- `<old>` is the line number before the change.
- `<new>` is the line number after the change.

View File

@ -9115,6 +9115,29 @@ The edge type for [`CiCatalogResourceVersion`](#cicatalogresourceversion).
| <a id="cicatalogresourceversionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cicatalogresourceversionedgenode"></a>`node` | [`CiCatalogResourceVersion`](#cicatalogresourceversion) | The item at the end of the edge. |
#### `CiCatalogResourcesComponentConnection`
The connection type for [`CiCatalogResourcesComponent`](#cicatalogresourcescomponent).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourcescomponentconnectionedges"></a>`edges` | [`[CiCatalogResourcesComponentEdge]`](#cicatalogresourcescomponentedge) | A list of edges. |
| <a id="cicatalogresourcescomponentconnectionnodes"></a>`nodes` | [`[CiCatalogResourcesComponent]`](#cicatalogresourcescomponent) | A list of nodes. |
| <a id="cicatalogresourcescomponentconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiCatalogResourcesComponentEdge`
The edge type for [`CiCatalogResourcesComponent`](#cicatalogresourcescomponent).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourcescomponentedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cicatalogresourcescomponentedgenode"></a>`node` | [`CiCatalogResourcesComponent`](#cicatalogresourcescomponent) | The item at the end of the edge. |
#### `CiConfigGroupConnection`
The connection type for [`CiConfigGroup`](#ciconfiggroup).
@ -15309,12 +15332,34 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="cicatalogresourceversionauthor"></a>`author` **{warning-solid}** | [`UserCore`](#usercore) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. User that created the version. |
| <a id="cicatalogresourceversioncommit"></a>`commit` **{warning-solid}** | [`Commit`](#commit) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Commit associated with the version. |
| <a id="cicatalogresourceversioncomponents"></a>`components` **{warning-solid}** | [`CiCatalogResourcesComponentConnection`](#cicatalogresourcescomponentconnection) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Components belonging to the catalog resource. |
| <a id="cicatalogresourceversioncreatedat"></a>`createdAt` **{warning-solid}** | [`Time`](#time) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Timestamp of when the version was created. |
| <a id="cicatalogresourceversionid"></a>`id` **{warning-solid}** | [`CiCatalogResourcesVersionID!`](#cicatalogresourcesversionid) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Global ID of the version. |
| <a id="cicatalogresourceversionreleasedat"></a>`releasedAt` **{warning-solid}** | [`Time`](#time) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Timestamp of when the version was released. |
| <a id="cicatalogresourceversiontagname"></a>`tagName` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Name of the tag associated with the version. |
| <a id="cicatalogresourceversiontagpath"></a>`tagPath` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Relative web path to the tag associated with the version. |
### `CiCatalogResourcesComponent`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourcescomponentid"></a>`id` **{warning-solid}** | [`CiCatalogResourcesComponentID!`](#cicatalogresourcescomponentid) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. ID of the component. |
| <a id="cicatalogresourcescomponentinputs"></a>`inputs` **{warning-solid}** | [`[CiCatalogResourcesComponentsInput!]`](#cicatalogresourcescomponentsinput) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Inputs for the component. |
| <a id="cicatalogresourcescomponentname"></a>`name` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Name of the component. |
| <a id="cicatalogresourcescomponentpath"></a>`path` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Path used to include the component. |
### `CiCatalogResourcesComponentsInput`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourcescomponentsinputdefault"></a>`default` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Default value for the input. |
| <a id="cicatalogresourcescomponentsinputname"></a>`name` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Name of the input. |
| <a id="cicatalogresourcescomponentsinputrequired"></a>`required` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Indicates if an input is required. |
### `CiConfig`
#### Fields
@ -31753,6 +31798,12 @@ A `CiCatalogResourceID` is a global ID. It is encoded as a string.
An example `CiCatalogResourceID` is: `"gid://gitlab/Ci::Catalog::Resource/1"`.
### `CiCatalogResourcesComponentID`
A `CiCatalogResourcesComponentID` is a global ID. It is encoded as a string.
An example `CiCatalogResourcesComponentID` is: `"gid://gitlab/Ci::Catalog::Resources::Component/1"`.
### `CiCatalogResourcesVersionID`
A `CiCatalogResourcesVersionID` is a global ID. It is encoded as a string.

View File

@ -91,7 +91,7 @@ GET /groups/:id/packages
| Attribute | Type | Required | Description |
|:----------------------|:---------------|:---------|:------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding). |
| `exclude_subgroups` | boolean | false | If the parameter is included as true, packages from projects from subgroups are not listed. Default is `false`. |
| `exclude_subgroups` | boolean | no | If the parameter is included as true, packages from projects from subgroups are not listed. Default is `false`. |
| `order_by` | string | no | The field to use as order. One of `created_at` (default), `name`, `version`, `type`, or `project_path`. |
| `sort` | string | no | The direction of the order, either `asc` (default) for ascending order or `desc` for descending order. |
| `package_type` | string | no | Filter the returned packages by type. One of `conan`, `maven`, `npm`, `pypi`, `composer`, `nuget`, `helm`, or `golang`. |

View File

@ -2940,7 +2940,7 @@ POST /projects/:id/push_rule
| `commit_message_negative_regex` | string | No | No commit message is allowed to match this, for example `ssh\:\/\/`. |
| `commit_message_regex` | string | No | All commit messages must match this, for example `Fixed \d+\..*`. |
| `deny_delete_tag` | boolean | No | Deny deleting a tag. |
| `file_name_regex` | string | No | All committed filenames must **not** match this, for example `(jar|exe)$`. |
| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar|exe)$`. |
| `max_file_size` | integer | No | Maximum file size (MB). |
| `member_check` | boolean | No | Restrict commits by author (email) to existing GitLab users. |
| `prevent_secrets` | boolean | No | GitLab rejects any files that are likely to contain secrets. |
@ -2964,7 +2964,7 @@ PUT /projects/:id/push_rule
| `commit_message_negative_regex` | string | No | No commit message is allowed to match this, for example `ssh\:\/\/`. |
| `commit_message_regex` | string | No | All commit messages must match this, for example `Fixed \d+\..*`. |
| `deny_delete_tag` | boolean | No | Deny deleting a tag. |
| `file_name_regex` | string | No | All committed filenames must **not** match this, for example `(jar|exe)$`. |
| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar|exe)$`. |
| `max_file_size` | integer | No | Maximum file size (MB). |
| `member_check` | boolean | No | Restrict commits by author (email) to existing GitLab users. |
| `prevent_secrets` | boolean | No | GitLab rejects any files that are likely to contain secrets. |

View File

@ -299,7 +299,7 @@ Example response:
```
NOTE:
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the filename and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the file name and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
### Scope: commits **(PREMIUM ALL)**
@ -375,7 +375,7 @@ Example response:
```
NOTE:
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the filename and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the file name and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
### Scope: notes **(PREMIUM ALL)**
@ -690,7 +690,7 @@ Example response:
```
NOTE:
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the filename and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the file name and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
### Scope: `commits` **(PREMIUM ALL)**
@ -766,7 +766,7 @@ Example response:
```
NOTE:
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the filename and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the file name and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
### Scope: `notes` **(PREMIUM ALL)**
@ -1071,12 +1071,12 @@ Filters are available for this scope:
To use a filter, include it in your query. For example: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.
Wiki blobs searches are performed on both filenames and contents. Search
Wiki blobs searches are performed on both file names and contents. Search
results:
- Found in filenames are displayed before results found in contents.
- Found in file names are displayed before results found in contents.
- May contain multiple matches for the same blob because the search string
might be found in both the filename and content, or might appear multiple
might be found in both the file name and content, or might appear multiple
times in the content.
```shell
@ -1103,7 +1103,7 @@ Example response:
```
NOTE:
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` are intended to be only the filename and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
`filename` is deprecated in favor of `path`. Both return the full path of the file inside the repository, but in the future `filename` is intended to be only the file name and not the full path. For details, see [issue 34521](https://gitlab.com/gitlab-org/gitlab/-/issues/34521).
### Scope: `commits` **(PREMIUM ALL)**
@ -1155,11 +1155,11 @@ Filters are available for this scope:
To use a filter, include it in your query. For example: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.
Blobs searches are performed on both filenames and contents. Search results:
Blobs searches are performed on both file names and contents. Search results:
- Found in filenames are displayed before results found in contents.
- Found in file names are displayed before results found in contents.
- May contain multiple matches for the same blob because the search string
might be found in both the filename and content, or might appear multiple
might be found in both the file name and content, or might appear multiple
times in the content.
```shell

View File

@ -128,7 +128,7 @@ Supported attributes:
| Attribute | Type | Required | Description |
|-----------------|----------------|----------|-------------|
| `project_id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `name` | string | Yes | The name of the file being uploaded. The filename must be unique in the project. |
| `name` | string | Yes | The name of the file being uploaded. The file name must be unique in the project. |
| `file` | file | Yes | The file being uploaded (5 MB limit). |
Example request:

View File

@ -133,8 +133,7 @@ is planned to add the ability to create a MR from here.
should be committed to
- `default-branch` (required): The branch that will be pre-selected during
the commit step. This can be changed by the user.
- `default-filename` (optional, default: `.gitlab-ci.yml`): The Filename
to be used for the file. This can be overridden in the template file.
- `default-filename` (optional, default: `.gitlab-ci.yml`): The file name to be used for the file. This can be overridden in the template file.
### Events

View File

@ -629,8 +629,7 @@ You can disable a specific Vale linting rule or all Vale linting rules for any p
document:
- To disable a specific rule, add a `<!-- vale gitlab.rulename = NO -->` tag before the text, and a
`<!-- vale gitlab.rulename = YES -->` tag after the text, replacing `rulename` with the filename
of a test in the
`<!-- vale gitlab.rulename = YES -->` tag after the text, replacing `rulename` with the file name of a test in the
[GitLab styles](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc/.linting/vale/styles/gitlab)
directory.
- To disable all Vale linting rules, add a `<!-- vale off -->` tag before the text, and a

View File

@ -84,8 +84,7 @@ end
```
In some cases, you must require multiple migration files to use them in your specs. Here, there's no
pattern between your spec file and the other migration file. You can provide the migration filename
like so:
pattern between your spec file and the other migration file. You can provide the migration file name like so:
```ruby
# frozen_string_literal: true

View File

@ -126,26 +126,6 @@ If you set an out of range value, GitLab automatically adjusts it to the default
Badges can be added to a project by Maintainers or Owners, and are visible on the project's overview page.
If you find that you have to add the same badges to several projects, you may want to add them at the [group level](#group-badges).
### Add a badge to a project
To add a new badge to a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Badges**.
1. Select **Add badge**.
1. Under **Link**, enter the URL that the badges should point to.
1. Under **Badge image URL**, enter the URL of the image that should be displayed.
1. Select **Add badge**.
After adding a badge to a project, you can see the badge in the list below the form.
### Edit or delete a project badge
To edit a badge, select **Edit** (**{pencil}**).
To delete a badge, select **Delete** (**{remove}**).
### Example project badge: Pipeline Status
A common project badge presents the GitLab CI pipeline status.
@ -177,26 +157,26 @@ If you need individual badges for each project, either:
- Add the badge at the [project level](#project-badges).
- Use [placeholders](#placeholders).
### Add a badge to a group
## View badges
To add a new badge to a group:
To view badges available in a project or group:
1. On the left sidebar, select **Search or go to** and find your group.
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Settings > General**.
1. Expand **Badges**.
## Add a badge
To add a new badge to a project or group:
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Settings > General**.
1. Expand **Badges**.
1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed.
1. Select **Add badge**.
After adding a badge to a group, you can see it in the list below the form.
### Edit or delete a group badge
To edit a badge, select **Edit** (**{pencil}**).
To delete a badge, select **Delete** (**{remove}**).
Badges associated with a group can be edited or deleted only at the [group level](#group-badges).
1. In the **Name** text box, enter the name of your badge.
1. In the **Link** text box, enter the URL that the badges should point to.
1. In the **Badge image URL** text box, enter the URL of the image you want to display for the badge.
1. Select **Add badge**.
## View the URL of pipeline badges
@ -283,6 +263,31 @@ To add a new badge with a custom image to a group or project:
To learn how to use custom images generated through a pipeline, see the documentation on
[accessing the latest job artifacts by URL](../../ci/jobs/job_artifacts.md#from-a-url).
## Edit a badge
To edit a badge in a project or group:
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Settings > General**.
1. Expand **Badges**.
1. Next to the badge you want to edit, select **Edit** (**{pencil}**).
1. Edit the **Name**, **Link**, or **Badge image URL**.
1. Select **Save changes**.
## Delete a badge
To delete a badge in a project or group:
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Settings > General**.
1. Expand **Badges**.
1. Next to the badge you want to delete, select **Delete** (**{remove}**).
1. On the confirmation dialog, select **Delete badge**.
1. Select **Save changes**.
NOTE:
Badges associated with a group can be edited or deleted only at the [group level](#group-badges).
## Placeholders
Both the URL a badge points to and the image URL can contain placeholders,
@ -302,9 +307,3 @@ Placeholders allow badges to expose otherwise-private information, such as the
default branch or commit SHA when the project is configured to have a private
repository. This behavior is intentional, as badges are intended to be used publicly. Avoid
using these placeholders if the information is sensitive.
## Configure badges through the API
You can also configure badges via the GitLab API. As in the settings, there is
a distinction between endpoints for badges at the
[project level](../../api/project_badges.md) and [group level](../../api/group_badges.md).

View File

@ -93,7 +93,7 @@ Provide feedback on this experimental feature in [issue 408994](https://gitlab.c
**Data usage**: When you use this feature, the following data is sent to the large language model referenced above:
- Contents of the file
- The filename
- The file name
## Generate suggested tests in merge requests
@ -118,4 +118,4 @@ Feedback on this experimental feature can be provided in [issue 408995](https://
**Data usage**: When you use this feature, the following data is sent to the large language model referenced above:
- Contents of the file
- The filename
- The file name

View File

@ -189,8 +189,7 @@ A merge request is created.
### Add attachments when creating a merge request by email
You can add commits to a merge request by adding
patches as attachments to the email. All attachments with a filename
ending in `.patch` are considered patches and are processed
patches as attachments to the email. All attachments with a file name ending in `.patch` are considered patches and are processed
ordered by name.
The combined size of the patches can be 2 MB.

View File

@ -209,8 +209,7 @@ These files can either be plain text or have the extension of a
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/19515) in GitLab 12.6.
GitLab can render OpenAPI specification files. The filename
must include `openapi` or `swagger` and the extension must be `yaml`,
GitLab can render OpenAPI specification files. The file name must include `openapi` or `swagger` and the extension must be `yaml`,
`yml`, or `json`. The following examples are all correct:
- `openapi.yml`

View File

@ -48,8 +48,7 @@ To configure Git to use your key:
git config --global gpg.format ssh
```
1. Specify which public SSH key to use as the signing key and change the filename
(`~/.ssh/examplekey.pub`) to the location of your key. The filename might
1. Specify which public SSH key to use as the signing key and change the file name (`~/.ssh/examplekey.pub`) to the location of your key. The file name might
differ, depending on how you generated your key:
```shell

View File

@ -77,7 +77,7 @@ relatively quickly to work, and they take you to another page in the project.
| <kbd>g</kbd> + <kbd>n</kbd> | Go to the [repository graph](#repository-graph) page (**Code > Repository graph**). |
| <kbd>g</kbd> + <kbd>d</kbd> | Go to repository charts (**Analyze > Repository analytics**). |
| <kbd>g</kbd> + <kbd>i</kbd> | Go to the project issues list (**Plan > Issues**). |
| <kbd>i</kbd> | Go to the New Issue page (**Pan > Issues**, select **New issue** ). |
| <kbd>i</kbd> | Go to the New Issue page (**Plan > Issues**, select **New issue** ). |
| <kbd>g</kbd> + <kbd>b</kbd> | Go to the project issue boards list (**Plan > Issue boards**). |
| <kbd>g</kbd> + <kbd>m</kbd> | Go to the project [merge requests](project/merge_requests/index.md) list (**Code > Merge requests**). |
| <kbd>g</kbd> + <kbd>p</kbd> | Go to the CI/CD pipelines list (**Build > Pipelines**). |

View File

@ -6,6 +6,21 @@ module Gitlab
class IssueEventImporter
attr_reader :issue_event, :project, :client
SUPPORTED_EVENTS = %w[
assigned
closed
cross-referenced
demilestoned
labeled
milestoned
renamed
reopened
review_request_removed
review_requested
unassigned
unlabeled
].freeze
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
# project - An instance of `Project`.
# client - An instance of `Gitlab::GithubImport::Client`.

View File

@ -29,7 +29,8 @@ module Gitlab
associated = associated.to_h
compose_associated_id!(parent_record, associated)
return if already_imported?(associated)
return if already_imported?(associated) || importer_class::SUPPORTED_EVENTS.exclude?(associated[:event])
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)

View File

@ -9,19 +9,6 @@ module Gitlab
# extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels])
# ```
class Extractor
CODE_REGEX = %r{
(?<code>
# Code blocks:
# ```
# Anything, including `/cmd arg` which are ignored by this filter
# ```
^```
.+?
\n```$
)
}mix
INLINE_CODE_REGEX = %r{
(?<inline_code>
# Inline code on separate rows:
@ -46,23 +33,7 @@ module Gitlab
)
}mix
QUOTE_BLOCK_REGEX = %r{
(?<html>
# Quote block:
# >>>
# Anything, including `/cmd arg` which are ignored by this filter
# >>>
^>>>
.+?
\n>>>$
)
}mix
EXCLUSION_REGEX = %r{#{INLINE_CODE_REGEX} | #{HTML_BLOCK_REGEX}}mix
EXCLUSION_REGEX_ORG = %r{
#{CODE_REGEX} | #{INLINE_CODE_REGEX} | #{HTML_BLOCK_REGEX} | #{QUOTE_BLOCK_REGEX}
}mix
attr_reader :command_definitions, :keep_actions
@ -93,16 +64,10 @@ module Gitlab
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
# msg #=> "hello\n/labels ~foo ~"bar baz"\n\nworld"
# ```
# TODO: target is only needed for feature flag
def extract_commands(content, only: nil, target: nil)
def extract_commands(content, only: nil)
return [content, []] unless content
actor = target&.project&.group if target.respond_to?(:project)
if Feature.enabled?(:quick_action_refactor, actor)
perform_regex(content, only: only)
else
perform_regex_org(content, only: only)
end
perform_regex(content, only: only)
end
# Encloses quick action commands into code span markdown
@ -112,13 +77,7 @@ module Gitlab
def redact_commands(content)
return "" unless content
# TODO: we don't have an actor at this point, so just check the global
# feature flag.
content, _ = if Feature.enabled?(:quick_action_refactor)
perform_regex(content, redact: true)
else
perform_regex_org(content, redact: true)
end
content, _ = perform_regex(content, redact: true)
content
end
@ -171,27 +130,6 @@ module Gitlab
[content.rstrip, commands.reject(&:empty?)]
end
def perform_regex_org(content, only: nil, redact: false)
names = command_names(limit_to_commands: only).map(&:to_s)
sub_names = substitution_names.map(&:to_s)
commands = []
content = content.dup
content.delete!("\r")
content.gsub!(commands_regex(names: names, sub_names: sub_names, use_org_regex: true)) do
command, output = if $~[:substitution]
process_substitutions($~)
else
process_commands($~, redact)
end
commands << command
output
end
[content.rstrip, commands.reject(&:empty?)]
end
def process_commands(matched_text, redact)
output = matched_text[0]
command = []
@ -235,10 +173,9 @@ module Gitlab
# It looks something like:
#
# /^\/(?<cmd>close|reopen|...)(?:( |$))(?<arg>[^\/\n]*)(?:\n|$)/
def commands_regex(names:, sub_names:, use_org_regex: false)
names += ['use_org_regex'] if use_org_regex
def commands_regex(names:, sub_names:)
@commands_regex[names] ||= %r{
#{use_org_regex ? EXCLUSION_REGEX_ORG : EXCLUSION_REGEX}
#{EXCLUSION_REGEX}
|
(?:
# Command such as:

View File

@ -23044,10 +23044,13 @@ msgstr ""
msgid "GroupImport|Unable to process group import file"
msgstr ""
msgid "GroupPage|Copy group ID"
msgid "GroupPage|Copy group ID: %{id}"
msgstr ""
msgid "GroupPage|Group ID: %{group_id}"
msgid "GroupPage|Group ID copied to clipboard."
msgstr ""
msgid "GroupPage|Group ID: %{id}"
msgstr ""
msgid "GroupRoadmap|%{dateWord} No end date"
@ -37491,10 +37494,13 @@ msgstr ""
msgid "ProjectOverview|You must sign in to star a project"
msgstr ""
msgid "ProjectPage|Copy project ID"
msgid "ProjectPage|Copy project ID: %{id}"
msgstr ""
msgid "ProjectPage|Project ID: %{project_id}"
msgid "ProjectPage|Project ID copied to clipboard."
msgstr ""
msgid "ProjectPage|Project ID: %{id}"
msgstr ""
msgid "ProjectPage|Project information"

View File

@ -10,12 +10,6 @@ module QA
view 'app/views/groups/_home_panel.html.haml' do
element 'new-project-button'
element :new_subgroup_button
element :group_id_content
end
view 'app/views/shared/members/_access_request_links.html.haml' do
element :leave_group_link
element 'request-access-link'
end
def click_subgroup(name)
@ -40,15 +34,21 @@ module QA
end
def group_id
find_element(:group_id_content).text.delete('Group ID: ').sub(/\n.*/, '')
find_element('group-id-content').text.delete('Group ID: ').sub(/\n.*/, '')
end
def leave_group
click_element :leave_group_link
click_element 'groups-projects-more-actions-dropdown'
wait_for_requests
click_element 'leave-group-link'
click_confirmation_ok_button
end
def click_request_access
click_element 'groups-projects-more-actions-dropdown'
wait_for_requests
click_element 'request-access-link'
end
end

View File

@ -32,7 +32,6 @@ module QA
view 'app/views/projects/_home_panel.html.haml' do
element 'project-name-content'
element 'project-id-content'
end
view 'app/views/projects/_sidebar.html.haml' do

View File

@ -31,7 +31,7 @@ module QA
stats = imported_project.project_import_status.dig(:stats, :imported)
expect(stats).to eq(
issue: 1,
issue_event: 16,
issue_event: 10,
pull_request: 1,
pull_request_review: 2,
pull_request_review_request: 1,

View File

@ -9,17 +9,22 @@ RSpec.describe 'Groups > Members > Leave group', feature_category: :groups_and_p
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:group) { create(:group) }
let(:more_actions_dropdown) do
find('[data-testid="groups-projects-more-actions-dropdown"] .gl-new-dropdown-custom-toggle')
end
before do
sign_in(user)
end
it 'guest leaves the group' do
it 'guest leaves the group', :js do
group.add_guest(user)
group.add_owner(other_user)
visit group_path(group)
more_actions_dropdown.click
click_link 'Leave group'
accept_gl_confirm(button_text: 'Leave group')
expect(page).to have_current_path(dashboard_groups_path, ignore_query: true)
expect(page).to have_content left_group_message(group)
@ -31,31 +36,33 @@ RSpec.describe 'Groups > Members > Leave group', feature_category: :groups_and_p
group.add_owner(other_user)
visit group_path(group, leave: 1)
accept_gl_confirm(button_text: 'Leave group')
wait_for_all_requests
expect(page).to have_current_path(dashboard_groups_path, ignore_query: true)
expect(group.users).not_to include(user)
end
it 'guest leaves the group as last member' do
it 'guest leaves the group as last member', :js do
group.add_guest(user)
visit group_path(group)
more_actions_dropdown.click
click_link 'Leave group'
accept_gl_confirm(button_text: 'Leave group')
expect(page).to have_current_path(dashboard_groups_path, ignore_query: true)
expect(page).to have_content left_group_message(group)
expect(group.users).not_to include(user)
end
it 'owner leaves the group if they are not the last owner' do
it 'owner leaves the group if they are not the last owner', :js do
group.add_owner(user)
group.add_owner(other_user)
visit group_path(group)
more_actions_dropdown.click
click_link 'Leave group'
accept_gl_confirm(button_text: 'Leave group')
expect(page).to have_current_path(dashboard_groups_path, ignore_query: true)
expect(page).to have_content left_group_message(group)
@ -66,6 +73,7 @@ RSpec.describe 'Groups > Members > Leave group', feature_category: :groups_and_p
group.add_owner(user)
visit group_path(group)
more_actions_dropdown.click
expect(page).not_to have_content 'Leave group'

View File

@ -3,10 +3,15 @@
require 'spec_helper'
RSpec.describe 'Groups > Members > Request access', feature_category: :groups_and_projects do
include Spec::Support::Helpers::ModalHelpers
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :public) }
let!(:project) { create(:project, :private, namespace: group) }
let(:more_actions_dropdown) do
find('[data-testid="groups-projects-more-actions-dropdown"] .gl-new-dropdown-custom-toggle')
end
before do
group.add_owner(owner)
@ -14,15 +19,19 @@ RSpec.describe 'Groups > Members > Request access', feature_category: :groups_an
visit group_path(group)
end
it 'request access feature is disabled' do
it 'request access feature is disabled', :js do
group.update!(request_access_enabled: false)
visit group_path(group)
more_actions_dropdown.click
expect(page).not_to have_content 'Request Access'
end
it 'user can request access to a group' do
perform_enqueued_jobs { click_link 'Request Access' }
it 'user can request access to a group', :js do
perform_enqueued_jobs do
more_actions_dropdown.click
click_link 'Request Access'
end
expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email_or_default]
expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group"
@ -30,18 +39,26 @@ RSpec.describe 'Groups > Members > Request access', feature_category: :groups_an
expect(group.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content 'Your request for access has been queued for review.'
more_actions_dropdown.click
expect(page).to have_content 'Withdraw Access Request'
expect(page).not_to have_content 'Leave group'
end
it 'user does not see private projects' do
perform_enqueued_jobs { click_link 'Request Access' }
it 'user does not see private projects', :js do
perform_enqueued_jobs do
more_actions_dropdown.click
click_link 'Request Access'
end
expect(page).not_to have_content project.name
end
it 'user does not see group in the Dashboard > Groups page' do
perform_enqueued_jobs { click_link 'Request Access' }
it 'user does not see group in the Dashboard > Groups page', :js do
perform_enqueued_jobs do
more_actions_dropdown.click
click_link 'Request Access'
end
visit dashboard_groups_path
@ -49,6 +66,7 @@ RSpec.describe 'Groups > Members > Request access', feature_category: :groups_an
end
it 'user is not listed in the group members page', :js do
more_actions_dropdown.click
click_link 'Request Access'
expect(group.requesters.exists?(user_id: user)).to be_truthy
@ -63,20 +81,24 @@ RSpec.describe 'Groups > Members > Request access', feature_category: :groups_an
end
end
it 'user can withdraw its request for access' do
it 'user can withdraw its request for access', :js do
more_actions_dropdown.click
click_link 'Request Access'
expect(group.requesters.exists?(user_id: user)).to be_truthy
more_actions_dropdown.click
click_link 'Withdraw Access Request'
accept_gl_confirm
expect(group.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the group has been withdrawn.'
expect(group.requesters.exists?(user_id: user)).to be_falsey
end
it 'member does not see the request access button' do
it 'member does not see the request access button', :js do
group.add_owner(user)
visit group_path(group)
more_actions_dropdown.click
expect(page).not_to have_content 'Request Access'
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'user reads pipeline status', :js, feature_category: :groups_and_projects do
RSpec.describe 'user reads pipeline status', :js, feature_category: :continuous_integration do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:v110_pipeline) { create_pipeline('v1.1.0', 'success') }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User browses a job', :js, feature_category: :groups_and_projects do
RSpec.describe 'User browses a job', :js, feature_category: :continuous_integration do
include Spec::Support::Helpers::ModalHelpers
let(:user) { create(:user) }

View File

@ -8,7 +8,7 @@ def visit_jobs_page
wait_for_requests
end
RSpec.describe 'User browses jobs', feature_category: :groups_and_projects do
RSpec.describe 'User browses jobs', feature_category: :continuous_integration do
describe 'Jobs', :js do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User triggers manual job with variables', :js, feature_category: :groups_and_projects do
RSpec.describe 'User triggers manual job with variables', :js, feature_category: :continuous_integration do
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project, :repository, namespace: user.namespace) }

View File

@ -8,16 +8,23 @@ RSpec.describe 'Projects > Members > Group requester cannot request access to pr
let(:owner) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
let(:more_actions_dropdown) do
find('[data-testid="groups-projects-more-actions-dropdown"] .gl-new-dropdown-custom-toggle')
end
before do
group.add_owner(owner)
sign_in(user)
visit group_path(group)
perform_enqueued_jobs { click_link 'Request Access' }
perform_enqueued_jobs do
more_actions_dropdown.click
click_link 'Request Access'
end
visit project_path(project)
end
it 'group requester does not see the request access / withdraw access request button' do
expect(page).not_to have_css '[data-testid="groups-projects-more-actions-dropdown"]'
expect(page).not_to have_content 'Request Access'
expect(page).not_to have_content 'Withdraw Access Request'
end

View File

@ -8,16 +8,21 @@ RSpec.describe 'Projects > Members > Member leaves project', feature_category: :
let(:user) { create(:user) }
let(:project) { create(:project, :repository, :with_namespace_settings) }
let(:more_actions_dropdown) do
find('[data-testid="groups-projects-more-actions-dropdown"] .gl-new-dropdown-custom-toggle')
end
before do
project.add_developer(user)
sign_in(user)
end
it 'user leaves project' do
it 'user leaves project', :js do
visit project_path(project)
more_actions_dropdown.click
click_link 'Leave project'
accept_gl_confirm(button_text: 'Leave project')
expect(page).to have_current_path(dashboard_projects_path, ignore_query: true)
expect(project.users.exists?(user.id)).to be_falsey

View File

@ -10,6 +10,9 @@ RSpec.describe 'Projects > Members > User requests access', :js, feature_categor
let_it_be(:project) { create(:project, :public, :repository) }
let(:owner) { project.first_owner }
let(:more_actions_dropdown) do
find('[data-testid="groups-projects-more-actions-dropdown"] .gl-new-dropdown-custom-toggle')
end
before do
sign_in(user)
@ -17,39 +20,46 @@ RSpec.describe 'Projects > Members > User requests access', :js, feature_categor
visit project_path(project)
end
it 'request access feature is disabled' do
it 'request access feature is disabled', :js do
project.update!(request_access_enabled: false)
visit project_path(project)
more_actions_dropdown.click
expect(page).not_to have_content 'Request Access'
end
it 'user can request access to a project' do
perform_enqueued_jobs { click_link 'Request Access' }
it 'user can request access to a project', :js do
perform_enqueued_jobs do
more_actions_dropdown.click
click_link 'Request Access'
end
expect(ActionMailer::Base.deliveries.map(&:to)).to match_array([[owner.notification_email_or_default], [maintainer.notification_email_or_default]])
expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.full_name} project"
expect(project.requesters.exists?(user_id: user)).to be_truthy
more_actions_dropdown.click
expect(page).to have_content 'Withdraw Access Request'
expect(page).not_to have_content 'Leave Project'
end
context 'code access is restricted' do
it 'user can request access' do
it 'user can request access', :js do
project.project_feature.update!(
repository_access_level: ProjectFeature::PRIVATE,
builds_access_level: ProjectFeature::PRIVATE,
merge_requests_access_level: ProjectFeature::PRIVATE
)
visit project_path(project)
more_actions_dropdown.click
expect(page).to have_content 'Request Access'
end
end
it 'user is not listed in the project members page' do
it 'user is not listed in the project members page', :js do
more_actions_dropdown.click
click_link 'Request Access'
expect(project.requesters.exists?(user_id: user)).to be_truthy
@ -64,13 +74,16 @@ RSpec.describe 'Projects > Members > User requests access', :js, feature_categor
end
end
it 'user can withdraw its request for access' do
it 'user can withdraw its request for access', :js do
more_actions_dropdown.click
click_link 'Request Access'
expect(project.requesters.exists?(user_id: user)).to be_truthy
more_actions_dropdown.click
accept_gl_confirm { click_link 'Withdraw Access Request' }
more_actions_dropdown.click
expect(page).not_to have_content 'Withdraw Access Request'
expect(page).to have_content 'Request Access'
end

View File

@ -7,6 +7,7 @@ export function createMockClient() {
servicesUrl: 'services-url',
operationsUrl: 'operations-url',
metricsUrl: 'metrics-url',
metricsSearchUrl: 'metrics-search-url',
});
Object.getOwnPropertyNames(mockClient)

View File

@ -0,0 +1,173 @@
import { GlDisclosureDropdownItem, GlDisclosureDropdown } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import moreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
describe('moreActionsDropdown', () => {
let wrapper;
const createComponent = ({ provideData = {}, propsData = {} } = {}) => {
wrapper = shallowMountExtended(moreActionsDropdown, {
provide: {
isGroup: false,
id: 1,
leavePath: '',
leaveConfirmMessage: '',
withdrawPath: '',
withdrawConfirmMessage: '',
requestAccessPath: '',
...provideData,
},
propsData,
stubs: {
GlDisclosureDropdownItem,
},
});
};
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const showDropdown = () => {
findDropdown().vm.$emit('show');
};
describe('copy id', () => {
describe('project namespace type', () => {
beforeEach(async () => {
createComponent({
provideData: {
id: 22,
},
});
await showDropdown();
});
it('has correct test id `copy-project-id`', () => {
expect(wrapper.findByTestId('copy-project-id').exists()).toBe(true);
expect(wrapper.findByTestId('copy-group-id').exists()).toBe(false);
});
it('renders copy project id with correct id', () => {
expect(wrapper.findByTestId('copy-project-id').text()).toBe('Copy project ID: 22');
});
});
describe('group namespace type', () => {
beforeEach(async () => {
createComponent({
provideData: {
isGroup: true,
id: 11,
},
});
await showDropdown();
});
it('has correct test id `copy-group-id`', () => {
expect(wrapper.findByTestId('copy-project-id').exists()).toBe(false);
expect(wrapper.findByTestId('copy-group-id').exists()).toBe(true);
});
it('renders copy group id with correct id', () => {
expect(wrapper.findByTestId('copy-group-id').text()).toBe('Copy group ID: 11');
});
});
});
describe('request access', () => {
it('does not render request access link', async () => {
createComponent();
await showDropdown();
expect(wrapper.findByTestId('request-access-link').exists()).toBe(false);
});
it('renders request access link', async () => {
createComponent({
provideData: {
requestAccessPath: 'http://request.path/path',
},
});
await showDropdown();
expect(wrapper.findByTestId('request-access-link').text()).toBe('Request Access');
expect(wrapper.findByTestId('request-access-link').attributes('href')).toBe(
'http://request.path/path',
);
});
});
describe('withdraw access', () => {
it('does not render withdraw access link', async () => {
createComponent();
await showDropdown();
expect(wrapper.findByTestId('withdraw-access-link').exists()).toBe(false);
});
it('renders withdraw access link', async () => {
createComponent({
provideData: {
withdrawPath: 'http://withdraw.path/path',
},
});
await showDropdown();
expect(wrapper.findByTestId('withdraw-access-link').text()).toBe('Withdraw Access Request');
expect(wrapper.findByTestId('withdraw-access-link').attributes('href')).toBe(
'http://withdraw.path/path',
);
});
});
describe('leave access', () => {
it('does not render leave link', async () => {
createComponent();
await showDropdown();
expect(wrapper.findByTestId('leave-project-link').exists()).toBe(false);
});
it('renders leave link', async () => {
createComponent({
provideData: {
leavePath: 'http://leave.path/path',
},
});
await showDropdown();
expect(wrapper.findByTestId('leave-project-link').exists()).toBe(true);
expect(wrapper.findByTestId('leave-project-link').text()).toBe('Leave project');
expect(wrapper.findByTestId('leave-project-link').attributes('href')).toBe(
'http://leave.path/path',
);
});
describe('when `isGroup` is set to `false`', () => {
it('use testid `leave-project-link`', async () => {
createComponent({
provideData: {
leavePath: 'http://leave.path/path',
},
});
await showDropdown();
expect(wrapper.findByTestId('leave-project-link').exists()).toBe(true);
expect(wrapper.findByTestId('leave-group-link').exists()).toBe(false);
});
});
describe('when `isGroup` is set to `true`', () => {
it('use testid `leave-group-link`', async () => {
createComponent({
provideData: {
isGroup: true,
leavePath: 'http://leave.path/path',
},
});
await showDropdown();
expect(wrapper.findByTestId('leave-project-link').exists()).toBe(false);
expect(wrapper.findByTestId('leave-group-link').exists()).toBe(true);
});
});
});
});

View File

@ -18,6 +18,7 @@ describe('buildClient', () => {
const servicesUrl = 'https://example.com/services';
const operationsUrl = 'https://example.com/services/$SERVICE_NAME$/operations';
const metricsUrl = 'https://example.com/metrics';
const metricsSearchUrl = 'https://example.com/metrics/search';
const FETCHING_TRACES_ERROR = 'traces are missing/invalid in the response';
const apiConfig = {
@ -26,6 +27,7 @@ describe('buildClient', () => {
servicesUrl,
operationsUrl,
metricsUrl,
metricsSearchUrl,
};
const getQueryParam = () => decodeURIComponent(axios.get.mock.calls[0][1].params.toString());
@ -531,4 +533,40 @@ describe('buildClient', () => {
expectErrorToBeReported(new Error(FETCHING_METRICS_ERROR));
});
});
describe('fetchMetric', () => {
it('fetches the metric from the API', async () => {
const data = { results: [] };
axiosMock.onGet(metricsSearchUrl).reply(200, data);
const result = await client.fetchMetric('name', 'type');
expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(metricsSearchUrl, {
withCredentials: true,
params: new URLSearchParams({ mname: 'name', mtype: 'type' }),
});
expect(result).toEqual(data.results);
});
it('rejects if results is missing from the response', async () => {
axiosMock.onGet(metricsSearchUrl).reply(200, {});
const e = 'metrics are missing/invalid in the response';
await expect(client.fetchMetric('name', 'type')).rejects.toThrow(e);
expectErrorToBeReported(new Error(e));
});
it('rejects if metric name is missing', async () => {
const e = 'fetchMetric() - metric name is required.';
await expect(client.fetchMetric()).rejects.toThrow(e);
expectErrorToBeReported(new Error(e));
});
it('rejects if metric type is missing', async () => {
const e = 'fetchMetric() - metric type is required.';
await expect(client.fetchMetric('name')).rejects.toThrow(e);
expectErrorToBeReported(new Error(e));
});
});
});

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::Catalog::Resources::ComponentType, feature_category: :pipeline_composition do
specify { expect(described_class.graphql_name).to eq('CiCatalogResourcesComponent') }
it 'exposes the expected fields' do
expected_fields = %i[
id
inputs
name
path
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::Catalog::Resources::Components::InputType, feature_category: :pipeline_composition do
specify { expect(described_class.graphql_name).to eq('CiCatalogResourcesComponentsInput') }
it 'exposes the expected fields' do
expected_fields = %i[
name
default
required
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -14,6 +14,7 @@ RSpec.describe Types::Ci::Catalog::Resources::VersionType, feature_category: :pi
tag_path
author
commit
components
]
expect(described_class).to have_graphql_fields(*expected_fields)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter, feature_category: :importers do
let(:client) { double }
let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
@ -192,5 +192,18 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
expect(counter).to eq 0
end
end
context 'when event is not supported' do
let(:issue_event) do
struct = Struct.new(:id, :event, :created_at, :issue, keyword_init: true)
struct.new(id: 1, event: 'not_supported_event', created_at: '2022-04-26 18:30:53 UTC')
end
it "doesn't process this event" do
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 0
end
end
end
end

View File

@ -355,17 +355,6 @@ RSpec.describe Gitlab::QuickActions::Extractor, feature_category: :team_planning
end
end
context 'when quick_action_refactor feature flag is off' do
it 'does extract commands in HTML comments' do
stub_feature_flags(quick_action_refactor: false)
msg = "<!--\n/assign @user\n-->"
_, commands = extractor.extract_commands(msg)
expect(commands).to match_array [['assign', '@user']]
end
end
it 'limits to passed commands when they are passed' do
msg = <<~MSG.strip
Hello, we should only extract the commands passed
@ -409,16 +398,5 @@ RSpec.describe Gitlab::QuickActions::Extractor, feature_category: :team_planning
expect(extractor.redact_commands(text)).to eq(expected)
end
end
context 'when quick_action_refactor feature flag is off' do
it 'does extract commands in HTML comments' do
stub_feature_flags(quick_action_refactor: false)
msg = "<!--\n/assign @user\n-->"
expected = "<!--\n`/assign @user`\n-->"
expect(extractor.redact_commands(msg)).to eq(expected)
end
end
end
end

View File

@ -33,6 +33,20 @@ RSpec.describe SwapColumnsForCiPipelinesPipelineIdBigintForSelfHost, feature_cat
end
end
after do
if connection.foreign_key_exists?(:ci_pipelines, name: :fk_4_auto_canceled_by_id)
connection.execute(
'ALTER TABLE "ci_pipelines" RENAME CONSTRAINT "fk_4_auto_canceled_by_id" TO "fk_262d4c2d19"'
)
end
if connection.foreign_key_exists?(:ci_pipelines, name: :fk_4_auto_canceled_by_id_convert_to_bigint)
connection.execute(
'ALTER TABLE "ci_pipelines" RENAME CONSTRAINT "fk_4_auto_canceled_by_id_convert_to_bigint" TO "fk_67e4288f3a"'
)
end
end
it 'swaps the foreign key properly' do
disable_migrations_output do
recorder = ActiveRecord::QueryRecorder.new { migrate! }

View File

@ -228,12 +228,8 @@ RSpec.describe ProjectPresenter do
let_it_be(:project) { create(:project, :empty_repo) }
describe '#storage_anchor_data' do
it 'returns storage data' do
expect(presenter.storage_anchor_data).to have_attributes(
is_link: true,
label: a_string_including('0 B'),
link: nil
)
it 'does not return storage data' do
expect(presenter.storage_anchor_data).to be_nil
end
end
@ -282,12 +278,8 @@ RSpec.describe ProjectPresenter do
let(:presenter) { described_class.new(project, current_user: user) }
describe '#storage_anchor_data' do
it 'returns storage data without usage quotas link for non-admin users' do
expect(presenter.storage_anchor_data).to have_attributes(
is_link: true,
label: a_string_including('0 B'),
link: nil
)
it 'does not return storage data for non-admin users' do
expect(presenter.storage_anchor_data).to be(nil)
end
it 'returns storage data with usage quotas link for admin users' do

View File

@ -15,7 +15,10 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
description: 'A simple component',
namespace: namespace,
star_count: 1,
files: { 'README.md' => '[link](README.md)' }
files: {
'README.md' => '[link](README.md)',
'templates/secret-detection.yml' => "spec:\n inputs:\n website:\n---\nimage: alpine_1"
}
)
end
@ -33,10 +36,12 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
subject(:post_query) { post_graphql(query, current_user: user) }
before_all do
namespace.add_developer(user)
end
context 'when the current user has permission to read the namespace catalog' do
it 'returns the resource with the expected data' do
namespace.add_developer(user)
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(
@ -63,15 +68,94 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
end
end
describe 'components' do
let(:query) do
<<~GQL
query {
ciCatalogResource(id: "#{resource.to_global_id}") {
id
versions {
nodes {
id
components {
nodes {
id
name
path
inputs {
name
default
required
}
}
}
}
}
}
}
GQL
end
context 'when the catalog resource has components' do
let_it_be(:inputs) do
{
website: nil,
environment: {
default: 'test'
},
tags: {
type: 'array'
}
}
end
let_it_be(:version) do
create(:release, :with_catalog_resource_version, project: project).catalog_resource_version
end
let_it_be(:components) do
create_list(:ci_catalog_resource_component, 2, version: version, inputs: inputs, path: 'templates/comp.yml')
end
it 'returns the resource with the component data' do
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(a_graphql_entity_for(resource))
expect(graphql_data_at(:ciCatalogResource, :versions, :nodes, :components, :nodes)).to contain_exactly(
a_graphql_entity_for(
components.first,
name: components.first.name,
path: components.first.path,
inputs: [
a_graphql_entity_for(
name: 'tags',
default: nil,
required: true
),
a_graphql_entity_for(
name: 'website',
default: nil,
required: true
),
a_graphql_entity_for(
name: 'environment',
default: 'test',
required: false
)
]
),
a_graphql_entity_for(
components.last,
name: components.last.name,
path: components.last.path
)
)
end
end
end
describe 'versions' do
before_all do
namespace.add_developer(user)
end
before do
stub_licensed_features(ci_namespace_catalog: true)
end
let(:query) do
<<~GQL
query {
@ -146,14 +230,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
end
describe 'latestVersion' do
before_all do
namespace.add_developer(user)
end
before do
stub_licensed_features(ci_namespace_catalog: true)
end
let(:query) do
<<~GQL
query {
@ -219,14 +295,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
end
describe 'rootNamespace' do
before_all do
namespace.add_developer(user)
end
before do
stub_licensed_features(ci_namespace_catalog: true)
end
let(:query) do
<<~GQL
query {
@ -255,10 +323,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
end
describe 'openIssuesCount' do
before do
stub_licensed_features(ci_namespace_catalog: true)
end
context 'when open_issue_count is requested' do
let(:query) do
<<~GQL
@ -274,8 +338,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
create(:issue, :opened, project: project)
create(:issue, :opened, project: project)
namespace.add_developer(user)
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(
@ -287,8 +349,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
context 'when open_issue_count is zero' do
it 'returns zero' do
namespace.add_developer(user)
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(
@ -302,10 +362,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
end
describe 'openMergeRequestsCount' do
before do
stub_licensed_features(ci_namespace_catalog: true)
end
context 'when merge_requests_count is requested' do
let(:query) do
<<~GQL
@ -320,8 +376,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
it 'returns the correct count' do
create(:merge_request, :opened, source_project: project)
namespace.add_developer(user)
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(
@ -333,8 +387,6 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
context 'when open merge_requests_count is zero' do
it 'returns zero' do
namespace.add_developer(user)
post_query
expect(graphql_data_at(:ciCatalogResource)).to match(

View File

@ -9,12 +9,6 @@ RSpec.describe 'groups/_home_panel' do
assign(:group, group)
end
it 'renders the group ID' do
render
expect(rendered).to have_content("Group ID: #{group.id}")
end
context 'admin area link' do
it 'renders admin area link for admin' do
allow(view).to receive(:current_user).and_return(create(:admin))

View File

@ -148,38 +148,6 @@ RSpec.describe 'projects/_home_panel' do
end
end
context 'project id' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
assign(:project, project)
allow(view).to receive(:current_user).and_return(user)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'user can read project' do
it 'is shown' do
allow(view).to receive(:can?).with(user, :read_project, project).and_return(true)
render
expect(rendered).to have_content("Project ID: #{project.id}")
end
end
context 'user cannot read project' do
it 'is not shown' do
allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
render
expect(rendered).not_to have_content("Project ID: #{project.id}")
end
end
end
context 'forks' do
let(:source_project) { create(:project, :repository) }
let(:project) { fork_project(source_project) }