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;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.focusInput();
|
||||
autosaveKey() {
|
||||
if (this.isLoggedIn) {
|
||||
return [
|
||||
s__('DesignManagement|Discussion'),
|
||||
getIdFromGraphQLId(this.noteableId),
|
||||
this.shortDiscussionId,
|
||||
].join('/');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
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…')"
|
||||
@input="handleInput"
|
||||
@keydown.meta.enter="submitForm"
|
||||
@keydown.ctrl.enter="submitForm"
|
||||
@keyup.esc.stop="cancelComment"
|
||||
>
|
||||
</textarea>
|
||||
</template>
|
||||
</markdown-field>
|
||||
: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"
|
||||
@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-editor
|
||||
v-model="releaseNotes"
|
||||
:render-markdown-path="markdownPreviewPath"
|
||||
:markdown-docs-path="markdownDocsPath"
|
||||
:add-spacing-classes="false"
|
||||
:textarea-value="formattedReleaseNotes"
|
||||
>
|
||||
<template #textarea>
|
||||
<textarea
|
||||
id="release-notes"
|
||||
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>
|
||||
: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,31 +46,11 @@ 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)
|
||||
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
|
||||
it 'has correct values for the standard JWT attributes' do
|
||||
aggregate_failures do
|
||||
expect(payload[:iss]).to eq(Gitlab.config.gitlab.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
|
||||
|
||||
|
|
|
|||
|
|
@ -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