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 = {
|
||||
[ACCESS_LEVEL_NO_ACCESS_INTEGER]: ACCESS_LEVEL_NO_ACCESS,
|
||||
[ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER]: ACCESS_LEVEL_MINIMAL_ACCESS,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<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 { s__, __ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
|
|
@ -32,7 +33,10 @@ const HIDE_TD_ON_MOBILE = '!gl-hidden lg:!gl-table-cell';
|
|||
*/
|
||||
|
||||
export default {
|
||||
name: 'PipelinesTable',
|
||||
cellHeight: 50,
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
GlTableLite,
|
||||
LegacyPipelineMiniGraph,
|
||||
PipelineFailedJobsWidget,
|
||||
|
|
@ -51,15 +55,15 @@ export default {
|
|||
},
|
||||
},
|
||||
props: {
|
||||
pipelines: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
updateGraphDropdown: {
|
||||
isCreatingPipeline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
pipelines: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
pipelineIdType: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -68,8 +72,16 @@ export default {
|
|||
return value === PIPELINE_IID_KEY || value === PIPELINE_ID_KEY;
|
||||
},
|
||||
},
|
||||
updateGraphDropdown: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return ['md', 'sm', 'xs'].includes(GlBreakpointInstance.getBreakpointSize());
|
||||
},
|
||||
tableFields() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -112,13 +124,19 @@ export default {
|
|||
return this.useFailedJobsWidget ? '!gl-pb-0 !gl-border-none' : 'pl-p-5!';
|
||||
},
|
||||
pipelinesWithDetails() {
|
||||
let { pipelines } = this;
|
||||
|
||||
if (this.isCreatingPipeline) {
|
||||
pipelines = [{ isLoading: true }, ...this.pipelines];
|
||||
}
|
||||
|
||||
if (this.useFailedJobsWidget) {
|
||||
return this.pipelines.map((p) => {
|
||||
pipelines = pipelines.map((p) => {
|
||||
return { ...p, _showDetails: true };
|
||||
});
|
||||
}
|
||||
|
||||
return this.pipelines;
|
||||
return pipelines;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -126,6 +144,9 @@ export default {
|
|||
const downstream = pipeline.triggered;
|
||||
return keepLatestDownstreamPipelines(downstream);
|
||||
},
|
||||
cellWidth(ref) {
|
||||
return this.$refs[ref]?.offsetWidth;
|
||||
},
|
||||
getProjectPath(item) {
|
||||
return cleanLeadingSeparator(item.project.full_path);
|
||||
},
|
||||
|
|
@ -144,6 +165,13 @@ export default {
|
|||
onCancelPipeline(pipeline) {
|
||||
this.$emit('cancel-pipeline', pipeline);
|
||||
},
|
||||
setLoaderPosition(ref) {
|
||||
if (this.isMobile) {
|
||||
return this.cellWidth(ref) / 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
trackPipelineMiniGraph() {
|
||||
this.track('click_minigraph', { label: TRACKING_CATEGORIES.table });
|
||||
},
|
||||
|
|
@ -172,11 +200,30 @@ export default {
|
|||
</template>
|
||||
|
||||
<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 #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
|
||||
v-else
|
||||
:pipeline="item"
|
||||
:pipeline-id-type="pipelineIdType"
|
||||
ref-color="gl-text-default"
|
||||
|
|
@ -184,11 +231,22 @@ export default {
|
|||
</template>
|
||||
|
||||
<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 #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
|
||||
v-else
|
||||
:downstream-pipelines="getDownstreamPipelines(item)"
|
||||
:pipeline-path="item.path"
|
||||
:stages="getStages(item)"
|
||||
|
|
@ -200,6 +258,7 @@ export default {
|
|||
|
||||
<template #cell(actions)="{ item }">
|
||||
<pipeline-operations
|
||||
v-if="!item.isLoading"
|
||||
:pipeline="item"
|
||||
@cancel-pipeline="onCancelPipeline"
|
||||
@refresh-pipelines-table="onRefreshPipelinesTable"
|
||||
|
|
@ -209,7 +268,7 @@ export default {
|
|||
|
||||
<template #row-details="{ item }">
|
||||
<pipeline-failed-jobs-widget
|
||||
v-if="useFailedJobsWidget"
|
||||
v-if="useFailedJobsWidget && !item.isLoading"
|
||||
:failed-jobs-count="failedJobsCount(item)"
|
||||
:is-pipeline-active="item.active"
|
||||
:pipeline-iid="item.iid"
|
||||
|
|
|
|||
|
|
@ -296,10 +296,11 @@ export default {
|
|||
</gl-button>
|
||||
|
||||
<pipelines-table
|
||||
:is-creating-pipeline="state.isRunningMergeRequestPipeline"
|
||||
:pipeline-id-type="$options.pipelineIdKey"
|
||||
:pipelines="state.pipelines"
|
||||
:update-graph-dropdown="updateGraphDropdown"
|
||||
:view-type="viewType"
|
||||
:pipeline-id-type="$options.pipelineIdKey"
|
||||
@cancel-pipeline="onCancelPipeline"
|
||||
@refresh-pipelines-table="onRefreshPipelinesTable"
|
||||
@retry-pipeline="onRetryPipeline"
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ class GfmAutoComplete {
|
|||
tpl += ' <small class="params"><%- params.join(" ") %></small>';
|
||||
}
|
||||
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',
|
||||
's16 gl-mr-2',
|
||||
)}<em><%- warning %></em></small>`;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ div.innerHTML = `
|
|||
</defs>
|
||||
</svg>
|
||||
<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 />
|
||||
Vite compiles frontend assets faster and eliminates the need for this message.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item
|
|||
|
||||
const ALLOWED_ICONS = ['issue-close'];
|
||||
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 {
|
||||
|
|
@ -115,23 +115,27 @@ export default {
|
|||
<template>
|
||||
<timeline-entry-item
|
||||
:id="noteAnchorId"
|
||||
:class="{ target: isTargetNote, 'pr-0': shouldShowDescriptionVersion }"
|
||||
class="note system-note note-wrapper"
|
||||
:class="{
|
||||
target: isTargetNote,
|
||||
'pr-0': shouldShowDescriptionVersion,
|
||||
}"
|
||||
class="system-note-v2"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
getIconColor,
|
||||
{
|
||||
'system-note-icon gl-bg-gray-50 gl-text-gray-600': isAllowedIcon,
|
||||
'system-note-tiny-dot !gl-bg-gray-900': !isAllowedIcon,
|
||||
'system-note-icon-v2 gl-h-6 gl-w-6 gl-ml-2 -gl-mt-1': 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"
|
||||
>
|
||||
<gl-icon v-if="isAllowedIcon" :size="12" :name="note.systemNoteIconName" />
|
||||
<gl-icon v-if="isAllowedIcon" :size="14" :name="note.systemNoteIconName" />
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="note-header">
|
||||
<div class="gl-ml-7">
|
||||
<div class="gl-flex gl-justify-between gl-items-start">
|
||||
<note-header
|
||||
:author="note.author"
|
||||
:created-at="note.createdAt"
|
||||
|
|
@ -153,11 +157,8 @@ export default {
|
|||
</template>
|
||||
</note-header>
|
||||
</div>
|
||||
<div class="note-body">
|
||||
<div
|
||||
v-if="shouldShowDescriptionVersion"
|
||||
class="description-version gl-relative !gl-pt-3 gl-pl-4"
|
||||
>
|
||||
<div class="note-body-v2 gl-pl-3 gl-pb-3">
|
||||
<div v-if="shouldShowDescriptionVersion" class="gl-relative !gl-pt-3">
|
||||
<pre v-if="isLoadingDescriptionVersion" class="loading-state">
|
||||
<gl-skeleton-loader />
|
||||
</pre>
|
||||
|
|
@ -165,7 +166,7 @@ export default {
|
|||
v-else
|
||||
v-safe-html="descriptionVersion"
|
||||
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>
|
||||
<gl-button
|
||||
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 'system_note_styles';
|
||||
@import './notes/system_notes_v2';
|
||||
|
||||
.issuable-details {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@import 'mixins_and_variables_and_functions';
|
||||
@import 'system_note_styles';
|
||||
@import './notes/system_notes_v2';
|
||||
|
||||
$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 RequestPayloadLogger
|
||||
include StrongPaginationParams
|
||||
include Gitlab::HttpRouterRuleContext
|
||||
include Gitlab::HttpRouter::RuleContext
|
||||
include Gitlab::HttpRouter::RuleMetrics
|
||||
|
||||
before_action :authenticate_user!, except: [:route_not_found]
|
||||
before_action :set_current_organization
|
||||
|
|
@ -42,6 +43,7 @@ class ApplicationController < BaseActionController
|
|||
before_action :active_user_check, unless: :devise_controller?
|
||||
before_action :set_usage_stats_consent_flag
|
||||
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
|
||||
# all other before filters that could have set the user.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ module TodosHelper
|
|||
if todo.resource_parent.is_a?(Group)
|
||||
todo.resource_parent.name
|
||||
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')
|
||||
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` |
|
||||
| `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` |
|
||||
| `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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Fulfillment
|
||||
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
|
||||
---
|
||||
|
||||
|
|
@ -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
|
||||
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 must assign the seats to billable users so that they can use GitLab Duo Pro.
|
||||
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.
|
||||
|
||||
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. On the subscription card, select the vertical ellipsis (**{ellipsis_v}**).
|
||||
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. From the **Payment method** dropdown list, select your payment method.
|
||||
1. Select **Purchase seats**.
|
||||
|
||||
## Assign GitLab Duo Pro seats
|
||||
## Assign GitLab Duo seats
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must purchase the GitLab Duo Pro add-on, or have the GitLab Duo Pro trial.
|
||||
- For self-managed and GitLab Dedicated, the GitLab Duo Pro add-on is available for GitLab 16.8 and later only.
|
||||
- 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 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
|
||||
|
||||
|
|
@ -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. 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
|
||||
|
||||
|
|
@ -61,14 +62,14 @@ Prerequisites:
|
|||
1. On the left sidebar, select **Subscription**.
|
||||
1. In **Subscription details**, to the right of **Last sync**, select
|
||||
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
|
||||
|
||||
For self-managed instances, to enable GitLab Duo features,
|
||||
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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
## 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:
|
||||
|
||||
- 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
|
||||
|
||||
|
|
@ -141,7 +142,7 @@ Prerequisites:
|
|||
1. Select **Continue**.
|
||||
1. If prompted, select the group that the trial should be applied to.
|
||||
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
|
||||
|
||||
|
|
@ -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).
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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/).
|
||||
- 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),
|
||||
[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.
|
||||
|
||||
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),
|
||||
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. Ensure that [seats are assigned to users](../../subscriptions/subscription-add-ons.md#assign-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-seats).
|
||||
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.
|
||||
- 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 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) | | | | ✓ | ✓ | |
|
||||
| Delete [environments](../ci/environments/index.md) | | | | ✓ | ✓ | |
|
||||
| View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | ✓ | ✓ | |
|
||||
| Use pipeline editor | | | | ✓ | ✓ | |
|
||||
| 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 |
|
||||
|-------------------------------------------------------------------------------------------------------------|------------|-------|----------|-----------|------------|-------|-------|
|
||||
| <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
|
||||
|
||||
|
|
@ -527,11 +528,11 @@ Group permissions for [GitLab Duo](../user/gitlab_duo/index.md):
|
|||
|
||||
| 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 [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>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
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Prerequisites:
|
|||
|
||||
- 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
|
||||
[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
|
||||
[configured proxy settings](../../../../subscriptions/subscription-add-ons.md#configure-network-and-proxy-settings).
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ module API
|
|||
feature_category: feature_category,
|
||||
**http_router_rule_context
|
||||
)
|
||||
|
||||
increment_http_router_metrics
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
@ -201,7 +203,8 @@ module API
|
|||
helpers ::API::Helpers::CommonHelpers
|
||||
helpers ::API::Helpers::PerformanceBarHelpers
|
||||
helpers ::API::Helpers::RateLimiter
|
||||
helpers Gitlab::HttpRouterRuleContext
|
||||
helpers Gitlab::HttpRouter::RuleContext
|
||||
helpers Gitlab::HttpRouter::RuleMetrics
|
||||
|
||||
namespace do
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -472,6 +472,47 @@ module Gitlab
|
|||
current_request.path.match(%r{access_tokens/\d+/rotate$}) ||
|
||||
current_request.path.match(%r{/personal_access_tokens/self/rotate$})
|
||||
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
|
||||
|
|
|
|||
|
|
@ -50,25 +50,6 @@ module Gitlab
|
|||
(user&.project_bot? || user&.service_account?) && api_request?
|
||||
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: [])
|
||||
# We may just be checking whether the user has :admin_mode access, so
|
||||
# don't construe an auth failure as a real failure.
|
||||
|
|
@ -131,28 +112,6 @@ module Gitlab
|
|||
deploy_token_allowed: api_request? || git_request?
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
module Sbom
|
||||
module Validators
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Custom role created on %{dateTime}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Custom roles"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32854,6 +32857,9 @@ msgstr ""
|
|||
msgid "MemberRole|Failed to delete role. %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Failed to fetch role."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Failed to fetch roles."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32944,9 +32950,15 @@ msgstr ""
|
|||
msgid "MemberRole|This role has been manually selected and will not sync to the LDAP sync role."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|To delete custom role, remove role from all users."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Update role"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@
|
|||
"colord": "^2.9.3",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"core-js": "^3.38.0",
|
||||
"core-js": "^3.38.1",
|
||||
"cron-validator": "^1.1.1",
|
||||
"cronstrue": "^1.122.0",
|
||||
"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 { mockTracking, unmockTracking } from 'helpers/tracking_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 findCiIcon = () => wrapper.findComponent(CiIcon);
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,14 @@ describe('Pipelines table in Commits and Merge requests', () => {
|
|||
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
|||
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
|
||||
import PageHeading from '~/vue_shared/components/page_heading.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 RuleDrawer from '~/projects/settings/branch_rules/components/view/rule_drawer.vue';
|
||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
|
|
@ -125,8 +126,7 @@ describe('View branch rules', () => {
|
|||
|
||||
const findBranchName = () => wrapper.findByTestId('branch');
|
||||
const findAllBranches = () => wrapper.findByTestId('all-branches');
|
||||
const findBranchProtectionCrud = () => wrapper.findByTestId('status-checks');
|
||||
const findBranchProtectionTitle = () => wrapper.findByTestId('crud-title');
|
||||
const findSettingsSection = () => wrapper.findComponent(SettingsSection);
|
||||
const findAllowedToMerge = () => wrapper.findByTestId('allowed-to-merge-content');
|
||||
const findAllowedToPush = () => wrapper.findByTestId('allowed-to-push-content');
|
||||
const findAllowForcePushToggle = () => wrapper.findByTestId('force-push-content');
|
||||
|
|
@ -176,14 +176,13 @@ describe('View branch rules', () => {
|
|||
});
|
||||
|
||||
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', () => {
|
||||
expect(findAllowedToPush().props()).toMatchObject({
|
||||
header: sprintf(I18N.allowedToPushHeader, {
|
||||
total: 2,
|
||||
}),
|
||||
header: 'Allowed to push and merge',
|
||||
count: 2,
|
||||
...protectionMockProps,
|
||||
});
|
||||
});
|
||||
|
|
@ -224,9 +223,8 @@ describe('View branch rules', () => {
|
|||
|
||||
it('renders a branch protection component for merge rules', () => {
|
||||
expect(findAllowedToMerge().props()).toMatchObject({
|
||||
header: sprintf(I18N.allowedToMergeHeader, {
|
||||
total: 2,
|
||||
}),
|
||||
header: 'Allowed to merge',
|
||||
count: 2,
|
||||
...protectionMockProps,
|
||||
});
|
||||
});
|
||||
|
|
@ -385,7 +383,7 @@ describe('View branch rules', () => {
|
|||
});
|
||||
|
||||
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', () => {
|
||||
let wrapper;
|
||||
|
||||
const findStickyHeader = () => wrapper.findComponent('[data-testid="header"]');
|
||||
const findStickyHeader = () => wrapper.find('[data-testid="header"]');
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
|
|
@ -58,11 +58,13 @@ describe('IssuableTitle', () => {
|
|||
|
||||
describe('template', () => {
|
||||
it('renders issuable title', async () => {
|
||||
const titleHtml = '<b>Sample</b> title';
|
||||
|
||||
const wrapperWithTitle = createComponent({
|
||||
...mockIssuableShowProps,
|
||||
issuable: {
|
||||
...mockIssuable,
|
||||
titleHtml: '<b>Sample</b> title',
|
||||
titleHtml,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -70,9 +72,7 @@ describe('IssuableTitle', () => {
|
|||
const titleEl = wrapperWithTitle.find('[data-testid="issuable-title"]');
|
||||
|
||||
expect(titleEl.exists()).toBe(true);
|
||||
expect(titleEl.html()).toBe(
|
||||
'<h1 dir="auto" data-testid="issuable-title" class="title gl-text-size-h-display"><b>Sample</b> title</h1>',
|
||||
);
|
||||
expect(titleEl.element.innerHTML).toBe('<b>Sample</b> title');
|
||||
|
||||
wrapperWithTitle.destroy();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -447,6 +447,14 @@ RSpec.describe TodosHelper do
|
|||
end
|
||||
|
||||
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
|
||||
result = helper.todo_parent_path(project_access_request_todo)
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator,
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
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.feature_category' => 'team_planning',
|
||||
'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'
|
||||
)
|
||||
end
|
||||
|
||||
get(api("/projects/#{project.id}/issues", user), headers: {
|
||||
'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)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,17 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
|
|||
sign_in(user)
|
||||
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
|
||||
expect_next_instance_of(RootController) do |controller|
|
||||
expect(controller).to receive(:index).and_wrap_original do |m, *args|
|
||||
|
|
@ -98,15 +109,79 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
|
|||
|
||||
expect(Gitlab::ApplicationContext.current).to include(
|
||||
'meta.http_router_rule_action' => 'classify',
|
||||
'meta.http_router_rule_type' => 'FirstCell'
|
||||
'meta.http_router_rule_type' => 'FIRST_CELL'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
get root_path, headers: {
|
||||
'X-Gitlab-Http-Router-Rule-Action' => 'classify',
|
||||
'X-Gitlab-Http-Router-Rule-Type' => 'FirstCell'
|
||||
}
|
||||
perform_request
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==
|
||||
|
||||
core-js@^3.29.1, core-js@^3.38.0, core-js@^3.6.5:
|
||||
version "3.38.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.0.tgz#8acb7c050bf2ccbb35f938c0d040132f6110f636"
|
||||
integrity sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==
|
||||
core-js@^3.29.1, core-js@^3.38.1, core-js@^3.6.5:
|
||||
version "3.38.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.1.tgz#aa375b79a286a670388a1a363363d53677c0383e"
|
||||
integrity sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
|
|
|
|||
Loading…
Reference in New Issue