Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml36
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml20
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/report_actions.vue1
-rw-r--r--app/assets/javascripts/batch_comments/mixins/resolved_status.js7
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js5
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js6
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags_table.vue2
-rw-r--r--app/assets/javascripts/header.js75
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue10
-rw-r--r--app/assets/javascripts/set_status_modal/constants.js2
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue18
-rw-r--r--app/assets/javascripts/super_sidebar/components/scroll_scrim.vue72
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_menu.vue2
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue8
-rw-r--r--app/assets/javascripts/super_sidebar/components/user_menu.vue49
-rw-r--r--app/assets/javascripts/super_sidebar/super_sidebar_bundle.js3
-rw-r--r--app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue8
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/job_log.scss47
-rw-r--r--app/assets/stylesheets/framework/super_sidebar.scss54
-rw-r--r--app/assets/stylesheets/page_bundles/build.scss48
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/services/projects/import_service.rb7
-rw-r--r--config/application.rb4
-rw-r--r--config/feature_flags/development/custom_roles_ui_saas.yml8
-rw-r--r--config/feature_flags/ops/global_search_epics_tab.yml2
-rw-r--r--danger/saas_feature/Dangerfile6
-rw-r--r--db/docs/batched_background_migrations/backfill_has_remediations_of_vulnerability_reads.yml6
-rw-r--r--db/post_migrate/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads.rb24
-rw-r--r--db/post_migrate/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads.rb32
-rw-r--r--db/schema_migrations/202310312048411
-rw-r--r--doc/ci/pipelines/merge_trains.md10
-rw-r--r--doc/ci/variables/predefined_variables.md1
-rw-r--r--doc/development/data_science/index.md2
-rw-r--r--doc/development/secure_coding_guidelines.md36
-rw-r--r--jest.config.snapshots.js46
-rw-r--r--lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads.rb6
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/base_input.rb3
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb4
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/number_input.rb4
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/string_input.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb33
-rw-r--r--lib/initializer_connections.rb14
-rw-r--r--lib/tasks/gitlab/db/validate_config.rake6
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json1
-rw-r--r--scripts/review_apps/base-config.yaml1
-rwxr-xr-xscripts/trigger-build.rb27
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb14
-rw-r--r--spec/frontend/boards/components/issue_board_filtered_search_spec.js3
-rw-r--r--spec/frontend/ci/job_details/components/manual_variables_form_spec.js7
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js12
-rw-r--r--spec/frontend/shortcuts_spec.js1
-rw-r--r--spec/frontend/super_sidebar/components/scroll_scrim_spec.js60
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js4
-rw-r--r--spec/frontend/super_sidebar/components/user_menu_spec.js143
-rw-r--r--spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js37
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb4
-rw-r--r--spec/lib/gitlab/database/schema_migrations/context_spec.rb5
-rw-r--r--spec/lib/gitlab/database/transaction/observer_spec.rb2
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb23
-rw-r--r--spec/migrations/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads_spec.rb (renamed from spec/migrations/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads_spec.rb)2
-rw-r--r--spec/scripts/trigger-build_spec.rb60
-rw-r--r--spec/services/projects/import_service_spec.rb78
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb2
69 files changed, 860 insertions, 409 deletions
diff --git a/.gitignore b/.gitignore
index 4eb7ce8303d..b6f5a8f514a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -107,6 +107,9 @@ tags.lock
tags.temp
.stylelintcache
.solargraph.yml
+jest-snapshot-test-match.json
+jest-test-report.json
+jest-snapshot-test-report.json
# Vite Ruby
/public/vite*
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 2afa69bbff8..278ee26d48d 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -306,6 +306,42 @@ jest-integration:
- run_timed_command "yarn jest:integration --ci"
needs: ["rspec-all frontend_fixture", "graphql-schema-dump"]
+jest-snapshot-vue3:
+ extends:
+ - .jest-base
+ - .frontend:rules:jest-snapshot
+ needs: ["rspec-all frontend_fixture"]
+ variables:
+ VUE_VERSION: 3
+ JEST_REPORT: jest-test-report.json
+ SNAPSHOT_TEST_REPORT: jest-snapshot-test-report.json
+ script:
+ - |
+ yarn jest:snapshots --ci --json --outputFile="${JEST_REPORT}" || echo 'Proceed to parsing test report...'
+ echo $(ruby -rjson -e 'puts JSON.generate(JSON.parse(File.read(ENV["JEST_REPORT"])).dig("snapshot"))') > "${SNAPSHOT_TEST_REPORT}"
+
+ echo " ============= snapshot test report start =============="
+ cat "${SNAPSHOT_TEST_REPORT}"
+ echo " ============= snapshot test report end ================"
+
+ snapshot_test_failed=$(ruby -rjson -e 'puts JSON.parse(File.read(ENV["SNAPSHOT_TEST_REPORT"])).dig("failure")')
+ if [[ "${snapshot_test_failed}" == "true" ]]
+ then
+ echo "You have failed snapshot tests! Exiting 1..."
+ exit 1
+ else
+ echo 'All snapshot tests passed! Exiting 0...'
+ exit 0
+ fi
+
+ artifacts:
+ name: snapshot_tests
+ expire_in: 31d
+ when: always
+ paths:
+ - jest-snapshot-test-match.json
+ - jest-snapshot-test-report.json
+
coverage-frontend:
extends:
- .default-retry
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 6a5bade71f4..ad315df370c 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1390,6 +1390,26 @@
changes: *frontend-build-patterns
allow_failure: true
+.frontend:rules:jest-snapshot:
+ rules:
+ - <<: *if-merge-request-labels-pipeline-expedite
+ when: never
+ - <<: *if-fork-merge-request
+ when: never
+ - <<: *if-merge-request-labels-run-all-jest
+ when: manual
+ allow_failure: true
+ - <<: *if-merge-request-labels-frontend-and-feature-flag
+ when: manual
+ allow_failure: true
+ - <<: *if-merge-request
+ changes: *frontend-dependency-patterns
+ when: manual
+ allow_failure: true
+ - <<: *if-merge-request
+ changes: [".gitlab/ci/rules.gitlab-ci.yml", ".gitlab/ci/frontend.gitlab-ci.yml"]
+ allow_failure: true
+
################
# Memory rules #
################
diff --git a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue
index e005e183c9f..3e9cc36b8b2 100644
--- a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue
@@ -64,7 +64,6 @@ export default {
},
computed: {
getDrawerHeaderHeight() {
- if (!this.showActionsDrawer || gon.use_new_navigation) return '0';
return getContentWrapperHeight();
},
isFormValid() {
diff --git a/app/assets/javascripts/batch_comments/mixins/resolved_status.js b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
index fddb843bb52..da7c7809bed 100644
--- a/app/assets/javascripts/batch_comments/mixins/resolved_status.js
+++ b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
@@ -25,11 +25,10 @@ export default {
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
- this.draft ? this.draft.discussion_id : this.discussionId,
+ 'draft' in this ? this.draft.discussion_id : this.discussionId,
);
- const discussionToBeResolved = this.draft
- ? this.draft.resolve_discussion
- : this.resolveDiscussion;
+ const discussionToBeResolved =
+ 'draft' in this ? this.draft.resolve_discussion : this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index 941662635ea..15229689306 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -538,13 +538,10 @@ const GLOBAL_SHORTCUTS_GROUP = {
GO_TO_YOUR_TODO_LIST,
TOGGLE_PERFORMANCE_BAR,
HIDE_APPEARING_CONTENT,
+ TOGGLE_SUPER_SIDEBAR,
],
};
-if (gon.use_new_navigation) {
- GLOBAL_SHORTCUTS_GROUP.keybindings.push(TOGGLE_SUPER_SIDEBAR);
-}
-
export const EDITING_SHORTCUTS_GROUP = {
id: 'editing',
name: __('Editing'),
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 9514ad853b0..1d6819d4b04 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -198,11 +198,7 @@ export default class Shortcuts {
}
static focusSearch(e) {
- if (gon.use_new_navigation) {
- document.querySelector('#super-sidebar-search')?.click();
- } else {
- document.querySelector('#search')?.focus();
- }
+ document.querySelector('#super-sidebar-search')?.click();
if (e.preventDefault) {
e.preventDefault();
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
index 257c482cf1d..b952e0059bb 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
@@ -67,7 +67,7 @@ export default {
return featureFlag.iid ? `^${featureFlag.iid}` : '';
},
canDeleteFlag(flag) {
- return !this.permissions || (flag.scopes || []).every((scope) => scope.can_update);
+ return (flag.scopes || []).every((scope) => scope.can_update);
},
setDeleteModalData(featureFlag) {
this.deleteFeatureFlagUrl = featureFlag.destroy_path;
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 7a729cc58c3..8f3681952c6 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,10 +1,8 @@
// TODO: Remove this with the removal of the old navigation.
// See https://gitlab.com/groups/gitlab-org/-/epics/11875.
-import Vue from 'vue';
import { highCountTrim } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
-import Translate from '~/vue_shared/translate';
/**
* Updates todo counter when todos are toggled.
@@ -29,76 +27,6 @@ export default function initTodoToggle() {
});
}
-export function initStatusTriggers() {
- const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger');
-
- if (setStatusModalTriggerEl) {
- setStatusModalTriggerEl.addEventListener('click', () => {
- const topNavbar = document.querySelector('.navbar-gitlab');
- const buttonWithinTopNav = topNavbar && topNavbar.contains(setStatusModalTriggerEl);
- Tracking.event(undefined, 'click_button', {
- label: 'user_edit_status',
- property: buttonWithinTopNav ? 'navigation_top' : 'nav_user_menu',
- });
-
- import(
- /* webpackChunkName: 'statusModalBundle' */ './set_status_modal/set_status_modal_wrapper.vue'
- )
- .then(({ default: SetStatusModalWrapper }) => {
- const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
- const statusModalElement = document.createElement('div');
- setStatusModalWrapperEl.appendChild(statusModalElement);
-
- Vue.use(Translate);
-
- // eslint-disable-next-line no-new
- new Vue({
- el: statusModalElement,
- data() {
- const {
- currentEmoji,
- defaultEmoji,
- currentMessage,
- currentAvailability,
- currentClearStatusAfter,
- } = setStatusModalWrapperEl.dataset;
-
- return {
- currentEmoji,
- defaultEmoji,
- currentMessage,
- currentAvailability,
- currentClearStatusAfter,
- };
- },
- render(createElement) {
- const {
- currentEmoji,
- defaultEmoji,
- currentMessage,
- currentAvailability,
- currentClearStatusAfter,
- } = this;
-
- return createElement(SetStatusModalWrapper, {
- props: {
- currentEmoji,
- defaultEmoji,
- currentMessage,
- currentAvailability,
- currentClearStatusAfter,
- },
- });
- },
- });
- })
- .catch(() => {});
- });
-
- setStatusModalTriggerEl.classList.add('ready');
- }
-}
-
function trackShowUserDropdownLink(trackEvent, elToTrack, el) {
const { trackLabel, trackProperty } = elToTrack.dataset;
@@ -119,7 +47,4 @@ export function initNavUserDropdownTracking() {
}
}
-if (!gon?.use_new_navigation) {
- requestIdleCallback(initStatusTriggers);
-}
requestIdleCallback(initNavUserDropdownTracking);
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index eaa99556994..729f248ca2b 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -187,6 +187,11 @@ export default {
return typeof this.drawioUrl === 'string' && this.drawioUrl.length > 0;
},
},
+ watch: {
+ title() {
+ this.updateCommitMessage();
+ },
+ },
mounted() {
if (!this.commitMessage) this.updateCommitMessage();
@@ -321,7 +326,6 @@ export default {
:required="true"
:autofocus="!pageInfo.persisted"
:placeholder="$options.i18n.title.placeholder"
- @input="updateCommitMessage"
/>
</gl-form-group>
</div>
@@ -361,8 +365,8 @@ export default {
:drawio-enabled="drawioEnabled"
@contentEditor="notifyContentEditorActive"
@markdownField="notifyContentEditorInactive"
- @keydown.ctrl.enter="submitFormShortcut"
- @keydown.meta.enter="submitFormShortcut"
+ @keydown.ctrl.enter="submitFormWithShortcut"
+ @keydown.meta.enter="submitFormWithShortcut"
/>
<div class="form-text gl-text-gray-600">
<gl-sprintf
diff --git a/app/assets/javascripts/set_status_modal/constants.js b/app/assets/javascripts/set_status_modal/constants.js
index 53e64db1497..86e24c29775 100644
--- a/app/assets/javascripts/set_status_modal/constants.js
+++ b/app/assets/javascripts/set_status_modal/constants.js
@@ -12,3 +12,5 @@ export const AVAILABILITY_STATUS = {
BUSY: 'busy',
NOT_SET: 'not_set',
};
+
+export const SET_STATUS_MODAL_ID = 'set-user-status-modal';
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 270d7f0d182..7f229e5c5ed 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -2,17 +2,18 @@
import { GlToast, GlTooltipDirective, GlModal } from '@gitlab/ui';
import Vue from 'vue';
import { createAlert } from '~/alert';
-import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
+import { BV_HIDE_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import { updateUserStatus } from '~/rest_api';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isUserBusy, computedClearStatusAfterValue } from './utils';
-import { AVAILABILITY_STATUS } from './constants';
+import { AVAILABILITY_STATUS, SET_STATUS_MODAL_ID } from './constants';
import SetStatusForm from './set_status_form.vue';
Vue.use(GlToast);
export default {
+ SET_STATUS_MODAL_ID,
components: {
GlModal,
SetStatusForm,
@@ -29,11 +30,13 @@ export default {
},
currentEmoji: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
currentMessage: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
currentAvailability: {
type: String,
@@ -51,7 +54,6 @@ export default {
defaultEmojiTag: '',
emoji: this.currentEmoji,
message: this.currentMessage,
- modalId: 'set-user-status-modal',
availability: isUserBusy(this.currentAvailability),
clearStatusAfter: null,
};
@@ -65,11 +67,11 @@ export default {
},
},
mounted() {
- this.$root.$emit(BV_SHOW_MODAL, this.modalId);
+ this.$emit('mounted');
},
methods: {
closeModal() {
- this.$root.$emit(BV_HIDE_MODAL, this.modalId);
+ this.$root.$emit(BV_HIDE_MODAL, SET_STATUS_MODAL_ID);
},
removeStatus() {
this.availability = false;
@@ -132,7 +134,7 @@ export default {
<template>
<gl-modal
:title="s__('SetStatusModal|Set a status')"
- :modal-id="modalId"
+ :modal-id="$options.SET_STATUS_MODAL_ID"
:action-primary="$options.actionPrimary"
:action-secondary="$options.actionSecondary"
modal-class="set-user-status-modal"
diff --git a/app/assets/javascripts/super_sidebar/components/scroll_scrim.vue b/app/assets/javascripts/super_sidebar/components/scroll_scrim.vue
new file mode 100644
index 00000000000..0e849b08a39
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/scroll_scrim.vue
@@ -0,0 +1,72 @@
+<script>
+export default {
+ name: 'ScrollScrim',
+ data() {
+ return {
+ topBoundaryVisible: true,
+ bottomBoundaryVisible: true,
+ };
+ },
+ computed: {
+ scrimClasses() {
+ return {
+ 'top-scrim-visible': !this.topBoundaryVisible,
+ 'bottom-scrim-visible gl-border-b': !this.bottomBoundaryVisible,
+ };
+ },
+ },
+ mounted() {
+ this.observeScroll();
+ },
+ beforeDestroy() {
+ this.scrollObserver?.disconnect();
+ },
+
+ methods: {
+ observeScroll() {
+ const root = this.$el;
+
+ const options = {
+ rootMargin: '8px',
+ root,
+ threshold: 1.0,
+ };
+
+ this.scrollObserver?.disconnect();
+
+ const observer = new IntersectionObserver((entries) => {
+ entries.forEach((entry) => {
+ this[entry.target?.$__visibilityProp] = entry.isIntersecting;
+ });
+ }, options);
+
+ const topBoundary = this.$refs['top-boundary'];
+ const bottomBoundary = this.$refs['bottom-boundary'];
+
+ topBoundary.$__visibilityProp = 'topBoundaryVisible';
+ observer.observe(topBoundary);
+
+ bottomBoundary.$__visibilityProp = 'bottomBoundaryVisible';
+ observer.observe(bottomBoundary);
+
+ this.scrollObserver = observer;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-scroll-scrim gl-overflow-auto" :class="scrimClasses">
+ <div class="top-scrim-wrapper">
+ <div class="top-scrim"></div>
+ </div>
+ <div ref="top-boundary"></div>
+
+ <slot></slot>
+
+ <div ref="bottom-boundary"></div>
+ <div class="bottom-scrim-wrapper">
+ <div class="bottom-scrim"></div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
index c04addf5262..5f067621814 100644
--- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
@@ -194,7 +194,7 @@ export default {
/>
<ul
aria-labelledby="super-sidebar-context-header"
- class="gl-p-0 gl-list-style-none"
+ class="gl-p-0 gl-mb-0 gl-list-style-none"
data-testid="non-static-items-section"
>
<template v-for="item in nonStaticItems">
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index 8f95dd1dcec..ebdc0026539 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -20,6 +20,7 @@ import HelpCenter from './help_center.vue';
import SidebarMenu from './sidebar_menu.vue';
import SidebarPeekBehavior from './sidebar_peek_behavior.vue';
import SidebarHoverPeekBehavior from './sidebar_hover_peek_behavior.vue';
+import ScrollScrim from './scroll_scrim.vue';
export default {
components: {
@@ -30,6 +31,7 @@ export default {
SidebarPeekBehavior,
SidebarHoverPeekBehavior,
SidebarPortalTarget,
+ ScrollScrim,
TrialStatusWidget: () =>
import('ee_component/contextual_sidebar/components/trial_status_widget.vue'),
TrialStatusPopover: () =>
@@ -202,7 +204,7 @@ export default {
<div
class="contextual-nav gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-overflow-hidden"
>
- <div class="gl-flex-grow-1 gl-overflow-auto" data-testid="nav-container">
+ <scroll-scrim class="gl-flex-grow-1" data-testid="nav-container">
<div
id="super-sidebar-context-header"
class="gl-px-5 gl-pt-3 gl-pb-2 gl-m-0 gl-reset-line-height gl-font-weight-bold gl-font-sm super-sidebar-context-header"
@@ -218,8 +220,8 @@ export default {
:update-pins-url="sidebarData.update_pins_url"
/>
<sidebar-portal-target />
- </div>
- <div class="gl-p-3">
+ </scroll-scrim>
+ <div class="gl-p-2">
<help-center ref="helpCenter" :sidebar-data="sidebarData" />
<gl-button
v-if="sidebarData.is_admin"
diff --git a/app/assets/javascripts/super_sidebar/components/user_menu.vue b/app/assets/javascripts/super_sidebar/components/user_menu.vue
index ef4ba8d9056..2823aeaee7b 100644
--- a/app/assets/javascripts/super_sidebar/components/user_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/user_menu.vue
@@ -5,11 +5,13 @@ import {
GlDisclosureDropdownGroup,
GlDisclosureDropdownItem,
GlButton,
+ GlModalDirective,
} from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, __, sprintf } from '~/locale';
import Tracking from '~/tracking';
import PersistentUserCallout from '~/persistent_user_callout';
+import { SET_STATUS_MODAL_ID } from '~/set_status_modal/constants';
import { USER_MENU_TRACKING_DEFAULTS, DROPDOWN_Y_OFFSET, IMPERSONATING_OFFSET } from '../constants';
import UserMenuProfileItem from './user_menu_profile_item.vue';
@@ -18,6 +20,7 @@ const DROPDOWN_X_OFFSET_BASE = -211;
const DROPDOWN_X_OFFSET_IMPERSONATING = DROPDOWN_X_OFFSET_BASE + IMPERSONATING_OFFSET;
export default {
+ SET_STATUS_MODAL_ID,
i18n: {
setStatus: s__('SetStatusModal|Set status'),
editStatus: s__('SetStatusModal|Edit status'),
@@ -36,9 +39,14 @@ export default {
GlDisclosureDropdownItem,
GlButton,
UserMenuProfileItem,
+ SetStatusModal: () =>
+ import(
+ /* webpackChunkName: 'statusModalBundle' */ '~/set_status_modal/set_status_modal_wrapper.vue'
+ ),
},
directives: {
SafeHtml,
+ GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
inject: ['isImpersonating'],
@@ -48,6 +56,11 @@ export default {
type: Object,
},
},
+ data() {
+ return {
+ setStatusModalReady: false,
+ };
+ },
computed: {
toggleText() {
return sprintf(__('%{user} user’s menu'), { user: this.data.name });
@@ -61,7 +74,8 @@ export default {
return {
text: statusLabel,
extraAttrs: {
- class: 'js-set-status-modal-trigger',
+ ...USER_MENU_TRACKING_DEFAULTS,
+ 'data-track-label': 'user_edit_status',
},
};
},
@@ -140,24 +154,22 @@ export default {
};
},
statusModalData() {
- const defaultData = {
- 'data-current-emoji': '',
- 'data-current-message': '',
- 'data-default-emoji': 'speech_balloon',
- };
+ if (!this.data?.status?.can_update) {
+ return null;
+ }
const { busy, customized } = this.data.status;
if (!busy && !customized) {
- return defaultData;
+ return {};
}
+ const { emoji, message, availability, clear_after: clearAfter } = this.data.status;
return {
- ...defaultData,
- 'data-current-emoji': this.data.status.emoji,
- 'data-current-message': this.data.status.message,
- 'data-current-availability': this.data.status.availability,
- 'data-current-clear-status-after': this.data.status.clear_after,
+ 'current-emoji': emoji || '',
+ 'current-message': message || '',
+ 'current-availability': availability || '',
+ 'current-clear-status-after': clearAfter || '',
};
},
buyPipelineMinutesCalloutData() {
@@ -248,7 +260,8 @@ export default {
<gl-disclosure-dropdown-group bordered>
<gl-disclosure-dropdown-item
- v-if="data.status.can_update"
+ v-if="setStatusModalReady && statusModalData"
+ v-gl-modal="$options.SET_STATUS_MODAL_ID"
:item="statusItem"
data-testid="status-item"
@action="closeDropdown"
@@ -304,11 +317,11 @@ export default {
@action="trackSignOut"
/>
</gl-disclosure-dropdown>
-
- <div
- v-if="data.status.can_update"
- class="js-set-status-modal-wrapper"
+ <set-status-modal
+ v-if="statusModalData"
+ default-emoji="speech_balloon"
v-bind="statusModalData"
- ></div>
+ @mounted="setStatusModalReady = true"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
index b4ad9a20b76..6aa974878d0 100644
--- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
+++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
@@ -3,7 +3,6 @@ import { GlToast } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import createDefaultClient from '~/lib/graphql';
-import { initStatusTriggers } from '../header';
import { JS_TOGGLE_EXPAND_CLASS } from './constants';
import createStore from './components/global_search/store';
import {
@@ -153,5 +152,3 @@ export const initSuperSidebarToggle = () => {
},
});
};
-
-requestIdleCallback(initStatusTriggers);
diff --git a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
index 3412848a9b7..a5c34b4b619 100644
--- a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
+++ b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
@@ -86,11 +86,7 @@ export default {
},
showSuperSidebarToggle() {
- return gon.use_new_navigation && sidebarState.isCollapsed;
- },
-
- topBarClasses() {
- return gon.use_new_navigation ? 'top-bar-fixed container-fluid' : '';
+ return sidebarState.isCollapsed;
},
},
@@ -124,7 +120,7 @@ export default {
<template>
<div>
- <div :class="topBarClasses" data-testid="top-bar">
+ <div class="top-bar-fixed container-fluid" data-testid="top-bar">
<div
class="top-bar-container gl-display-flex gl-align-items-center gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
>
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 4d4144fe9dd..9118c2a3a50 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -26,7 +26,6 @@
@import 'framework/highlight';
@import 'framework/lists';
@import 'framework/logo';
-@import 'framework/job_log';
@import 'framework/markdown_area';
@import 'framework/media_object';
@import 'framework/modal';
diff --git a/app/assets/stylesheets/framework/job_log.scss b/app/assets/stylesheets/framework/job_log.scss
deleted file mode 100644
index e409facd081..00000000000
--- a/app/assets/stylesheets/framework/job_log.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-.job-log {
- font-family: $monospace-font;
- padding: $gl-padding-8 $input-horizontal-padding;
- margin: 0 0 $gl-padding-8;
- font-size: 13px;
- word-break: break-all;
- word-wrap: break-word;
- color: color-yiq($builds-log-bg);
- border-radius: 0 0 $border-radius-default $border-radius-default;
- min-height: 42px;
- background-color: $builds-log-bg;
-}
-
-.log-line {
- padding: 1px $gl-padding-8 1px $job-log-line-padding;
- min-height: $gl-line-height-20;
-}
-
-.line-number {
- color: $gray-500;
- padding: 0 $gl-padding-8;
- min-width: $job-line-number-width;
- margin-left: -$job-line-number-margin;
- padding-right: 1em;
- user-select: none;
-
- &:hover,
- &:active,
- &:visited {
- text-decoration: underline;
- color: $gray-500;
- }
-}
-
-.collapsible-line {
- &:hover {
- background-color: rgba($white, 0.2);
- }
-
- .arrow {
- margin-left: -$job-arrow-margin;
- }
-}
-
-.loader-animation {
- @include build-loader-animation;
-}
diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss
index add5758090f..c8bf2877b5a 100644
--- a/app/assets/stylesheets/framework/super_sidebar.scss
+++ b/app/assets/stylesheets/framework/super_sidebar.scss
@@ -404,3 +404,57 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4;
}
}
}
+
+
+// Styles for the ScrollScrim component.
+// Should eventually be moved to gitlab-ui.
+// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1869
+
+$scroll-scrim-height: 2.25rem;
+
+.gl-scroll-scrim {
+ .top-scrim-wrapper,
+ .bottom-scrim-wrapper {
+ height: $scroll-scrim-height;
+ opacity: 0;
+ position: sticky;
+ z-index: 1;
+ display: block;
+ left: 0;
+ right: 0;
+ pointer-events: none;
+ transition: opacity 0.1s;
+ }
+
+ .top-scrim-wrapper {
+ top: 0;
+ margin-bottom: -$scroll-scrim-height;
+
+ .top-scrim {
+ background: linear-gradient(180deg, var(--sidebar-background, $gray-10) 0%, $transparent-rgba 100%);
+ }
+ }
+
+ .bottom-scrim-wrapper {
+ bottom: 0;
+ margin-top: -$scroll-scrim-height;
+
+ .bottom-scrim {
+ background: linear-gradient(180deg, $transparent-rgba 0%, var(--sidebar-background, $gray-10));
+ }
+ }
+
+ .top-scrim,
+ .bottom-scrim {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+
+ &.top-scrim-visible .top-scrim-wrapper,
+ &.bottom-scrim-visible .bottom-scrim-wrapper {
+ opacity: 1;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/build.scss b/app/assets/stylesheets/page_bundles/build.scss
index 6165ee6e8b4..9b96c224d88 100644
--- a/app/assets/stylesheets/page_bundles/build.scss
+++ b/app/assets/stylesheets/page_bundles/build.scss
@@ -166,3 +166,51 @@
margin-bottom: 0;
}
}
+
+.job-log {
+ font-family: $monospace-font;
+ padding: $gl-padding-8 $input-horizontal-padding;
+ margin: 0 0 $gl-padding-8;
+ font-size: 13px;
+ word-break: break-all;
+ word-wrap: break-word;
+ color: color-yiq($builds-log-bg);
+ border-radius: 0 0 $border-radius-default $border-radius-default;
+ min-height: 42px;
+ background-color: $builds-log-bg;
+}
+
+.log-line {
+ padding: 1px $gl-padding-8 1px $job-log-line-padding;
+ min-height: $gl-line-height-20;
+}
+
+.line-number {
+ color: $gray-500;
+ padding: 0 $gl-padding-8;
+ min-width: $job-line-number-width;
+ margin-left: -$job-line-number-margin;
+ padding-right: 1em;
+ user-select: none;
+
+ &:hover,
+ &:active,
+ &:visited {
+ text-decoration: underline;
+ color: $gray-500;
+ }
+}
+
+.collapsible-line {
+ &:hover {
+ background-color: rgba($white, 0.2);
+ }
+
+ .arrow {
+ margin-left: -$job-arrow-margin;
+ }
+}
+
+.loader-animation {
+ @include build-loader-animation;
+}
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c8fc10e6c94..4c4a2dd7875 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1722,6 +1722,7 @@ class MergeRequest < ApplicationRecord
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED', value: ProtectedBranch.protected?(target_project, target_branch).to_s)
variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title)
+ variables.append(key: 'CI_MERGE_REQUEST_DESCRIPTION', value: description)
variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee_username_list) if assignees.present?
variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone
variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present?
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index fde56d8429e..e8a684e5da4 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -171,6 +171,8 @@ module Projects
project.import_url,
schemes: Project::VALID_IMPORT_PROTOCOLS,
ports: Project::VALID_IMPORT_PORTS,
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?,
dns_rebind_protection: dns_rebind_protection?)
.then do |(import_url, resolved_host)|
next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https])
@@ -179,6 +181,11 @@ module Projects
end
end
+ def allow_local_requests?
+ Rails.env.development? && # There is no known usecase for this in non-development environments
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+
def dns_rebind_protection?
return false if Gitlab.http_proxy_env?
diff --git a/config/application.rb b/config/application.rb
index 847577f68cb..7fd209b1191 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -613,8 +613,8 @@ module Gitlab
# https://github.com/rails/rails/blob/fdf840f69a2e33d78a9d40b91d9b7fddb76711e9/activerecord/lib/active_record/railtie.rb#L308
initializer :clear_active_connections_again, after: :set_routes_reloader_hook do
# rubocop:disable Database/MultipleDatabases
- ActiveRecord::Base.clear_active_connections!
- ActiveRecord::Base.flush_idle_connections!
+ ActiveRecord::Base.connection_handler.clear_active_connections!(ActiveRecord::Base.current_role)
+ ActiveRecord::Base.connection_handler.flush_idle_connections!(ActiveRecord::Base.current_role)
# rubocop:enable Database/MultipleDatabases
end
diff --git a/config/feature_flags/development/custom_roles_ui_saas.yml b/config/feature_flags/development/custom_roles_ui_saas.yml
deleted file mode 100644
index 6ad2150f597..00000000000
--- a/config/feature_flags/development/custom_roles_ui_saas.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: custom_roles_ui_saas
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130089
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423077
-milestone: '16.4'
-type: development
-group: group::authorization
-default_enabled: true
diff --git a/config/feature_flags/ops/global_search_epics_tab.yml b/config/feature_flags/ops/global_search_epics_tab.yml
index 42067e9ad93..965f77e3133 100644
--- a/config/feature_flags/ops/global_search_epics_tab.yml
+++ b/config/feature_flags/ops/global_search_epics_tab.yml
@@ -5,4 +5,4 @@ rollout_issue_url:
milestone: '16.5'
type: ops
group: group::global search
-default_enabled: false
+default_enabled: true
diff --git a/danger/saas_feature/Dangerfile b/danger/saas_feature/Dangerfile
index 38ca87fb5fd..159201f8904 100644
--- a/danger/saas_feature/Dangerfile
+++ b/danger/saas_feature/Dangerfile
@@ -13,7 +13,11 @@ SUGGEST_COMMENT
def check_yaml(saas_feature)
mr_group_label = helper.group_label
- message_for_missing_group!(saas_feature: saas_feature, mr_group_label: mr_group_label) if saas_feature.group.nil?
+ if saas_feature.group.nil?
+ message_for_missing_group!(saas_feature: saas_feature, mr_group_label: mr_group_label)
+ else
+ message_for_group!(saas_feature: saas_feature, mr_group_label: mr_group_label)
+ end
rescue Psych::Exception
# YAML could not be parsed, fail the build.
fail "#{helper.html_link(saas_feature.path)} isn't valid YAML! #{SEE_DOC.capitalize}"
diff --git a/db/docs/batched_background_migrations/backfill_has_remediations_of_vulnerability_reads.yml b/db/docs/batched_background_migrations/backfill_has_remediations_of_vulnerability_reads.yml
index 05a5f909270..b52816a9897 100644
--- a/db/docs/batched_background_migrations/backfill_has_remediations_of_vulnerability_reads.yml
+++ b/db/docs/batched_background_migrations/backfill_has_remediations_of_vulnerability_reads.yml
@@ -1,7 +1,9 @@
---
migration_job_name: BackfillHasRemediationsOfVulnerabilityReads
-description: Backfills has_remediations column for vulnerability_reads table.
+description: Backfills has_remediations column for vulnerability_reads table.
+ Originally introduced via https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133714
+ RE-ran because there was a error in remediation ingestion logic.
feature_category: database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133714
-milestone: 16.5
+milestone: 16.7
queued_migration_version: 20231011142714
diff --git a/db/post_migrate/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads.rb b/db/post_migrate/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads.rb
index 2c1eebbfaa5..12de83a384e 100644
--- a/db/post_migrate/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads.rb
+++ b/db/post_migrate/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads.rb
@@ -9,19 +9,15 @@ class QueueBackfillHasRemediationsOfVulnerabilityReads < Gitlab::Database::Migra
restrict_gitlab_migration gitlab_schema: :gitlab_main
disable_ddl_transaction!
- def up
- queue_batched_background_migration(
- MIGRATION,
- :vulnerability_reads,
- :vulnerability_id,
- job_interval: DELAY_INTERVAL,
- queued_migration_version: '20231011142714',
- batch_size: BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE
- )
- end
+ # per: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#requeuing-batched-background-migrations
+ # > When you requeue the batched background migration, turn the original queuing
+ # > into a no-op by clearing up the #up and #down methods of the migration
+ # > performing the requeuing. Otherwise, the batched background migration is
+ # > queued multiple times on systems that are upgrading multiple patch releases
+ # > at once.
+ #
+ # being re-run via https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135747
+ def up; end
- def down
- delete_batched_background_migration(MIGRATION, :vulnerability_reads, :vulnerability_id, [])
- end
+ def down; end
end
diff --git a/db/post_migrate/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads.rb b/db/post_migrate/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads.rb
new file mode 100644
index 00000000000..e6641622349
--- /dev/null
+++ b/db/post_migrate/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+# rubocop: disable BackgroundMigration/DictionaryFile -- queued/introduced before the rule is introduced
+
+class RequeueBackfillHasRemediationsOfVulnerabilityReads < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ MIGRATION = "BackfillHasRemediationsOfVulnerabilityReads"
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+ SUB_BATCH_SIZE = 50
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+ disable_ddl_transaction!
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :vulnerability_reads,
+ :vulnerability_id,
+ job_interval: DELAY_INTERVAL,
+ queued_migration_version: '20231031204841',
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :vulnerability_reads, :vulnerability_id, [])
+ end
+end
+# rubocop: enable BackgroundMigration/DictionaryFile
diff --git a/db/schema_migrations/20231031204841 b/db/schema_migrations/20231031204841
new file mode 100644
index 00000000000..e6ab24c922b
--- /dev/null
+++ b/db/schema_migrations/20231031204841
@@ -0,0 +1 @@
+a1bbcd9430acc48bc271dd041c2999932d24d15bfa2ef8766d7bf9920d2d3539 \ No newline at end of file
diff --git a/doc/ci/pipelines/merge_trains.md b/doc/ci/pipelines/merge_trains.md
index a54087262e7..6734c94f65e 100644
--- a/doc/ci/pipelines/merge_trains.md
+++ b/doc/ci/pipelines/merge_trains.md
@@ -162,13 +162,15 @@ When you remove a merge request from a merge train:
## Skip the merge train and merge immediately
If you have a high-priority merge request, like a critical patch that must
-be merged urgently, select **Merge Immediately**.
+be merged urgently, you can select **Merge Immediately**.
When you merge a merge request immediately:
-- The current merge train is recreated.
-- All pipelines restart.
-- Redundant pipelines [are cancelled](#automatic-pipeline-cancellation).
+- The commits from the merge request are merged, ignoring the status of the merge train.
+- The merge train pipelines for all other merge requests on the train [are cancelled](#automatic-pipeline-cancellation).
+- A new merge train starts and all the merge requests from the original merge train are added to this new merge train,
+ with a new merge train pipeline for each. These new merge train pipelines now contain
+ the commits added by the merge request that was merged immediately.
WARNING:
Merging immediately can use a lot of CI/CD resources. Use this option
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 7cdb2305d09..e1df32b4224 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -158,6 +158,7 @@ These variables are available when:
| `CI_MERGE_REQUEST_APPROVED` | 14.1 | all | Approval status of the merge request. `true` when [merge request approvals](../../user/project/merge_requests/approvals/index.md) is available and the merge request has been approved. |
| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of usernames of assignees for the merge request. |
| `CI_MERGE_REQUEST_ID` | 11.6 | all | The instance-level ID of the merge request. This is a unique ID across all projects on the GitLab instance. |
+| `CI_MERGE_REQUEST_DESCRIPTION` | 16.7 | all | The description of the merge request. |
| `CI_MERGE_REQUEST_IID` | 11.6 | all | The project-level IID (internal ID) of the merge request. This ID is unique for the current project, and is the number used in the merge request URL, page title, and other visible locations. |
| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request. |
| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request. |
diff --git a/doc/development/data_science/index.md b/doc/development/data_science/index.md
index 132b6cc3d11..632acb8602f 100644
--- a/doc/development/data_science/index.md
+++ b/doc/development/data_science/index.md
@@ -4,4 +4,6 @@ group: ModelOps
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---
+# Data Science
+
- [Model Registry](model_registry/index.md)
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index 946826e72da..17cda7ca1d3 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -1495,11 +1495,43 @@ Logging helps track events for debugging. Logging also allows the application to
- An audit trail for log edits must be available.
- To avoid data loss, logs must be saved on different storage.
-### Who to contact if you have questions
+## URL Spoofing
+
+We want to protect our users from bad actors who might try to use GitLab
+features to redirect other users to malicious sites.
+
+Many features in GitLab allow users to post links to external websites. It is
+important that the destination of any user-specified link is made very clear
+to the user.
+
+### `external_redirect_path`
+
+When presenting links provided by users, if the actual URL is hidden, use the `external_redirect_path`
+helper method to redirect the user to a warning page first. For example:
+
+```ruby
+# Bad :(
+# This URL comes from User-Land and may not be safe...
+# We need the user to *see* where they are going.
+link_to foo_social_url(@user), title: "Foo Social" do
+ sprite_icon('question-o')
+end
+
+# Good :)
+# The external_redirect "leaving GitLab" page will show the URL to the user
+# before they leave.
+link_to external_redirect_path(url: foo_social_url(@user)), title: "Foo" do
+ sprite_icon('question-o')
+end
+```
+
+Also see this [real-life usage](https://gitlab.com/gitlab-org/gitlab/-/blob/bdba5446903ff634fb12ba695b2de99b6d6881b5/app/helpers/application_helper.rb#L378) as an example.
+
+## Who to contact if you have questions
For general guidance, contact the [Application Security](https://about.gitlab.com/handbook/security/security-engineering/application-security/) team.
-### Related topics
+## Related topics
- [Log system in GitLab](../administration/logs/index.md)
- [Audit event development guidelines](../development/audit_event_guide/index.md))
diff --git a/jest.config.snapshots.js b/jest.config.snapshots.js
new file mode 100644
index 00000000000..eb74ae4947a
--- /dev/null
+++ b/jest.config.snapshots.js
@@ -0,0 +1,46 @@
+const fs = require('fs');
+const path = require('path');
+const baseConfig = require('./jest.config.base');
+
+function findSnapshotTestsFromDir(dir, results = []) {
+ fs.readdirSync(dir).forEach((file) => {
+ const fullPath = path.join(dir, file);
+ if (fs.lstatSync(fullPath).isDirectory()) {
+ findSnapshotTestsFromDir(fullPath, results);
+ } else {
+ const fileContent = fs.readFileSync(fullPath, 'utf8');
+ if (/toMatchSnapshot|toMatchInlineSnapshot/.test(fileContent)) {
+ results.push(`<rootDir>/${fullPath}`);
+ }
+ }
+ });
+ return results;
+}
+
+function saveArrayToFile(array, fileName) {
+ fs.writeFile(fileName, JSON.stringify(array, null, 2), (err) => {
+ if (err) {
+ console.error(`Error writing Array data to ${fileName}:`, err);
+ }
+ });
+}
+
+module.exports = () => {
+ const testMatch = [
+ ...findSnapshotTestsFromDir('spec/frontend'),
+ ...findSnapshotTestsFromDir('ee/spec/frontend'),
+ ];
+
+ const { CI, SNAPSHOT_TEST_MATCH_FILE } = process.env;
+ if (CI && SNAPSHOT_TEST_MATCH_FILE) {
+ saveArrayToFile(testMatch, SNAPSHOT_TEST_MATCH_FILE);
+ }
+
+ return {
+ ...baseConfig('spec/frontend'),
+ roots: ['<rootDir>/spec/frontend'],
+ rootsEE: ['<rootDir>/ee/spec/frontend'],
+ rootsJH: ['<rootDir>/jh/spec/frontend'],
+ testMatch,
+ };
+};
diff --git a/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads.rb b/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads.rb
index 83acd8a27f7..84b0f5c97df 100644
--- a/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads.rb
+++ b/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads.rb
@@ -20,6 +20,8 @@ module Gitlab
def perform
each_sub_batch do |sub_batch|
+ reset_has_remediations_attribute(sub_batch)
+
update_query = update_query_for(sub_batch)
connection.execute(update_query)
@@ -28,6 +30,10 @@ module Gitlab
private
+ def reset_has_remediations_attribute(sub_batch)
+ sub_batch.update_all(has_remediations: false)
+ end
+
def update_query_for(sub_batch)
subquery = sub_batch.joins("
INNER JOIN vulnerability_occurrences ON
diff --git a/lib/gitlab/ci/config/interpolation/inputs/base_input.rb b/lib/gitlab/ci/config/interpolation/inputs/base_input.rb
index 987268b0525..e506645df11 100644
--- a/lib/gitlab/ci/config/interpolation/inputs/base_input.rb
+++ b/lib/gitlab/ci/config/interpolation/inputs/base_input.rb
@@ -10,9 +10,8 @@ module Gitlab
class BaseInput
ArgumentNotValidError = Class.new(StandardError)
- # Checks whether the class matches the type in the specification
def self.matches?(spec)
- raise NotImplementedError
+ spec.is_a?(Hash) && spec[:type] == type_name
end
# Human readable type used in error messages
diff --git a/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb
index 4c34f7e7fdd..51845a2fea8 100644
--- a/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb
+++ b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb
@@ -8,10 +8,6 @@ module Gitlab
class BooleanInput < BaseInput
extend ::Gitlab::Utils::Override
- def self.matches?(spec)
- spec.is_a?(Hash) && spec[:type] == type_name
- end
-
def self.type_name
'boolean'
end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/number_input.rb b/lib/gitlab/ci/config/interpolation/inputs/number_input.rb
index 59bc057749a..bb023a8a85b 100644
--- a/lib/gitlab/ci/config/interpolation/inputs/number_input.rb
+++ b/lib/gitlab/ci/config/interpolation/inputs/number_input.rb
@@ -8,10 +8,6 @@ module Gitlab
class NumberInput < BaseInput
extend ::Gitlab::Utils::Override
- def self.matches?(spec)
- spec.is_a?(Hash) && spec[:type] == type_name
- end
-
def self.type_name
'number'
end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/string_input.rb b/lib/gitlab/ci/config/interpolation/inputs/string_input.rb
index 01b9d34a883..3c4868b299c 100644
--- a/lib/gitlab/ci/config/interpolation/inputs/string_input.rb
+++ b/lib/gitlab/ci/config/interpolation/inputs/string_input.rb
@@ -17,7 +17,7 @@ module Gitlab
# inputs:
# foo:
# ```
- spec.nil? || (spec.is_a?(Hash) && [nil, type_name].include?(spec[:type]))
+ spec.nil? || super || (spec.is_a?(Hash) && !spec.key?(:type))
end
def self.type_name
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 9b32f185279..6dee9a404f4 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -548,43 +548,10 @@ module Gitlab
end
end
- def self.storage_metadata_file_path(storage)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- File.join(
- Gitlab.config.repositories.storages[storage].legacy_disk_path, GITALY_METADATA_FILENAME
- )
- end
- end
-
- def self.can_use_disk?(storage)
- cached_value = MUTEX.synchronize do
- @can_use_disk ||= {}
- @can_use_disk[storage]
- end
-
- return cached_value unless cached_value.nil?
-
- gitaly_filesystem_id = filesystem_id(storage)
- direct_filesystem_id = filesystem_id_from_disk(storage)
-
- MUTEX.synchronize do
- @can_use_disk[storage] = gitaly_filesystem_id.present? &&
- gitaly_filesystem_id == direct_filesystem_id
- end
- end
-
def self.filesystem_id(storage)
Gitlab::GitalyClient::ServerService.new(storage).storage_info&.filesystem_id
end
- def self.filesystem_id_from_disk(storage)
- metadata_file = File.read(storage_metadata_file_path(storage))
- metadata_hash = Gitlab::Json.parse(metadata_file)
- metadata_hash['gitaly_filesystem_id']
- rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError
- nil
- end
-
def self.filesystem_disk_available(storage)
Gitlab::GitalyClient::ServerService.new(storage).storage_disk_statistics&.available
end
diff --git a/lib/initializer_connections.rb b/lib/initializer_connections.rb
index ae2809b7604..c39340b1d3c 100644
--- a/lib/initializer_connections.rb
+++ b/lib/initializer_connections.rb
@@ -11,15 +11,17 @@ module InitializerConnections
def self.raise_if_new_database_connection
return yield if Gitlab::Utils.to_boolean(ENV['SKIP_RAISE_ON_INITIALIZE_CONNECTIONS'])
- previous_connection_counts = ActiveRecord::Base.connection_handler.connection_pool_list.to_h do |pool|
- [pool.db_config.name, pool.connections.size]
- end
+ previous_connection_counts =
+ ActiveRecord::Base.connection_handler.connection_pool_list(ApplicationRecord.current_role).to_h do |pool|
+ [pool.db_config.name, pool.connections.size]
+ end
yield
- new_connection_counts = ActiveRecord::Base.connection_handler.connection_pool_list.to_h do |pool|
- [pool.db_config.name, pool.connections.size]
- end
+ new_connection_counts =
+ ActiveRecord::Base.connection_handler.connection_pool_list(ApplicationRecord.current_role).to_h do |pool|
+ [pool.db_config.name, pool.connections.size]
+ end
raise_database_connection_made_error unless previous_connection_counts == new_connection_counts
end
diff --git a/lib/tasks/gitlab/db/validate_config.rake b/lib/tasks/gitlab/db/validate_config.rake
index f42d30e9817..3b6b58bd8f5 100644
--- a/lib/tasks/gitlab/db/validate_config.rake
+++ b/lib/tasks/gitlab/db/validate_config.rake
@@ -87,7 +87,11 @@ namespace :gitlab do
# Skip if databases are yet to be provisioned
next unless connection[:identifier] && shared_connection[:identifier]
- unless connection[:identifier] == shared_connection[:identifier]
+ connection_identifier, shared_connection_identifier = [
+ connection[:identifier], shared_connection[:identifier]
+ ].map { |identifier| identifier.slice("system_identifier", "current_database") }
+
+ unless connection_identifier == shared_connection_identifier
warnings << "- The '#{connection[:name]}' since it is using 'database_tasks: false' " \
"should share database with '#{share_with}:'."
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1f456f2b813..c80fb71bc19 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -23069,6 +23069,9 @@ msgstr ""
msgid "GroupSAML|Could not create SAML group link: %{errors}."
msgstr ""
+msgid "GroupSAML|Custom roles"
+msgstr ""
+
msgid "GroupSAML|Default membership role"
msgstr ""
@@ -23168,6 +23171,9 @@ msgstr ""
msgid "GroupSAML|Some to-do items may be hidden because your SAML session has expired. Select the group’s path to reauthenticate and view the hidden to-do items."
msgstr ""
+msgid "GroupSAML|Standard roles"
+msgstr ""
+
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to %{linkStart}reset it%{linkEnd}."
msgstr ""
@@ -56719,6 +56725,9 @@ msgstr ""
msgid "ciReport|Automatically apply the patch in a new branch"
msgstr ""
+msgid "ciReport|Automatically opens a merge request with a solution generated by AI"
+msgstr ""
+
msgid "ciReport|Base pipeline codequality artifact not found"
msgstr ""
@@ -56901,6 +56910,9 @@ msgstr ""
msgid "ciReport|RPS"
msgstr ""
+msgid "ciReport|Resolve with AI"
+msgstr ""
+
msgid "ciReport|Resolve with merge request"
msgstr ""
diff --git a/package.json b/package.json
index 5fd2092af1b..c327cb27d9f 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"jest:integration": "jest --config jest.config.integration.js",
"jest:scripts": "jest --config jest.config.scripts.js",
"jest:quarantine": "grep -r 'quarantine:' spec/frontend ee/spec/frontend",
+ "jest:snapshots": "jest --config jest.config.snapshots.js",
"lint:eslint": "node scripts/frontend/eslint.js",
"lint:eslint:fix": "node scripts/frontend/eslint.js --fix",
"lint:eslint:all": "node scripts/frontend/eslint.js .",
diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml
index 721733f6f68..0ba0648cf01 100644
--- a/scripts/review_apps/base-config.yaml
+++ b/scripts/review_apps/base-config.yaml
@@ -161,6 +161,7 @@ gitlab-runner:
memory: 150Mi
nodeSelector:
preemptible: "true"
+ terminationGracePeriodSeconds: 60 # Wait for 1min before killing gitlab-runner
podAnnotations:
<<: *safe-to-evict
diff --git a/scripts/trigger-build.rb b/scripts/trigger-build.rb
index 98ca8112d62..19b39ce7023 100755
--- a/scripts/trigger-build.rb
+++ b/scripts/trigger-build.rb
@@ -25,6 +25,7 @@ module Trigger
class Base
# Can be overridden
+ STABLE_BRANCH_REGEX = /^[\d-]+-stable(-ee|-jh)?$/
def self.access_token
ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
end
@@ -113,21 +114,33 @@ module Trigger
end
def stable_branch?
- ENV['CI_COMMIT_REF_NAME'] =~ /^[\d-]+-stable(-ee|-jh)?$/
+ ENV['CI_COMMIT_REF_NAME'] =~ STABLE_BRANCH_REGEX
+ end
+
+ def mr_target_stable_branch?
+ ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'] =~ STABLE_BRANCH_REGEX
end
def fallback_ref
- if trigger_stable_branch_if_detected? && stable_branch?
- if ENV['CI_PROJECT_NAMESPACE'] == 'gitlab-cn'
- ENV['CI_COMMIT_REF_NAME'].delete_suffix('-jh')
- elsif ENV['CI_PROJECT_NAMESPACE'] == 'gitlab-org'
- ENV['CI_COMMIT_REF_NAME'].delete_suffix('-ee')
- end
+ return primary_ref unless trigger_stable_branch_if_detected?
+
+ if stable_branch?
+ normalize_stable_branch_name(ENV['CI_COMMIT_REF_NAME'])
+ elsif mr_target_stable_branch?
+ normalize_stable_branch_name(ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'])
else
primary_ref
end
end
+ def normalize_stable_branch_name(branch_name)
+ if ENV['CI_PROJECT_NAMESPACE'] == 'gitlab-cn'
+ branch_name.delete_suffix('-jh')
+ elsif ENV['CI_PROJECT_NAMESPACE'] == 'gitlab-org'
+ branch_name.delete_suffix('-ee')
+ end
+ end
+
def ref
ENV.fetch(ref_param_name, fallback_ref)
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 439839cfad5..e60589a161b 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -188,7 +188,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
end
context 'user status', :js do
- def select_emoji(emoji_name, is_modal = false)
+ def select_emoji(emoji_name)
toggle_button = find('.emoji-menu-toggle-button')
toggle_button.click
emoji_button = find("gl-emoji[data-name=\"#{emoji_name}\"]")
@@ -330,10 +330,12 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
find_by_testid('user-dropdown').click
within_testid('user-dropdown') do
- find('.js-set-status-modal-trigger.ready')
+ expect(page).to have_button(text: button_text, visible: :visible)
click_button button_text
end
+
+ expect(page.find('#set-user-status-modal')).to be_visible
end
def open_user_status_modal
@@ -386,7 +388,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
it 'adds emoji to user status' do
emoji = 'grinning'
open_user_status_modal
- select_emoji(emoji, true)
+ select_emoji(emoji)
set_user_status_in_modal
visit_user
@@ -415,7 +417,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
it 'opens the emoji modal again after closing it' do
open_user_status_modal
- select_emoji('grinning', true)
+ select_emoji('grinning')
find('.emoji-menu-toggle-button').click
@@ -428,7 +430,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
emoji = 'grinning'
open_user_status_modal
- select_emoji(emoji, true)
+ select_emoji(emoji)
expect(page.all('.award-control .js-counter')).to all(have_content('0'))
end
@@ -451,7 +453,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
emoji = 'grinning'
message = 'Playing outside'
open_user_status_modal
- select_emoji(emoji, true)
+ select_emoji(emoji)
find_field(s_("SetStatusModal|What's your status?")).native.send_keys(message)
set_user_status_in_modal
diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
index 1edb6812af0..39cdde295aa 100644
--- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
@@ -23,6 +23,9 @@ describe('IssueBoardFilter', () => {
fullPath: 'gitlab-org',
isGroupBoard: true,
},
+ mocks: {
+ $apollo: {},
+ },
});
};
diff --git a/spec/frontend/ci/job_details/components/manual_variables_form_spec.js b/spec/frontend/ci/job_details/components/manual_variables_form_spec.js
index 3391cafb4fc..7f7a8c7c93e 100644
--- a/spec/frontend/ci/job_details/components/manual_variables_form_spec.js
+++ b/spec/frontend/ci/job_details/components/manual_variables_form_spec.js
@@ -1,7 +1,6 @@
import { GlSprintf, GlLink } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
import { createAlert } from '~/alert';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -24,9 +23,8 @@ import {
mockJobRetryMutationData,
} from '../mock_data';
-const localVue = createLocalVue();
jest.mock('~/alert');
-localVue.use(VueApollo);
+Vue.use(VueApollo);
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -62,7 +60,6 @@ describe('Manual Variables Form', () => {
]);
const options = {
- localVue,
apolloProvider: mockApollo,
};
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index fc50afcb01d..47663360ce8 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -1,5 +1,5 @@
import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
@@ -43,11 +43,10 @@ describe('noteActions', () => {
store.state.isPromoteCommentToTimelineEventInProgress = isPromotionInProgress;
};
- const mountNoteActions = (propsData, computed) => {
- return mount(noteActions, {
+ const mountNoteActions = (propsData) => {
+ return shallowMount(noteActions, {
store,
propsData,
- computed,
stubs: {
GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
methods: {
@@ -190,15 +189,14 @@ describe('noteActions', () => {
};
beforeEach(() => {
- wrapper = mountNoteActions(props, {
- targetType: () => 'issue',
- });
+ wrapper = mountNoteActions(props);
store.state.noteableData = {
current_user: {
can_set_issue_metadata: true,
},
};
store.state.userData = userDataMock;
+ store.state.noteableData.targetType = 'issue';
});
afterEach(() => {
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index ca72426cb44..a348ee77be1 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -106,7 +106,6 @@ describe('Shortcuts', () => {
let event;
beforeEach(() => {
- window.gon.use_new_navigation = true;
event = new KeyboardEvent('keydown', { cancelable: true });
Shortcuts.focusSearch(event);
});
diff --git a/spec/frontend/super_sidebar/components/scroll_scrim_spec.js b/spec/frontend/super_sidebar/components/scroll_scrim_spec.js
new file mode 100644
index 00000000000..ff1e9968f9b
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/scroll_scrim_spec.js
@@ -0,0 +1,60 @@
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ScrollScrim from '~/super_sidebar/components/scroll_scrim.vue';
+import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
+
+describe('ScrollScrim', () => {
+ let wrapper;
+ const { trigger: triggerIntersection } = useMockIntersectionObserver();
+
+ const createWrapper = () => {
+ wrapper = shallowMountExtended(ScrollScrim, {});
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ const findTopBoundary = () => wrapper.vm.$refs['top-boundary'];
+ const findBottomBoundary = () => wrapper.vm.$refs['bottom-boundary'];
+
+ describe('top scrim', () => {
+ describe('when top boundary is visible', () => {
+ it('does not show', async () => {
+ triggerIntersection(findTopBoundary(), { entry: { isIntersecting: true } });
+ await nextTick();
+
+ expect(wrapper.classes()).not.toContain('top-scrim-visible');
+ });
+ });
+
+ describe('when top boundary is not visible', () => {
+ it('does show', async () => {
+ triggerIntersection(findTopBoundary(), { entry: { isIntersecting: false } });
+ await nextTick();
+
+ expect(wrapper.classes()).toContain('top-scrim-visible');
+ });
+ });
+ });
+
+ describe('bottom scrim', () => {
+ describe('when bottom boundary is visible', () => {
+ it('does not show', async () => {
+ triggerIntersection(findBottomBoundary(), { entry: { isIntersecting: true } });
+ await nextTick();
+
+ expect(wrapper.classes()).not.toContain('bottom-scrim-visible');
+ });
+ });
+
+ describe('when bottom boundary is not visible', () => {
+ it('does show', async () => {
+ triggerIntersection(findBottomBoundary(), { entry: { isIntersecting: false } });
+ await nextTick();
+
+ expect(wrapper.classes()).toContain('bottom-scrim-visible');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index 31fa7cd3ec4..ed176195fdb 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -302,8 +302,8 @@ describe('SuperSidebar component', () => {
createWrapper();
});
- it('allows overflow', () => {
- expect(findNavContainer().classes()).toContain('gl-overflow-auto');
+ it('allows overflow with scroll scrim', () => {
+ expect(findNavContainer().element.tagName).toContain('SCROLL-SCRIM');
});
});
diff --git a/spec/frontend/super_sidebar/components/user_menu_spec.js b/spec/frontend/super_sidebar/components/user_menu_spec.js
index 45a60fce00a..ba675c8b3f5 100644
--- a/spec/frontend/super_sidebar/components/user_menu_spec.js
+++ b/spec/frontend/super_sidebar/components/user_menu_spec.js
@@ -1,8 +1,10 @@
import { GlAvatar, GlDisclosureDropdown } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import UserMenu from '~/super_sidebar/components/user_menu.vue';
import UserMenuProfileItem from '~/super_sidebar/components/user_menu_profile_item.vue';
+import SetStatusModal from '~/set_status_modal/set_status_modal_wrapper.vue';
import { mockTracking } from 'helpers/tracking_helper';
import PersistentUserCallout from '~/persistent_user_callout';
import { userMenuMockData, userMenuMockStatus, userMenuMockPipelineMinutes } from '../mock_data';
@@ -13,6 +15,7 @@ describe('UserMenu component', () => {
const GlEmoji = { template: '<img/>' };
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
+ const findSetStatusModal = () => wrapper.findComponent(SetStatusModal);
const showDropdown = () => findDropdown().vm.$emit('shown');
const closeDropdownSpy = jest.fn();
@@ -28,6 +31,7 @@ describe('UserMenu component', () => {
stubs: {
GlEmoji,
GlAvatar: true,
+ SetStatusModal: stubComponent(SetStatusModal),
...stubs,
},
provide: {
@@ -91,31 +95,46 @@ describe('UserMenu component', () => {
describe('User status item', () => {
let item;
- const setItem = ({ can_update, busy, customized, stubs } = {}) => {
- createWrapper({ status: { ...userMenuMockStatus, can_update, busy, customized } }, stubs);
+ const setItem = async ({
+ can_update: canUpdate = false,
+ busy = false,
+ customized = false,
+ stubs,
+ } = {}) => {
+ createWrapper(
+ { status: { ...userMenuMockStatus, can_update: canUpdate, busy, customized } },
+ stubs,
+ );
+ // Mock mounting the modal if we can update
+ if (canUpdate) {
+ expect(wrapper.vm.setStatusModalReady).toEqual(false);
+ findSetStatusModal().vm.$emit('mounted');
+ await nextTick();
+ expect(wrapper.vm.setStatusModalReady).toEqual(true);
+ }
item = wrapper.findByTestId('status-item');
};
describe('When user cannot update the status', () => {
- it('does not render the status menu item', () => {
- setItem();
+ it('does not render the status menu item', async () => {
+ await setItem();
expect(item.exists()).toBe(false);
});
});
describe('When user can update the status', () => {
- it('renders the status menu item', () => {
- setItem({ can_update: true });
+ it('renders the status menu item', async () => {
+ await setItem({ can_update: true });
expect(item.exists()).toBe(true);
+ expect(item.find('button').attributes()).toMatchObject({
+ 'data-track-property': 'nav_user_menu',
+ 'data-track-action': 'click_link',
+ 'data-track-label': 'user_edit_status',
+ });
});
- it('should set the CSS class for triggering status update modal', () => {
- setItem({ can_update: true });
- expect(item.find('.js-set-status-modal-trigger').exists()).toBe(true);
- });
-
- it('should close the dropdown when status modal opened', () => {
- setItem({
+ it('should close the dropdown when status modal opened', async () => {
+ await setItem({
can_update: true,
stubs: {
GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
@@ -139,57 +158,75 @@ describe('UserMenu component', () => {
${true} | ${true} | ${'Edit status'}
`(
'when busy is "$busy" and customized is "$customized" the label is "$label"',
- ({ busy, customized, label }) => {
- setItem({ can_update: true, busy, customized });
+ async ({ busy, customized, label }) => {
+ await setItem({ can_update: true, busy, customized });
expect(item.text()).toBe(label);
},
);
});
+ });
+ });
+
+ describe('set status modal', () => {
+ describe('when the user cannot update the status', () => {
+ it('should not render the modal', () => {
+ createWrapper({
+ status: { ...userMenuMockStatus, can_update: false },
+ });
+
+ expect(findSetStatusModal().exists()).toBe(false);
+ });
+ });
- describe('Status update modal wrapper', () => {
- const findModalWrapper = () => wrapper.find('.js-set-status-modal-wrapper');
+ describe('when the user can update the status', () => {
+ describe.each`
+ busy | customized
+ ${true} | ${true}
+ ${true} | ${false}
+ ${false} | ${true}
+ `('and the status is busy or customized', ({ busy, customized }) => {
+ it('should pass the current status to the modal', () => {
+ createWrapper({
+ status: { ...userMenuMockStatus, can_update: true, busy, customized },
+ });
- it('renders the modal wrapper', () => {
- setItem({ can_update: true });
- expect(findModalWrapper().exists()).toBe(true);
+ expect(findSetStatusModal().exists()).toBe(true);
+ expect(findSetStatusModal().props()).toMatchObject({
+ defaultEmoji: 'speech_balloon',
+ currentEmoji: userMenuMockStatus.emoji,
+ currentMessage: userMenuMockStatus.message,
+ currentAvailability: userMenuMockStatus.availability,
+ currentClearStatusAfter: userMenuMockStatus.clear_after,
+ });
});
- describe('when user cannot update status', () => {
- it('sets default data attributes', () => {
- setItem({ can_update: true });
- expect(findModalWrapper().attributes()).toMatchObject({
- 'data-current-emoji': '',
- 'data-current-message': '',
- 'data-default-emoji': 'speech_balloon',
- });
+ it('casts falsey values to empty strings', () => {
+ createWrapper({
+ status: { can_update: true, busy, customized },
+ });
+
+ expect(findSetStatusModal().exists()).toBe(true);
+ expect(findSetStatusModal().props()).toMatchObject({
+ defaultEmoji: 'speech_balloon',
+ currentEmoji: '',
+ currentMessage: '',
+ currentAvailability: '',
+ currentClearStatusAfter: '',
});
});
+ });
+
+ describe('and the status is neither busy nor customized', () => {
+ it('should pass an empty status to the modal', () => {
+ createWrapper({
+ status: { ...userMenuMockStatus, can_update: true, busy: false, customized: false },
+ });
- describe.each`
- busy | customized
- ${true} | ${true}
- ${true} | ${false}
- ${false} | ${true}
- ${false} | ${false}
- `(`when user can update status`, ({ busy, customized }) => {
- it(`and ${busy ? 'is busy' : 'is not busy'} and status ${
- customized ? 'is' : 'is not'
- } customized sets user status data attributes`, () => {
- setItem({ can_update: true, busy, customized });
- if (busy || customized) {
- expect(findModalWrapper().attributes()).toMatchObject({
- 'data-current-emoji': userMenuMockStatus.emoji,
- 'data-current-message': userMenuMockStatus.message,
- 'data-current-availability': userMenuMockStatus.availability,
- 'data-current-clear-status-after': userMenuMockStatus.clear_after,
- });
- } else {
- expect(findModalWrapper().attributes()).toMatchObject({
- 'data-current-emoji': '',
- 'data-current-message': '',
- 'data-default-emoji': 'speech_balloon',
- });
- }
+ expect(findSetStatusModal().exists()).toBe(true);
+ expect(findSetStatusModal().props()).toMatchObject({
+ defaultEmoji: 'speech_balloon',
+ currentEmoji: '',
+ currentMessage: '',
});
});
});
diff --git a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
index 109b7732539..716de45f4b4 100644
--- a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
@@ -116,21 +116,23 @@ describe('Experimental new namespace creation app', () => {
expect(findLegacyContainer().exists()).toBe(true);
});
- describe.each`
- featureFlag | isSuperSidebarCollapsed | isToggleVisible
- ${true} | ${true} | ${true}
- ${true} | ${false} | ${false}
- ${false} | ${true} | ${false}
- ${false} | ${false} | ${false}
- `('Super sidebar toggle', ({ featureFlag, isSuperSidebarCollapsed, isToggleVisible }) => {
- beforeEach(() => {
- sidebarState.isCollapsed = isSuperSidebarCollapsed;
- gon.use_new_navigation = featureFlag;
- createComponent();
+ describe('SuperSidebarToggle', () => {
+ describe('when collapsed', () => {
+ it('shows sidebar toggle', () => {
+ sidebarState.isCollapsed = true;
+ createComponent();
+
+ expect(findSuperSidebarToggle().exists()).toBe(true);
+ });
});
- it(`${isToggleVisible ? 'is visible' : 'is not visible'}`, () => {
- expect(findSuperSidebarToggle().exists()).toBe(isToggleVisible);
+ describe('when not collapsed', () => {
+ it('does not show sidebar toggle', () => {
+ sidebarState.isCollapsed = false;
+ createComponent();
+
+ expect(findSuperSidebarToggle().exists()).toBe(false);
+ });
});
});
@@ -170,17 +172,10 @@ describe('Experimental new namespace creation app', () => {
});
describe('top bar', () => {
- it('adds "top-bar-fixed" and "container-fluid" classes when new navigation enabled', () => {
- gon.use_new_navigation = true;
+ it('has "top-bar-fixed" and "container-fluid" classes', () => {
createComponent();
expect(findTopBar().classes()).toEqual(['top-bar-fixed', 'container-fluid']);
});
-
- it('does not add classes when new navigation is not enabled', () => {
- createComponent();
-
- expect(findTopBar().classes()).toEqual([]);
- });
});
});
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
index 30036ee68ed..b0a514cb1e2 100644
--- a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
@@ -4,8 +4,34 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do
describe '.matches?' do
- it 'is not implemented' do
- expect { described_class.matches?(double) }.to raise_error(NotImplementedError)
+ context 'when given is a hash' do
+ before do
+ stub_const('TestInput', Class.new(described_class))
+
+ TestInput.class_eval do
+ def self.type_name
+ 'test'
+ end
+ end
+ end
+
+ context 'when the spec type matches the input type' do
+ it 'returns true' do
+ expect(TestInput.matches?({ type: 'test' })).to be_truthy
+ end
+ end
+
+ context 'when the spec type does not match the input type' do
+ it 'returns false' do
+ expect(TestInput.matches?({ type: 'string' })).to be_falsey
+ end
+ end
+ end
+
+ context 'when not given a hash' do
+ it 'returns false' do
+ expect(described_class.matches?([])).to be_falsey
+ end
end
end
diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
index 860a1fd30bd..eb98c1cc7e6 100644
--- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
@@ -113,6 +113,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
merge_request.source_branch
).to_s,
'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description,
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
@@ -214,6 +215,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => merge_request.source_branch_sha,
'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description,
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
index 3650ca1d904..9570a25238e 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -15,7 +15,9 @@ RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin, :d
end
let(:config) { ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash.merge(pool: 1) }
- let(:pool) { model.establish_connection(config) }
+ let(:pool) do
+ model.establish_connection(ActiveRecord::DatabaseConfigurations::HashConfig.new(Rails.env, 'main', config))
+ end
it 'calls the force disconnect callback on checkin' do
connection = pool.connection
diff --git a/spec/lib/gitlab/database/schema_migrations/context_spec.rb b/spec/lib/gitlab/database/schema_migrations/context_spec.rb
index 6a614e2488f..ed83ed9e744 100644
--- a/spec/lib/gitlab/database/schema_migrations/context_spec.rb
+++ b/spec/lib/gitlab/database/schema_migrations/context_spec.rb
@@ -25,12 +25,15 @@ RSpec.describe Gitlab::Database::SchemaMigrations::Context do
context 'multiple databases', :reestablished_active_record_base do
before do
- connection_class.establish_connection(
+ db_config =
ActiveRecord::Base
.connection_pool
.db_config
.configuration_hash
.merge(configuration_overrides)
+
+ connection_class.establish_connection(
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(Rails.env, 'main', db_config)
)
end
diff --git a/spec/lib/gitlab/database/transaction/observer_spec.rb b/spec/lib/gitlab/database/transaction/observer_spec.rb
index 778212add66..2d5a59a2d5d 100644
--- a/spec/lib/gitlab/database/transaction/observer_spec.rb
+++ b/spec/lib/gitlab/database/transaction/observer_spec.rb
@@ -21,6 +21,8 @@ RSpec.describe Gitlab::Database::Transaction::Observer, feature_category: :datab
it 'tracks transaction data', :aggregate_failures do
ActiveRecord::Base.transaction do
+ User.first
+
ActiveRecord::Base.transaction(requires_new: true) do
User.first
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index 7e0435c815b..624e2b5c144 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -244,9 +244,9 @@ RSpec.describe Gitlab::Database::WithLockRetries, feature_category: :database do
it 'executes `SET LOCAL lock_timeout` using the configured timeout value in milliseconds' do
expect(connection).to receive(:execute).with("RESET idle_in_transaction_session_timeout; RESET lock_timeout").and_call_original
- expect(connection).to receive(:execute).with("SAVEPOINT active_record_1", "TRANSACTION").and_call_original
+ expect(connection).to receive(:create_savepoint).with('active_record_1')
expect(connection).to receive(:execute).with("SET LOCAL lock_timeout TO '15ms'").and_call_original
- expect(connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1", "TRANSACTION").and_call_original
+ expect(connection).to receive(:release_savepoint).with('active_record_1')
subject.run {}
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 3813e40fbe3..796fe75521a 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -40,16 +40,6 @@ RSpec.describe Gitlab::GitalyClient, feature_category: :gitaly do
end
end
- describe '.filesystem_id_from_disk' do
- it 'catches errors' do
- [Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
- stub_file_read(described_class.storage_metadata_file_path('default'), error: error)
-
- expect(described_class.filesystem_id_from_disk('default')).to be_nil
- end
- end
- end
-
describe '.filesystem_id' do
it 'returns an empty string when the relevant storage status is not found in the response' do
response = double("response")
@@ -361,19 +351,6 @@ RSpec.describe Gitlab::GitalyClient, feature_category: :gitaly do
end
end
- describe '.can_use_disk?' do
- it 'properly caches a false result' do
- # spec_helper stubs this globally
- allow(described_class).to receive(:can_use_disk?).and_call_original
- expect(described_class).to receive(:filesystem_id).once
- expect(described_class).to receive(:filesystem_id_from_disk).once
-
- 2.times do
- described_class.can_use_disk?('unknown')
- end
- end
- end
-
describe '.connection_data' do
it 'returns connection data' do
address = 'tcp://localhost:9876'
diff --git a/spec/migrations/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads_spec.rb b/spec/migrations/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads_spec.rb
index 27ecc255a2a..2640fbfe932 100644
--- a/spec/migrations/20231011142714_queue_backfill_has_remediations_of_vulnerability_reads_spec.rb
+++ b/spec/migrations/20231031204841_requeue_backfill_has_remediations_of_vulnerability_reads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_migration!
-RSpec.describe QueueBackfillHasRemediationsOfVulnerabilityReads, feature_category: :database do
+RSpec.describe RequeueBackfillHasRemediationsOfVulnerabilityReads, feature_category: :database do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
diff --git a/spec/scripts/trigger-build_spec.rb b/spec/scripts/trigger-build_spec.rb
index f46adb1a9f1..a1bedd19ed3 100644
--- a/spec/scripts/trigger-build_spec.rb
+++ b/spec/scripts/trigger-build_spec.rb
@@ -236,14 +236,62 @@ RSpec.describe Trigger, feature_category: :tooling do
describe "TRIGGER_BRANCH" do
context 'when CNG_BRANCH is not set' do
- it 'sets TRIGGER_BRANCH to master' do
- stub_env('CI_PROJECT_NAMESPACE', 'gitlab-org')
- expect(subject.variables['TRIGGER_BRANCH']).to eq('master')
+ context 'with gitlab-org' do
+ before do
+ stub_env('CI_PROJECT_NAMESPACE', 'gitlab-org')
+ end
+
+ it 'sets TRIGGER_BRANCH to master if the commit ref is master' do
+ stub_env('CI_COMMIT_REF_NAME', 'master')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', nil)
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('master')
+ end
+
+ it 'sets the TRIGGER_BRANCH to master if the commit is part of an MR targeting master' do
+ stub_env('CI_COMMIT_REF_NAME', 'feature_branch')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', 'master')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('master')
+ end
+
+ it 'sets TRIGGER_BRANCH to stable branch if the commit ref is a stable branch' do
+ stub_env('CI_COMMIT_REF_NAME', '16-6-stable-ee')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('16-6-stable')
+ end
+
+ it 'sets the TRIGGER_BRANCH to stable branch if the commit is part of an MR targeting stable branch' do
+ stub_env('CI_COMMIT_REF_NAME', 'feature_branch')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', '16-6-stable-ee')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('16-6-stable')
+ end
end
- it 'sets TRIGGER_BRANCH to main-jh on JH side' do
- stub_env('CI_PROJECT_NAMESPACE', 'gitlab-cn')
- expect(subject.variables['TRIGGER_BRANCH']).to eq('main-jh')
+ context 'with gitlab-cn' do
+ before do
+ stub_env('CI_PROJECT_NAMESPACE', 'gitlab-cn')
+ end
+
+ it 'sets TRIGGER_BRANCH to main-jh if commit ref is main-jh' do
+ stub_env('CI_COMMIT_REF_NAME', 'main-jh')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', nil)
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('main-jh')
+ end
+
+ it 'sets the TRIGGER_BRANCH to main-jh if the commit is part of an MR targeting main-jh' do
+ stub_env('CI_COMMIT_REF_NAME', 'feature_branch')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', 'main-jh')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('main-jh')
+ end
+
+ it 'sets TRIGGER_BRANCH to 16-6-stable if commit ref is a stable branch' do
+ stub_env('CI_COMMIT_REF_NAME', '16-6-stable-jh')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('16-6-stable')
+ end
+
+ it 'sets the TRIGGER_BRANCH to 16-6-stable if the commit is part of an MR targeting 16-6-stable-jh' do
+ stub_env('CI_COMMIT_REF_NAME', 'feature_branch')
+ stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', '16-6-stable-jh')
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('16-6-stable')
+ end
end
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 16b9d2618ca..1aac6ac8adb 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -352,13 +352,53 @@ RSpec.describe Projects::ImportService, feature_category: :importers do
end
end
+ context 'when import is a local request' do
+ before do
+ project.import_url = "http://127.0.0.1/group/project"
+ end
+
+ context 'when local network requests are enabled' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'returns an error' do
+ expect(project.repository).not_to receive(:import_repository)
+ expect(subject.execute).to include(
+ status: :error,
+ message: end_with('Requests to localhost are not allowed')
+ )
+ end
+
+ context 'when environment is development' do
+ before do
+ stub_rails_env('development')
+ end
+
+ it 'imports successfully' do
+ expect(project.repository)
+ .to receive(:import_repository)
+ .and_return(true)
+ expect(subject.execute[:status]).to eq(:success)
+ end
+ end
+ end
+ end
+
context 'when DNS rebind protection is disabled' do
before do
allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(false)
project.import_url = "https://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
- .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: false)
+ .with(
+ project.import_url,
+ ports: Project::VALID_IMPORT_PORTS,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ allow_local_network: false,
+ allow_localhost: false,
+ dns_rebind_protection: false
+ )
.and_return([Addressable::URI.parse("https://example.com/group/project"), nil])
end
@@ -386,7 +426,14 @@ RSpec.describe Projects::ImportService, feature_category: :importers do
project.import_url = "https://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
- .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
+ .with(
+ project.import_url,
+ ports: Project::VALID_IMPORT_PORTS,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ allow_local_network: false,
+ allow_localhost: false,
+ dns_rebind_protection: true
+ )
.and_return([Addressable::URI.parse("https://172.16.123.1/group/project"), 'example.com'])
end
@@ -407,7 +454,14 @@ RSpec.describe Projects::ImportService, feature_category: :importers do
project.import_url = 'https://gitlab.com/gitlab-org/gitlab-development-kit'
allow(Gitlab::UrlBlocker).to receive(:validate!)
- .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
+ .with(
+ project.import_url,
+ ports: Project::VALID_IMPORT_PORTS,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ allow_local_network: false,
+ allow_localhost: false,
+ dns_rebind_protection: true
+ )
.and_return([Addressable::URI.parse('https://[2606:4700:90:0:f22e:fbec:5bed:a9b9]/gitlab-org/gitlab-development-kit'), 'gitlab.com'])
end
@@ -430,7 +484,14 @@ RSpec.describe Projects::ImportService, feature_category: :importers do
project.import_url = "http://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
- .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
+ .with(
+ project.import_url,
+ ports: Project::VALID_IMPORT_PORTS,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ allow_local_network: false,
+ allow_localhost: false,
+ dns_rebind_protection: true
+ )
.and_return([Addressable::URI.parse("http://172.16.123.1/group/project"), 'example.com'])
end
@@ -452,7 +513,14 @@ RSpec.describe Projects::ImportService, feature_category: :importers do
project.import_url = "git://example.com/group/project.git"
allow(Gitlab::UrlBlocker).to receive(:validate!)
- .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
+ .with(
+ project.import_url,
+ ports: Project::VALID_IMPORT_PORTS,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ allow_local_network: false,
+ allow_localhost: false,
+ dns_rebind_protection: true
+ )
.and_return([Addressable::URI.parse("git://172.16.123.1/group/project"), 'example.com'])
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index 4dcf58d3ebb..39aca404480 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -550,7 +550,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor
with_them do
it 'outputs changed message for automation after operations happen' do
- allow(ActiveRecord::Base.connection.schema_migration).to receive(:table_exists?).and_return(schema_migration_table_exists)
+ allow(ActiveRecord::Base.connection).to receive_message_chain(:schema_migration, :table_exists?).and_return(schema_migration_table_exists)
allow_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(needs_migrations)
expect { run_rake_task('gitlab:db:unattended') }.to output(/^#{rake_output}$/).to_stdout
end