Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8d4eff3fd9
commit
51aa153c0d
|
|
@ -81,6 +81,10 @@ export const BASE_ROLES = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const BASE_ROLES_WITHOUT_MINIMAL_ACCESS = BASE_ROLES.filter(
|
||||||
|
({ accessLevel }) => accessLevel !== ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER,
|
||||||
|
);
|
||||||
|
|
||||||
export const ACCESS_LEVEL_LABELS = {
|
export const ACCESS_LEVEL_LABELS = {
|
||||||
[ACCESS_LEVEL_NO_ACCESS_INTEGER]: ACCESS_LEVEL_NO_ACCESS,
|
[ACCESS_LEVEL_NO_ACCESS_INTEGER]: ACCESS_LEVEL_NO_ACCESS,
|
||||||
[ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER]: ACCESS_LEVEL_MINIMAL_ACCESS,
|
[ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER]: ACCESS_LEVEL_MINIMAL_ACCESS,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
|
import { GlSkeletonLoader, GlTableLite, GlTooltipDirective } from '@gitlab/ui';
|
||||||
|
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
|
||||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||||
import { s__, __ } from '~/locale';
|
import { s__, __ } from '~/locale';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
|
|
@ -32,7 +33,10 @@ const HIDE_TD_ON_MOBILE = '!gl-hidden lg:!gl-table-cell';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'PipelinesTable',
|
||||||
|
cellHeight: 50,
|
||||||
components: {
|
components: {
|
||||||
|
GlSkeletonLoader,
|
||||||
GlTableLite,
|
GlTableLite,
|
||||||
LegacyPipelineMiniGraph,
|
LegacyPipelineMiniGraph,
|
||||||
PipelineFailedJobsWidget,
|
PipelineFailedJobsWidget,
|
||||||
|
|
@ -51,15 +55,15 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
pipelines: {
|
isCreatingPipeline: {
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
updateGraphDropdown: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
pipelines: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
pipelineIdType: {
|
pipelineIdType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|
@ -68,8 +72,16 @@ export default {
|
||||||
return value === PIPELINE_IID_KEY || value === PIPELINE_ID_KEY;
|
return value === PIPELINE_IID_KEY || value === PIPELINE_ID_KEY;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
updateGraphDropdown: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isMobile() {
|
||||||
|
return ['md', 'sm', 'xs'].includes(GlBreakpointInstance.getBreakpointSize());
|
||||||
|
},
|
||||||
tableFields() {
|
tableFields() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -112,13 +124,19 @@ export default {
|
||||||
return this.useFailedJobsWidget ? '!gl-pb-0 !gl-border-none' : 'pl-p-5!';
|
return this.useFailedJobsWidget ? '!gl-pb-0 !gl-border-none' : 'pl-p-5!';
|
||||||
},
|
},
|
||||||
pipelinesWithDetails() {
|
pipelinesWithDetails() {
|
||||||
|
let { pipelines } = this;
|
||||||
|
|
||||||
|
if (this.isCreatingPipeline) {
|
||||||
|
pipelines = [{ isLoading: true }, ...this.pipelines];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.useFailedJobsWidget) {
|
if (this.useFailedJobsWidget) {
|
||||||
return this.pipelines.map((p) => {
|
pipelines = pipelines.map((p) => {
|
||||||
return { ...p, _showDetails: true };
|
return { ...p, _showDetails: true };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.pipelines;
|
return pipelines;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -126,6 +144,9 @@ export default {
|
||||||
const downstream = pipeline.triggered;
|
const downstream = pipeline.triggered;
|
||||||
return keepLatestDownstreamPipelines(downstream);
|
return keepLatestDownstreamPipelines(downstream);
|
||||||
},
|
},
|
||||||
|
cellWidth(ref) {
|
||||||
|
return this.$refs[ref]?.offsetWidth;
|
||||||
|
},
|
||||||
getProjectPath(item) {
|
getProjectPath(item) {
|
||||||
return cleanLeadingSeparator(item.project.full_path);
|
return cleanLeadingSeparator(item.project.full_path);
|
||||||
},
|
},
|
||||||
|
|
@ -144,6 +165,13 @@ export default {
|
||||||
onCancelPipeline(pipeline) {
|
onCancelPipeline(pipeline) {
|
||||||
this.$emit('cancel-pipeline', pipeline);
|
this.$emit('cancel-pipeline', pipeline);
|
||||||
},
|
},
|
||||||
|
setLoaderPosition(ref) {
|
||||||
|
if (this.isMobile) {
|
||||||
|
return this.cellWidth(ref) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
trackPipelineMiniGraph() {
|
trackPipelineMiniGraph() {
|
||||||
this.track('click_minigraph', { label: TRACKING_CATEGORIES.table });
|
this.track('click_minigraph', { label: TRACKING_CATEGORIES.table });
|
||||||
},
|
},
|
||||||
|
|
@ -172,11 +200,30 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(status)="{ item }">
|
<template #cell(status)="{ item }">
|
||||||
<pipeline-status-badge :pipeline="item" />
|
<div v-if="item.isLoading" ref="status">
|
||||||
|
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('status')">
|
||||||
|
<rect height="30" rx="4" ry="4" :width="cellWidth('status')" />
|
||||||
|
</gl-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
<pipeline-status-badge v-else :pipeline="item" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(pipeline)="{ item }">
|
<template #cell(pipeline)="{ item }">
|
||||||
|
<div v-if="item.isLoading" ref="pipeline">
|
||||||
|
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('pipeline')">
|
||||||
|
<rect height="14" rx="4" ry="4" :width="cellWidth('pipeline')" />
|
||||||
|
<rect
|
||||||
|
height="10"
|
||||||
|
rx="4"
|
||||||
|
ry="4"
|
||||||
|
:width="cellWidth('pipeline') / 2"
|
||||||
|
:x="setLoaderPosition('pipeline')"
|
||||||
|
y="20"
|
||||||
|
/>
|
||||||
|
</gl-skeleton-loader>
|
||||||
|
</div>
|
||||||
<pipeline-url
|
<pipeline-url
|
||||||
|
v-else
|
||||||
:pipeline="item"
|
:pipeline="item"
|
||||||
:pipeline-id-type="pipelineIdType"
|
:pipeline-id-type="pipelineIdType"
|
||||||
ref-color="gl-text-default"
|
ref-color="gl-text-default"
|
||||||
|
|
@ -184,11 +231,22 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(triggerer)="{ item }">
|
<template #cell(triggerer)="{ item }">
|
||||||
<pipeline-triggerer :pipeline="item" />
|
<div v-if="item.isLoading" ref="triggerer" class="gl-ml-3">
|
||||||
|
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('triggerer')">
|
||||||
|
<rect :height="34" rx="50" ry="50" :width="34" />
|
||||||
|
</gl-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
<pipeline-triggerer v-else :pipeline="item" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(stages)="{ item }">
|
<template #cell(stages)="{ item }">
|
||||||
|
<div v-if="item.isLoading" ref="stages">
|
||||||
|
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('stages')">
|
||||||
|
<rect height="20" rx="10" ry="10" :width="cellWidth('stages')" />
|
||||||
|
</gl-skeleton-loader>
|
||||||
|
</div>
|
||||||
<legacy-pipeline-mini-graph
|
<legacy-pipeline-mini-graph
|
||||||
|
v-else
|
||||||
:downstream-pipelines="getDownstreamPipelines(item)"
|
:downstream-pipelines="getDownstreamPipelines(item)"
|
||||||
:pipeline-path="item.path"
|
:pipeline-path="item.path"
|
||||||
:stages="getStages(item)"
|
:stages="getStages(item)"
|
||||||
|
|
@ -200,6 +258,7 @@ export default {
|
||||||
|
|
||||||
<template #cell(actions)="{ item }">
|
<template #cell(actions)="{ item }">
|
||||||
<pipeline-operations
|
<pipeline-operations
|
||||||
|
v-if="!item.isLoading"
|
||||||
:pipeline="item"
|
:pipeline="item"
|
||||||
@cancel-pipeline="onCancelPipeline"
|
@cancel-pipeline="onCancelPipeline"
|
||||||
@refresh-pipelines-table="onRefreshPipelinesTable"
|
@refresh-pipelines-table="onRefreshPipelinesTable"
|
||||||
|
|
@ -209,7 +268,7 @@ export default {
|
||||||
|
|
||||||
<template #row-details="{ item }">
|
<template #row-details="{ item }">
|
||||||
<pipeline-failed-jobs-widget
|
<pipeline-failed-jobs-widget
|
||||||
v-if="useFailedJobsWidget"
|
v-if="useFailedJobsWidget && !item.isLoading"
|
||||||
:failed-jobs-count="failedJobsCount(item)"
|
:failed-jobs-count="failedJobsCount(item)"
|
||||||
:is-pipeline-active="item.active"
|
:is-pipeline-active="item.active"
|
||||||
:pipeline-iid="item.iid"
|
:pipeline-iid="item.iid"
|
||||||
|
|
|
||||||
|
|
@ -296,10 +296,11 @@ export default {
|
||||||
</gl-button>
|
</gl-button>
|
||||||
|
|
||||||
<pipelines-table
|
<pipelines-table
|
||||||
|
:is-creating-pipeline="state.isRunningMergeRequestPipeline"
|
||||||
|
:pipeline-id-type="$options.pipelineIdKey"
|
||||||
:pipelines="state.pipelines"
|
:pipelines="state.pipelines"
|
||||||
:update-graph-dropdown="updateGraphDropdown"
|
:update-graph-dropdown="updateGraphDropdown"
|
||||||
:view-type="viewType"
|
:view-type="viewType"
|
||||||
:pipeline-id-type="$options.pipelineIdKey"
|
|
||||||
@cancel-pipeline="onCancelPipeline"
|
@cancel-pipeline="onCancelPipeline"
|
||||||
@refresh-pipelines-table="onRefreshPipelinesTable"
|
@refresh-pipelines-table="onRefreshPipelinesTable"
|
||||||
@retry-pipeline="onRetryPipeline"
|
@retry-pipeline="onRetryPipeline"
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ class GfmAutoComplete {
|
||||||
tpl += ' <small class="params"><%- params.join(" ") %></small>';
|
tpl += ' <small class="params"><%- params.join(" ") %></small>';
|
||||||
}
|
}
|
||||||
if (value.warning && value.icon && value.icon === 'confidential') {
|
if (value.warning && value.icon && value.icon === 'confidential') {
|
||||||
tpl += `<small class="description gl-display-flex gl-align-items-center">${spriteIcon(
|
tpl += `<small class="description gl-flex gl-items-center">${spriteIcon(
|
||||||
'eye-slash',
|
'eye-slash',
|
||||||
's16 gl-mr-2',
|
's16 gl-mr-2',
|
||||||
)}<em><%- warning %></em></small>`;
|
)}<em><%- warning %></em></small>`;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ div.innerHTML = `
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<h2>Don't want to see this message anymore?</h2>
|
<h2>Don't want to see this message anymore?</h2>
|
||||||
<p class="gl-text-body">
|
<p class="gl-text-primary">
|
||||||
Follow the documentation to switch to using Vite.<br />
|
Follow the documentation to switch to using Vite.<br />
|
||||||
Vite compiles frontend assets faster and eliminates the need for this message.
|
Vite compiles frontend assets faster and eliminates the need for this message.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item
|
||||||
|
|
||||||
const ALLOWED_ICONS = ['issue-close'];
|
const ALLOWED_ICONS = ['issue-close'];
|
||||||
const ICON_COLORS = {
|
const ICON_COLORS = {
|
||||||
'issue-close': '!gl-bg-blue-100 gl-text-blue-700',
|
'issue-close': '!gl-bg-blue-100 gl-text-blue-700 icon-info',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -115,23 +115,27 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<timeline-entry-item
|
<timeline-entry-item
|
||||||
:id="noteAnchorId"
|
:id="noteAnchorId"
|
||||||
:class="{ target: isTargetNote, 'pr-0': shouldShowDescriptionVersion }"
|
:class="{
|
||||||
class="note system-note note-wrapper"
|
target: isTargetNote,
|
||||||
|
'pr-0': shouldShowDescriptionVersion,
|
||||||
|
}"
|
||||||
|
class="system-note-v2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
getIconColor,
|
getIconColor,
|
||||||
{
|
{
|
||||||
'system-note-icon gl-bg-gray-50 gl-text-gray-600': isAllowedIcon,
|
'system-note-icon-v2 gl-h-6 gl-w-6 gl-ml-2 -gl-mt-1': isAllowedIcon,
|
||||||
'system-note-tiny-dot !gl-bg-gray-900': !isAllowedIcon,
|
'system-note-dot gl-h-3 gl-w-3 gl-mt-3 -gl-top-1 gl-ml-4 gl-border-2 gl-border-gray-50 gl-border-solid gl-bg-gray-900':
|
||||||
|
!isAllowedIcon,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
class="gl-relative gl-float-left gl-flex gl-items-center gl-justify-center gl-rounded-full"
|
class="gl-relative gl-float-left gl-flex gl-items-center gl-justify-center gl-rounded-full"
|
||||||
>
|
>
|
||||||
<gl-icon v-if="isAllowedIcon" :size="12" :name="note.systemNoteIconName" />
|
<gl-icon v-if="isAllowedIcon" :size="14" :name="note.systemNoteIconName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="timeline-content">
|
<div class="gl-ml-7">
|
||||||
<div class="note-header">
|
<div class="gl-flex gl-justify-between gl-items-start">
|
||||||
<note-header
|
<note-header
|
||||||
:author="note.author"
|
:author="note.author"
|
||||||
:created-at="note.createdAt"
|
:created-at="note.createdAt"
|
||||||
|
|
@ -153,11 +157,8 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
</note-header>
|
</note-header>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-body">
|
<div class="note-body-v2 gl-pl-3 gl-pb-3">
|
||||||
<div
|
<div v-if="shouldShowDescriptionVersion" class="gl-relative !gl-pt-3">
|
||||||
v-if="shouldShowDescriptionVersion"
|
|
||||||
class="description-version gl-relative !gl-pt-3 gl-pl-4"
|
|
||||||
>
|
|
||||||
<pre v-if="isLoadingDescriptionVersion" class="loading-state">
|
<pre v-if="isLoadingDescriptionVersion" class="loading-state">
|
||||||
<gl-skeleton-loader />
|
<gl-skeleton-loader />
|
||||||
</pre>
|
</pre>
|
||||||
|
|
@ -165,7 +166,7 @@ export default {
|
||||||
v-else
|
v-else
|
||||||
v-safe-html="descriptionVersion"
|
v-safe-html="descriptionVersion"
|
||||||
data-testid="description-version-diff"
|
data-testid="description-version-diff"
|
||||||
class="wrapper gl-mt-3 gl-whitespace-pre-wrap gl-pr-7"
|
class="gl-mt-3 gl-whitespace-pre-wrap gl-pr-7"
|
||||||
></pre>
|
></pre>
|
||||||
<gl-button
|
<gl-button
|
||||||
v-if="displayDeleteButton"
|
v-if="displayDeleteButton"
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/**
|
|
||||||
Shared styles for system note dot and icon styles used for MR, Issue, Work Item
|
|
||||||
*/
|
|
||||||
.system-note-tiny-dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-left: 12px;
|
|
||||||
margin-right: 8px;
|
|
||||||
border: 2px solid var(--gray-50, $gray-50);
|
|
||||||
|
|
||||||
.gl-dark .modal-body & {
|
|
||||||
border-color: var(--gray-100, $gray-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-note-icon {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-left: 6px;
|
|
||||||
|
|
||||||
// stylelint-disable-next-line gitlab/no-gl-class
|
|
||||||
&.gl-bg-green-100 {
|
|
||||||
--bg-color: var(--green-100, #{$green-100});
|
|
||||||
}
|
|
||||||
|
|
||||||
// stylelint-disable-next-line gitlab/no-gl-class
|
|
||||||
&.gl-bg-red-100 {
|
|
||||||
--bg-color: var(--red-100, #{$red-100});
|
|
||||||
}
|
|
||||||
|
|
||||||
// stylelint-disable-next-line gitlab/no-gl-class
|
|
||||||
&.gl-bg-blue-100 {
|
|
||||||
--bg-color: var(--blue-100, #{$blue-100});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-note-icon:not(.mr-system-note-empty)::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 1px);
|
|
||||||
bottom: 100%;
|
|
||||||
width: 2px;
|
|
||||||
height: 20px;
|
|
||||||
background: linear-gradient(to bottom, transparent, var(--bg-color));
|
|
||||||
|
|
||||||
.system-note:first-child & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-note-icon:not(.mr-system-note-empty)::after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 1px);
|
|
||||||
top: 100%;
|
|
||||||
width: 2px;
|
|
||||||
height: 20px;
|
|
||||||
background: linear-gradient(to bottom, var(--bg-color), transparent);
|
|
||||||
|
|
||||||
.system-note:last-child & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
@import 'mixins_and_variables_and_functions';
|
@import 'mixins_and_variables_and_functions';
|
||||||
@import 'system_note_styles';
|
|
||||||
@import './notes/system_notes_v2';
|
@import './notes/system_notes_v2';
|
||||||
|
|
||||||
.issuable-details {
|
.issuable-details {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
@import 'mixins_and_variables_and_functions';
|
@import 'mixins_and_variables_and_functions';
|
||||||
@import 'system_note_styles';
|
|
||||||
@import './notes/system_notes_v2';
|
@import './notes/system_notes_v2';
|
||||||
|
|
||||||
$work-item-field-inset-shadow: inset 0 0 0 $gl-border-size-1 var(--gray-200, $gray-200) !important;
|
$work-item-field-inset-shadow: inset 0 0 0 $gl-border-size-1 var(--gray-200, $gray-200) !important;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ class ApplicationController < BaseActionController
|
||||||
include CheckRateLimit
|
include CheckRateLimit
|
||||||
include RequestPayloadLogger
|
include RequestPayloadLogger
|
||||||
include StrongPaginationParams
|
include StrongPaginationParams
|
||||||
include Gitlab::HttpRouterRuleContext
|
include Gitlab::HttpRouter::RuleContext
|
||||||
|
include Gitlab::HttpRouter::RuleMetrics
|
||||||
|
|
||||||
before_action :authenticate_user!, except: [:route_not_found]
|
before_action :authenticate_user!, except: [:route_not_found]
|
||||||
before_action :set_current_organization
|
before_action :set_current_organization
|
||||||
|
|
@ -42,6 +43,7 @@ class ApplicationController < BaseActionController
|
||||||
before_action :active_user_check, unless: :devise_controller?
|
before_action :active_user_check, unless: :devise_controller?
|
||||||
before_action :set_usage_stats_consent_flag
|
before_action :set_usage_stats_consent_flag
|
||||||
before_action :check_impersonation_availability
|
before_action :check_impersonation_availability
|
||||||
|
before_action :increment_http_router_metrics
|
||||||
|
|
||||||
# Make sure the `auth_user` is memoized so it can be logged, we do this after
|
# Make sure the `auth_user` is memoized so it can be logged, we do this after
|
||||||
# all other before filters that could have set the user.
|
# all other before filters that could have set the user.
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,12 @@ module TodosHelper
|
||||||
if todo.resource_parent.is_a?(Group)
|
if todo.resource_parent.is_a?(Group)
|
||||||
todo.resource_parent.name
|
todo.resource_parent.name
|
||||||
else
|
else
|
||||||
|
# Note: A todo with neither project nor group is invalid.
|
||||||
|
# But still we heard from users with such todos in their database,
|
||||||
|
# and for these users the /dashboard/todos page returned 500.
|
||||||
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/388051
|
||||||
|
return unless todo.project.present?
|
||||||
|
|
||||||
title = content_tag(:span, todo.project.name, class: 'project-name')
|
title = content_tag(:span, todo.project.name, class: 'project-name')
|
||||||
namespace = content_tag(:span, "#{todo.project.namespace.human_name} / ", class: 'namespace-name')
|
namespace = content_tag(:span, "#{todo.project.namespace.human_name} / ", class: 'namespace-name')
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -188,6 +188,7 @@ The following metrics are available:
|
||||||
| `gitlab_keeparound_refs_created_total` | Counter | 16.10 | Counts the number of keep-around refs actually created | `source` |
|
| `gitlab_keeparound_refs_created_total` | Counter | 16.10 | Counts the number of keep-around refs actually created | `source` |
|
||||||
| `search_advanced_index_repair_total` | Counter | 17.3 | Counts the number of index repair operations | `document_type` |
|
| `search_advanced_index_repair_total` | Counter | 17.3 | Counts the number of index repair operations | `document_type` |
|
||||||
| `search_advanced_boolean_settings` | Gauge | 17.3 | Current state of Advanced search boolean settings | `name` |
|
| `search_advanced_boolean_settings` | Gauge | 17.3 | Current state of Advanced search boolean settings | `name` |
|
||||||
|
| `gitlab_http_router_rule_total` | Counter | 17.4 | Counts occurrences of HTTP Router rule's `rule_action` and `rule_type` | `rule_action`, `rule_type` |
|
||||||
|
|
||||||
## Metrics controlled by a feature flag
|
## Metrics controlled by a feature flag
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
stage: Fulfillment
|
stage: Fulfillment
|
||||||
group: Subscription management
|
group: Subscription management
|
||||||
description: Seat assignment, GitLab Duo Pro add-on
|
description: Seat assignment, GitLab Duo add-on
|
||||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -16,29 +16,30 @@ Subscription add-ons are purchased as additional seats in your subscription.
|
||||||
Access to features provided by subscription add-ons is managed through seat assignment. Subscription
|
Access to features provided by subscription add-ons is managed through seat assignment. Subscription
|
||||||
add-ons can be assigned to billable users only.
|
add-ons can be assigned to billable users only.
|
||||||
|
|
||||||
## Purchase GitLab Duo Pro seats
|
## Purchase GitLab Duo seats
|
||||||
|
|
||||||
You can purchase additional GitLab Duo Pro seats for your group namespace or self-managed instance. After you complete the purchase,
|
You can purchase additional GitLab Duo Pro or GitLab Duo Enterprise seats for your group namespace or self-managed instance. After you complete the purchase, you must assign the seats to billable users so that they can use GitLab Duo.
|
||||||
you must assign the seats to billable users so that they can use GitLab Duo Pro.
|
|
||||||
|
|
||||||
To purchase GitLab Duo Pro seats, you can use the Customers Portal, or you can contact the [GitLab Sales team](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/).
|
To purchase GitLab Duo Pro seats, you can use the Customers Portal, or you can contact the [GitLab Sales team](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/). To purchase GitLab Duo Enterprise, contact the [GitLab Sales team](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/).
|
||||||
|
|
||||||
1. Sign in to the [GitLab Customers Portal](https://customers.gitlab.com/).
|
1. Sign in to the [GitLab Customers Portal](https://customers.gitlab.com/).
|
||||||
1. On the subscription card, select the vertical ellipsis (**{ellipsis_v}**).
|
1. On the subscription card, select the vertical ellipsis (**{ellipsis_v}**).
|
||||||
1. Select **Buy GitLab Duo Pro**.
|
1. Select **Buy GitLab Duo Pro**.
|
||||||
1. Enter the number of seats for GitLab Duo Pro.
|
1. Enter the number of seats for GitLab Duo.
|
||||||
1. Review the **Purchase summary** section.
|
1. Review the **Purchase summary** section.
|
||||||
1. From the **Payment method** dropdown list, select your payment method.
|
1. From the **Payment method** dropdown list, select your payment method.
|
||||||
1. Select **Purchase seats**.
|
1. Select **Purchase seats**.
|
||||||
|
|
||||||
## Assign GitLab Duo Pro seats
|
## Assign GitLab Duo seats
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- You must purchase the GitLab Duo Pro add-on, or have the GitLab Duo Pro trial.
|
- You must purchase a GitLab Duo add-on, or have an active GitLab Duo trial.
|
||||||
- For self-managed and GitLab Dedicated, the GitLab Duo Pro add-on is available for GitLab 16.8 and later only.
|
- For self-managed and GitLab Dedicated:
|
||||||
|
- The GitLab Duo Pro add-on is available in GitLab 16.8 and later.
|
||||||
|
- The GitLab Duo Enterprise add-on is only available in GitLab 17.3 and later.
|
||||||
|
|
||||||
After you purchase GitLab Duo Pro, you can assign seats to billable users to grant access to the add-on.
|
After you purchase GitLab Duo, you can assign seats to billable users to grant access to the add-on.
|
||||||
|
|
||||||
### For GitLab.com
|
### For GitLab.com
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ After you purchase GitLab Duo Pro, you can assign seats to billable users to gra
|
||||||
1. Select **Settings > GitLab Duo**.
|
1. Select **Settings > GitLab Duo**.
|
||||||
1. To the right of the user, turn on the toggle to assign GitLab Duo Pro.
|
1. To the right of the user, turn on the toggle to assign GitLab Duo Pro.
|
||||||
|
|
||||||
To use Code Suggestions in any project or group, a user must be assigned a seat in at least one top-level group.
|
To use GitLab Duo features in any project or group, a user must be assigned a seat in at least one top-level group.
|
||||||
|
|
||||||
### For self-managed
|
### For self-managed
|
||||||
|
|
||||||
|
|
@ -61,14 +62,14 @@ Prerequisites:
|
||||||
1. On the left sidebar, select **Subscription**.
|
1. On the left sidebar, select **Subscription**.
|
||||||
1. In **Subscription details**, to the right of **Last sync**, select
|
1. In **Subscription details**, to the right of **Last sync**, select
|
||||||
synchronize subscription (**{retry}**).
|
synchronize subscription (**{retry}**).
|
||||||
1. To the right of the user, turn on the toggle to assign GitLab Duo Pro.
|
1. To the right of the user, turn on the toggle to assign GitLab Duo.
|
||||||
|
|
||||||
#### Configure network and proxy settings
|
#### Configure network and proxy settings
|
||||||
|
|
||||||
For self-managed instances, to enable GitLab Duo features,
|
For self-managed instances, to enable GitLab Duo features,
|
||||||
you must [enable network connectivity](../user/ai_features_enable.md).
|
you must [enable network connectivity](../user/ai_features_enable.md).
|
||||||
|
|
||||||
## Assign and remove GitLab Duo Pro seats in bulk
|
## Assign and remove GitLab Duo seats in bulk
|
||||||
|
|
||||||
You can assign or remove seats in bulk for multiple users.
|
You can assign or remove seats in bulk for multiple users.
|
||||||
|
|
||||||
|
|
@ -88,13 +89,13 @@ You can assign or remove seats in bulk for multiple users.
|
||||||
|
|
||||||
Administrators of self-managed instances can use a [Rake task](../raketasks/user_management.md#bulk-assign-users-to-gitlab-duo-pro) to assign or remove seats in bulk.
|
Administrators of self-managed instances can use a [Rake task](../raketasks/user_management.md#bulk-assign-users-to-gitlab-duo-pro) to assign or remove seats in bulk.
|
||||||
|
|
||||||
## Purchase additional GitLab Duo Pro seats
|
## Purchase additional GitLab Duo seats
|
||||||
|
|
||||||
You can purchase additional GitLab Duo Pro seats for your group namespace or self-managed instance. After you complete the purchase, the seats are added to the total number of GitLab Duo Pro seats in your subscription.
|
You can purchase additional GitLab Duo Pro or GitLab Duo Enterprise seats for your group namespace or self-managed instance. After you complete the purchase, the seats are added to the total number of GitLab Duo seats in your subscription.
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- You must purchase the GitLab Duo Pro add-on.
|
- You must purchase the GitLab Duo Pro or GitLab Duo Enterprise add-on.
|
||||||
|
|
||||||
### For GitLab.com
|
### For GitLab.com
|
||||||
|
|
||||||
|
|
@ -141,7 +142,7 @@ Prerequisites:
|
||||||
1. Select **Continue**.
|
1. Select **Continue**.
|
||||||
1. If prompted, select the group that the trial should be applied to.
|
1. If prompted, select the group that the trial should be applied to.
|
||||||
1. Select **Activate my trial**.
|
1. Select **Activate my trial**.
|
||||||
1. [Assign seats](#assign-gitlab-duo-pro-seats) to the users who need access.
|
1. [Assign seats](#assign-gitlab-duo-seats) to the users who need access.
|
||||||
|
|
||||||
### On Self-managed and GitLab Dedicated
|
### On Self-managed and GitLab Dedicated
|
||||||
|
|
||||||
|
|
@ -162,11 +163,11 @@ Prerequisites:
|
||||||
- Ensure the email you submit for trial registration matches the email of the [subscription contact](customers_portal.md#change-your-subscription-contact).
|
- Ensure the email you submit for trial registration matches the email of the [subscription contact](customers_portal.md#change-your-subscription-contact).
|
||||||
1. Select **Submit**.
|
1. Select **Submit**.
|
||||||
|
|
||||||
The trial automatically syncs to your instance within 24 hours. After the trial has synced, [assign seats](#assign-gitlab-duo-pro-seats) to users that you want to access GitLab Duo Pro.
|
The trial automatically syncs to your instance within 24 hours. After the trial has synced, [assign seats](#assign-gitlab-duo-seats) to users that you want to access GitLab Duo.
|
||||||
|
|
||||||
## Automatic seat removal for seat overages
|
## Automatic seat removal for seat overages
|
||||||
|
|
||||||
If your quantity of purchased GitLab Duo Pro seats is reduced, seat assignments are automatically removed to match the seat quantity available in the subscription.
|
If your quantity of purchased GitLab Duo add-on seats is reduced, seat assignments are automatically removed to match the seat quantity available in the subscription.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ GitLab Duo features that are generally available are automatically turned on for
|
||||||
- For the best experience, you should upgrade to the [latest version of GitLab](https://about.gitlab.com/releases/categories/releases/).
|
- For the best experience, you should upgrade to the [latest version of GitLab](https://about.gitlab.com/releases/categories/releases/).
|
||||||
- If you have GitLab Dedicated, you must have [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md).
|
- If you have GitLab Dedicated, you must have [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md).
|
||||||
- For some generally available features, like [Code Suggestions](../project/repository/code_suggestions/index.md),
|
- For some generally available features, like [Code Suggestions](../project/repository/code_suggestions/index.md),
|
||||||
[you must assign seats](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats)
|
[you must assign seats](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats)
|
||||||
to the users you want to have access.
|
to the users you want to have access.
|
||||||
|
|
||||||
GitLab Duo features that are experimental or beta are turned off by default
|
GitLab Duo features that are experimental or beta are turned off by default
|
||||||
|
|
@ -235,8 +235,8 @@ You can use command-line tools such as `curl` to verify the connectivity.
|
||||||
In addition to [turning on GitLab Duo features](turn_on_off.md#prerequisites),
|
In addition to [turning on GitLab Duo features](turn_on_off.md#prerequisites),
|
||||||
you can also do the following:
|
you can also do the following:
|
||||||
|
|
||||||
1. Verify that [subscription seats have been purchased](../../subscriptions/subscription-add-ons.md#purchase-gitlab-duo-pro-seats).
|
1. Verify that [subscription seats have been purchased](../../subscriptions/subscription-add-ons.md#purchase-gitlab-duo-seats).
|
||||||
1. Ensure that [seats are assigned to users](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats).
|
1. Ensure that [seats are assigned to users](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats).
|
||||||
1. For IDE users with the [GitLab Duo extension](../../user/project/repository/code_suggestions/supported_extensions.md#supported-editor-extensions):
|
1. For IDE users with the [GitLab Duo extension](../../user/project/repository/code_suggestions/supported_extensions.md#supported-editor-extensions):
|
||||||
- Verify that the extension is up-to-date.
|
- Verify that the extension is up-to-date.
|
||||||
- Run extension setting health checks, and test the authentication.
|
- Run extension setting health checks, and test the authentication.
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ Project Owners can do any listed action, and also can delete pipelines:
|
||||||
| Run CI/CD job | | | | ✓ | ✓ | |
|
| Run CI/CD job | | | | ✓ | ✓ | |
|
||||||
| Run CI/CD pipeline for a protected branch | | | | ✓ | ✓ | Developers and maintainers: Only if the user is [allowed to merge or push to the protected branch](../ci/pipelines/index.md#pipeline-security-on-protected-branches). |
|
| Run CI/CD pipeline for a protected branch | | | | ✓ | ✓ | Developers and maintainers: Only if the user is [allowed to merge or push to the protected branch](../ci/pipelines/index.md#pipeline-security-on-protected-branches). |
|
||||||
| Stop [environments](../ci/environments/index.md) | | | | ✓ | ✓ | |
|
| Stop [environments](../ci/environments/index.md) | | | | ✓ | ✓ | |
|
||||||
|
| Delete [environments](../ci/environments/index.md) | | | | ✓ | ✓ | |
|
||||||
| View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | ✓ | ✓ | |
|
| View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | ✓ | ✓ | |
|
||||||
| Use pipeline editor | | | | ✓ | ✓ | |
|
| Use pipeline editor | | | | ✓ | ✓ | |
|
||||||
| Run [interactive web terminals](../ci/interactive_web_terminal/index.md) | | | | ✓ | ✓ | |
|
| Run [interactive web terminals](../ci/interactive_web_terminal/index.md) | | | | ✓ | ✓ | |
|
||||||
|
|
@ -352,7 +353,7 @@ Project permissions for [GitLab Duo](gitlab_duo/index.md):
|
||||||
| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes |
|
| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes |
|
||||||
|-------------------------------------------------------------------------------------------------------------|------------|-------|----------|-----------|------------|-------|-------|
|
|-------------------------------------------------------------------------------------------------------------|------------|-------|----------|-----------|------------|-------|-------|
|
||||||
| <br>Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-project) | | | | | ✓ | ✓ | |
|
| <br>Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-project) | | | | | ✓ | ✓ | |
|
||||||
| <br>Use Duo features | | ✓ | ✓ | ✓ | ✓ | ✓ | Code Suggestions requires a [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats). |
|
| <br>Use Duo features | | ✓ | ✓ | ✓ | ✓ | ✓ | Code Suggestions requires a [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). |
|
||||||
|
|
||||||
## Group members permissions
|
## Group members permissions
|
||||||
|
|
||||||
|
|
@ -527,11 +528,11 @@ Group permissions for [GitLab Duo](../user/gitlab_duo/index.md):
|
||||||
|
|
||||||
| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes |
|
| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes |
|
||||||
|-------------------------------------------------------------------------------------------------------------|------------|-------|----------|-----------|------------|-------|-------|
|
|-------------------------------------------------------------------------------------------------------------|------------|-------|----------|-----------|------------|-------|-------|
|
||||||
| <br>Purchase [Duo seats](../subscriptions/subscription-add-ons.md#purchase-additional-gitlab-duo-pro-seats) | | | | | | ✓ | |
|
| <br>Purchase [Duo seats](../subscriptions/subscription-add-ons.md#purchase-additional-gitlab-duo-seats) | | | | | | ✓ | |
|
||||||
| <br>Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-group) | | | | | ✓ | ✓ | |
|
| <br>Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-group) | | | | | ✓ | ✓ | |
|
||||||
| <br>Configure [self-hosted models](../administration/self_hosted_models/configure_duo_features.md) | | | | | | ✓ | |
|
| <br>Configure [self-hosted models](../administration/self_hosted_models/configure_duo_features.md) | | | | | | ✓ | |
|
||||||
| <br>Enable [beta and experimental features](gitlab_duo/turn_on_off.md#turn-on-beta-and-experimental-features) | | | | | | ✓ | |
|
| <br>Enable [beta and experimental features](gitlab_duo/turn_on_off.md#turn-on-beta-and-experimental-features) | | | | | | ✓ | |
|
||||||
| <br>Use Duo features | | | ✓ | ✓ | ✓ | ✓ | Requires [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats). |
|
| <br>Use Duo features | | | ✓ | ✓ | ✓ | ✓ | Requires [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). |
|
||||||
|
|
||||||
## Users with Minimal Access
|
## Users with Minimal Access
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ Prerequisites:
|
||||||
|
|
||||||
- You must have [one of the supported IDE extensions](supported_extensions.md#supported-editor-extensions).
|
- You must have [one of the supported IDE extensions](supported_extensions.md#supported-editor-extensions).
|
||||||
- Your organization must have purchased the GitLab Duo Pro add-on and
|
- Your organization must have purchased the GitLab Duo Pro add-on and
|
||||||
[assigned you a seat](../../../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats).
|
[assigned you a seat](../../../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats).
|
||||||
- For self-managed GitLab, you must have GitLab 16.8 or later, and have
|
- For self-managed GitLab, you must have GitLab 16.8 or later, and have
|
||||||
[configured proxy settings](../../../../subscriptions/subscription-add-ons.md#configure-network-and-proxy-settings).
|
[configured proxy settings](../../../../subscriptions/subscription-add-ons.md#configure-network-and-proxy-settings).
|
||||||
To use Code Suggestions:
|
To use Code Suggestions:
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ A flash message with Code Suggestions check status is displayed at the top of th
|
||||||
If suggestions are not displayed, follow these steps:
|
If suggestions are not displayed, follow these steps:
|
||||||
|
|
||||||
1. Ensure you have [installed a supported IDE extension](supported_extensions.md#supported-editor-extensions)
|
1. Ensure you have [installed a supported IDE extension](supported_extensions.md#supported-editor-extensions)
|
||||||
1. Ensure your administrator has [assigned you a seat](../../../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats).
|
1. Ensure your administrator has [assigned you a seat](../../../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats).
|
||||||
|
|
||||||
If suggestions are still not displayed, try the following troubleshooting steps.
|
If suggestions are still not displayed, try the following troubleshooting steps.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ module API
|
||||||
feature_category: feature_category,
|
feature_category: feature_category,
|
||||||
**http_router_rule_context
|
**http_router_rule_context
|
||||||
)
|
)
|
||||||
|
|
||||||
|
increment_http_router_metrics
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
@ -201,7 +203,8 @@ module API
|
||||||
helpers ::API::Helpers::CommonHelpers
|
helpers ::API::Helpers::CommonHelpers
|
||||||
helpers ::API::Helpers::PerformanceBarHelpers
|
helpers ::API::Helpers::PerformanceBarHelpers
|
||||||
helpers ::API::Helpers::RateLimiter
|
helpers ::API::Helpers::RateLimiter
|
||||||
helpers Gitlab::HttpRouterRuleContext
|
helpers Gitlab::HttpRouter::RuleContext
|
||||||
|
helpers Gitlab::HttpRouter::RuleMetrics
|
||||||
|
|
||||||
namespace do
|
namespace do
|
||||||
after do
|
after do
|
||||||
|
|
|
||||||
|
|
@ -472,6 +472,47 @@ module Gitlab
|
||||||
current_request.path.match(%r{access_tokens/\d+/rotate$}) ||
|
current_request.path.match(%r{access_tokens/\d+/rotate$}) ||
|
||||||
current_request.path.match(%r{/personal_access_tokens/self/rotate$})
|
current_request.path.match(%r{/personal_access_tokens/self/rotate$})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# To prevent Rack Attack from incorrectly rate limiting
|
||||||
|
# authenticated Git activity, we need to authenticate the user
|
||||||
|
# from other means (e.g. HTTP Basic Authentication) only if the
|
||||||
|
# request originated from a Git or Git LFS
|
||||||
|
# request. Repositories::GitHttpClientController or
|
||||||
|
# Repositories::LfsApiController normally does the authentication,
|
||||||
|
# but Rack Attack runs before those controllers.
|
||||||
|
def find_user_for_git_or_lfs_request
|
||||||
|
return unless git_or_lfs_request?
|
||||||
|
|
||||||
|
find_user_from_lfs_token || find_user_from_basic_auth_password
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_user_from_personal_access_token_for_api_or_git
|
||||||
|
return unless api_request? || git_or_lfs_request?
|
||||||
|
|
||||||
|
find_user_from_personal_access_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_user_from_dependency_proxy_token
|
||||||
|
return unless dependency_proxy_request?
|
||||||
|
|
||||||
|
token, _ = ActionController::HttpAuthentication::Token.token_and_options(current_request)
|
||||||
|
|
||||||
|
return unless token
|
||||||
|
|
||||||
|
user_or_deploy_token = ::DependencyProxy::AuthTokenService.user_or_deploy_token_from_jwt(token)
|
||||||
|
|
||||||
|
# Do not return deploy tokens
|
||||||
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/342481
|
||||||
|
return unless user_or_deploy_token.is_a?(::User)
|
||||||
|
|
||||||
|
user_or_deploy_token
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
nil # invalid id used return no user
|
||||||
|
end
|
||||||
|
|
||||||
|
def dependency_proxy_request?
|
||||||
|
Gitlab::PathRegex.dependency_proxy_route_regex.match?(current_request.path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -50,25 +50,6 @@ module Gitlab
|
||||||
(user&.project_bot? || user&.service_account?) && api_request?
|
(user&.project_bot? || user&.service_account?) && api_request?
|
||||||
end
|
end
|
||||||
|
|
||||||
# To prevent Rack Attack from incorrectly rate limiting
|
|
||||||
# authenticated Git activity, we need to authenticate the user
|
|
||||||
# from other means (e.g. HTTP Basic Authentication) only if the
|
|
||||||
# request originated from a Git or Git LFS
|
|
||||||
# request. Repositories::GitHttpClientController or
|
|
||||||
# Repositories::LfsApiController normally does the authentication,
|
|
||||||
# but Rack Attack runs before those controllers.
|
|
||||||
def find_user_for_git_or_lfs_request
|
|
||||||
return unless git_or_lfs_request?
|
|
||||||
|
|
||||||
find_user_from_lfs_token || find_user_from_basic_auth_password
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_user_from_personal_access_token_for_api_or_git
|
|
||||||
return unless api_request? || git_or_lfs_request?
|
|
||||||
|
|
||||||
find_user_from_personal_access_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_access_token?(scopes: [])
|
def valid_access_token?(scopes: [])
|
||||||
# We may just be checking whether the user has :admin_mode access, so
|
# We may just be checking whether the user has :admin_mode access, so
|
||||||
# don't construe an auth failure as a real failure.
|
# don't construe an auth failure as a real failure.
|
||||||
|
|
@ -131,28 +112,6 @@ module Gitlab
|
||||||
deploy_token_allowed: api_request? || git_request?
|
deploy_token_allowed: api_request? || git_request?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user_from_dependency_proxy_token
|
|
||||||
return unless dependency_proxy_request?
|
|
||||||
|
|
||||||
token, _ = ActionController::HttpAuthentication::Token.token_and_options(current_request)
|
|
||||||
|
|
||||||
return unless token
|
|
||||||
|
|
||||||
user_or_deploy_token = ::DependencyProxy::AuthTokenService.user_or_deploy_token_from_jwt(token)
|
|
||||||
|
|
||||||
# Do not return deploy tokens
|
|
||||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/342481
|
|
||||||
return unless user_or_deploy_token.is_a?(::User)
|
|
||||||
|
|
||||||
user_or_deploy_token
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
nil # invalid id used return no user
|
|
||||||
end
|
|
||||||
|
|
||||||
def dependency_proxy_request?
|
|
||||||
Gitlab::PathRegex.dependency_proxy_route_regex.match?(current_request.path)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ module Gitlab
|
||||||
module Sbom
|
module Sbom
|
||||||
module Validators
|
module Validators
|
||||||
class CyclonedxSchemaValidator
|
class CyclonedxSchemaValidator
|
||||||
SUPPORTED_SPEC_VERSIONS = %w[1.4 1.5].freeze
|
SUPPORTED_SPEC_VERSIONS = %w[1.4 1.5 1.6].freeze
|
||||||
|
|
||||||
SCHEMA_BASE_PATH = Rails.root.join('app', 'validators', 'json_schemas', 'cyclonedx').freeze
|
SCHEMA_BASE_PATH = Rails.root.join('app', 'validators', 'json_schemas', 'cyclonedx').freeze
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module HttpRouter
|
||||||
|
module RuleContext
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
# This module is used to log the headers set by the HTTP Router.
|
||||||
|
# Refer: https://gitlab.com/gitlab-org/cells/http-router/-/blob/main/src/header.ts
|
||||||
|
# to obtain the list of headers.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. Include this concern in base controller:
|
||||||
|
# include Gitlab::HttpRouter::RuleContext
|
||||||
|
# Or, in the API layer as a helper
|
||||||
|
# helpers Gitlab::HttpRouter::RuleContext
|
||||||
|
#
|
||||||
|
# 2. Use the `http_router_rule_context` method when pushing to Gitlab::ApplicationContext:
|
||||||
|
# Gitlab::ApplicationContext.push(**router_rule_context)
|
||||||
|
|
||||||
|
# These values should be kept in sync with the values in the HTTP Router.
|
||||||
|
# https://gitlab.com/gitlab-org/cells/http-router/-/blob/main/src/rules/types.d.ts
|
||||||
|
|
||||||
|
ALLOWED_ROUTER_RULE_ACTIONS = %w[classify proxy].freeze
|
||||||
|
# We do not expect a type for `proxy` rules
|
||||||
|
ROUTER_RULE_ACTIONS_WITHOUT_TYPE = %w[proxy].freeze
|
||||||
|
ALLOWED_ROUTER_RULE_TYPES = %w[FIRST_CELL SESSION_PREFIX].freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def http_router_rule_context
|
||||||
|
{
|
||||||
|
http_router_rule_action: sanitized_http_router_rule_action,
|
||||||
|
http_router_rule_type: sanitized_http_router_rule_type
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitized_http_router_rule_action
|
||||||
|
sanitize_value(
|
||||||
|
request.headers['X-Gitlab-Http-Router-Rule-Action'],
|
||||||
|
ALLOWED_ROUTER_RULE_ACTIONS
|
||||||
|
)
|
||||||
|
end
|
||||||
|
strong_memoize_attr :sanitized_http_router_rule_action
|
||||||
|
|
||||||
|
def sanitized_http_router_rule_type
|
||||||
|
sanitize_router_rule_type(
|
||||||
|
request.headers['X-Gitlab-Http-Router-Rule-Type'],
|
||||||
|
sanitized_http_router_rule_action,
|
||||||
|
ALLOWED_ROUTER_RULE_TYPES
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize_value(value, allowed_values)
|
||||||
|
return if value.blank?
|
||||||
|
|
||||||
|
allowed_values.include?(value) ? value : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize_router_rule_type(value, sanitized_http_router_rule_action, allowed_values)
|
||||||
|
# Considerations:
|
||||||
|
# - `type` cannot exist without an `action`
|
||||||
|
# - Some actions (`proxy`) are not expected to have a corresponding `type`, so we perform an early return.
|
||||||
|
return if sanitized_http_router_rule_action.blank?
|
||||||
|
return if ROUTER_RULE_ACTIONS_WITHOUT_TYPE.include?(sanitized_http_router_rule_action)
|
||||||
|
|
||||||
|
sanitize_value(value, allowed_values)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module HttpRouter
|
||||||
|
module RuleMetrics
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include HttpRouter::RuleContext
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
def increment_http_router_metrics
|
||||||
|
context = http_router_rule_context
|
||||||
|
increment_http_router_rule_counter(context[:http_router_rule_action], context[:http_router_rule_type])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def increment_http_router_rule_counter(http_router_rule_action, http_router_rule_type)
|
||||||
|
# `action` should be present, but `type` is optional
|
||||||
|
return if http_router_rule_action.blank?
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
rule_action: http_router_rule_action,
|
||||||
|
rule_type: http_router_rule_type
|
||||||
|
}
|
||||||
|
|
||||||
|
http_router_rule_counter.increment(labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
def http_router_rule_counter
|
||||||
|
name = :gitlab_http_router_rule_total
|
||||||
|
comment = 'Total number of HTTP router rule invocations'
|
||||||
|
|
||||||
|
::Gitlab::Metrics.counter(name, comment)
|
||||||
|
end
|
||||||
|
strong_memoize_attr :http_router_rule_counter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Gitlab
|
|
||||||
module HttpRouterRuleContext
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
# This module is used to log the headers set by the HTTP Router.
|
|
||||||
# Refer: https://gitlab.com/gitlab-org/cells/http-router/-/blob/main/src/header.ts
|
|
||||||
# to obtain the list of headers.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# 1. Include this concern in base controller:
|
|
||||||
# include Gitlab::HttpRouterRuleContext
|
|
||||||
# Or, in the API layer as a helper
|
|
||||||
# helpers Gitlab::HttpRouterRuleContext
|
|
||||||
#
|
|
||||||
# 2. Use the `http_router_rule_context` method when pushing to Gitlab::ApplicationContext:
|
|
||||||
# Gitlab::ApplicationContext.push(**router_rule_context)
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def http_router_rule_context
|
|
||||||
{
|
|
||||||
http_router_rule_action: request.headers['X-Gitlab-Http-Router-Rule-Action'],
|
|
||||||
http_router_rule_type: request.headers['X-Gitlab-Http-Router-Rule-Type']
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -32824,6 +32824,9 @@ msgstr ""
|
||||||
msgid "MemberRole|Custom role"
|
msgid "MemberRole|Custom role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "MemberRole|Custom role created on %{dateTime}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "MemberRole|Custom roles"
|
msgid "MemberRole|Custom roles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -32854,6 +32857,9 @@ msgstr ""
|
||||||
msgid "MemberRole|Failed to delete role. %{error}"
|
msgid "MemberRole|Failed to delete role. %{error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "MemberRole|Failed to fetch role."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "MemberRole|Failed to fetch roles."
|
msgid "MemberRole|Failed to fetch roles."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -32944,9 +32950,15 @@ msgstr ""
|
||||||
msgid "MemberRole|This role has been manually selected and will not sync to the LDAP sync role."
|
msgid "MemberRole|This role has been manually selected and will not sync to the LDAP sync role."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "MemberRole|This role is available by default and cannot be changed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "MemberRole|To delete custom role, remove role from all group members."
|
msgid "MemberRole|To delete custom role, remove role from all group members."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "MemberRole|To delete custom role, remove role from all users."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "MemberRole|Update role"
|
msgid "MemberRole|Update role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"compression-webpack-plugin": "^5.0.2",
|
"compression-webpack-plugin": "^5.0.2",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"core-js": "^3.38.0",
|
"core-js": "^3.38.1",
|
||||||
"cron-validator": "^1.1.1",
|
"cron-validator": "^1.1.1",
|
||||||
"cronstrue": "^1.122.0",
|
"cronstrue": "^1.122.0",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { GlTableLite } from '@gitlab/ui';
|
import { GlTableLite, GlSkeletonLoader } from '@gitlab/ui';
|
||||||
import fixture from 'test_fixtures/pipelines/pipelines.json';
|
import fixture from 'test_fixtures/pipelines/pipelines.json';
|
||||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
|
@ -57,6 +57,7 @@ describe('Pipelines Table', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||||
const findGlTableLite = () => wrapper.findComponent(GlTableLite);
|
const findGlTableLite = () => wrapper.findComponent(GlTableLite);
|
||||||
const findCiIcon = () => wrapper.findComponent(CiIcon);
|
const findCiIcon = () => wrapper.findComponent(CiIcon);
|
||||||
const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
|
const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
|
||||||
|
|
@ -216,6 +217,36 @@ describe('Pipelines Table', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('async pipeline creation', () => {
|
||||||
|
describe('when isCreatingPipeline is enabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent({ props: { isCreatingPipeline: true } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Adds an additional loader row to the pipelines table', () => {
|
||||||
|
expect(findTableRows()).toHaveLength(pipelines.length + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the skeleton loader', () => {
|
||||||
|
expect(findSkeletonLoader().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when isCreatingPipeline is disabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not add a loader row to the pipelines table', () => {
|
||||||
|
expect(findTableRows()).toHaveLength(pipelines.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render skeleton loader', () => {
|
||||||
|
expect(findSkeletonLoader().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('events', () => {
|
describe('events', () => {
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,14 @@ describe('Pipelines table in Commits and Merge requests', () => {
|
||||||
|
|
||||||
expect(findRunPipelineBtn().props('disabled')).toBe(false);
|
expect(findRunPipelineBtn().props('disabled')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets isCreatingPipeline to true in pipelines table', async () => {
|
||||||
|
expect(findPipelinesTable().props('isCreatingPipeline')).toBe(false);
|
||||||
|
|
||||||
|
await findRunPipelineBtn().trigger('click');
|
||||||
|
|
||||||
|
expect(findPipelinesTable().props('isCreatingPipeline')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when asyncMergeRequestPipelineCreation is disabled', () => {
|
describe('when asyncMergeRequestPipelineCreation is disabled', () => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||||
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
|
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
|
||||||
import PageHeading from '~/vue_shared/components/page_heading.vue';
|
import PageHeading from '~/vue_shared/components/page_heading.vue';
|
||||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||||
|
import SettingsSection from '~/vue_shared/components/settings/settings_section.vue';
|
||||||
import RuleView from '~/projects/settings/branch_rules/components/view/index.vue';
|
import RuleView from '~/projects/settings/branch_rules/components/view/index.vue';
|
||||||
import RuleDrawer from '~/projects/settings/branch_rules/components/view/rule_drawer.vue';
|
import RuleDrawer from '~/projects/settings/branch_rules/components/view/rule_drawer.vue';
|
||||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||||
|
|
@ -125,8 +126,7 @@ describe('View branch rules', () => {
|
||||||
|
|
||||||
const findBranchName = () => wrapper.findByTestId('branch');
|
const findBranchName = () => wrapper.findByTestId('branch');
|
||||||
const findAllBranches = () => wrapper.findByTestId('all-branches');
|
const findAllBranches = () => wrapper.findByTestId('all-branches');
|
||||||
const findBranchProtectionCrud = () => wrapper.findByTestId('status-checks');
|
const findSettingsSection = () => wrapper.findComponent(SettingsSection);
|
||||||
const findBranchProtectionTitle = () => wrapper.findByTestId('crud-title');
|
|
||||||
const findAllowedToMerge = () => wrapper.findByTestId('allowed-to-merge-content');
|
const findAllowedToMerge = () => wrapper.findByTestId('allowed-to-merge-content');
|
||||||
const findAllowedToPush = () => wrapper.findByTestId('allowed-to-push-content');
|
const findAllowedToPush = () => wrapper.findByTestId('allowed-to-push-content');
|
||||||
const findAllowForcePushToggle = () => wrapper.findByTestId('force-push-content');
|
const findAllowForcePushToggle = () => wrapper.findByTestId('force-push-content');
|
||||||
|
|
@ -176,14 +176,13 @@ describe('View branch rules', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a branch protection title', () => {
|
it('renders a branch protection title', () => {
|
||||||
expect(findBranchProtectionTitle().exists()).toBe(true);
|
expect(findSettingsSection().attributes('heading')).toBe('Protect branch');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a branch protection component for push rules', () => {
|
it('renders a branch protection component for push rules', () => {
|
||||||
expect(findAllowedToPush().props()).toMatchObject({
|
expect(findAllowedToPush().props()).toMatchObject({
|
||||||
header: sprintf(I18N.allowedToPushHeader, {
|
header: 'Allowed to push and merge',
|
||||||
total: 2,
|
count: 2,
|
||||||
}),
|
|
||||||
...protectionMockProps,
|
...protectionMockProps,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -224,9 +223,8 @@ describe('View branch rules', () => {
|
||||||
|
|
||||||
it('renders a branch protection component for merge rules', () => {
|
it('renders a branch protection component for merge rules', () => {
|
||||||
expect(findAllowedToMerge().props()).toMatchObject({
|
expect(findAllowedToMerge().props()).toMatchObject({
|
||||||
header: sprintf(I18N.allowedToMergeHeader, {
|
header: 'Allowed to merge',
|
||||||
total: 2,
|
count: 2,
|
||||||
}),
|
|
||||||
...protectionMockProps,
|
...protectionMockProps,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -385,7 +383,7 @@ describe('View branch rules', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not render Protect Branch section', () => {
|
it('does not render Protect Branch section', () => {
|
||||||
expect(findBranchProtectionCrud().exists()).toBe(false);
|
expect(findSettingsSection().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const createComponent = (propsData = issuableTitleProps) =>
|
||||||
describe('IssuableTitle', () => {
|
describe('IssuableTitle', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const findStickyHeader = () => wrapper.findComponent('[data-testid="header"]');
|
const findStickyHeader = () => wrapper.find('[data-testid="header"]');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = createComponent();
|
wrapper = createComponent();
|
||||||
|
|
@ -58,11 +58,13 @@ describe('IssuableTitle', () => {
|
||||||
|
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('renders issuable title', async () => {
|
it('renders issuable title', async () => {
|
||||||
|
const titleHtml = '<b>Sample</b> title';
|
||||||
|
|
||||||
const wrapperWithTitle = createComponent({
|
const wrapperWithTitle = createComponent({
|
||||||
...mockIssuableShowProps,
|
...mockIssuableShowProps,
|
||||||
issuable: {
|
issuable: {
|
||||||
...mockIssuable,
|
...mockIssuable,
|
||||||
titleHtml: '<b>Sample</b> title',
|
titleHtml,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -70,9 +72,7 @@ describe('IssuableTitle', () => {
|
||||||
const titleEl = wrapperWithTitle.find('[data-testid="issuable-title"]');
|
const titleEl = wrapperWithTitle.find('[data-testid="issuable-title"]');
|
||||||
|
|
||||||
expect(titleEl.exists()).toBe(true);
|
expect(titleEl.exists()).toBe(true);
|
||||||
expect(titleEl.html()).toBe(
|
expect(titleEl.element.innerHTML).toBe('<b>Sample</b> title');
|
||||||
'<h1 dir="auto" data-testid="issuable-title" class="title gl-text-size-h-display"><b>Sample</b> title</h1>',
|
|
||||||
);
|
|
||||||
|
|
||||||
wrapperWithTitle.destroy();
|
wrapperWithTitle.destroy();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,14 @@ RSpec.describe TodosHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when todo resource parent is not a group' do
|
context 'when todo resource parent is not a group' do
|
||||||
|
context 'when todo belongs to no project either' do
|
||||||
|
let(:todo) { build(:todo, group: nil, project: nil, user: user) }
|
||||||
|
|
||||||
|
subject(:result) { helper.todo_parent_path(todo) }
|
||||||
|
|
||||||
|
it { expect(result).to eq(nil) }
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns project title with namespace' do
|
it 'returns project title with namespace' do
|
||||||
result = helper.todo_parent_path(project_access_request_todo)
|
result = helper.todo_parent_path(project_access_request_todo)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator,
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when spec version is supported' do
|
context 'when spec version is supported' do
|
||||||
where(:spec_version) { %w[1.4 1.5] }
|
where(:spec_version) { %w[1.4 1.5 1.6] }
|
||||||
|
|
||||||
with_them do
|
with_them do
|
||||||
it_behaves_like 'a validator that performs the expected validations'
|
it_behaves_like 'a validator that performs the expected validations'
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,80 @@ RSpec.describe API::API, feature_category: :system_access do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'counter metrics', :aggregate_failures do
|
||||||
|
let_it_be(:project) { create(:project, :public) }
|
||||||
|
let_it_be(:user) { project.first_owner }
|
||||||
|
let_it_be(:http_router_rule_counter) { Gitlab::Metrics.counter(:gitlab_http_router_rule_total, 'description') }
|
||||||
|
|
||||||
|
let(:perform_request) { get(api("/projects/#{project.id}", user), headers: headers) }
|
||||||
|
|
||||||
|
context 'when the headers are present' do
|
||||||
|
context 'for classify action' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'FIRST_CELL'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'increments the counter' do
|
||||||
|
expect { perform_request }
|
||||||
|
.to change { http_router_rule_counter.get(rule_action: 'classify', rule_type: 'FIRST_CELL') }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for proxy action' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'proxy'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'increments the counter' do
|
||||||
|
expect { perform_request }
|
||||||
|
.to change { http_router_rule_counter.get(rule_action: 'proxy', rule_type: nil) }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for invalid action and type' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'invalid',
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'invalid'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }
|
||||||
|
.to change { http_router_rule_counter.get(rule_action: 'invalid', rule_type: 'invalid') }.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when action is not present and type is present' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'FIRST_CELL'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: nil, rule_type: 'FIRST_CELL')
|
||||||
|
}.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the headers are absent' do
|
||||||
|
let(:headers) { {} }
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }
|
||||||
|
.to change { http_router_rule_counter.get(rule_action: nil, rule_type: nil) }.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'logging', :aggregate_failures do
|
describe 'logging', :aggregate_failures do
|
||||||
let_it_be(:project) { create(:project, :public) }
|
let_it_be(:project) { create(:project, :public) }
|
||||||
let_it_be(:user) { project.first_owner }
|
let_it_be(:user) { project.first_owner }
|
||||||
|
|
@ -132,14 +206,14 @@ RSpec.describe API::API, feature_category: :system_access do
|
||||||
'meta.client_id' => a_string_matching(%r{\Auser/.+}),
|
'meta.client_id' => a_string_matching(%r{\Auser/.+}),
|
||||||
'meta.feature_category' => 'team_planning',
|
'meta.feature_category' => 'team_planning',
|
||||||
'meta.http_router_rule_action' => 'classify',
|
'meta.http_router_rule_action' => 'classify',
|
||||||
'meta.http_router_rule_type' => 'FirstCell',
|
'meta.http_router_rule_type' => 'FIRST_CELL',
|
||||||
'route' => '/api/:version/projects/:id/issues'
|
'route' => '/api/:version/projects/:id/issues'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
get(api("/projects/#{project.id}/issues", user), headers: {
|
get(api("/projects/#{project.id}/issues", user), headers: {
|
||||||
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
||||||
'X-Gitlab-Http-Router-Rule-Type' => 'FirstCell'
|
'X-Gitlab-Http-Router-Rule-Type' => 'FIRST_CELL'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,17 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'FIRST_CELL'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
subject(:perform_request) do
|
||||||
|
get root_path, headers: headers
|
||||||
|
end
|
||||||
|
|
||||||
it 'includes the HTTP ROUTER headers in ApplicationContext' do
|
it 'includes the HTTP ROUTER headers in ApplicationContext' do
|
||||||
expect_next_instance_of(RootController) do |controller|
|
expect_next_instance_of(RootController) do |controller|
|
||||||
expect(controller).to receive(:index).and_wrap_original do |m, *args|
|
expect(controller).to receive(:index).and_wrap_original do |m, *args|
|
||||||
|
|
@ -98,16 +109,80 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
|
||||||
|
|
||||||
expect(Gitlab::ApplicationContext.current).to include(
|
expect(Gitlab::ApplicationContext.current).to include(
|
||||||
'meta.http_router_rule_action' => 'classify',
|
'meta.http_router_rule_action' => 'classify',
|
||||||
'meta.http_router_rule_type' => 'FirstCell'
|
'meta.http_router_rule_type' => 'FIRST_CELL'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get root_path, headers: {
|
perform_request
|
||||||
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
end
|
||||||
'X-Gitlab-Http-Router-Rule-Type' => 'FirstCell'
|
|
||||||
|
context 'for counters' do
|
||||||
|
let(:http_router_rule_counter) { Gitlab::Metrics.counter(:gitlab_http_router_rule_total, 'description') }
|
||||||
|
|
||||||
|
context 'when the headers are present' do
|
||||||
|
context 'for classify action' do
|
||||||
|
it 'increments the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: 'classify', rule_type: 'FIRST_CELL')
|
||||||
|
}.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for proxy action' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'proxy'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'increments the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: 'proxy', rule_type: nil)
|
||||||
|
}.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for invalid action and type' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Action' => 'invalid',
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'invalid'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: 'invalid', rule_type: 'invalid')
|
||||||
|
}.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when action is not present and type is present' do
|
||||||
|
let(:headers) do
|
||||||
|
{
|
||||||
|
'X-Gitlab-Http-Router-Rule-Type' => 'FIRST_CELL'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: nil, rule_type: 'FIRST_CELL')
|
||||||
|
}.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the headers are absent' do
|
||||||
|
let(:headers) { {} }
|
||||||
|
|
||||||
|
it 'does not increment the counter' do
|
||||||
|
expect { perform_request }.to change {
|
||||||
|
http_router_rule_counter.get(rule_action: nil, rule_type: nil)
|
||||||
|
}.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'static context middleware', feature_category: :error_budgets do
|
describe 'static context middleware', feature_category: :error_budgets do
|
||||||
|
|
|
||||||
|
|
@ -5017,10 +5017,10 @@ core-js-pure@^3.30.2:
|
||||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.0.tgz#4660033304a050215ae82e476bd2513a419fbb34"
|
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.0.tgz#4660033304a050215ae82e476bd2513a419fbb34"
|
||||||
integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==
|
integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==
|
||||||
|
|
||||||
core-js@^3.29.1, core-js@^3.38.0, core-js@^3.6.5:
|
core-js@^3.29.1, core-js@^3.38.1, core-js@^3.6.5:
|
||||||
version "3.38.0"
|
version "3.38.1"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.0.tgz#8acb7c050bf2ccbb35f938c0d040132f6110f636"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.1.tgz#aa375b79a286a670388a1a363363d53677c0383e"
|
||||||
integrity sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==
|
integrity sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue