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