diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-11 15:09:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-11 15:09:43 +0300 |
commit | f20820d7faa4b0cc31d4b40be66f1aad5f8442bc (patch) | |
tree | 87dff45ebcf5b6cbb3db6c53185b08724e88d414 | |
parent | 289ce60cdc5fe83278ff01f9506db862e6f8e9ac (diff) |
Add latest changes from gitlab-org/gitlab@master
88 files changed, 327 insertions, 513 deletions
diff --git a/.rubocop_todo/rspec/useless_dynamic_definition.yml b/.rubocop_todo/rspec/useless_dynamic_definition.yml index 94a53324dee..75bea5601ae 100644 --- a/.rubocop_todo/rspec/useless_dynamic_definition.yml +++ b/.rubocop_todo/rspec/useless_dynamic_definition.yml @@ -5,7 +5,6 @@ RSpec/UselessDynamicDefinition: - 'ee/spec/factories/ci/job_artifacts.rb' - 'ee/spec/factories/ci/pipelines.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_security_scans_metric_spec.rb' - - 'ee/spec/support/shared_examples/lib/gitlab/elastic/search_results_shared_examples.rb' - 'spec/models/ci/resource_group_spec.rb' - 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb' - 'spec/support/helpers/cycle_analytics_helpers/test_generation.rb' diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue index 31531c90b94..1cd5854740e 100644 --- a/app/assets/javascripts/badges/components/badge.vue +++ b/app/assets/javascripts/badges/components/badge.vue @@ -80,7 +80,7 @@ export default { :href="linkUrl" target="_blank" rel="noopener noreferrer" - data-qa-selector="badge_image_link" + data-testid="badge-image-link" :data-qa-link-url="linkUrl" > <img diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue index b69890572eb..12c9662b30d 100644 --- a/app/assets/javascripts/badges/components/badge_list.vue +++ b/app/assets/javascripts/badges/components/badge_list.vue @@ -100,7 +100,7 @@ export default { <template> <div> <gl-loading-icon v-show="isLoading" size="md" /> - <div data-qa-selector="badge_list_content"> + <div data-testid="badge-list-content"> <gl-table :empty-text="emptyMessage" :fields="fields" @@ -109,7 +109,7 @@ export default { :current-page="currentPage" stacked="md" show-empty - data-qa-selector="badge_list" + data-testid="badge-list" > <template #cell(name)="{ item }"> <label class="label-bold str-truncated mb-0">{{ item.name }}</label> diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue index f82983a79a7..fac45f32464 100644 --- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue +++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue @@ -6,7 +6,6 @@ import { __ } from '~/locale'; import { createAlert } from '~/alert'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import { scrollToElement } from '~/lib/utils/common_utils'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants'; import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking'; @@ -23,7 +22,6 @@ export default { SummarizeMyReview: () => import('ee_component/batch_comments/components/summarize_my_review.vue'), }, - mixins: [glFeatureFlagsMixin()], inject: { canSummarize: { default: false }, }, @@ -151,7 +149,6 @@ export default { <markdown-editor ref="markdownEditor" v-model="noteData.note" - :enable-content-editor="Boolean(glFeatures.contentEditorOnIssues)" class="js-no-autosize" :is-submitting="isSubmitting" :render-markdown-path="getNoteableData.preview_note_path" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_environments_dropdown.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_environments_dropdown.vue index abd272ee24b..77af643cbb3 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_environments_dropdown.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_environments_dropdown.vue @@ -24,10 +24,6 @@ export default { type: Array, required: true, }, - hasEnvScopeQuery: { - type: Boolean, - required: true, - }, selectedEnvironmentScope: { type: String, required: false, @@ -46,26 +42,17 @@ export default { composedCreateButtonLabel() { return sprintf(__('Create wildcard: %{searchTerm}'), { searchTerm: this.searchTerm }); }, - filteredEnvironments() { - const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); - return this.environments.filter((environment) => { - return environment.toLowerCase().includes(lowerCasedSearchTerm); - }); - }, isDropdownLoading() { - return this.areEnvironmentsLoading && this.hasEnvScopeQuery && !this.isDropdownShown; + return this.areEnvironmentsLoading && !this.isDropdownShown; }, isDropdownSearching() { - return this.areEnvironmentsLoading && this.hasEnvScopeQuery && this.isDropdownShown; + return this.areEnvironmentsLoading && this.isDropdownShown; }, searchedEnvironments() { - // If hasEnvScopeQuery (applies only to projects for now), search query will be fired so this - // component will already receive filtered environments during the refetch. - // Otherwise (applies to groups), search the existing list of environments in the frontend - let filtered = this.hasEnvScopeQuery ? this.environments : this.filteredEnvironments; + let filtered = this.environments; // If there is no search term, make sure to include * - if (this.hasEnvScopeQuery && !this.searchTerm) { + if (!this.searchTerm) { filtered = uniq([...filtered, '*']); } @@ -85,9 +72,7 @@ export default { ); }, shouldRenderDivider() { - return ( - (this.hasEnvScopeQuery || this.shouldRenderCreateButton) && !this.areEnvironmentsLoading - ); + return !this.areEnvironmentsLoading; }, environmentScopeLabel() { return convertEnvironmentScope(this.selectedEnvironmentScope); @@ -97,9 +82,7 @@ export default { debouncedSearch: debounce(function debouncedSearch(searchTerm) { const newSearchTerm = searchTerm.trim(); this.searchTerm = newSearchTerm; - if (this.hasEnvScopeQuery) { - this.$emit('search-environment-scope', newSearchTerm); - } + this.$emit('search-environment-scope', newSearchTerm); }, 500), selectEnvironment(selected) { this.$emit('select-environment', selected); @@ -137,7 +120,7 @@ export default { > <template #footer> <gl-dropdown-divider v-if="shouldRenderDivider" /> - <div v-if="hasEnvScopeQuery" data-testid="max-envs-notice"> + <div data-testid="max-envs-notice"> <gl-dropdown-item class="gl-list-style-none" disabled> <gl-sprintf :message="$options.i18n.maxEnvsNote" class="gl-font-sm"> <template #limit> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue index 14410d8b344..5b2984e5249 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue @@ -311,7 +311,6 @@ export default { <ci-environments-dropdown v-if="areScopedVariablesAvailable" class="gl-mb-5" - has-env-scope-query :are-environments-loading="areEnvironmentsLoading" :environments="environments" :selected-environment-scope="variable.environmentScope" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue index 2d135056073..cc664d76267 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue @@ -38,7 +38,6 @@ import { VARIABLE_ACTIONS, variableOptions, } from '../constants'; -import { createJoinedEnvironments } from '../utils'; import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens'; @@ -90,10 +89,6 @@ export default { required: false, default: false, }, - hasEnvScopeQuery: { - type: Boolean, - required: true, - }, mode: { type: String, required: true, @@ -147,13 +142,6 @@ export default { isTipVisible() { return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key); }, - environmentsList() { - if (this.hasEnvScopeQuery) { - return this.environments; - } - - return createJoinedEnvironments(this.variables, this.environments, this.newEnvironments); - }, maskedFeedback() { return this.displayMaskedError ? __('This variable value does not meet the masking requirements.') @@ -404,9 +392,8 @@ export default { <ci-environments-dropdown v-if="areScopedVariablesAvailable" :are-environments-loading="areEnvironmentsLoading" - :has-env-scope-query="hasEnvScopeQuery" :selected-environment-scope="variable.environmentScope" - :environments="environmentsList" + :environments="environments" @select-environment="setEnvironmentScope" @search-environment-scope="$emit('search-environment-scope', $event)" /> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue index 482f6da5617..2d25a4be9e7 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue @@ -37,10 +37,6 @@ export default { required: false, default: false, }, - hasEnvScopeQuery: { - type: Boolean, - required: true, - }, isLoading: { type: Boolean, required: false, @@ -125,7 +121,6 @@ export default { :are-environments-loading="areEnvironmentsLoading" :are-scoped-variables-available="areScopedVariablesAvailable" :environments="environments" - :has-env-scope-query="hasEnvScopeQuery" :hide-environment-scope="hideEnvironmentScope" :variables="variables" :mode="mode" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue index 3d5ed327dc7..ebc0db6fad1 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue @@ -285,7 +285,6 @@ export default { :are-scoped-variables-available="areScopedVariablesAvailable" :entity="entity" :environments="environments" - :has-env-scope-query="hasEnvScopeQuery" :hide-environment-scope="hideEnvironmentScope" :is-loading="isLoading" :max-variable-limit="maxVariableLimit" diff --git a/app/assets/javascripts/ci/ci_variable_list/utils.js b/app/assets/javascripts/ci/ci_variable_list/utils.js index 1faa97a5f73..a7e020206ea 100644 --- a/app/assets/javascripts/ci/ci_variable_list/utils.js +++ b/app/assets/javascripts/ci/ci_variable_list/utils.js @@ -1,29 +1,6 @@ -import { uniq } from 'lodash'; import { allEnvironments } from './constants'; /** - * This function takes a list of variable, environments and - * new environments added through the scope dropdown - * and create a new Array that concatenate the environment list - * with the environment scopes find in the variable list. This is - * useful for variable settings so that we can render a list of all - * environment scopes available based on the list of envs, the ones the user - * added explictly and what is found under each variable. - * @param {Array} variables - * @param {Array} environments - * @returns {Array} - Array of environments - */ - -export const createJoinedEnvironments = ( - variables = [], - environments = [], - newEnvironments = [], -) => { - const scopesFromVariables = variables.map((variable) => variable.environmentScope); - return uniq([...environments, ...newEnvironments, ...scopesFromVariables]).sort(); -}; - -/** * This function job is to convert the * wildcard to text when applicable * in the UI. It uses a constants to compare the incoming value to that * of the * and then apply the corresponding label if applicable. If there diff --git a/app/assets/javascripts/content_editor/extensions/selection.js b/app/assets/javascripts/content_editor/extensions/selection.js index 2e0bb29e5a1..0c24207b395 100644 --- a/app/assets/javascripts/content_editor/extensions/selection.js +++ b/app/assets/javascripts/content_editor/extensions/selection.js @@ -6,12 +6,22 @@ export default Extension.create({ name: 'selection', addProseMirrorPlugins() { + let contextMenuVisible = false; + return [ new Plugin({ key: new PluginKey('selection'), props: { + handleDOMEvents: { + contextmenu() { + contextMenuVisible = true; + setTimeout(() => { + contextMenuVisible = false; + }); + }, + }, decorations(state) { - if (state.selection.empty) return null; + if (state.selection.empty || contextMenuVisible) return null; return DecorationSet.create(state.doc, [ Decoration.inline(state.selection.from, state.selection.to, { diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 17e650644b3..0897232cf89 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -561,7 +561,14 @@ const linkType = (sourceMarkdown) => { return LINK_HTML; }; -const normalizeUrl = (url) => decodeURIComponent(removeLastSlashInUrlPath(removeUrlProtocol(url))); +const normalizeUrl = (url) => { + const processedUrl = removeLastSlashInUrlPath(removeUrlProtocol(url)); + try { + return decodeURIComponent(processedUrl); + } catch { + return processedUrl; + } +}; /** * Validates that the provided URL is a valid GFM autolink diff --git a/app/assets/javascripts/design_management/components/design_description/description_form.vue b/app/assets/javascripts/design_management/components/design_description/description_form.vue index 413442074f0..6be643e88dc 100644 --- a/app/assets/javascripts/design_management/components/design_description/description_form.vue +++ b/app/assets/javascripts/design_management/components/design_description/description_form.vue @@ -4,7 +4,6 @@ import SafeHtml from '~/vue_shared/directives/safe_html'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; -import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; import { toggleMarkCheckboxes } from '~/behaviors/markdown/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -36,7 +35,6 @@ export default { placeholder: s__('DesignManagement|Write a comment or drag your files here…'), 'aria-label': s__('DesignManagement|Design description'), }, - mixins: [glFeaturesFlagMixin()], markdownDocsPath: helpPagePath('user/markdown'), quickActionsDocsPath: helpPagePath('user/project/quick_actions'), props: { @@ -174,7 +172,6 @@ export default { :render-markdown-path="markdownPreviewPath" :markdown-docs-path="$options.markdownDocsPath" :form-field-props="$options.formFieldProps" - :enable-content-editor="Boolean(glFeatures.contentEditorOnIssues)" :quick-actions-docs-path="$options.quickActionsDocsPath" :autosave-key="autosaveKey" enable-autocomplete diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue index 91dbd86418c..4b492e48095 100644 --- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue @@ -16,7 +16,7 @@ import GroupSelect from './group_select.vue'; import InviteGroupNotification from './invite_group_notification.vue'; export default { - name: 'InviteMembersModal', + name: 'InviteGroupsModal', components: { GroupSelect, InviteModalBase, diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index 5a891e23faf..18d22395104 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -253,7 +253,6 @@ export default { <gl-modal ref="modal" :modal-id="modalId" - data-qa-selector="invite_members_modal_content" data-testid="invite-modal" size="sm" dialog-class="gl-mx-5" diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index d31b56c0277..756585683c8 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -185,12 +185,12 @@ export default { default: false, }, issueId: { - type: Number, + type: String, required: false, default: null, }, issueIid: { - type: Number, + type: String, required: false, default: null, }, @@ -521,7 +521,6 @@ export default { :project-namespace="projectNamespace" :can-attach-file="canAttachFile" :enable-autocomplete="enableAutocomplete" - :issue-id="issueId" :issuable-type="issuableType" @updateForm="setFormState" /> diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 5735a518a3e..369aa694739 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -74,12 +74,12 @@ export default { default: 0, }, issueId: { - type: Number, + type: String, required: false, default: null, }, issueIid: { - type: Number, + type: String, required: false, default: null, }, @@ -365,7 +365,7 @@ export default { addHierarchyChild({ cache, fullPath: this.fullPath, - iid: String(this.issueIid), + iid: this.issueIid, workItem: workItemCreate.workItem, }), }); @@ -400,7 +400,7 @@ export default { removeHierarchyChild({ cache, fullPath: this.fullPath, - iid: String(this.issueIid), + iid: this.issueIid, workItem: { id }, }), }); diff --git a/app/assets/javascripts/issues/show/components/fields/description.vue b/app/assets/javascripts/issues/show/components/fields/description.vue index efe1619ed1f..10323b99665 100644 --- a/app/assets/javascripts/issues/show/components/fields/description.vue +++ b/app/assets/javascripts/issues/show/components/fields/description.vue @@ -2,7 +2,6 @@ <script> import { __ } from '~/locale'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; -import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking'; import { ISSUE_NOTEABLE_TYPE } from '~/notes/constants'; import updateMixin from '../../mixins/update'; @@ -11,7 +10,7 @@ export default { components: { MarkdownEditor, }, - mixins: [updateMixin, glFeaturesFlagMixin()], + mixins: [updateMixin], props: { value: { type: String, @@ -71,7 +70,6 @@ export default { <label class="sr-only" for="issue-description">{{ __('Description') }}</label> <markdown-editor ref="markdownEditor" - :enable-content-editor="Boolean(glFeatures.contentEditorOnIssues)" class="gl-mt-3" :value="value" :render-markdown-path="markdownPreviewPath" diff --git a/app/assets/javascripts/issues/show/components/form.vue b/app/assets/javascripts/issues/show/components/form.vue index 047bdcdcefc..c2248d66860 100644 --- a/app/assets/javascripts/issues/show/components/form.vue +++ b/app/assets/javascripts/issues/show/components/form.vue @@ -2,10 +2,7 @@ <script> import { GlAlert } from '@gitlab/ui'; import { getDraft, updateDraft, getLockVersion, clearDraft } from '~/lib/utils/autosave'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPENAME_ISSUE, TYPENAME_USER } from '~/graphql_shared/constants'; import { TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../event_hub'; import EditActions from './edit_actions.vue'; import DescriptionField from './fields/description.vue'; @@ -24,7 +21,6 @@ export default { IssuableTypeField, LockedWarning, }, - mixins: [glFeatureFlagMixin()], props: { endpoint: { type: String, @@ -78,11 +74,6 @@ export default { required: false, default: '', }, - issueId: { - type: Number, - required: false, - default: null, - }, }, data() { const autosaveKey = [document.location.pathname, document.location.search]; @@ -110,12 +101,6 @@ export default { showTypeField() { return [TYPE_INCIDENT, TYPE_ISSUE].includes(this.issuableType); }, - resourceId() { - return this.issueId && convertToGraphQLId(TYPENAME_ISSUE, this.issueId); - }, - userId() { - return convertToGraphQLId(TYPENAME_USER, gon.current_user_id); - }, }, watch: { formData: { diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index b94f88f690e..cd5c6f4825a 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -131,8 +131,8 @@ export function initIssuableApp(store) { isLocked: this.getNoteableData?.discussion_locked, issuableStatus: this.getNoteableData?.state, issuableType: issueType, - issueId: this.getNoteableData?.id, - issueIid: this.getNoteableData?.iid, + issueId: this.getNoteableData?.id.toString(), + issueIid: this.getNoteableData?.iid.toString(), showTitleBorder: issueType !== TYPE_INCIDENT, }, }); diff --git a/app/assets/javascripts/members/components/members_tabs.vue b/app/assets/javascripts/members/components/members_tabs.vue index de5c9eb5a55..449ad20e7ab 100644 --- a/app/assets/javascripts/members/components/members_tabs.vue +++ b/app/assets/javascripts/members/components/members_tabs.vue @@ -22,7 +22,7 @@ export const TABS = [ { namespace: MEMBER_TYPES.group, title: __('Groups'), - attrs: { 'data-qa-selector': 'groups_list_tab' }, + attrs: { 'data-testid': 'groups-list-tab' }, queryParamValue: TAB_QUERY_PARAM_VALUES.group, }, { diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 142f07973f2..329d6cfec00 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -16,7 +16,6 @@ import { sprintf } from '~/locale'; import { badgeState } from '~/merge_requests/components/merge_request_header.vue'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking'; import * as constants from '../constants'; @@ -49,7 +48,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - mixins: [glFeatureFlagsMixin(), issuableStateMixin], + mixins: [issuableStateMixin], props: { noteableType: { type: String, @@ -361,7 +360,6 @@ export default { > <markdown-editor ref="markdownEditor" - :enable-content-editor="Boolean(glFeatures.contentEditorOnIssues)" :value="note" :render-markdown-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 8073428c108..f8a0db93e37 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -5,7 +5,6 @@ import { mapGetters, mapActions, mapState } from 'vuex'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; -import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking'; import eventHub from '../event_hub'; import issuableStateMixin from '../mixins/issuable_state'; @@ -24,7 +23,7 @@ export default { GlLink, GlFormCheckbox, }, - mixins: [issuableStateMixin, resolvable, glFeaturesFlagMixin()], + mixins: [issuableStateMixin, resolvable], props: { noteBody: { type: String, @@ -224,9 +223,6 @@ export default { placeholder: { link: ['startTag', 'endTag'] }, }; }, - enableContentEditor() { - return Boolean(this.glFeatures.contentEditorOnIssues); - }, codeSuggestionsConfig() { return { canSuggest: this.canSuggest, @@ -361,7 +357,6 @@ export default { <comment-field-layout :noteable-data="getNoteableData" :is-internal-note="isInternalNote"> <markdown-editor ref="markdownEditor" - :enable-content-editor="enableContentEditor" :value="updatedNoteBody" :render-markdown-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" diff --git a/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js b/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js index 6c2f084591e..f7fb1339bbc 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js +++ b/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js @@ -105,7 +105,6 @@ export function mountMarkdownEditor(options = {}) { return h(MarkdownEditor, { props: { setFacade, - enableContentEditor: Boolean(gon.features?.contentEditorOnIssues), value: formFieldValue, renderMarkdownPath, markdownDocsPath, diff --git a/app/controllers/admin/topics_controller.rb b/app/controllers/admin/topics_controller.rb index c4de600dd1d..40cad2d26f4 100644 --- a/app/controllers/admin/topics_controller.rb +++ b/app/controllers/admin/topics_controller.rb @@ -8,10 +8,6 @@ class Admin::TopicsController < Admin::ApplicationController feature_category :groups_and_projects - before_action do - push_frontend_feature_flag(:content_editor_on_issues, current_user) - end - def index @topics = Projects::TopicsFinder.new(params: params.permit(:search)).execute.page(params[:page]).without_count end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index cbed75019f2..5f6b55ea928 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -9,10 +9,6 @@ class Groups::MilestonesController < Groups::ApplicationController feature_category :team_planning urgency :low - before_action do - push_frontend_feature_flag(:content_editor_on_issues, group) - end - def index respond_to do |format| format.html do diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index fc7865c949d..ac9726112c2 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -45,8 +45,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:preserve_unchanged_markdown, project) - push_frontend_feature_flag(:content_editor_on_issues, project&.group) - push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?) push_frontend_feature_flag(:service_desk_new_note_email_native_attachments, project) push_frontend_feature_flag(:saved_replies, current_user) push_frontend_feature_flag(:issues_grid_view) diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index 81ff6c215f9..1af0ce3c35e 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -7,11 +7,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont feature_category :code_review_workflow - before_action do - push_frontend_feature_flag(:content_editor_on_issues, project&.group) - push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?) - end - private # Normally the methods with `check_(\w+)_available!` pattern are diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 858fddb313f..d57cb0a5926 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -36,8 +36,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end before_action only: [:show, :diffs] do - push_frontend_feature_flag(:content_editor_on_issues, project&.group) - push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?) push_frontend_feature_flag(:core_security_mr_widget_counts, project) push_frontend_feature_flag(:issue_assignees_widget, @project) push_frontend_feature_flag(:moved_mr_sidebar, project) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 1f4e5b54500..35b65dbce7e 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -24,10 +24,6 @@ class Projects::MilestonesController < Projects::ApplicationController feature_category :team_planning urgency :low - before_action do - push_frontend_feature_flag(:content_editor_on_issues, @project) - end - def index @sort = params[:sort] || 'due_date_asc' @milestones = milestones.sort_by_attribute(@sort) diff --git a/app/events/merge_requests/unblocked_state_event.rb b/app/events/merge_requests/unblocked_state_event.rb new file mode 100644 index 00000000000..2cf79059cf7 --- /dev/null +++ b/app/events/merge_requests/unblocked_state_event.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module MergeRequests + class UnblockedStateEvent < Gitlab::EventStore::Event + def schema + { + 'type' => 'object', + 'required' => %w[ + current_user_id + merge_request_id + ], + 'properties' => { + 'current_user_id' => { 'type' => 'integer' }, + 'merge_request_id' => { 'type' => 'integer' } + } + } + end + end +end diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb index da620c85676..437118c36e8 100644 --- a/app/models/bulk_imports/entity.rb +++ b/app/models/bulk_imports/entity.rb @@ -144,9 +144,9 @@ class BulkImports::Entity < ApplicationRecord File.join(base_resource_path, 'export_relations') end - def export_relations_url_path - if bulk_import.supports_batched_export? - Gitlab::Utils.add_url_parameters(export_relations_url_path_base, batched: true) + def export_relations_url_path(batched: false) + if batched && bulk_import.supports_batched_export? + Gitlab::Utils.add_url_parameters(export_relations_url_path_base, batched: batched) else export_relations_url_path_base end diff --git a/app/models/group.rb b/app/models/group.rb index f3a501cfb04..587451ac195 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -885,10 +885,6 @@ class Group < Namespace ].compact.min end - def content_editor_on_issues_feature_flag_enabled? - feature_flag_enabled_for_self_or_ancestor?(:content_editor_on_issues) - end - def work_items_feature_flag_enabled? feature_flag_enabled_for_self_or_ancestor?(:work_items) end diff --git a/app/models/integrations/discord.rb b/app/models/integrations/discord.rb index 6aca9fc9921..7917d17eb62 100644 --- a/app/models/integrations/discord.rb +++ b/app/models/integrations/discord.rb @@ -43,7 +43,7 @@ module Integrations end def self.supported_events - %w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page] + %w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page deployment] end def configurable_channels? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 72b0632f9fd..9ac9f94310d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1237,13 +1237,14 @@ class MergeRequest < ApplicationRecord def mergeable?( skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false, check_mergeability_retry_lease: false, - skip_draft_check: false, skip_rebase_check: false) + skip_draft_check: false, skip_rebase_check: false, skip_blocked_check: false) return false unless mergeable_state?( skip_ci_check: skip_ci_check, - skip_draft_check: skip_draft_check, skip_discussions_check: skip_discussions_check, - skip_approved_check: skip_approved_check + skip_draft_check: skip_draft_check, + skip_approved_check: skip_approved_check, + skip_blocked_check: skip_blocked_check ) check_mergeability(sync_retry_lease: check_mergeability_retry_lease) @@ -1276,14 +1277,15 @@ class MergeRequest < ApplicationRecord def mergeable_state?( skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false, - skip_draft_check: false) + skip_draft_check: false, skip_blocked_check: false) additional_checks = execute_merge_checks( mergeable_state_checks, params: { skip_ci_check: skip_ci_check, skip_discussions_check: skip_discussions_check, skip_approved_check: skip_approved_check, - skip_draft_check: skip_draft_check + skip_draft_check: skip_draft_check, + skip_blocked_check: skip_blocked_check } ) additional_checks.success? diff --git a/app/models/project.rb b/app/models/project.rb index 2e331c391b1..612931e7d40 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3187,10 +3187,6 @@ class Project < ApplicationRecord creator.banned? && team.max_member_access(creator.id) == Gitlab::Access::OWNER end - def content_editor_on_issues_feature_flag_enabled? - group&.content_editor_on_issues_feature_flag_enabled? || Feature.enabled?(:content_editor_on_issues, self) - end - def work_items_feature_flag_enabled? group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self) end diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb index 306bac7daae..aac90c20b53 100644 --- a/app/serializers/merge_request_noteable_entity.rb +++ b/app/serializers/merge_request_noteable_entity.rb @@ -15,8 +15,8 @@ class MergeRequestNoteableEntity < IssuableEntity project_tree_path(merge_request.source_project, merge_request.source_branch) end - expose :target_branch_path, if: -> (merge_request) { merge_request.source_project } do |merge_request| - project_tree_path(merge_request.source_project, merge_request.target_branch) + expose :target_branch_path, if: -> (merge_request) { merge_request.target_project } do |merge_request| + project_tree_path(merge_request.target_project, merge_request.target_branch) end expose :diff_head_sha diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb index caefa9af24a..545e4d9b7dc 100644 --- a/app/services/auto_merge/base_service.rb +++ b/app/services/auto_merge/base_service.rb @@ -63,7 +63,7 @@ module AutoMerge !merge_request.broken? && (skip_draft_check(merge_request) || !merge_request.draft?) && merge_request.mergeable_discussions_state? && - !merge_request.merge_blocked_by_other_mrs? && + (skip_blocked_check(merge_request) || !merge_request.merge_blocked_by_other_mrs?) && yield end end @@ -114,5 +114,10 @@ module AutoMerge def skip_draft_check(merge_request) false end + + # Will skip the blocked check or not when checking if strategy is available + def skip_blocked_check(merge_request) + false + end end end diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index e72762f2ae5..da25dee1e88 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,6 +1,6 @@ .container .gl-text-center.gl-pt-6.gl-pb-7 - %h2.gl-font-size-h1{ data: { qa_selector: 'welcome_title_content' } } + %h2.gl-font-size-h1{ data: { testid: 'welcome-title-content' } } = _('Welcome to GitLab') %p.gl-m-0 = _('Faster releases. Better code. Less pain.') diff --git a/app/views/layouts/component_preview.html.haml b/app/views/layouts/component_preview.html.haml index 8217ac13c52..4ef7ba04868 100644 --- a/app/views/layouts/component_preview.html.haml +++ b/app/views/layouts/component_preview.html.haml @@ -5,6 +5,12 @@ - else = stylesheet_link_tag "application_dark" = stylesheet_link_tag "application_utilities_dark" + + - if ::Feature.enabled?(:font_display_swap, current_user) + = stylesheet_link_tag_defer "fonts_swap" + - else + = stylesheet_link_tag_defer "fonts_optional" + %body .gl-mt-6{ class: (params[:lookbook][:display][:layout] == "fluid" ? "container-fluid" : "container") } - if params[:lookbook][:display][:bg_dark] diff --git a/app/workers/bulk_imports/export_request_worker.rb b/app/workers/bulk_imports/export_request_worker.rb index 1b2183e96b4..44759916f99 100644 --- a/app/workers/bulk_imports/export_request_worker.rb +++ b/app/workers/bulk_imports/export_request_worker.rb @@ -125,7 +125,7 @@ module BulkImports end def export_url - entity.export_relations_url_path + entity.export_relations_url_path(batched: Feature.enabled?(:bulk_imports_batched_import_export)) end end end diff --git a/app/workers/bulk_imports/finish_batched_pipeline_worker.rb b/app/workers/bulk_imports/finish_batched_pipeline_worker.rb index 91365d23dea..b1f3757e058 100644 --- a/app/workers/bulk_imports/finish_batched_pipeline_worker.rb +++ b/app/workers/bulk_imports/finish_batched_pipeline_worker.rb @@ -43,7 +43,7 @@ module BulkImports end def import_in_progress? - tracker.batches.any?(&:started?) + tracker.batches.any? { |b| b.started? || b.created? } end end end diff --git a/app/workers/bulk_imports/pipeline_batch_worker.rb b/app/workers/bulk_imports/pipeline_batch_worker.rb index 634d7ed3c87..6230d517641 100644 --- a/app/workers/bulk_imports/pipeline_batch_worker.rb +++ b/app/workers/bulk_imports/pipeline_batch_worker.rb @@ -14,15 +14,16 @@ module BulkImports def perform(batch_id) @batch = ::BulkImports::BatchTracker.find(batch_id) @tracker = @batch.tracker + @pending_retry = false try_obtain_lease { run } ensure - ::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id) + ::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id) unless pending_retry end private - attr_reader :batch, :tracker + attr_reader :batch, :tracker, :pending_retry def run return batch.skip! if tracker.failed? || tracker.finished? @@ -31,6 +32,7 @@ module BulkImports tracker.pipeline_class.new(context).run batch.finish! rescue BulkImports::RetryPipelineError => e + @pending_retry = true retry_batch(e) rescue StandardError => e fail_batch(e) diff --git a/config/application.rb b/config/application.rb index ad62648868f..bc4809d0c53 100644 --- a/config/application.rb +++ b/config/application.rb @@ -430,7 +430,7 @@ module Gitlab config.middleware.insert_before ActionDispatch::RemoteIp, ::Gitlab::Middleware::HandleIpSpoofAttackError - config.middleware.insert_after Rails::Rack::Logger, ::Gitlab::Middleware::HandleMalformedStrings + config.middleware.insert_after ActionDispatch::ShowExceptions, ::Gitlab::Middleware::HandleMalformedStrings config.middleware.insert_after ::Gitlab::Middleware::HandleMalformedStrings, ::Gitlab::Middleware::PathTraversalCheck diff --git a/config/feature_flags/development/content_editor_on_issues.yml b/config/feature_flags/development/bulk_imports_batched_import_export.yml index 79aaccee828..4afb715b1ee 100644 --- a/config/feature_flags/development/content_editor_on_issues.yml +++ b/config/feature_flags/development/bulk_imports_batched_import_export.yml @@ -1,8 +1,8 @@ --- -name: content_editor_on_issues -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98703 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375172 -milestone: '15.5' +name: bulk_imports_batched_import_export +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124434 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/406559 +milestone: '16.2' type: development -group: group::knowledge -default_enabled: true +group: group::import and integrate +default_enabled: false diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md index e2f88f3dfb8..3c2d43d196a 100644 --- a/doc/administration/geo/replication/troubleshooting.md +++ b/doc/administration/geo/replication/troubleshooting.md @@ -503,6 +503,46 @@ This check is also required when using a mixture of GitLab deployments. The loca ## Fixing PostgreSQL database replication errors +The following sections outline troubleshooting steps for fixing replication error messages (indicated by `Database replication working? ... no` in the +[`geo:check` output](#health-check-rake-task). +The instructions present here mostly assume a single-node Geo Linux package deployment, and might need to be adapted to different environments. + +### Removing an inactive replication slot + +Replication slots are marked as 'inactive' when the replication client (a secondary site) connected to the slot disconnects. +Inactive replication slots cause WAL files to be retained, because they are sent to the client when it reconnects and the slot becomes active once more. +If the secondary site is not able to reconnect, use the following steps to remove its corresponding inactive replication slot: + +1. [Start a PostgreSQL console session](https://docs.gitlab.com/omnibus/settings/database.html#connecting-to-the-postgresql-database) on the Geo primary site's database node: + + ```shell + sudo gitlab-psql -d gitlabhq_production + ``` + + NOTE: + Using `gitlab-rails dbconsole` does not work, because managing replication slots requires superuser permissions. + +1. View the replication slots and remove them if they are inactive: + + ```sql + SELECT * FROM pg_replication_slots; + ``` + + Slots where `active` is `f` are inactive. + + - When this slot should be active, because you have a **secondary** site configured using that slot, + look for the [PostgreSQL logs](../../logs/index.md#postgresql-logs) for the **secondary** site, + to view why the replication is not running. + - If you are no longer using the slot (for example, you no longer have Geo enabled), or the secondary site is no longer able to reconnect, + you should remove it using the PostgreSQL console session: + + ```sql + SELECT pg_drop_replication_slot('<name_of_inactive_slot>'); + ``` + +1. Follow either the steps [to remove that Geo site](remove_geo_site.md) if it's no longer required, + or [re-initiate the replication process](../setup/database.md#step-3-initiate-the-replication-process), which recreates the replication slot correctly. + ### Message: `WARNING: oldest xmin is far in the past` and `pg_wal` size growing If a replication slot is inactive, @@ -517,18 +557,7 @@ HINT: Close open transactions soon to avoid wraparound problems. You might also need to commit or roll back old prepared transactions, or drop stale replication slots. ``` -To fix this: - -1. [Connect to the primary database](https://docs.gitlab.com/omnibus/settings/database.html#connecting-to-the-bundled-postgresql-database). - -1. Run `SELECT * FROM pg_replication_slots;`. - Note the `slot_name` that reports `active` as `f` (false). - -1. Follow [the steps to remove that Geo site](remove_geo_site.md). - -The following sections outline troubleshooting steps for fixing replication -error messages (indicated by `Database replication working? ... no` in the -[`geo:check` output](#health-check-rake-task). +To fix this, you should [remove the inactive replication slot](#removing-an-inactive-replication-slot) and re-initiate the replication. ### Message: `ERROR: replication slots can only be used if max_replication_slots > 0`? @@ -568,35 +597,9 @@ the default 30 minutes. Adjust as required for your installation. ### Message: "PANIC: could not write to file `pg_xlog/xlogtemp.123`: No space left on device" Determine if you have any unused replication slots in the **primary** database. This can cause large amounts of -log data to build up in `pg_xlog`. Removing the unused slots can reduce the amount of space used in the `pg_xlog`. - -1. Start a PostgreSQL console session: +log data to build up in `pg_xlog`. - ```shell - sudo gitlab-psql - ``` - - NOTE: - Using `gitlab-rails dbconsole` does not work, because managing replication slots requires superuser permissions. - -1. View your replication slots: - - ```sql - SELECT * FROM pg_replication_slots; - ``` - -Slots where `active` is `f` are not active. - -- When this slot should be active, because you have a **secondary** site configured using that slot, - sign in on the web interface for the **secondary** site and check the [PostgreSQL logs](../../logs/index.md#postgresql-logs) - to view why the replication is not running. - -- If you are no longer using the slot (for example, you no longer have Geo enabled), you can remove it with in the - PostgreSQL console session: - - ```sql - SELECT pg_drop_replication_slot('<name_of_extra_slot>'); - ``` +[Removing the inactive slots](#removing-an-inactive-replication-slot) can reduce the amount of space used in the `pg_xlog`. ### Message: "ERROR: canceling statement due to conflict with recovery" diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index b3778e89b19..442e9638d86 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -284,10 +284,10 @@ If [`artifacts:expire_in`](../ci/yaml/index.md#artifactsexpire_in) is used to se an expiry for the artifacts, they are marked for deletion right after that date passes. Otherwise, they expire per the [default artifacts expiration setting](../administration/settings/continuous_integration.md). -Artifacts are cleaned up by the `expire_build_artifacts_worker` cron job which Sidekiq +Artifacts are deleted by the `expire_build_artifacts_worker` cron job which Sidekiq runs every 7 minutes (`*/7 * * * *` in [Cron](../topics/cron/index.md) syntax). -To change the default schedule on which the artifacts are expired: +To change the default schedule on which expired artifacts are deleted: ::Tabs diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index cdb70ca715b..724dcc2046a 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -454,3 +454,15 @@ main: == [advisory_lock_connection] object_id: 173580, pg_backend_pid: 5532 ``` The messages returned are informational and can be ignored. + +### PostgreSQL socket errors when executing the `gitlab:env:info` Rake task + +After running `sudo gitlab-rake gitlab:env:info` on Gitaly or other non-Rails nodes , you might see the following error: + +```plaintext +PG::ConnectionBad: could not connect to server: No such file or directory +Is the server running locally and accepting +connections on Unix domain socket "/var/opt/gitlab/postgresql/.s.PGSQL.5432"? +``` + +This is because, in a multi-node environment, the `gitlab:env:info` Rake task should only be executed on the nodes running **GitLab Rails**. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 7ab3415f756..6858d8f0b84 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1425,6 +1425,7 @@ Input type: `AuditEventsStreamingHeadersCreateInput` | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="mutationauditeventsstreamingheaderscreateactive"></a>`active` | [`Boolean`](#boolean) | Boolean option determining whether header is active or not. | | <a id="mutationauditeventsstreamingheaderscreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationauditeventsstreamingheaderscreatedestinationid"></a>`destinationId` | [`AuditEventsExternalAuditEventDestinationID!`](#auditeventsexternalauditeventdestinationid) | Destination to associate header with. | | <a id="mutationauditeventsstreamingheaderscreatekey"></a>`key` | [`String!`](#string) | Header key. | @@ -1486,6 +1487,7 @@ Input type: `AuditEventsStreamingInstanceHeadersCreateInput` | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="mutationauditeventsstreaminginstanceheaderscreateactive"></a>`active` | [`Boolean`](#boolean) | Boolean option determining whether header is active or not. | | <a id="mutationauditeventsstreaminginstanceheaderscreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationauditeventsstreaminginstanceheaderscreatedestinationid"></a>`destinationId` | [`AuditEventsInstanceExternalAuditEventDestinationID!`](#auditeventsinstanceexternalauditeventdestinationid) | Instance level external destination to associate header with. | | <a id="mutationauditeventsstreaminginstanceheaderscreatekey"></a>`key` | [`String!`](#string) | Header key. | @@ -24730,7 +24732,7 @@ Represents the scan result policy. | <a id="scanresultpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. | | <a id="scanresultpolicyeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. | | <a id="scanresultpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. | -| <a id="scanresultpolicygroupapprovers"></a>`groupApprovers` | [`[Group!]`](#group) | Approvers of the group type. | +| <a id="scanresultpolicygroupapprovers"></a>`groupApprovers` **{warning-solid}** | [`[Group!]`](#group) | **Deprecated** in 16.5. Use `allGroupApprovers`. | | <a id="scanresultpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. | | <a id="scanresultpolicyroleapprovers"></a>`roleApprovers` | [`[MemberAccessLevelName!]`](#memberaccesslevelname) | Approvers of the role type. Users belonging to these role(s) alone will be approvers. | | <a id="scanresultpolicysource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. | diff --git a/doc/api/integrations.md b/doc/api/integrations.md index 202513482ef..47ab7ab805c 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -483,6 +483,8 @@ Parameters: | `confidential_issue_channel` | string | false | The webhook override to receive notifications for confidential issue events. | | `confidential_note_events` | boolean | false | Enable notifications for confidential note events. | | `confidential_note_channel` | string | false | The webhook override to receive notifications for confidential note events. | +| `deployment_events` | boolean | false | Enable notifications for deployment events. | +| `deployment_channel` | string | false | The webhook override to receive notifications for deployment events. | | `issues_events` | boolean | false | Enable notifications for issue events. | | `issue_channel` | string | false | The webhook override to receive notifications for issue events. | | `merge_requests_events` | boolean | false | Enable notifications for merge request events. | diff --git a/doc/ci/components/index.md b/doc/ci/components/index.md index e0379bf783d..aeee871612f 100644 --- a/doc/ci/components/index.md +++ b/doc/ci/components/index.md @@ -34,7 +34,8 @@ If a component requires different versioning from other components, the componen To create a components repository, you must: 1. [Create a new project](../../user/project/index.md#create-a-blank-project) with a `README.md` file. -1. Create a `template.yml` file inside the project's root directory that contains the configuration you want to provide as a component. +1. Create either a single file or a templates directory according to the [directory structure](#directory-structure). + For example: ```yaml @@ -106,8 +107,8 @@ For example, the following component could be referenced with `gitlab.com/my-use #### Component configurations saved in any directory (deprecated) -NOTE: -Saving component configurations through this directory structure is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/415855). +WARNING: +Saving component configurations through this directory structure is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/415855) and should be avoided. Components configurations can be saved through the following directory structure, containing: diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md index 723a7716327..4401a7e3fb1 100644 --- a/doc/development/ai_features/index.md +++ b/doc/development/ai_features/index.md @@ -61,6 +61,7 @@ Use [this snippet](https://gitlab.com/gitlab-org/gitlab/-/snippets/2554994) for Feature.enable(:openai_experimentation) ``` +1. Ensure you have followed [the process to obtain an EE license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee-developer-licenses) for your local instance 1. Simulate the GDK to [simulate SaaS](../ee_features.md#simulate-a-saas-instance) and ensure the group you want to test has an Ultimate license 1. Enable `Experimental features` and `Third-party AI services` 1. Go to the group with the Ultimate license @@ -101,24 +102,18 @@ To populate the embedding database for GitLab chat: In order to obtain a GCP service key for local development, please follow the steps below: - Create a sandbox GCP project by visiting [this page](https://about.gitlab.com/handbook/infrastructure-standards/#individual-environment) and following the instructions, or by requesting access to our existing group GCP project by using [this template](https://gitlab.com/gitlab-com/it/infra/issue-tracker/-/issues/new?issuable_template=gcp_group_account_iam_update_request). -- Once you have access to an individual or shared GCP project, navigate to - the project by visiting - [https://gitlabsandbox.cloud](https://gitlabsandbox.cloud) and selecting the - project name. On the project page, select `Open GCP Console` -- In the GCP console, type `IAM & Admin` into the search box. Then go to `IAM & Admin` > `Service Accounts` and select `Create service account`. -- Name the service account something specific to what you're using it for. Select `Create and Continue`. Under `Grant this service account access to project`, select the role `Vertex AI User`. Select `Continue` then `Done` -- Select your new service account and `Keys` > `Add Key` > `Create new key`. Use default Key type of `JSON`. This will download the **private** JSON credentials for your service account. - If you are using an individual GCP project, you may also need to enable the Vertex AI API: 1. Go to **APIs & Services > Enabled APIs & services**. 1. Select **+ Enable APIs and Services**. 1. Search for `Vertex AI API`. 1. Select **Vertex AI API**, then select **Enable**. +- Install the [`gcloud` CLI](https://cloud.google.com/sdk/docs/install) +- Authenticate locally with GCP using the [`gcloud auth application-default login`](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) command. - Open the Rails console. Update the settings to: ```ruby # PROJECT_ID = "your-gcp-project-name" -Gitlab::CurrentSettings.update(vertex_ai_credentials: File.read('/YOUR_FILE.json')) Gitlab::CurrentSettings.update(vertex_ai_project: PROJECT_ID) ``` diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index 0ea49ff387f..0a314bccc8f 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -190,17 +190,11 @@ To archive multiple designs at once: ## Markdown and rich text editors for descriptions -<!-- When content_editor_on_issues flag is removed, move version notes - to "Add a design to an issue", update that topic, and delete this one. --> - > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/388449) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `content_editor_on_issues`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375172) in GitLab 16.2. +> - Feature flag `content_editor_on_issues` removed in GitLab 16.5. -FLAG: -On self-managed GitLab, by default the rich text editor is available. To hide it, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `content_editor_on_issues`. -On GitLab.com, this feature is available. - -When this feature is enabled, you can use the Markdown and rich text editor in design descriptions. +You can use the Markdown and rich text editor in design descriptions. It's the same editor you use for comments across GitLab. ## Reorder designs diff --git a/doc/user/project/merge_requests/reviews/suggestions.md b/doc/user/project/merge_requests/reviews/suggestions.md index 2b046399c4e..90a276dc303 100644 --- a/doc/user/project/merge_requests/reviews/suggestions.md +++ b/doc/user/project/merge_requests/reviews/suggestions.md @@ -71,10 +71,7 @@ suggestion. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/388449) in GitLab 16.1 [with a flag](../../../../administration/feature_flags.md) named `content_editor_on_issues`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375172) in GitLab 16.2. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, an administrator can [disable the feature flag](../../../../administration/feature_flags.md) named `content_editor_on_issues`. -On GitLab.com, this feature is available. +> - Feature flag `content_editor_on_issues` removed in GitLab 16.5. When you insert suggestions, you can use the WYSIWYG [rich text editor](https://about.gitlab.com/direction/plan/knowledge/content_editor/) to move diff --git a/doc/user/rich_text_editor.md b/doc/user/rich_text_editor.md index c60c89eb0de..fe3ac56b79c 100644 --- a/doc/user/rich_text_editor.md +++ b/doc/user/rich_text_editor.md @@ -12,15 +12,7 @@ type: index, reference > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382636) for [discussions](discussions/index.md), and creating and editing issues and merge requests in GitLab 15.11 with the same flag. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/407507) for epics in GitLab 16.1 with the same flag. > - Feature flag `content_editor_on_issues` enabled by default in GitLab 16.2. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, an administrator -can [disable the feature flag](../administration/feature_flags.md) named `content_editor_on_issues`. -On GitLab.com, this feature is available. - -The rich text editor is a "what you see is what you get" (WYSIWYG) editor so you can use -[GitLab Flavored Markdown](markdown.md) in descriptions and comments, even if you can't remember all -of its syntax. +> - Feature flag `content_editor_on_issues` removed in GitLab 16.5. ![Rich text editor in GitLab](img/rich_text_editor_01_v16_2.png) diff --git a/lib/api/ml/mlflow/runs.rb b/lib/api/ml/mlflow/runs.rb index 5b6afffaae1..ac052d8bff5 100644 --- a/lib/api/ml/mlflow/runs.rb +++ b/lib/api/ml/mlflow/runs.rb @@ -65,7 +65,7 @@ module API type: String, desc: 'Token for pagination' end - get 'search', urgency: :low do + post 'search', urgency: :low do params[:experiment_id] = params[:experiment_ids][0] max_results = [params[:max_results], 1000].min diff --git a/lib/gitlab/auth/ldap/auth_hash.rb b/lib/gitlab/auth/ldap/auth_hash.rb index 5435355f136..6d1d1519fc2 100644 --- a/lib/gitlab/auth/ldap/auth_hash.rb +++ b/lib/gitlab/auth/ldap/auth_hash.rb @@ -6,6 +6,8 @@ module Gitlab module Auth module Ldap class AuthHash < Gitlab::Auth::OAuth::AuthHash + extend ::Gitlab::Utils::Override + def uid @uid ||= Gitlab::Auth::Ldap::Person.normalize_dn(super) end @@ -44,6 +46,12 @@ module Gitlab def ldap_config @ldap_config ||= Gitlab::Auth::Ldap::Config.new(self.provider) end + + # Overrding this method as LDAP allows email as the username ! + override :get_username + def get_username + username_claims.map { |claim| get_from_auth_hash_or_info(claim) }.find(&:presence) + end end end end diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb index cce08750296..e5ab203044d 100644 --- a/lib/gitlab/auth/o_auth/auth_hash.rb +++ b/lib/gitlab/auth/o_auth/auth_hash.rb @@ -96,7 +96,10 @@ module Gitlab end def get_username - username_claims.map { |claim| get_from_auth_hash_or_info(claim) }.find { |name| name.presence } + username_claims.map { |claim| get_from_auth_hash_or_info(claim) } + .find { |name| name.presence } + &.split("@") + &.first end def username_and_email diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b5eaf6f7d65..9a8562f2852 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -30648,6 +30648,12 @@ msgstr "" msgid "NamespaceLimits|Exclusion added successfully" msgstr "" +msgid "NamespaceLimits|Export .csv" +msgstr "" + +msgid "NamespaceLimits|Export a csv file with Free Tier anonymized namespace storage usage statistics. This is an asynchronous operation, once the file is generated we will send it to your registered email." +msgstr "" + msgid "NamespaceLimits|Free Tier" msgstr "" @@ -30657,6 +30663,9 @@ msgstr "" msgid "NamespaceLimits|Namespace limits could not be loaded. Reload the page to try again." msgstr "" +msgid "NamespaceLimits|Namespaces Statistics" +msgstr "" + msgid "NamespaceLimits|Notifications Limit" msgstr "" diff --git a/qa/qa/page/component/badges.rb b/qa/qa/page/component/badges.rb index 9d2959c010e..8c0907d17b4 100644 --- a/qa/qa/page/component/badges.rb +++ b/qa/qa/page/component/badges.rb @@ -16,12 +16,12 @@ module QA end view 'app/assets/javascripts/badges/components/badge_list.vue' do - element :badge_list_content - element :badge_list + element 'badge-list-content' + element 'badge-list' end view 'app/assets/javascripts/badges/components/badge.vue' do - element :badge_image_link + element 'badge-image-link' end def show_badge_add_form @@ -45,14 +45,14 @@ module QA end def has_badge?(badge_name) - within_element(:badge_list_content) do - has_element?(:badge_list, text: badge_name) + within_element('badge-list-content') do + has_element?('badge-list', text: badge_name) end end def has_visible_badge_image_link?(link_url) - within_element(:badge_list_content) do - has_element?(:badge_image_link, link_url: link_url) + within_element('badge-list-content') do + has_element?('badge-image-link', link_url: link_url) end end end diff --git a/qa/qa/page/component/members/invite_members_modal.rb b/qa/qa/page/component/members/invite_members_modal.rb index ae51213b3e2..b9d0b382ba1 100644 --- a/qa/qa/page/component/members/invite_members_modal.rb +++ b/qa/qa/page/component/members/invite_members_modal.rb @@ -14,7 +14,7 @@ module QA base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do element :invite_button element :access_level_dropdown - element :invite_members_modal_content + element 'invite-modal' end base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do @@ -41,7 +41,7 @@ module QA def add_member(username, access_level = 'Developer', refresh_page: true) open_invite_members_modal - within_element(:invite_members_modal_content) do + within_element('invite-modal') do fill_element(:members_token_select_input, username) Support::WaitForRequests.wait_for_requests click_button(username, match: :prefer_exact) @@ -54,7 +54,7 @@ module QA def invite_group(group_name, access_level = 'Guest', refresh_page: true) open_invite_group_modal - within_element(:invite_members_modal_content) do + within_element('invite-modal') do click_button 'Select a group' Support::WaitForRequests.wait_for_requests diff --git a/qa/qa/page/component/members/members_table.rb b/qa/qa/page/component/members/members_table.rb index b71455373e7..8bc20da83af 100644 --- a/qa/qa/page/component/members/members_table.rb +++ b/qa/qa/page/component/members/members_table.rb @@ -39,7 +39,7 @@ module QA end base.view 'app/assets/javascripts/members/components/members_tabs.vue' do - element :groups_list_tab + element 'groups-list-tab' end base.view 'app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue' do @@ -82,7 +82,7 @@ module QA end def remove_group(group_name) - click_element :groups_list_tab + click_element 'groups-list-tab' within_element(:member_row, text: group_name) do click_element :remove_group_link_button @@ -92,7 +92,7 @@ module QA end def has_group?(group_name) - click_element :groups_list_tab + click_element 'groups-list-tab' has_element?(:member_row, text: group_name) end end diff --git a/qa/qa/page/dashboard/welcome.rb b/qa/qa/page/dashboard/welcome.rb index 6f645e168c7..a7c293a2661 100644 --- a/qa/qa/page/dashboard/welcome.rb +++ b/qa/qa/page/dashboard/welcome.rb @@ -5,11 +5,11 @@ module QA module Dashboard class Welcome < Page::Base view 'app/views/dashboard/projects/_zero_authorized_projects.html.haml' do - element :welcome_title_content + element 'welcome-title-content' end def has_welcome_title?(text) - has_element?(:welcome_title_content, text: text) + has_element?('welcome-title-content', text: text) end def self.path diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb index 1fbd9a75dc5..9e56d000070 100644 --- a/qa/qa/page/search/results.rb +++ b/qa/qa/page/search/results.rb @@ -40,7 +40,7 @@ module QA end def has_project?(project_name) - has_element?(:project_content, project_name: project_name) + has_element?('project-content', project_name: project_name) end private diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 1a9d40ae926..bc93e6caccb 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -7,18 +7,6 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie create(:merge_request, source_project: project, target_project: project, description: '# Description header') end - context 'feature flags' do - let_it_be(:project) { create(:project, :public, :repository) } - - it 'pushes content_editor_on_issues feature flag to frontend' do - stub_feature_flags(content_editor_on_issues: true) - - visit merge_request_path(merge_request) - - expect(page).to have_pushed_frontend_feature_flags(contentEditorOnIssues: true) - end - end - context 'when a merge request does not have repository' do let(:project) { create(:project, :public, :repository) } diff --git a/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js index 7942950494b..353b5fd3c47 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js @@ -10,7 +10,6 @@ describe('Ci environments dropdown', () => { const defaultProps = { areEnvironmentsLoading: false, environments: envs, - hasEnvScopeQuery: false, selectedEnvironmentScope: '', }; @@ -51,32 +50,23 @@ describe('Ci environments dropdown', () => { }); describe('Search term is empty', () => { - describe.each` - hasEnvScopeQuery | status | defaultEnvStatus | firstItemValue | envIndices - ${true} | ${'exists'} | ${'prepends'} | ${'*'} | ${[1, 2, 3]} - ${false} | ${'does not exist'} | ${'does not prepend'} | ${envs[0]} | ${[0, 1, 2]} - `( - 'when query for fetching environment scope $status', - ({ defaultEnvStatus, firstItemValue, hasEnvScopeQuery, envIndices }) => { - beforeEach(() => { - createComponent({ props: { environments: envs, hasEnvScopeQuery } }); - }); - - it(`${defaultEnvStatus} * in listbox`, () => { - expect(findListboxItemByIndex(0).text()).toBe(firstItemValue); - }); - - it('renders all environments', () => { - expect(findListboxItemByIndex(envIndices[0]).text()).toBe(envs[0]); - expect(findListboxItemByIndex(envIndices[1]).text()).toBe(envs[1]); - expect(findListboxItemByIndex(envIndices[2]).text()).toBe(envs[2]); - }); - - it('does not display active checkmark', () => { - expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true); - }); - }, - ); + beforeEach(() => { + createComponent({ props: { environments: envs } }); + }); + + it(`prepends * in listbox`, () => { + expect(findListboxItemByIndex(0).text()).toBe('*'); + }); + + it('renders all environments', () => { + expect(findListboxItemByIndex(1).text()).toBe(envs[0]); + expect(findListboxItemByIndex(2).text()).toBe(envs[1]); + expect(findListboxItemByIndex(3).text()).toBe(envs[2]); + }); + + it('does not display active checkmark', () => { + expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true); + }); }); describe('when `*` is the value of selectedEnvironmentScope props', () => { @@ -92,40 +82,13 @@ describe('Ci environments dropdown', () => { }); }); - describe('when environments are not fetched via graphql', () => { + describe('when fetching environments', () => { const currentEnv = envs[2]; beforeEach(() => { createComponent(); }); - it('filters on the frontend and renders only the environment searched for', async () => { - await findListbox().vm.$emit('search', currentEnv); - - expect(findAllListboxItems()).toHaveLength(1); - expect(findListboxItemByIndex(0).text()).toBe(currentEnv); - }); - - it('does not emit event when searching', async () => { - expect(wrapper.emitted('search-environment-scope')).toBeUndefined(); - - await findListbox().vm.$emit('search', currentEnv); - - expect(wrapper.emitted('search-environment-scope')).toBeUndefined(); - }); - - it('does not display note about max environments shown', () => { - expect(findMaxEnvNote().exists()).toBe(false); - }); - }); - - describe('when fetching environments via graphql', () => { - const currentEnv = envs[2]; - - beforeEach(() => { - createComponent({ props: { hasEnvScopeQuery: true } }); - }); - it('renders dropdown divider', () => { expect(findDropdownDivider().exists()).toBe(true); }); @@ -137,7 +100,7 @@ describe('Ci environments dropdown', () => { }); it('renders dropdown loading icon while fetch query is loading', () => { - createComponent({ props: { areEnvironmentsLoading: true, hasEnvScopeQuery: true } }); + createComponent({ props: { areEnvironmentsLoading: true } }); expect(findListbox().props('loading')).toBe(true); expect(findListbox().props('searching')).toBe(false); @@ -145,7 +108,7 @@ describe('Ci environments dropdown', () => { }); it('renders search loading icon while search query is loading and dropdown is open', async () => { - createComponent({ props: { areEnvironmentsLoading: true, hasEnvScopeQuery: true } }); + createComponent({ props: { areEnvironmentsLoading: true } }); await findListbox().vm.$emit('shown'); expect(findListbox().props('loading')).toBe(false); @@ -185,7 +148,7 @@ describe('Ci environments dropdown', () => { describe('when creating a new environment scope from a search term', () => { const searchTerm = 'new-env'; beforeEach(() => { - createComponent({ searchTerm, props: { hasEnvScopeQuery: true } }); + createComponent({ searchTerm }); }); it('sets new environment scope as the selected environment scope', async () => { diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js index 7dce23f72c0..5ba9b3b8c20 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js @@ -12,12 +12,10 @@ import { ENVIRONMENT_SCOPE_LINK_TITLE, AWS_TIP_TITLE, AWS_TIP_MESSAGE, - groupString, instanceString, - projectString, variableOptions, } from '~/ci/ci_variable_list/constants'; -import { mockEnvs, mockVariablesWithScopes, mockVariablesWithUniqueScopes } from '../mocks'; +import { mockVariablesWithScopes } from '../mocks'; import ModalStub from '../stubs'; describe('Ci variable modal', () => { @@ -46,7 +44,6 @@ describe('Ci variable modal', () => { areScopedVariablesAvailable: true, environments: [], hideEnvironmentScope: false, - hasEnvScopeQuery: false, mode: ADD_VARIABLE_ACTION, selectedVariable: {}, variables: [], @@ -352,42 +349,6 @@ describe('Ci variable modal', () => { expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink); }); - - describe('when query for envioronment scope exists', () => { - beforeEach(() => { - createComponent({ - props: { - environments: mockEnvs, - hasEnvScopeQuery: true, - variables: mockVariablesWithUniqueScopes(projectString), - }, - }); - }); - - it('does not merge environment scope sources', () => { - const expectedLength = mockEnvs.length; - - expect(findCiEnvironmentsDropdown().props('environments')).toHaveLength(expectedLength); - }); - }); - - describe('when feature flag is disabled', () => { - const mockGroupVariables = mockVariablesWithUniqueScopes(groupString); - beforeEach(() => { - createComponent({ - props: { - environments: mockEnvs, - variables: mockGroupVariables, - }, - }); - }); - - it('merges environment scope sources', () => { - const expectedLength = mockGroupVariables.length + mockEnvs.length; - - expect(findCiEnvironmentsDropdown().props('environments')).toHaveLength(expectedLength); - }); - }); }); describe('and section is hidden', () => { diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js index 79dd638e2bd..527c15ffd25 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js @@ -23,7 +23,6 @@ describe('Ci variable table', () => { environments: mapEnvironmentNames(mockEnvs), hideEnvironmentScope: false, isLoading: false, - hasEnvScopeQuery: false, maxVariableLimit: 5, pageInfo: { after: '' }, variables: mockVariablesWithScopes(projectString), @@ -70,7 +69,6 @@ describe('Ci variable table', () => { areEnvironmentsLoading: defaultProps.areEnvironmentsLoading, areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable, environments: defaultProps.environments, - hasEnvScopeQuery: defaultProps.hasEnvScopeQuery, hideEnvironmentScope: defaultProps.hideEnvironmentScope, variables: defaultProps.variables, mode: ADD_VARIABLE_ACTION, diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js index 6fa1915f3c1..c90ff4cc682 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js @@ -52,7 +52,6 @@ const mockProvide = { const defaultProps = { areScopedVariablesAvailable: true, - hasEnvScopeQuery: false, pageInfo: {}, hideEnvironmentScope: false, refetchAfterMutation: false, @@ -514,7 +513,6 @@ describe('Ci Variable Shared Component', () => { areEnvironmentsLoading: false, areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable, hideEnvironmentScope: defaultProps.hideEnvironmentScope, - hasEnvScopeQuery: props.hasEnvScopeQuery, pageInfo: defaultProps.pageInfo, isLoading: false, maxVariableLimit, diff --git a/spec/frontend/ci/ci_variable_list/mocks.js b/spec/frontend/ci/ci_variable_list/mocks.js index 41dfc0ebfda..9c9c99ad5ea 100644 --- a/spec/frontend/ci/ci_variable_list/mocks.js +++ b/spec/frontend/ci/ci_variable_list/mocks.js @@ -189,7 +189,6 @@ export const createProjectProps = () => { componentName: 'ProjectVariable', entity: 'project', fullPath: '/namespace/project/', - hasEnvScopeQuery: true, id: 'gid://gitlab/Project/20', mutationData: { [ADD_MUTATION_ACTION]: addProjectVariable, @@ -214,7 +213,6 @@ export const createGroupProps = () => { componentName: 'GroupVariable', entity: 'group', fullPath: '/my-group', - hasEnvScopeQuery: false, id: 'gid://gitlab/Group/20', mutationData: { [ADD_MUTATION_ACTION]: addGroupVariable, @@ -233,7 +231,6 @@ export const createGroupProps = () => { export const createInstanceProps = () => { return { componentName: 'InstanceVariable', - hasEnvScopeQuery: false, entity: '', mutationData: { [ADD_MUTATION_ACTION]: addAdminVariable, diff --git a/spec/frontend/ci/ci_variable_list/utils_spec.js b/spec/frontend/ci/ci_variable_list/utils_spec.js index beeae71376a..fbcf0e7c5a5 100644 --- a/spec/frontend/ci/ci_variable_list/utils_spec.js +++ b/spec/frontend/ci/ci_variable_list/utils_spec.js @@ -1,58 +1,7 @@ -import { - createJoinedEnvironments, - convertEnvironmentScope, - mapEnvironmentNames, -} from '~/ci/ci_variable_list/utils'; +import { convertEnvironmentScope, mapEnvironmentNames } from '~/ci/ci_variable_list/utils'; import { allEnvironments } from '~/ci/ci_variable_list/constants'; describe('utils', () => { - const environments = ['dev', 'prod']; - const newEnvironments = ['staging']; - - describe('createJoinedEnvironments', () => { - it('returns only `environments` if `variables` argument is undefined', () => { - const variables = undefined; - - expect(createJoinedEnvironments(variables, environments, [])).toEqual(environments); - }); - - it('returns a list of environments and environment scopes taken from variables in alphabetical order', () => { - const envScope1 = 'new1'; - const envScope2 = 'new2'; - - const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }]; - - expect(createJoinedEnvironments(variables, environments, [])).toEqual([ - environments[0], - envScope1, - envScope2, - environments[1], - ]); - }); - - it('returns combined list with new environments included', () => { - const variables = undefined; - - expect(createJoinedEnvironments(variables, environments, newEnvironments)).toEqual([ - ...environments, - ...newEnvironments, - ]); - }); - - it('removes duplicate environments', () => { - const envScope1 = environments[0]; - const envScope2 = 'new2'; - - const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }]; - - expect(createJoinedEnvironments(variables, environments, [])).toEqual([ - environments[0], - envScope2, - environments[1], - ]); - }); - }); - describe('convertEnvironmentScope', () => { it('converts the * to the `All environments` text', () => { expect(convertEnvironmentScope('*')).toBe(allEnvironments.text); diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 3eb00f69345..548c6030ed7 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -206,6 +206,14 @@ describe('markdownSerializer', () => { ); }); + it('correctly serializes a malformed URL-encoded link', () => { + expect( + serialize( + paragraph(link({ href: 'https://example.com/%E0%A4%A' }, 'https://example.com/%E0%A4%A')), + ), + ).toBe('https://example.com/%E0%A4%A'); + }); + it('correctly serializes a link with a title', () => { expect( serialize( diff --git a/spec/frontend/design_management/components/design_description/description_form_spec.js b/spec/frontend/design_management/components/design_description/description_form_spec.js index f7feff98da3..7d68a3b80d5 100644 --- a/spec/frontend/design_management/components/design_description/description_form_spec.js +++ b/spec/frontend/design_management/components/design_description/description_form_spec.js @@ -42,7 +42,6 @@ describe('Design description form', () => { showEditor = false, isSubmitting = false, designVariables = mockDesignVariables, - contentEditorOnIssues = false, designUpdateMutationHandler = mockDesignUpdateMutationHandler, } = {}) => { mockApollo = createMockApollo([[updateDesignDescriptionMutation, designUpdateMutationHandler]]); @@ -52,11 +51,6 @@ describe('Design description form', () => { markdownPreviewPath: '/gitlab-org/gitlab-test/preview_markdown?target_type=Issue', designVariables, }, - provide: { - glFeatures: { - contentEditorOnIssues, - }, - }, apolloProvider: mockApollo, data() { return { @@ -131,7 +125,7 @@ describe('Design description form', () => { expect(findMarkdownEditor().props()).toMatchObject({ value: 'Test description', renderMarkdownPath: '/gitlab-org/gitlab-test/preview_markdown?target_type=Issue', - enableContentEditor: false, + enableContentEditor: true, formFieldProps, autofocus: true, enableAutocomplete: true, diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 93860aaa925..25e89db7957 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -69,8 +69,8 @@ describe('Description component', () => { wrapper = shallowMountExtended(Description, { apolloProvider: mockApollo, propsData: { - issueId: 1, - issueIid: 1, + issueId: '1', + issueIid: '1', ...initialProps, ...props, }, diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js index 83b927d3699..e1d2809be9d 100644 --- a/spec/frontend/issues/show/components/fields/description_spec.js +++ b/spec/frontend/issues/show/components/fields/description_spec.js @@ -10,7 +10,7 @@ describe('Description field component', () => { let trackingSpy; const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor); - const mountComponent = ({ description = 'test', contentEditorOnIssues = false } = {}) => { + const mountComponent = ({ description = 'test' } = {}) => { wrapper = shallowMount(DescriptionField, { attachTo: document.body, propsData: { @@ -18,11 +18,6 @@ describe('Description field component', () => { markdownDocsPath: '/', value: description, }, - provide: { - glFeatures: { - contentEditorOnIssues, - }, - }, stubs: { MarkdownField, }, @@ -33,15 +28,7 @@ describe('Description field component', () => { trackingSpy = mockTracking(undefined, null, jest.spyOn); jest.spyOn(eventHub, '$emit'); - mountComponent({ contentEditorOnIssues: true }); - }); - - it('passes feature flag to the MarkdownEditorComponent', () => { - expect(findMarkdownEditor().props('enableContentEditor')).toBe(true); - - mountComponent({ contentEditorOnIssues: false }); - - expect(findMarkdownEditor().props('enableContentEditor')).toBe(false); + mountComponent(); }); it('uses the MarkdownEditor component to edit markdown', () => { diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index 4ce4ca92440..1309fd79c14 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -308,15 +308,8 @@ describe('issue_comment_form component', () => { }); }); - it('hides content editor switcher if feature flag content_editor_on_issues is off', () => { - mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: false } }); - - expect(wrapper.text()).not.toContain('Switch to rich text editing'); - }); - - it('shows content editor switcher if feature flag content_editor_on_issues is on', () => { - mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: true } }); - + it('shows content editor switcher', () => { + mountComponent({ mountFunction: mount }); expect(wrapper.text()).toContain('Switch to rich text editing'); }); diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index 83779f191f3..e2072ebd04d 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -76,14 +76,8 @@ describe('issue_note_form component', () => { }); }); - it('hides content editor switcher if feature flag content_editor_on_issues is off', () => { - createComponentWrapper({}, { contentEditorOnIssues: false }); - - expect(wrapper.text()).not.toContain('Switch to rich text editing'); - }); - - it('shows content editor switcher if feature flag content_editor_on_issues is on', () => { - createComponentWrapper({}, { contentEditorOnIssues: true }); + it('shows content editor switcher', () => { + createComponentWrapper(); expect(wrapper.text()).toContain('Switch to rich text editing'); }); diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb index 8c50b2acac6..9382f0f0cd6 100644 --- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb @@ -90,6 +90,22 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management end end + context 'when username claim is in email format' do + let(:info_hash) do + { + email: nil, + name: 'GitLab test', + nickname: 'GitLab@gitlabsandbox.onmicrosoft.com', + uid: uid_ascii + } + end + + it 'creates proper email and username fields' do + expect(auth_hash.username).to eql 'GitLab' + expect(auth_hash.email).to eql 'temp-email-for-oauth-GitLab@gitlab.localhost' + end + end + context 'name not provided' do before do info_hash.delete(:name) diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb index 90b462ea515..3e98ba0973e 100644 --- a/spec/models/bulk_imports/entity_spec.rb +++ b/spec/models/bulk_imports/entity_spec.rb @@ -271,7 +271,7 @@ RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers d import = build(:bulk_import, source_version: '16.2.0') entity = build(:bulk_import_entity, :project_entity, bulk_import: import) - expect(entity.export_relations_url_path) + expect(entity.export_relations_url_path(batched: true)) .to eq("/projects/#{entity.source_xid}/export_relations?batched=true") end end @@ -280,7 +280,7 @@ RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers d it 'returns export relations url' do entity = build(:bulk_import_entity) - expect(entity.export_relations_url_path) + expect(entity.export_relations_url_path(batched: true)) .to eq("/groups/#{entity.source_xid}/export_relations") end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index cd40a9f9c7d..96ef36a5b75 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -3456,13 +3456,6 @@ RSpec.describe Group, feature_category: :groups_and_projects do end end - describe '#content_editor_on_issues_feature_flag_enabled?' do - it_behaves_like 'checks self and root ancestor feature flag' do - let(:feature_flag) { :content_editor_on_issues } - let(:feature_flag_method) { :content_editor_on_issues_feature_flag_enabled? } - end - end - describe '#work_items_feature_flag_enabled?' do it_behaves_like 'checks self and root ancestor feature flag' do let(:feature_flag) { :work_items } diff --git a/spec/models/integrations/discord_spec.rb b/spec/models/integrations/discord_spec.rb index 76b5e89d04f..bff4529211f 100644 --- a/spec/models/integrations/discord_spec.rb +++ b/spec/models/integrations/discord_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe Integrations::Discord, feature_category: :integrations do - it_behaves_like "chat integration", "Discord notifications" do + it_behaves_like "chat integration", "Discord notifications", supports_deployments: true do let(:client) { Discordrb::Webhooks::Client } let(:client_arguments) { { url: webhook_url } } let(:payload) do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 23306e46237..2126e6a4cc2 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -8795,16 +8795,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr end end - describe '#content_editor_on_issues_feature_flag_enabled?' do - let_it_be(:group_project) { create(:project, :in_subgroup) } - - it_behaves_like 'checks parent group feature flag' do - let(:feature_flag_method) { :content_editor_on_issues_feature_flag_enabled? } - let(:feature_flag) { :content_editor_on_issues } - let(:subject_project) { group_project } - end - end - describe '#work_items_mvc_feature_flag_enabled?' do let_it_be(:group_project) { create(:project, :in_subgroup) } diff --git a/spec/requests/api/ml/mlflow/runs_spec.rb b/spec/requests/api/ml/mlflow/runs_spec.rb index af04c387830..75b70dd867a 100644 --- a/spec/requests/api/ml/mlflow/runs_spec.rb +++ b/spec/requests/api/ml/mlflow/runs_spec.rb @@ -185,7 +185,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end end - describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/runs/search' do + describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/runs/search' do let_it_be(:search_experiment) { create(:ml_experiments, user: nil, project: project) } let_it_be(:first_candidate) do create(:ml_candidates, experiment: search_experiment, name: 'c', user: nil).tap do |c| @@ -215,6 +215,8 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do } end + let(:request) { post api(route), params: params, headers: headers } + it 'searches runs for a project', :aggregate_failures do is_expected.to have_gitlab_http_status(:ok) is_expected.to match_response_schema('ml/search_runs') @@ -231,7 +233,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do params = default_params.merge(page_token: json_response['next_page_token']) - get api(route), params: params, headers: headers + post api(route), params: params, headers: headers second_page_response = Gitlab::Json.parse(response.body) second_page_runs = second_page_response['runs'] diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb index ba4f49c8c52..325a340a8ae 100644 --- a/spec/services/auto_merge/base_service_spec.rb +++ b/spec/services/auto_merge/base_service_spec.rb @@ -309,21 +309,23 @@ RSpec.describe AutoMerge::BaseService, feature_category: :code_review_workflow d let(:merge_request) { create(:merge_request) } - where(:can_be_merged, :open, :broken, :discussions, :blocked, :draft, :skip_draft, :result) do - true | true | false | true | false | false | false | true - true | true | false | true | false | false | true | true - true | true | false | true | false | true | true | true - true | true | false | true | false | true | false | false - false | true | false | true | false | false | false | false - true | false | false | true | false | false | false | false - true | true | true | true | false | false | false | false - true | true | false | false | false | false | false | false - true | true | false | true | true | false | false | false + where(:can_be_merged, :open, :broken, :discussions, :blocked, :draft, :skip_draft, :skip_blocked, :result) do + true | true | false | true | false | false | false | false | true + true | true | false | true | false | false | true | true | true + true | true | false | true | false | true | true | false | true + true | true | false | true | true | false | false | true | true + true | true | false | true | false | true | false | false | false + false | true | false | true | false | false | false | false | false + true | false | false | true | false | false | false | false | false + true | true | true | true | false | false | false | false | false + true | true | false | false | false | false | false | false | false + true | true | false | true | true | false | false | false | false end with_them do before do allow(service).to receive(:skip_draft_check).and_return(skip_draft) + allow(service).to receive(:skip_blocked_check).and_return(skip_blocked) allow(merge_request).to receive(:can_be_merged_by?).and_return(can_be_merged) allow(merge_request).to receive(:open?).and_return(open) allow(merge_request).to receive(:broken?).and_return(broken) diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 0ce54fbc31f..27b9ca901ef 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples "chat integration" do |integration_name| +RSpec.shared_examples "chat integration" do |integration_name, supports_deployments: false| describe "Associations" do it { is_expected.to belong_to :project } end @@ -26,8 +26,14 @@ RSpec.shared_examples "chat integration" do |integration_name| end describe '.supported_events' do - it 'does not support deployment_events' do - expect(described_class.supported_events).not_to include('deployment') + if supports_deployments + it 'supports deployment_events' do + expect(described_class.supported_events).to include('deployment') + end + else + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end end end @@ -375,7 +381,11 @@ RSpec.shared_examples "chat integration" do |integration_name| let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) } - it_behaves_like "untriggered #{integration_name} integration" + if supports_deployments + it_behaves_like "triggered #{integration_name} integration" + else + it_behaves_like "untriggered #{integration_name} integration" + end end end end diff --git a/spec/workers/bulk_imports/export_request_worker_spec.rb b/spec/workers/bulk_imports/export_request_worker_spec.rb index 7be25261cdc..0acc44c5cbf 100644 --- a/spec/workers/bulk_imports/export_request_worker_spec.rb +++ b/spec/workers/bulk_imports/export_request_worker_spec.rb @@ -127,6 +127,20 @@ RSpec.describe BulkImports::ExportRequestWorker, feature_category: :importers do described_class.new.perform(entity.id) end + + context 'when bulk_imports_batched_import_export feature flag is disabled' do + it 'requests relation export without batched param' do + stub_feature_flags(bulk_imports_batched_import_export: false) + + expected_url = "/projects/#{entity.source_xid}/export_relations" + + expect_next_instance_of(BulkImports::Clients::HTTP) do |client| + expect(client).to receive(:post).with(expected_url) + end + + described_class.new.perform(entity.id) + end + end end end diff --git a/spec/workers/bulk_imports/finish_batched_pipeline_worker_spec.rb b/spec/workers/bulk_imports/finish_batched_pipeline_worker_spec.rb index 5454aeb02b5..5beb11c64aa 100644 --- a/spec/workers/bulk_imports/finish_batched_pipeline_worker_spec.rb +++ b/spec/workers/bulk_imports/finish_batched_pipeline_worker_spec.rb @@ -39,7 +39,7 @@ RSpec.describe BulkImports::FinishBatchedPipelineWorker, feature_category: :impo end context 'when import is in progress' do - it 're-enqueues' do + it 're-enqueues for any started batches' do create(:bulk_import_batch_tracker, :started, tracker: pipeline_tracker) expect(described_class) @@ -48,6 +48,16 @@ RSpec.describe BulkImports::FinishBatchedPipelineWorker, feature_category: :impo subject.perform(pipeline_tracker.id) end + + it 're-enqueues for any created batches' do + create(:bulk_import_batch_tracker, :created, tracker: pipeline_tracker) + + expect(described_class) + .to receive(:perform_in) + .with(described_class::REQUEUE_DELAY, pipeline_tracker.id) + + subject.perform(pipeline_tracker.id) + end end context 'when pipeline tracker is stale' do diff --git a/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb b/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb index 3c33910b62c..78ce52c41b4 100644 --- a/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb +++ b/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb @@ -102,6 +102,7 @@ RSpec.describe BulkImports::PipelineBatchWorker, feature_category: :importers do end expect(described_class).to receive(:perform_in).with(60, batch.id) + expect(BulkImports::FinishBatchedPipelineWorker).not_to receive(:perform_async).with(tracker.id) subject.perform(batch.id) |