Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6659634b2b
commit
c8419b0227
|
|
@ -1,6 +1,6 @@
|
|||
include:
|
||||
- project: gitlab-org/quality/pipeline-common
|
||||
ref: 7.13.2
|
||||
ref: 7.13.3
|
||||
file:
|
||||
- /ci/danger-review.yml
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -78,10 +78,6 @@
|
|||
.btn-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || "" })
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
1bdc6128604324a7bebec587ed935dfd2e91838f36e3ca68fadf695a48b32d24
|
||||
|
|
@ -0,0 +1 @@
|
|||
20c7fd677cfa00821b67761f9d406d1bf4cfdf65831c3c96910ccb43986b9926
|
||||
|
|
@ -0,0 +1 @@
|
|||
2269867e97f1194f376979f964912f386aa5248966601a46e27ebb1b72d9e96a
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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**). |
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export function createMockClient() {
|
|||
servicesUrl: 'services-url',
|
||||
operationsUrl: 'operations-url',
|
||||
metricsUrl: 'metrics-url',
|
||||
metricsSearchUrl: 'metrics-search-url',
|
||||
});
|
||||
|
||||
Object.getOwnPropertyNames(mockClient)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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! }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue