Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									edde77d99a
								
							
						
					
					
						commit
						e7f151b0c0
					
				|  | @ -204,6 +204,7 @@ trigger-omnibus-env: | |||
|       SECURITY_SOURCES=$([[ ! "$CI_PROJECT_NAMESPACE" =~ ^gitlab-org\/security ]] || echo "true") | ||||
|       echo "SECURITY_SOURCES=${SECURITY_SOURCES:-false}" > $BUILD_ENV | ||||
|       echo "OMNIBUS_GITLAB_CACHE_UPDATE=${OMNIBUS_GITLAB_CACHE_UPDATE:-false}" >> $BUILD_ENV | ||||
|       echo "OMNIBUS_GITLAB_CACHE_EDITION=${OMNIBUS_GITLAB_CACHE_EDITION}" >> $BUILD_ENV | ||||
|       for version_file in *_VERSION; do echo "$version_file=$(cat $version_file)" >> $BUILD_ENV; done | ||||
|       echo "OMNIBUS_GITLAB_BUILD_ON_ALL_OS=${OMNIBUS_GITLAB_BUILD_ON_ALL_OS:-false}" >> $BUILD_ENV | ||||
|       ruby -e 'puts "FULL_RUBY_VERSION=#{RUBY_VERSION}"' >> $BUILD_ENV | ||||
|  |  | |||
|  | @ -0,0 +1,19 @@ | |||
| <script> | ||||
| export default { | ||||
|   name: 'BoardCutLine', | ||||
|   props: { | ||||
|     cutLineText: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="board-cut-line gl-display-flex gl-mb-3 gl-text-red-700 gl-align-items-center"> | ||||
|     <span class="gl-px-2 gl-font-sm gl-font-weight-bold" data-testid="cut-line-text">{{ | ||||
|       cutLineText | ||||
|     }}</span> | ||||
|   </div> | ||||
| </template> | ||||
|  | @ -29,6 +29,7 @@ import { shouldCloneCard, moveItemVariables } from '../boards_util'; | |||
| import eventHub from '../eventhub'; | ||||
| import BoardCard from './board_card.vue'; | ||||
| import BoardNewIssue from './board_new_issue.vue'; | ||||
| import BoardCutLine from './board_cut_line.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   draggableItemTypes: DraggableItemTypes, | ||||
|  | @ -42,6 +43,7 @@ export default { | |||
|   components: { | ||||
|     BoardCard, | ||||
|     BoardNewIssue, | ||||
|     BoardCutLine, | ||||
|     BoardNewEpic: () => import('ee_component/boards/components/board_new_epic.vue'), | ||||
|     GlLoadingIcon, | ||||
|     GlIntersectionObserver, | ||||
|  | @ -154,6 +156,16 @@ export default { | |||
|     boardListItems() { | ||||
|       return this.currentList?.[`${this.issuableType}s`].nodes || []; | ||||
|     }, | ||||
|     beforeCutLine() { | ||||
|       return this.boardItemsSizeExceedsMax | ||||
|         ? this.boardListItems.slice(0, this.list.maxIssueCount) | ||||
|         : this.boardListItems; | ||||
|     }, | ||||
|     afterCutLine() { | ||||
|       return this.boardItemsSizeExceedsMax | ||||
|         ? this.boardListItems.slice(this.list.maxIssueCount) | ||||
|         : []; | ||||
|     }, | ||||
|     listQueryVariables() { | ||||
|       return { | ||||
|         fullPath: this.fullPath, | ||||
|  | @ -174,6 +186,11 @@ export default { | |||
|         issuableType: this.isEpicBoard ? 'epics' : 'issues', | ||||
|       }); | ||||
|     }, | ||||
|     wipLimitText() { | ||||
|       return sprintf(__('Work in progress limit: %{wipLimit}'), { | ||||
|         wipLimit: this.list.maxIssueCount, | ||||
|       }); | ||||
|     }, | ||||
|     toggleFormEventPrefix() { | ||||
|       return this.isEpicBoard ? toggleFormEventPrefix.epic : toggleFormEventPrefix.issue; | ||||
|     }, | ||||
|  | @ -653,7 +670,7 @@ export default { | |||
|       :data-board="list.id" | ||||
|       :data-board-type="list.listType" | ||||
|       :class="{ | ||||
|         'gl-bg-red-100 gl-rounded-bottom-left-base gl-rounded-bottom-right-base': boardItemsSizeExceedsMax, | ||||
|         'gl-bg-red-50 gl-rounded-bottom-left-base gl-rounded-bottom-right-base': boardItemsSizeExceedsMax, | ||||
|         'gl-overflow-hidden': disableScrollingWhenMutationInProgress, | ||||
|         'gl-overflow-y-auto': !disableScrollingWhenMutationInProgress, | ||||
|       }" | ||||
|  | @ -664,7 +681,32 @@ export default { | |||
|       @end="handleDragOnEnd" | ||||
|     > | ||||
|       <board-card | ||||
|         v-for="(item, index) in boardListItems" | ||||
|         v-for="(item, index) in beforeCutLine" | ||||
|         ref="issue" | ||||
|         :key="item.id" | ||||
|         :index="index" | ||||
|         :list="list" | ||||
|         :item="item" | ||||
|         :data-draggable-item-type="$options.draggableItemTypes.card" | ||||
|         :show-work-item-type-icon="!isEpicBoard" | ||||
|       > | ||||
|         <board-card-move-to-position | ||||
|           v-if="showMoveToPosition" | ||||
|           :item="item" | ||||
|           :index="index" | ||||
|           :list="list" | ||||
|           :list-items-length="boardListItems.length" | ||||
|           @moveToPosition="moveToPosition($event, index, item)" | ||||
|         /> | ||||
|         <gl-intersection-observer | ||||
|           v-if="isObservableItem(index)" | ||||
|           data-testid="board-card-gl-io" | ||||
|           @appear="onReachingListBottom" | ||||
|         /> | ||||
|       </board-card> | ||||
|       <board-cut-line v-if="boardItemsSizeExceedsMax" :cut-line-text="wipLimitText" /> | ||||
|       <board-card | ||||
|         v-for="(item, index) in afterCutLine" | ||||
|         ref="issue" | ||||
|         :key="item.id" | ||||
|         :index="index" | ||||
|  |  | |||
|  | @ -110,6 +110,9 @@ export default { | |||
|     itemsCount() { | ||||
|       return this.isEpicBoard ? this.list.metadata.epicsCount : this.boardList?.issuesCount; | ||||
|     }, | ||||
|     boardItemsSizeExceedsMax() { | ||||
|       return this.list.maxIssueCount > 0 && this.itemsCount > this.list.maxIssueCount; | ||||
|     }, | ||||
|     listAssignee() { | ||||
|       return this.list?.assignee?.username || ''; | ||||
|     }, | ||||
|  | @ -333,6 +336,7 @@ export default { | |||
|       'gl-h-full': list.collapsed, | ||||
|       'gl-bg-gray-50': isSwimlanesHeader, | ||||
|       'gl-border-t-solid gl-border-4 gl-rounded-top-left-base gl-rounded-top-right-base': isLabelList, | ||||
|       'gl-bg-red-50 gl-rounded-top-left-base gl-rounded-top-right-base': boardItemsSizeExceedsMax, | ||||
|     }" | ||||
|     :style="headerStyle" | ||||
|     class="board-header gl-relative" | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ export default { | |||
| 
 | ||||
| <template> | ||||
|   <div class="item-count text-nowrap"> | ||||
|     <span :class="{ 'text-danger': issuesExceedMax }" data-testid="board-items-count"> | ||||
|     <span :class="{ 'gl-text-red-700': issuesExceedMax }" data-testid="board-items-count"> | ||||
|       {{ itemsSize }} | ||||
|     </span> | ||||
|     <span v-if="isMaxLimitSet" class="max-issue-size"> | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| <script> | ||||
| import { GlButton, GlAlert } from '@gitlab/ui'; | ||||
| import { helpPagePath } from '~/helpers/help_page_helper'; | ||||
| import { s__ } from '~/locale'; | ||||
| import Autosave from '~/autosave'; | ||||
| import { s__, __ } from '~/locale'; | ||||
| import { isLoggedIn } from '~/lib/utils/common_utils'; | ||||
| import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils'; | ||||
| import MarkdownField from '~/vue_shared/components/markdown/field.vue'; | ||||
| import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; | ||||
| import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; | ||||
| import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants'; | ||||
| import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; | ||||
| import { | ||||
|   ADD_DISCUSSION_COMMENT_ERROR, | ||||
|  | @ -27,7 +28,7 @@ export default { | |||
|   }, | ||||
|   markdownDocsPath: helpPagePath('user/markdown'), | ||||
|   components: { | ||||
|     MarkdownField, | ||||
|     MarkdownEditor, | ||||
|     GlButton, | ||||
|     GlAlert, | ||||
|   }, | ||||
|  | @ -78,6 +79,14 @@ export default { | |||
|       noteUpdateDirty: false, | ||||
|       isLoggedIn: isLoggedIn(), | ||||
|       errorMessage: '', | ||||
|       formFieldProps: { | ||||
|         id: 'design-reply', | ||||
|         name: 'design-reply', | ||||
|         'aria-label': __('Description'), | ||||
|         placeholder: __('Write a comment…'), | ||||
|         'data-testid': 'note-textarea', | ||||
|         class: 'note-textarea js-gfm-input js-autosize markdown-area', | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|  | @ -92,9 +101,16 @@ export default { | |||
|     shortDiscussionId() { | ||||
|       return isGid(this.discussionId) ? getIdFromGraphQLId(this.discussionId) : this.discussionId; | ||||
|     }, | ||||
|     autosaveKey() { | ||||
|       if (this.isLoggedIn) { | ||||
|         return [ | ||||
|           s__('DesignManagement|Discussion'), | ||||
|           getIdFromGraphQLId(this.noteableId), | ||||
|           this.shortDiscussionId, | ||||
|         ].join('/'); | ||||
|       } | ||||
|       return ''; | ||||
|     }, | ||||
|   mounted() { | ||||
|     this.focusInput(); | ||||
|   }, | ||||
|   beforeDestroy() { | ||||
|     /** | ||||
|  | @ -104,9 +120,7 @@ export default { | |||
|      * so we're safe to clear autosave data here conditionally. | ||||
|      */ | ||||
|     this.$nextTick(() => { | ||||
|       if (!this.noteUpdateDirty) { | ||||
|         this.autosaveDiscussion?.reset(); | ||||
|       } | ||||
|       markdownEditorEventHub.$emit(CLEAR_AUTOSAVE_ENTRY_EVENT, this.autosaveKey); | ||||
|     }); | ||||
|   }, | ||||
|   methods: { | ||||
|  | @ -181,20 +195,7 @@ export default { | |||
|       } | ||||
| 
 | ||||
|       this.$emit('cancel-form'); | ||||
|       this.autosaveDiscussion.reset(); | ||||
|     }, | ||||
|     focusInput() { | ||||
|       this.$refs.textarea.focus(); | ||||
|       this.initAutosaveComment(); | ||||
|     }, | ||||
|     initAutosaveComment() { | ||||
|       if (this.isLoggedIn) { | ||||
|         this.autosaveDiscussion = new Autosave(this.$refs.textarea, [ | ||||
|           s__('DesignManagement|Discussion'), | ||||
|           getIdFromGraphQLId(this.noteableId), | ||||
|           this.shortDiscussionId, | ||||
|         ]); | ||||
|       } | ||||
|       markdownEditorEventHub.$emit(CLEAR_AUTOSAVE_ENTRY_EVENT, this.autosaveKey); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | @ -207,31 +208,19 @@ export default { | |||
|         {{ errorMessage }} | ||||
|       </gl-alert> | ||||
|     </div> | ||||
|     <markdown-field | ||||
|       :markdown-preview-path="markdownPreviewPath" | ||||
|       :enable-autocomplete="true" | ||||
|       :textarea-value="noteText" | ||||
|     <markdown-editor | ||||
|       v-model="noteText" | ||||
|       autofocus | ||||
|       :markdown-docs-path="$options.markdownDocsPath" | ||||
|       class="bordered-box" | ||||
|     > | ||||
|       <template #textarea> | ||||
|         <textarea | ||||
|           ref="textarea" | ||||
|           v-model.trim="noteText" | ||||
|           class="note-textarea js-gfm-input js-autosize markdown-area" | ||||
|           dir="auto" | ||||
|           data-supports-quick-actions="false" | ||||
|           data-testid="note-textarea" | ||||
|           :aria-label="__('Description')" | ||||
|           :placeholder="__('Write a comment…')" | ||||
|       :render-markdown-path="markdownPreviewPath" | ||||
|       :enable-autocomplete="true" | ||||
|       :supports-quick-actions="false" | ||||
|       :form-field-props="formFieldProps" | ||||
|       @input="handleInput" | ||||
|       @keydown.meta.enter="submitForm" | ||||
|       @keydown.ctrl.enter="submitForm" | ||||
|           @keyup.esc.stop="cancelComment" | ||||
|         > | ||||
|         </textarea> | ||||
|       </template> | ||||
|     </markdown-field> | ||||
|       @keydown.esc.stop="cancelComment" | ||||
|     /> | ||||
|     <slot name="resolve-checkbox"></slot> | ||||
|     <div class="note-form-actions gl-display-flex gl-mt-4!"> | ||||
|       <gl-button | ||||
|  |  | |||
|  | @ -276,9 +276,6 @@ export default { | |||
|     }, | ||||
|     openCommentForm(annotationCoordinates) { | ||||
|       this.annotationCoordinates = annotationCoordinates; | ||||
|       if (this.$refs.newDiscussionForm) { | ||||
|         this.$refs.newDiscussionForm.focusInput(); | ||||
|       } | ||||
|     }, | ||||
|     closeCommentForm(data) { | ||||
|       this.annotationCoordinates = null; | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ import { __ } from '~/locale'; | |||
| import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue'; | ||||
| import { BACK_URL_PARAM } from '~/releases/constants'; | ||||
| import { putCreateReleaseNotification } from '~/releases/release_notification_service'; | ||||
| import MarkdownField from '~/vue_shared/components/markdown/field.vue'; | ||||
| import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; | ||||
| import AssetLinksForm from './asset_links_form.vue'; | ||||
| import ConfirmDeleteModal from './confirm_delete_modal.vue'; | ||||
| import TagField from './tag_field.vue'; | ||||
|  | @ -31,11 +31,22 @@ export default { | |||
|     GlLink, | ||||
|     GlSprintf, | ||||
|     ConfirmDeleteModal, | ||||
|     MarkdownField, | ||||
|     MarkdownEditor, | ||||
|     AssetLinksForm, | ||||
|     MilestoneCombobox, | ||||
|     TagField, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       formFieldProps: { | ||||
|         id: 'release-notes', | ||||
|         name: 'release-notes', | ||||
|         class: 'note-textarea js-gfm-input js-autosize markdown-area', | ||||
|         'aria-label': __('Release notes'), | ||||
|         placeholder: __('Write your release notes or drag your files here…'), | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState('editNew', [ | ||||
|       'isExistingRelease', | ||||
|  | @ -71,7 +82,7 @@ export default { | |||
|     }, | ||||
|     releaseNotes: { | ||||
|       get() { | ||||
|         return this.$store.state.editNew.release.description; | ||||
|         return this.$store.state.editNew.release.description || this.formattedReleaseNotes; | ||||
|       }, | ||||
|       set(notes) { | ||||
|         this.updateReleaseNotes(notes); | ||||
|  | @ -220,25 +231,13 @@ export default { | |||
|       </gl-form-group> | ||||
|       <gl-form-group :label="__('Release notes')" data-testid="release-notes"> | ||||
|         <div class="common-note-form"> | ||||
|           <markdown-field | ||||
|             :can-attach-file="true" | ||||
|             :markdown-preview-path="markdownPreviewPath" | ||||
|             :markdown-docs-path="markdownDocsPath" | ||||
|             :add-spacing-classes="false" | ||||
|             :textarea-value="formattedReleaseNotes" | ||||
|           > | ||||
|             <template #textarea> | ||||
|               <textarea | ||||
|                 id="release-notes" | ||||
|           <markdown-editor | ||||
|             v-model="releaseNotes" | ||||
|                 class="note-textarea js-gfm-input js-autosize markdown-area" | ||||
|                 dir="auto" | ||||
|                 data-supports-quick-actions="false" | ||||
|                 :aria-label="__('Release notes')" | ||||
|                 :placeholder="__('Write your release notes or drag your files here…')" | ||||
|               ></textarea> | ||||
|             </template> | ||||
|           </markdown-field> | ||||
|             :render-markdown-path="markdownPreviewPath" | ||||
|             :markdown-docs-path="markdownDocsPath" | ||||
|             :supports-quick-actions="false" | ||||
|             :form-field-props="formFieldProps" | ||||
|           /> | ||||
|         </div> | ||||
|       </gl-form-group> | ||||
|       <gl-form-group v-if="!isExistingRelease"> | ||||
|  |  | |||
|  | @ -242,3 +242,12 @@ | |||
|     height: 100px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .board-cut-line { | ||||
|   &::before, &::after { | ||||
|     content: ''; | ||||
|     height: 1px; | ||||
|     flex: 1; | ||||
|     border-top: 1px dashed $red-700; | ||||
|   } | ||||
| } | ||||
|  | @ -30,16 +30,16 @@ module CascadingNamespaceSettingAttribute | |||
|     # similar to Rails' `attr_accessor`, defines convenience methods such as | ||||
|     # a reader, writer, and validators. | ||||
|     # | ||||
|     # Example: `cascading_attr :delayed_project_removal` | ||||
|     # Example: `cascading_attr :toggle_security_policy_custom_ci` | ||||
|     # | ||||
|     # Public methods defined: | ||||
|     # - `delayed_project_removal` | ||||
|     # - `delayed_project_removal=` | ||||
|     # - `delayed_project_removal_locked?` | ||||
|     # - `delayed_project_removal_locked_by_ancestor?` | ||||
|     # - `delayed_project_removal_locked_by_application_setting?` | ||||
|     # - `delayed_project_removal?` (only defined for boolean attributes) | ||||
|     # - `delayed_project_removal_locked_ancestor` - Returns locked namespace settings object (only namespace_id) | ||||
|     # - `toggle_security_policy_custom_ci` | ||||
|     # - `toggle_security_policy_custom_ci=` | ||||
|     # - `toggle_security_policy_custom_ci_locked?` | ||||
|     # - `toggle_security_policy_custom_ci_locked_by_ancestor?` | ||||
|     # - `toggle_security_policy_custom_ci_locked_by_application_setting?` | ||||
|     # - `toggle_security_policy_custom_ci?` (only defined for boolean attributes) | ||||
|     # - `toggle_security_policy_custom_ci_locked_ancestor` - Returns locked namespace settings object (only namespace_id) | ||||
|     # | ||||
|     # Defined validators ensure attribute value cannot be updated if locked by | ||||
|     # an ancestor or application settings. | ||||
|  |  | |||
|  | @ -8,9 +8,11 @@ module Integrations | |||
| 
 | ||||
|     field :token, | ||||
|       type: :password, | ||||
|       description: -> { _('The Slack token.') }, | ||||
|       non_empty_password_title: -> { s_('ProjectService|Enter new token') }, | ||||
|       non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') }, | ||||
|       placeholder: '' | ||||
|       placeholder: '', | ||||
|       required: true | ||||
| 
 | ||||
|     def self.title | ||||
|       'Slack slash commands' | ||||
|  |  | |||
|  | @ -8,8 +8,8 @@ class NamespaceSetting < ApplicationRecord | |||
| 
 | ||||
|   ignore_column :project_import_level, remove_with: '16.10', remove_after: '2024-02-22' | ||||
|   ignore_column :third_party_ai_features_enabled, remove_with: '16.10', remove_after: '2024-02-22' | ||||
|   ignore_column %i[delayed_project_removal lock_delayed_project_removal], remove_with: '16.10', remove_after: '2024-02-22' | ||||
| 
 | ||||
|   cascading_attr :delayed_project_removal | ||||
|   cascading_attr :toggle_security_policy_custom_ci | ||||
|   cascading_attr :toggle_security_policies_policy_scope | ||||
| 
 | ||||
|  | @ -40,8 +40,6 @@ class NamespaceSetting < ApplicationRecord | |||
| 
 | ||||
|   NAMESPACE_SETTINGS_PARAMS = %i[ | ||||
|     default_branch_name | ||||
|     delayed_project_removal | ||||
|     lock_delayed_project_removal | ||||
|     resource_access_token_creation_allowed | ||||
|     prevent_sharing_groups_outside_hierarchy | ||||
|     new_user_signups_cap | ||||
|  |  | |||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: oidc_issuer_url | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135049 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429855 | ||||
| milestone: '16.6' | ||||
| type: development | ||||
| group: group::pipeline security | ||||
| default_enabled: false | ||||
|  | @ -0,0 +1,27 @@ | |||
| - title: "Deprecate `fmt` job in Terraform Module CI/CD template" | ||||
|   # The milestones for the deprecation announcement, and the removal. | ||||
|   removal_milestone: "17.0" | ||||
|   announcement_milestone: "16.9" | ||||
|   # Change breaking_change to false if needed. | ||||
|   breaking_change: true | ||||
|   # The stage and GitLab username of the person reporting the change, | ||||
|   # and a link to the deprecation issue | ||||
|   reporter: timofurrer | ||||
|   stage: deploy | ||||
|   issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/440249 | ||||
|   body: |  # (required) Don't change this line. | ||||
|     The `fmt` job in the Terraform Module CI/CD templates is deprecated and will be removed in GitLab 17.0. | ||||
|     This affects the following templates: | ||||
| 
 | ||||
|     - `Terraform-Module.gitlab-ci.yml` | ||||
|     - `Terraform/Module-Base.gitlab-ci.yml` | ||||
| 
 | ||||
|     You can manually add back a Terraform `fmt` job to your pipeline using: | ||||
| 
 | ||||
|     ```yaml | ||||
|     fmt: | ||||
|       image: hashicorp/terraform | ||||
|       script: terraform fmt -chdir "$TF_ROOT" -check -diff -recursive | ||||
|     ``` | ||||
| 
 | ||||
|     You can also use the `fmt` template from the [OpenTofu CI/CD component](https://gitlab.com/components/opentofu). | ||||
|  | @ -18,7 +18,7 @@ and the following external authentication and authorization providers: | |||
|   and 389 Server. | ||||
|   - [Google Secure LDAP](ldap/google_secure_ldap.md) | ||||
| - [SAML for GitLab.com groups](../../user/group/saml_sso/index.md) | ||||
| - [Smartcard](smartcard.md) | ||||
| - [Smart card](smartcard.md) | ||||
| 
 | ||||
| NOTE: | ||||
| UltraAuth has removed their software which supports OmniAuth integration. We have therefore removed all references to UltraAuth integration. | ||||
|  | @ -32,7 +32,7 @@ For more information, see the links shown on this page for each external provide | |||
| |-------------------------------------------------|-----------------------------------------|------------------------------------| | ||||
| | **User Provisioning**                           | SCIM<br>SAML <sup>1</sup> | LDAP <sup>1</sup><br>SAML <sup>1</sup><br>[OmniAuth Providers](../../integration/omniauth.md#supported-providers) <sup>1</sup><br>SCIM  | | ||||
| | **User Detail Updating** (not group management) | Not Available                           | LDAP Sync                          | | ||||
| | **Authentication**                              | SAML at top-level group (1 provider)    | LDAP (multiple providers)<br>Generic OAuth 2.0<br>SAML (only 1 permitted per unique provider)<br>Kerberos<br>JWT<br>Smartcard<br>[OmniAuth Providers](../../integration/omniauth.md#supported-providers) (only 1 permitted per unique provider) | | ||||
| | **Authentication**                              | SAML at top-level group (1 provider)    | LDAP (multiple providers)<br>Generic OAuth 2.0<br>SAML (only 1 permitted per unique provider)<br>Kerberos<br>JWT<br>Smart card<br>[OmniAuth Providers](../../integration/omniauth.md#supported-providers) (only 1 permitted per unique provider) | | ||||
| | **Provider-to-GitLab Role Sync**                | SAML Group Sync                         | LDAP Group Sync<br>SAML Group Sync ([GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/285150) and later) | | ||||
| | **User Removal**                                | SCIM (remove user from top-level group) | LDAP (remove user from groups and block from the instance)<br>SCIM | | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,22 +4,22 @@ group: Authentication | |||
| 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 | ||||
| --- | ||||
| 
 | ||||
| # Smartcard authentication | ||||
| # Smart card authentication | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier:** Premium, Ultimate | ||||
| **Offering:** Self-managed | ||||
| 
 | ||||
| GitLab supports authentication using smartcards. | ||||
| GitLab supports authentication using smart cards. | ||||
| 
 | ||||
| ## Existing password authentication | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33669) in GitLab 12.6. | ||||
| 
 | ||||
| By default, existing users can continue to sign in with a username and password when smartcard | ||||
| By default, existing users can continue to sign in with a username and password when smart card | ||||
| authentication is enabled. | ||||
| 
 | ||||
| To force existing users to use only smartcard authentication, | ||||
| To force existing users to use only smart card authentication, | ||||
| [disable username and password authentication](../settings/sign_in_restrictions.md#password-authentication-enabled). | ||||
| 
 | ||||
| ## Authentication methods | ||||
|  | @ -34,12 +34,11 @@ GitLab supports two authentication methods: | |||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/726) in GitLab 11.6 as an experimental feature. | ||||
| 
 | ||||
| WARNING: | ||||
| Smartcard authentication against local databases may change or be removed completely in future | ||||
| releases. | ||||
| Smart card authentication against local databases may change or be removed completely in future releases. | ||||
| 
 | ||||
| Smartcards with X.509 certificates can be used to authenticate with GitLab. | ||||
| Smart cards with X.509 certificates can be used to authenticate with GitLab. | ||||
| 
 | ||||
| To use a smartcard with an X.509 certificate to authenticate against a local | ||||
| To use a smart card with an X.509 certificate to authenticate against a local | ||||
| database with GitLab, `CN` and `emailAddress` must be defined in the | ||||
| certificate. For example: | ||||
| 
 | ||||
|  | @ -60,14 +59,14 @@ Certificate: | |||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8605) in GitLab 12.3. | ||||
| 
 | ||||
| Smartcards with X.509 certificates using SAN extensions can be used to authenticate | ||||
| Smart cards with X.509 certificates using SAN extensions can be used to authenticate | ||||
| with GitLab. | ||||
| 
 | ||||
| NOTE: | ||||
| This is an experimental feature. Smartcard authentication against local databases may | ||||
| This is an experimental feature. Smart card authentication against local databases may | ||||
| change or be removed completely in future releases. | ||||
| 
 | ||||
| To use a smartcard with an X.509 certificate to authenticate against a local | ||||
| To use a smart card with an X.509 certificate to authenticate against a local | ||||
| database with GitLab, in: | ||||
| 
 | ||||
| - GitLab 12.4 and later, at least one of the `subjectAltName` (SAN) extensions | ||||
|  | @ -101,7 +100,7 @@ Certificate: | |||
| 
 | ||||
| ### Authentication against an LDAP server | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7693) in GitLab 11.8 as an experimental feature. Smartcard authentication against an LDAP server may change or be removed completely in the future. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7693) in GitLab 11.8 as an experimental feature. Smart card authentication against an LDAP server may change or be removed completely in the future. | ||||
| 
 | ||||
| GitLab implements a standard way of certificate matching following | ||||
| [RFC4523](https://www.rfc-editor.org/rfc/rfc4523). It uses the | ||||
|  | @ -116,14 +115,14 @@ Active Directory doesn't support the `certificateExactMatch` matching rule so | |||
| [it is not supported at this time](https://gitlab.com/gitlab-org/gitlab/-/issues/327491). For | ||||
| more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/328074). | ||||
| 
 | ||||
| ## Configure GitLab for smartcard authentication | ||||
| ## Configure GitLab for smart card authentication | ||||
| 
 | ||||
| For Linux package installations: | ||||
| 
 | ||||
| 1. Edit `/etc/gitlab/gitlab.rb`: | ||||
| 
 | ||||
|    ```ruby | ||||
|    # Allow smartcard authentication | ||||
|    # Allow smart card authentication | ||||
|    gitlab_rails['smartcard_enabled'] = true | ||||
| 
 | ||||
|    # Path to a file containing a CA certificate | ||||
|  | @ -215,9 +214,9 @@ For self-compiled installations: | |||
| 1. Edit `config/gitlab.yml`: | ||||
| 
 | ||||
|    ```yaml | ||||
|    ## Smartcard authentication settings | ||||
|    ## Smart card authentication settings | ||||
|    smartcard: | ||||
|      # Allow smartcard authentication | ||||
|      # Allow smart card authentication | ||||
|      enabled: true | ||||
| 
 | ||||
|      # Path to a file containing a CA certificate | ||||
|  | @ -251,7 +250,7 @@ For Linux package installations: | |||
| 
 | ||||
| For self-compiled installations: | ||||
| 
 | ||||
| 1. Add the `san_extensions` line to `config/gitlab.yml` within the smartcard section: | ||||
| 1. Add the `san_extensions` line to `config/gitlab.yml` within the smart card section: | ||||
| 
 | ||||
|    ```yaml | ||||
|    smartcard: | ||||
|  | @ -276,7 +275,7 @@ For Linux package installations: | |||
|    gitlab_rails['ldap_servers'] = YAML.load <<-EOS | ||||
|    main: | ||||
|      # snip... | ||||
|      # Enable smartcard authentication against the LDAP server. Valid values | ||||
|      # Enable smart card authentication against the LDAP server. Valid values | ||||
|      # are "false", "optional", and "required". | ||||
|      smartcard_auth: optional | ||||
|    EOS | ||||
|  | @ -295,7 +294,7 @@ For self-compiled installations: | |||
|        servers: | ||||
|          main: | ||||
|            # snip... | ||||
|            # Enable smartcard authentication against the LDAP server. Valid values | ||||
|            # Enable smart card authentication against the LDAP server. Valid values | ||||
|            # are "false", "optional", and "required". | ||||
|            smartcard_auth: optional | ||||
|    ``` | ||||
|  | @ -303,7 +302,7 @@ For self-compiled installations: | |||
| 1. Save the file and [restart](../restart_gitlab.md#self-compiled-installations) | ||||
|    GitLab for the changes to take effect. | ||||
| 
 | ||||
| ### Require browser session with smartcard sign-in for Git access | ||||
| ### Require browser session with smart card sign-in for Git access | ||||
| 
 | ||||
| For Linux package installations: | ||||
| 
 | ||||
|  | @ -321,19 +320,19 @@ For self-compiled installations: | |||
| 1. Edit `config/gitlab.yml`: | ||||
| 
 | ||||
|    ```yaml | ||||
|    ## Smartcard authentication settings | ||||
|    ## Smart card authentication settings | ||||
|    smartcard: | ||||
|      # snip... | ||||
|      # Browser session with smartcard sign-in is required for Git access | ||||
|      # Browser session with smart card sign-in is required for Git access | ||||
|      required_for_git_access: true | ||||
|    ``` | ||||
| 
 | ||||
| 1. Save the file and [restart](../restart_gitlab.md#self-compiled-installations) | ||||
|    GitLab for the changes to take effect. | ||||
| 
 | ||||
| ## Passwords for users created via smartcard authentication | ||||
| ## Passwords for users created via smart card authentication | ||||
| 
 | ||||
| The [Generated passwords for users created through integrated authentication](../../security/passwords_for_integrated_authentication_methods.md) guide provides an overview of how GitLab generates and sets passwords for users created via smartcard authentication. | ||||
| The [Generated passwords for users created through integrated authentication](../../security/passwords_for_integrated_authentication_methods.md) guide provides an overview of how GitLab generates and sets passwords for users created via smart card authentication. | ||||
| 
 | ||||
| <!-- ## Troubleshooting | ||||
| 
 | ||||
|  |  | |||
|  | @ -774,7 +774,7 @@ DRIs: | |||
| | Leadership                   | Mark Nuzzo             | | ||||
| | Product                      | Dov Hershkovitch       | | ||||
| | Engineering                  | Fabio Pitino           | | ||||
| | UX                           | Kevin Comoli (interim), Sunjung Park          | | ||||
| | UX                           | Sunjung Park           | | ||||
| 
 | ||||
| Domain experts: | ||||
| 
 | ||||
|  |  | |||
|  | @ -103,6 +103,9 @@ To find a domain expert: | |||
| NOTE: | ||||
| Reviewer roulette is an internal tool for use on GitLab.com, and not available for use on customer installations. | ||||
| 
 | ||||
| NOTE: | ||||
| Until %16.11, GitLab is running [an experiment](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/377) to remove hungriness and busy indicators. | ||||
| 
 | ||||
| The [Danger bot](dangerbot.md) randomly picks a reviewer and a maintainer for | ||||
| each area of the codebase that your merge request seems to touch. It makes | ||||
| **recommendations** for developer reviewers and you should override it if you think someone else is a better | ||||
|  | @ -140,7 +143,7 @@ page, with these behaviors: | |||
|   not counted. These MRs are usually backports, and maintainers or reviewers usually | ||||
|   do not need much time reviewing them. | ||||
| 
 | ||||
| - Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji | ||||
| - 'Hungriness' for reviews: Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji | ||||
|   is 🔵 `:large_blue_circle:` are more likely to be picked. This applies to both reviewers and trainee maintainers. | ||||
|   - Reviewers with 🔵 `:large_blue_circle:` are two times as likely to be picked as other reviewers. | ||||
|   - [Trainee maintainers](https://handbook.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer) with 🔵 `:large_blue_circle:` are three times as likely to be picked as other reviewers. | ||||
|  |  | |||
|  | @ -148,10 +148,12 @@ class Ci::PipelineCreatedEvent < Gitlab::EventStore::Event | |||
| end | ||||
| ``` | ||||
| 
 | ||||
| The schema is validated immediately when we initialize the event object so we can ensure that | ||||
| publishers follow the contract with the subscribers. | ||||
| The schema, which must be a valid [JSON schema](https://json-schema.org/specification), is validated | ||||
| by the [`JSONSchemer`](https://github.com/davishmcclurg/json_schemer) gem. The validation happens | ||||
| immediately when you initialize the event object to ensure that publishers follow the contract | ||||
| with the subscribers. | ||||
| 
 | ||||
| We recommend using optional properties as much as possible, which require fewer rollouts for schema changes. | ||||
| You should use optional properties as much as possible, which require fewer rollouts for schema changes. | ||||
| However, `required` properties could be used for unique identifiers of the event's subject. For example: | ||||
| 
 | ||||
| - `pipeline_id` can be a required property for a `Ci::PipelineCreatedEvent`. | ||||
|  | @ -375,6 +377,21 @@ it 'publishes a ProjectCreatedEvent with project id and namespace id' do | |||
| end | ||||
| ``` | ||||
| 
 | ||||
| When you publish multiple events, you can also check for non-published events. | ||||
| 
 | ||||
| ```ruby | ||||
| it 'publishes a ProjectCreatedEvent with project id and namespace id' do | ||||
|   # The project ID is generated when `create_project` | ||||
|   # is called in the `expect` block. | ||||
|   expected_data = { project_id: kind_of(Numeric), namespace_id: group_id } | ||||
| 
 | ||||
|   expect { create_project(user, name: 'Project', path: 'project', namespace_id: group_id) } | ||||
|     .to publish_event(Projects::ProjectCreatedEvent) | ||||
|     .with(expected_data) | ||||
|     .and not_publish_event(Projects::ProjectDeletedEvent) | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| ### Testing the subscriber | ||||
| 
 | ||||
| The subscriber must ensure that a published event can be consumed correctly. For this purpose | ||||
|  |  | |||
|  | @ -141,7 +141,7 @@ To help you migrate your data to GitLab Dedicated, you can choose from the follo | |||
| 
 | ||||
| The following GitLab application features are not available: | ||||
| 
 | ||||
| - LDAP, Smartcard, or Kerberos authentication | ||||
| - LDAP, smart card, or Kerberos authentication | ||||
| - Multiple login providers | ||||
| - GitLab Pages | ||||
| - FortiAuthenticator, or FortiToken 2FA | ||||
|  |  | |||
|  | @ -578,6 +578,34 @@ These fields (`architectureName`, `ipAddress`, `platformName`, `revision`, `vers | |||
| 
 | ||||
| <div class="deprecation breaking-change" data-milestone="17.0"> | ||||
| 
 | ||||
| ### Deprecate `fmt` job in Terraform Module CI/CD template | ||||
| 
 | ||||
| <div class="deprecation-notes"> | ||||
| - Announced in GitLab <span class="milestone">16.9</span> | ||||
| - Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) | ||||
| - To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/440249). | ||||
| </div> | ||||
| 
 | ||||
| The `fmt` job in the Terraform Module CI/CD templates is deprecated and will be removed in GitLab 17.0. | ||||
| This affects the following templates: | ||||
| 
 | ||||
| - `Terraform-Module.gitlab-ci.yml` | ||||
| - `Terraform/Module-Base.gitlab-ci.yml` | ||||
| 
 | ||||
| You can manually add back a Terraform `fmt` job to your pipeline using: | ||||
| 
 | ||||
| ```yaml | ||||
| fmt: | ||||
|   image: hashicorp/terraform | ||||
|   script: terraform fmt -chdir "$TF_ROOT" -check -diff -recursive | ||||
| ``` | ||||
| 
 | ||||
| You can also use the `fmt` template from the [OpenTofu CI/CD component](https://gitlab.com/components/opentofu). | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <div class="deprecation breaking-change" data-milestone="17.0"> | ||||
| 
 | ||||
| ### Deprecate `message` field from Vulnerability Management features | ||||
| 
 | ||||
| <div class="deprecation-notes"> | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ SAML Group Sync only manages a group if that group has one or more SAML group li | |||
| 
 | ||||
| You must configure the SAML group links before you configure SAML Group Sync. | ||||
| 
 | ||||
| When SAML is enabled, users with the Maintainer or Owner role see a new menu | ||||
| When SAML is enabled, users with the Owner role see a new menu | ||||
| item in group **Settings > SAML Group Links**. | ||||
| 
 | ||||
| - You can configure one or more **SAML Group Links** to map a SAML identity | ||||
|  |  | |||
|  | @ -438,7 +438,7 @@ DETAILS: | |||
| > - Moved to GitLab Premium in 13.9. | ||||
| 
 | ||||
| You can set a work in progress (WIP) limit for each issue list on an issue board. When a limit is | ||||
| set, the list's header shows the number of issues in the list and the soft limit of issues. | ||||
| set, the list's header shows the number of issues in the list and the soft limit of issues. A line in the list separates items within the limit from those in excess of the limit. | ||||
| You cannot set a WIP limit on the default lists (**Open** and **Closed**). | ||||
| 
 | ||||
| Examples: | ||||
|  | @ -446,7 +446,7 @@ Examples: | |||
| - When you have a list with four issues and a limit of five, the header shows **4/5**. | ||||
|   If you exceed the limit, the current number of issues is shown in red. | ||||
| - You have a list with five issues with a limit of five. When you move another issue to that list, | ||||
|   the list's header displays **6/5**, with the six shown in red. | ||||
|   the list's header displays **6/5**, with the six shown in red. The work in progress line is shown before the sixth issue. | ||||
| 
 | ||||
| Prerequisites: | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,25 +11,19 @@ DETAILS: | |||
| **Offering:** SaaS, self-managed | ||||
| 
 | ||||
| > - **Merge when pipeline succeeds** and **Add to merge train when pipeline succeeds** [renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/409530) to **Auto-merge** in GitLab 16.0 [with a flag](../../../administration/feature_flags.md) named `auto_merge_labels_mr_widget`. Enabled by default. | ||||
| > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120922) in GitLab 16.0. Feature flag `auto_merge_labels_mr_widget` removed. | ||||
| 
 | ||||
| If you review a merge request and it's ready to merge, but the pipeline hasn't | ||||
| completed yet, you can set it to auto-merge. You don't | ||||
| have to remember later to merge the work manually: | ||||
| If the content of a merge request is ready to merge, use **Set to auto-merge** on | ||||
| the merge request. You don't have to remember later to merge the work manually. If set, | ||||
| a merge request auto-merges when all these conditions are met: | ||||
| 
 | ||||
| - The merge request pipeline must complete successfully. | ||||
| - All required approvals must be given. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| NOTE: | ||||
| [In GitLab 16.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/359057), **Merge when pipeline succeeds** and **Add to merge train when pipeline succeeds** are renamed **Set to auto-merge**. | ||||
| 
 | ||||
| If the pipeline succeeds, the merge request is merged. If the pipeline fails, the | ||||
| author can either retry any failed jobs, or push new commits to fix the failure: | ||||
| 
 | ||||
| - If a retried job succeeds on the second try, the merge request is merged. | ||||
| - If new commits are added to the merge request, GitLab cancels the request | ||||
|   to ensure the new changes are reviewed before merge. | ||||
| - If new commits are added to the target branch of the merge request and | ||||
|   fast-forward only merge request is configured, GitLab cancels the request | ||||
|   to prevent merge conflicts. | ||||
| The [merge when checks pass](#merge-when-checks-pass) feature, available in | ||||
| GitLab 16.9 and later, adds more checks to the auto-merge process. | ||||
| 
 | ||||
| ## Auto-merge a merge request | ||||
| 
 | ||||
|  | @ -57,6 +51,42 @@ If a new comment is added to the merge request after you select **Auto-merge**, | |||
| but before the pipeline completes, GitLab blocks the merge until you | ||||
| resolve all existing threads. | ||||
| 
 | ||||
| ### Merge when pipeline succeeds | ||||
| 
 | ||||
| If the pipeline succeeds, the merge request is merged. If the pipeline fails, the | ||||
| author can either retry any failed jobs, or push new commits to fix the failure: | ||||
| 
 | ||||
| - If a retried job succeeds on the second try, the merge request is merged. | ||||
| - If new commits are added to the merge request, GitLab cancels the request | ||||
|   to ensure the new changes are reviewed before merge. | ||||
| - If new commits are added to the target branch of the merge request and | ||||
|   fast-forward only merge request is configured, GitLab cancels the request | ||||
|   to prevent merge conflicts. | ||||
| 
 | ||||
| ### Merge when checks pass | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier:** Free, Premium, Ultimate | ||||
| **Offering:** SaaS | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10874) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `merge_when_checks_pass` and `additional_merge_when_checks_ready`. Disabled by default. | ||||
| > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/412995) in GitLab 16.9. | ||||
| 
 | ||||
| FLAG: | ||||
| On self-managed GitLab, by default this feature is not available. To enable the feature, | ||||
| an administrator can [enable the feature flags](../../../administration/feature_flags.md) | ||||
| named `merge_when_checks_pass` and `additional_merge_when_checks_ready`. | ||||
| On GitLab.com, this feature is available. | ||||
| 
 | ||||
| In GitLab 16.9 and later, **Merge when checks pass** adds more checks to the auto-merge | ||||
| process. When set to auto-merge, all of these checks must pass for a merge request to merge: | ||||
| 
 | ||||
| - The merge request pipeline must complete successfully. | ||||
| - All required approvals must be given. | ||||
| - The merge request must not be a **Draft**. | ||||
| - All discussions must be resolved. | ||||
| - All blocking merge requests must be merged or closed. | ||||
| 
 | ||||
| ## Cancel an auto-merge | ||||
| 
 | ||||
| If a merge request is set to auto-merge, you can cancel it. | ||||
|  | @ -110,8 +140,6 @@ despite a newer but failed branch pipeline. | |||
| 
 | ||||
| ### Allow merge after skipped pipelines | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211482) in GitLab 13.1. | ||||
| 
 | ||||
| When the **Pipelines must succeed** checkbox is checked, | ||||
| [skipped pipelines](../../../ci/pipelines/index.md#skip-a-pipeline) prevent | ||||
| merge requests from being merged. | ||||
|  |  | |||
|  | @ -30,20 +30,8 @@ module API | |||
|     end | ||||
| 
 | ||||
|     SLASH_COMMAND_INTEGRATIONS = { | ||||
|       'mattermost-slash-commands' => [ | ||||
|         { | ||||
|           name: :token, | ||||
|           type: String, | ||||
|           desc: 'The Mattermost token' | ||||
|         } | ||||
|       ], | ||||
|       'slack-slash-commands' => [ | ||||
|         { | ||||
|           name: :token, | ||||
|           type: String, | ||||
|           desc: 'The Slack token' | ||||
|         } | ||||
|       ] | ||||
|       'mattermost-slash-commands' => ::Integrations::MattermostSlashCommands.api_fields, | ||||
|       'slack-slash-commands' => ::Integrations::SlackSlashCommands.api_fields | ||||
|     }.freeze | ||||
| 
 | ||||
|     helpers do | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ module Gitlab | |||
| 
 | ||||
|       def reserved_claims | ||||
|         super.merge({ | ||||
|           iss: Feature.enabled?(:oidc_issuer_url) ? Gitlab.config.gitlab.url : Settings.gitlab.base_url, | ||||
|           iss: Gitlab.config.gitlab.url, | ||||
|           sub: "project_path:#{project.full_path}:ref_type:#{ref_type}:ref:#{source_ref}", | ||||
|           aud: aud, | ||||
|           wlif: wlif | ||||
|  |  | |||
|  | @ -49471,6 +49471,9 @@ msgstr "" | |||
| msgid "The Slack notifications integration is deprecated and will be removed in a future release. To continue to receive notifications from Slack, use the GitLab for Slack app instead. %{learn_more_link_start}Learn more%{link_end}." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "The Slack token." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "The Snowplow cookie domain." | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -55944,6 +55947,9 @@ msgstr "" | |||
| msgid "Work in progress limit" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Work in progress limit: %{wipLimit}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Work item parent removed successfully" | ||||
| msgstr "" | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises'; | |||
| import createComponent from 'jest/boards/board_list_helper'; | ||||
| import { ESC_KEY_CODE } from '~/lib/utils/keycodes'; | ||||
| import BoardCard from '~/boards/components/board_card.vue'; | ||||
| import BoardCutLine from '~/boards/components/board_cut_line.vue'; | ||||
| import eventHub from '~/boards/eventhub'; | ||||
| import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; | ||||
| import listIssuesQuery from '~/boards/graphql/lists_issues.query.graphql'; | ||||
|  | @ -22,6 +23,8 @@ describe('Board list component', () => { | |||
|   const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver); | ||||
|   const findBoardListCount = () => wrapper.find('.board-list-count'); | ||||
| 
 | ||||
|   const maxIssueCountWarningClass = '.gl-bg-red-50'; | ||||
| 
 | ||||
|   const triggerInfiniteScroll = () => findIntersectionObserver().vm.$emit('appear'); | ||||
| 
 | ||||
|   const startDrag = ( | ||||
|  | @ -143,34 +146,48 @@ describe('Board list component', () => { | |||
| 
 | ||||
|   describe('max issue count warning', () => { | ||||
|     describe('when issue count exceeds max issue count', () => { | ||||
|       it('sets background to gl-bg-red-100', async () => { | ||||
|         wrapper = createComponent({ listProps: { issuesCount: 4, maxIssueCount: 3 } }); | ||||
| 
 | ||||
|       beforeEach(async () => { | ||||
|         wrapper = createComponent({ listProps: { issuesCount: 4, maxIssueCount: 2 } }); | ||||
|         await waitForPromises(); | ||||
|         const block = wrapper.find('.gl-bg-red-100'); | ||||
|       }); | ||||
|       it('sets background to warning color', () => { | ||||
|         const block = wrapper.find(maxIssueCountWarningClass); | ||||
| 
 | ||||
|         expect(block.exists()).toBe(true); | ||||
|         expect(block.attributes('class')).toContain( | ||||
|           'gl-rounded-bottom-left-base gl-rounded-bottom-right-base', | ||||
|         ); | ||||
|       }); | ||||
|       it('shows cut line', () => { | ||||
|         const cutline = wrapper.findComponent(BoardCutLine); | ||||
|         expect(cutline.exists()).toBe(true); | ||||
|         expect(cutline.props('cutLineText')).toEqual('Work in progress limit: 2'); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('when list issue count does NOT exceed list max issue count', () => { | ||||
|       it('does not sets background to gl-bg-red-100', async () => { | ||||
|       beforeEach(async () => { | ||||
|         wrapper = createComponent({ list: { issuesCount: 2, maxIssueCount: 3 } }); | ||||
|         await waitForPromises(); | ||||
| 
 | ||||
|         expect(wrapper.find('.gl-bg-red-100').exists()).toBe(false); | ||||
|       }); | ||||
|       it('does not sets background to warning color', () => { | ||||
|         expect(wrapper.find(maxIssueCountWarningClass).exists()).toBe(false); | ||||
|       }); | ||||
|       it('does not show cut line', () => { | ||||
|         expect(wrapper.findComponent(BoardCutLine).exists()).toBe(false); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('when list max issue count is 0', () => { | ||||
|       it('does not sets background to gl-bg-red-100', async () => { | ||||
|       beforeEach(async () => { | ||||
|         wrapper = createComponent({ list: { maxIssueCount: 0 } }); | ||||
|         await waitForPromises(); | ||||
| 
 | ||||
|         expect(wrapper.find('.gl-bg-red-100').exists()).toBe(false); | ||||
|       }); | ||||
|       it('does not sets background to warning color', () => { | ||||
|         expect(wrapper.find(maxIssueCountWarningClass).exists()).toBe(false); | ||||
|       }); | ||||
|       it('does not show cut line', () => { | ||||
|         expect(wrapper.findComponent(BoardCutLine).exists()).toBe(false); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import BoardCutLine from '~/boards/components/board_cut_line.vue'; | ||||
| 
 | ||||
| describe('BoardCutLine', () => { | ||||
|   let wrapper; | ||||
|   const cutLineText = 'Work in progress limit: 3'; | ||||
| 
 | ||||
|   const createComponent = (props) => { | ||||
|     wrapper = shallowMount(BoardCutLine, { propsData: props }); | ||||
|   }; | ||||
| 
 | ||||
|   describe('when cut line is shown', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent({ cutLineText }); | ||||
|     }); | ||||
| 
 | ||||
|     it('contains cut line text in the template', () => { | ||||
|       expect(wrapper.find('[data-testid="cut-line-text"]').text()).toContain( | ||||
|         `Work in progress limit: 3`, | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('does not contain other text in the template', () => { | ||||
|       expect(wrapper.find('[data-testid="cut-line-text"]').text()).not.toContain(`unexpected`); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | @ -2,19 +2,17 @@ import { shallowMount } from '@vue/test-utils'; | |||
| import IssueCount from '~/boards/components/item_count.vue'; | ||||
| 
 | ||||
| describe('IssueCount', () => { | ||||
|   let vm; | ||||
|   let wrapper; | ||||
|   let maxIssueCount; | ||||
|   let itemsSize; | ||||
| 
 | ||||
|   const createComponent = (props) => { | ||||
|     vm = shallowMount(IssueCount, { propsData: props }); | ||||
|     wrapper = shallowMount(IssueCount, { propsData: props }); | ||||
|   }; | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     maxIssueCount = 0; | ||||
|     itemsSize = 0; | ||||
| 
 | ||||
|     if (vm) vm.destroy(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('when maxIssueCount is zero', () => { | ||||
|  | @ -25,11 +23,11 @@ describe('IssueCount', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('contains issueSize in the template', () => { | ||||
|       expect(vm.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|       expect(wrapper.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|     }); | ||||
| 
 | ||||
|     it('does not contains maxIssueCount in the template', () => { | ||||
|       expect(vm.find('.max-issue-size').exists()).toBe(false); | ||||
|       expect(wrapper.find('.max-issue-size').exists()).toBe(false); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -42,15 +40,15 @@ describe('IssueCount', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('contains issueSize in the template', () => { | ||||
|       expect(vm.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|       expect(wrapper.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|     }); | ||||
| 
 | ||||
|     it('contains maxIssueCount in the template', () => { | ||||
|       expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount)); | ||||
|       expect(wrapper.find('.max-issue-size').text()).toContain(String(maxIssueCount)); | ||||
|     }); | ||||
| 
 | ||||
|     it('does not have text-danger class when issueSize is less than maxIssueCount', () => { | ||||
|       expect(vm.classes('.text-danger')).toBe(false); | ||||
|     it('does not have red text when issueSize is less than maxIssueCount', () => { | ||||
|       expect(wrapper.classes('.gl-text-red-700')).toBe(false); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -63,15 +61,15 @@ describe('IssueCount', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('contains issueSize in the template', () => { | ||||
|       expect(vm.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|       expect(wrapper.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize)); | ||||
|     }); | ||||
| 
 | ||||
|     it('contains maxIssueCount in the template', () => { | ||||
|       expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount)); | ||||
|       expect(wrapper.find('.max-issue-size').text()).toContain(String(maxIssueCount)); | ||||
|     }); | ||||
| 
 | ||||
|     it('has text-danger class', () => { | ||||
|       expect(vm.find('.text-danger').text()).toEqual(String(itemsSize)); | ||||
|     it('has red text', () => { | ||||
|       expect(wrapper.find('.gl-text-red-700').text()).toEqual(String(itemsSize)); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ import { GlAlert } from '@gitlab/ui'; | |||
| import { mount } from '@vue/test-utils'; | ||||
| import Vue from 'vue'; | ||||
| import VueApollo from 'vue-apollo'; | ||||
| import Autosave from '~/autosave'; | ||||
| import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; | ||||
| import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants'; | ||||
| import createMockApollo from 'helpers/mock_apollo_helper'; | ||||
| import waitForPromises from 'helpers/wait_for_promises'; | ||||
| import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; | ||||
|  | @ -94,6 +95,12 @@ describe('Design reply form component', () => { | |||
|     expect(findTextarea().element).toEqual(document.activeElement); | ||||
|   }); | ||||
| 
 | ||||
|   it('allows switching to rich text', () => { | ||||
|     createComponent(); | ||||
| 
 | ||||
|     expect(wrapper.text()).toContain('Switch to rich text editing'); | ||||
|   }); | ||||
| 
 | ||||
|   it('renders "Attach a file or image" button in markdown toolbar', () => { | ||||
|     createComponent(); | ||||
| 
 | ||||
|  | @ -118,23 +125,6 @@ describe('Design reply form component', () => { | |||
|     expect(findSubmitButton().html()).toMatchSnapshot(); | ||||
|   }); | ||||
| 
 | ||||
|   it.each` | ||||
|     discussionId                         | shortDiscussionId | ||||
|     ${undefined}                         | ${'new'} | ||||
|     ${'gid://gitlab/DiffDiscussion/123'} | ${123} | ||||
|   `(
 | ||||
|     'initializes autosave support on discussion with proper key', | ||||
|     ({ discussionId, shortDiscussionId }) => { | ||||
|       createComponent({ props: { discussionId } }); | ||||
| 
 | ||||
|       expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [ | ||||
|         'Discussion', | ||||
|         6, | ||||
|         shortDiscussionId, | ||||
|       ]); | ||||
|     }, | ||||
|   ); | ||||
| 
 | ||||
|   describe('when form has no text', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent(); | ||||
|  | @ -155,7 +145,7 @@ describe('Design reply form component', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('emits cancelForm event on pressing escape button on textarea', () => { | ||||
|       findTextarea().trigger('keyup.esc'); | ||||
|       findTextarea().trigger('keydown.esc'); | ||||
| 
 | ||||
|       expect(wrapper.emitted('cancel-form')).toHaveLength(1); | ||||
|     }); | ||||
|  | @ -261,7 +251,7 @@ describe('Design reply form component', () => { | |||
|     it('emits cancelForm event on Escape key if text was not changed', () => { | ||||
|       createComponent(); | ||||
| 
 | ||||
|       findTextarea().trigger('keyup.esc'); | ||||
|       findTextarea().trigger('keydown.esc'); | ||||
| 
 | ||||
|       expect(wrapper.emitted('cancel-form')).toHaveLength(1); | ||||
|     }); | ||||
|  | @ -271,7 +261,7 @@ describe('Design reply form component', () => { | |||
| 
 | ||||
|       findTextarea().setValue(mockComment); | ||||
| 
 | ||||
|       findTextarea().trigger('keyup.esc'); | ||||
|       findTextarea().trigger('keydown.esc'); | ||||
| 
 | ||||
|       expect(confirmAction).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | @ -282,7 +272,7 @@ describe('Design reply form component', () => { | |||
|       createComponent({ props: { value: mockComment } }); | ||||
|       findTextarea().setValue('Comment changed'); | ||||
| 
 | ||||
|       findTextarea().trigger('keyup.esc'); | ||||
|       findTextarea().trigger('keydown.esc'); | ||||
| 
 | ||||
|       expect(confirmAction).toHaveBeenCalled(); | ||||
| 
 | ||||
|  | @ -296,7 +286,7 @@ describe('Design reply form component', () => { | |||
|       createComponent({ props: { value: mockComment } }); | ||||
|       findTextarea().setValue('Comment changed'); | ||||
| 
 | ||||
|       findTextarea().trigger('keyup.esc'); | ||||
|       findTextarea().trigger('keydown.esc'); | ||||
| 
 | ||||
|       expect(confirmAction).toHaveBeenCalled(); | ||||
|       await waitForPromises(); | ||||
|  | @ -306,11 +296,12 @@ describe('Design reply form component', () => { | |||
|   }); | ||||
| 
 | ||||
|   describe('when component is destroyed', () => { | ||||
|     it('calls autosave.reset', async () => { | ||||
|       const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); | ||||
|     it('clears autosave entry', async () => { | ||||
|       const clearAutosaveSpy = jest.fn(); | ||||
|       markdownEditorEventHub.$on(CLEAR_AUTOSAVE_ENTRY_EVENT, clearAutosaveSpy); | ||||
|       createComponent(); | ||||
|       await wrapper.destroy(); | ||||
|       expect(autosaveResetSpy).toHaveBeenCalled(); | ||||
|       expect(clearAutosaveSpy).toHaveBeenCalled(); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -37,7 +37,6 @@ import { mockCreateImageNoteDiffResponse } from '../../mock_data/apollo_mock'; | |||
| jest.mock('~/alert'); | ||||
| jest.mock('~/api.js'); | ||||
| 
 | ||||
| const focusInput = jest.fn(); | ||||
| const mockCacheObject = { | ||||
|   readQuery: jest.fn().mockReturnValue(mockProject), | ||||
|   writeQuery: jest.fn(), | ||||
|  | @ -51,9 +50,6 @@ const mockPageLayoutElement = { | |||
| }; | ||||
| const DesignReplyForm = { | ||||
|   template: '<div><textarea ref="textarea"></textarea></div>', | ||||
|   methods: { | ||||
|     focusInput, | ||||
|   }, | ||||
| }; | ||||
| const mockDesignNoDiscussions = { | ||||
|   ...design, | ||||
|  | @ -219,22 +215,6 @@ describe('Design management design index page', () => { | |||
|     expect(findDesignReplyForm().exists()).toBe(true); | ||||
|   }); | ||||
| 
 | ||||
|   it('keeps new discussion form focused', () => { | ||||
|     createComponent( | ||||
|       { loading: false }, | ||||
|       { | ||||
|         data: { | ||||
|           design, | ||||
|           annotationCoordinates, | ||||
|         }, | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 }); | ||||
| 
 | ||||
|     expect(focusInput).toHaveBeenCalled(); | ||||
|   }); | ||||
| 
 | ||||
|   it('sends a update and closes the form when mutation is completed', async () => { | ||||
|     createComponent( | ||||
|       { loading: false }, | ||||
|  |  | |||
|  | @ -16,7 +16,6 @@ import { putCreateReleaseNotification } from '~/releases/release_notification_se | |||
| import AssetLinksForm from '~/releases/components/asset_links_form.vue'; | ||||
| import ConfirmDeleteModal from '~/releases/components/confirm_delete_modal.vue'; | ||||
| import { BACK_URL_PARAM } from '~/releases/constants'; | ||||
| import MarkdownField from '~/vue_shared/components/markdown/field.vue'; | ||||
| import { ValidationResult } from '~/lib/utils/ref_validator'; | ||||
| 
 | ||||
| const originalRelease = originalOneReleaseForEditingQueryResponse.data.project.release; | ||||
|  | @ -41,6 +40,7 @@ describe('Release edit/new component', () => { | |||
|       release, | ||||
|       isExistingRelease: true, | ||||
|       projectPath, | ||||
|       markdownPreviewPath: 'path/to/markdown/preview', | ||||
|       markdownDocsPath: 'path/to/markdown/docs', | ||||
|       releasesPagePath, | ||||
|       projectId: '8', | ||||
|  | @ -54,6 +54,7 @@ describe('Release edit/new component', () => { | |||
|       saveRelease: jest.fn(), | ||||
|       addEmptyAssetLink: jest.fn(), | ||||
|       deleteRelease: jest.fn(), | ||||
|       updateReleaseNotes: jest.fn(), | ||||
|     }; | ||||
| 
 | ||||
|     getters = { | ||||
|  | @ -173,15 +174,14 @@ describe('Release edit/new component', () => { | |||
|       expect(wrapper.find('#release-notes').element.value).toBe(release.description); | ||||
|     }); | ||||
| 
 | ||||
|     it('sets the preview text to be the formatted release notes', () => { | ||||
|       const notes = getters.formattedReleaseNotes(); | ||||
|       expect(wrapper.findComponent(MarkdownField).props('textareaValue')).toBe(notes); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders the "Save changes" button as type="submit"', () => { | ||||
|       expect(findSubmitButton().attributes('type')).toBe('submit'); | ||||
|     }); | ||||
| 
 | ||||
|     it('allows switching to rich text editor', () => { | ||||
|       expect(wrapper.html()).toContain('Switch to rich text editing'); | ||||
|     }); | ||||
| 
 | ||||
|     it('calls saveRelease when the form is submitted', () => { | ||||
|       findForm().trigger('submit'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do | |||
|   end | ||||
| 
 | ||||
|   describe '#cascading_namespace_settings_popover_data' do | ||||
|     attribute = :delayed_project_removal | ||||
|     attribute = :toggle_security_policy_custom_ci | ||||
| 
 | ||||
|     subject do | ||||
|       helper.cascading_namespace_settings_popover_data( | ||||
|  | @ -94,7 +94,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do | |||
|   end | ||||
| 
 | ||||
|   describe '#cascading_namespace_setting_locked?' do | ||||
|     let(:attribute) { :delayed_project_removal } | ||||
|     let(:attribute) { :toggle_security_policy_custom_ci } | ||||
| 
 | ||||
|     context 'when `group` argument is `nil`' do | ||||
|       it 'returns `false`' do | ||||
|  | @ -110,13 +110,13 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do | |||
| 
 | ||||
|     context 'when `*_locked?` method does exist' do | ||||
|       before do | ||||
|         allow(admin_group.namespace_settings).to receive(:delayed_project_removal_locked?).and_return(true) | ||||
|         allow(admin_group.namespace_settings).to receive(:toggle_security_policy_custom_ci_locked?).and_return(true) | ||||
|       end | ||||
| 
 | ||||
|       it 'calls corresponding `*_locked?` method' do | ||||
|         helper.cascading_namespace_setting_locked?(attribute, admin_group, include_self: true) | ||||
| 
 | ||||
|         expect(admin_group.namespace_settings).to have_received(:delayed_project_removal_locked?).with(include_self: true) | ||||
|         expect(admin_group.namespace_settings).to have_received(:toggle_security_policy_custom_ci_locked?).with(include_self: true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -0,0 +1,44 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require "spec_helper" | ||||
| 
 | ||||
| RSpec.describe API::Helpers::IntegrationsHelpers, feature_category: :integrations do | ||||
|   let(:base_classes) { Integration::BASE_CLASSES.map(&:constantize) } | ||||
|   let(:development_classes) { [Integrations::MockCi, Integrations::MockMonitoring] } | ||||
|   let(:instance_level_classes) { [Integrations::BeyondIdentity] } | ||||
| 
 | ||||
|   describe '.chat_notification_flags' do | ||||
|     it 'returns correct values' do | ||||
|       expect(described_class.chat_notification_flags).to match_array( | ||||
|         [ | ||||
|           { | ||||
|             required: false, | ||||
|             name: :notify_only_broken_pipelines, | ||||
|             type: ::Grape::API::Boolean, | ||||
|             desc: 'Send notifications for broken pipelines' | ||||
|           } | ||||
|         ] | ||||
|       ) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '.integrations' do | ||||
|     it 'has correct integrations' do | ||||
|       expect(described_class.integrations.keys.map(&:underscore)) | ||||
|         .to match_array(described_class.integration_classes.map(&:to_param)) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '.integration_classes' do | ||||
|     it 'returns correct integrations' do | ||||
|       expect(described_class.integration_classes) | ||||
|         .to match_array(Integration.descendants.without(base_classes, development_classes, instance_level_classes)) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '.development_integration_classes' do | ||||
|     it 'returns correct integrations' do | ||||
|       expect(described_class.development_integration_classes).to eq(development_classes) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -46,25 +46,6 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :secrets_management do | |||
|       expect(payload).not_to include(:user_identities) | ||||
|     end | ||||
| 
 | ||||
|     context 'when oidc_issuer_url is disabled' do | ||||
|       before do | ||||
|         stub_feature_flags(oidc_issuer_url: false) | ||||
|       end | ||||
| 
 | ||||
|       it 'has correct values for the standard JWT attributes' do | ||||
|         aggregate_failures do | ||||
|           expect(payload[:iss]).to eq(Settings.gitlab.base_url) | ||||
|           expect(payload[:aud]).to eq(Settings.gitlab.base_url) | ||||
|           expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when oidc_issuer_url is enabled' do | ||||
|       before do | ||||
|         stub_feature_flags(oidc_issuer_url: true) | ||||
|       end | ||||
| 
 | ||||
|     it 'has correct values for the standard JWT attributes' do | ||||
|       aggregate_failures do | ||||
|         expect(payload[:iss]).to eq(Gitlab.config.gitlab.url) | ||||
|  | @ -72,7 +53,6 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :secrets_management do | |||
|         expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") | ||||
|       end | ||||
|     end | ||||
|     end | ||||
| 
 | ||||
|     context 'when given an aud' do | ||||
|       let(:aud) { 'AWS' } | ||||
|  |  | |||
|  | @ -448,8 +448,12 @@ RSpec.describe NamespaceSetting, feature_category: :groups_and_projects, type: : | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#delayed_project_removal' do | ||||
|     it_behaves_like 'a cascading namespace setting boolean attribute', settings_attribute_name: :delayed_project_removal | ||||
|   describe '#toggle_security_policy_custom_ci' do | ||||
|     it_behaves_like 'a cascading namespace setting boolean attribute', settings_attribute_name: :toggle_security_policy_custom_ci | ||||
|   end | ||||
| 
 | ||||
|   describe '#toggle_security_policies_policy_scope' do | ||||
|     it_behaves_like 'a cascading namespace setting boolean attribute', settings_attribute_name: :toggle_security_policies_policy_scope | ||||
|   end | ||||
| 
 | ||||
|   describe 'default_branch_protection_defaults' do | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue