diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-16 21:09:09 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-16 21:09:09 +0000 |
commit | cffe2c2c348d86d67298fa6516d49c36d696ab2d (patch) | |
tree | 9511c62edd677012325946d19e4d2df4a9733aa1 | |
parent | 7ff36fc6e9f0f5dffb41bfc79b4f07b2ce93e1c7 (diff) | |
download | gitlab-ce-cffe2c2c348d86d67298fa6516d49c36d696ab2d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
65 files changed, 1088 insertions, 750 deletions
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index c9b6a4f9913..5a47e76d597 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -86,48 +86,43 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => { /** * Render an alert at the top of the page, or, optionally an - * arbitrary existing container. - * - * This alert is always dismissible. - * - * Usage: - * - * 1. Render a new alert + * arbitrary existing container. This alert is always dismissible. * + * @example + * // Render a new alert * import { createAlert, VARIANT_WARNING } from '~/flash'; * * createAlert({ message: 'My error message' }); * createAlert({ message: 'My warning message', variant: VARIANT_WARNING }); * - * 2. Dismiss this alert programmatically - * + * @example + * // Dismiss this alert programmatically * const alert = createAlert({ message: 'Message' }); * * // ... * * alert.dismiss(); * - * 3. Respond to the alert being dismissed - * - * createAlert({ message: 'Message', onDismiss: () => { ... }}); + * @example + * // Respond to the alert being dismissed + * createAlert({ message: 'Message', onDismiss: () => {} }); * - * @param {Object} options Options to control the flash message - * @param {String} options.message Alert message text - * @param {String?} options.variant Which GlAlert variant to use, should be VARIANT_SUCCESS, VARIANT_WARNING, VARIANT_DANGER, VARIANT_INFO or VARIANT_TIP. Defaults to VARIANT_DANGER. - * @param {Object?} options.parent Reference to parent element under which alert needs to appear. Defaults to `document`. - * @param {Function?} options.onDismiss Handler to call when this alert is dismissed. - * @param {Object?} options.containerSelector Selector for the container of the alert - * @param {Object?} options.primaryButton Object describing primary button of alert - * @param {String?} link Href of primary button - * @param {String?} text Text of primary button - * @param {Function?} clickHandler Handler to call when primary button is clicked on. The click event is sent as an argument. - * @param {Object?} options.secondaryButton Object describing secondary button of alert - * @param {String?} link Href of secondary button - * @param {String?} text Text of secondary button - * @param {Function?} clickHandler Handler to call when secondary button is clicked on. The click event is sent as an argument. - * @param {Boolean?} options.captureError Whether to send error to Sentry - * @param {Object} options.error Error to be captured in Sentry - * @returns + * @param {object} options - Options to control the flash message + * @param {string} options.message - Alert message text + * @param {VARIANT_SUCCESS|VARIANT_WARNING|VARIANT_DANGER|VARIANT_INFO|VARIANT_TIP} [options.variant] - Which GlAlert variant to use; it defaults to VARIANT_DANGER. + * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`. + * @param {Function} [options.onDismiss] - Handler to call when this alert is dismissed. + * @param {string} [options.containerSelector] - Selector for the container of the alert + * @param {object} [options.primaryButton] - Object describing primary button of alert + * @param {string} [options.primaryButton.link] - Href of primary button + * @param {string} [options.primaryButton.text] - Text of primary button + * @param {Function} [options.primaryButton.clickHandler] - Handler to call when primary button is clicked on. The click event is sent as an argument. + * @param {object} [options.secondaryButton] - Object describing secondary button of alert + * @param {string} [options.secondaryButton.link] - Href of secondary button + * @param {string} [options.secondaryButton.text] - Text of secondary button + * @param {Function} [options.secondaryButton.clickHandler] - Handler to call when secondary button is clicked on. The click event is sent as an argument. + * @param {boolean} [options.captureError] - Whether to send error to Sentry + * @param {object} [options.error] - Error to be captured in Sentry */ const createAlert = function createAlert({ message, @@ -208,22 +203,24 @@ const createAlert = function createAlert({ }; /** - * Flash banner supports different types of Flash configurations - * along with ability to provide actionConfig which can be used to show - * additional action or link on banner next to message + * @deprecated use `createAlert` instead + * + * Flash banner supports different types of Flash configurations + * along with ability to provide actionConfig which can be used to show + * additional action or link on banner next to message * - * @param {Object} options Options to control the flash message - * @param {String} options.message Flash message text - * @param {String} options.type Type of Flash, it can be `notice`, `success`, `warning` or `alert` (default) - * @param {Object} options.parent Reference to parent element under which Flash needs to appear - * @param {Object} options.actionConfig Map of config to show action on banner - * @param {String} href URL to which action config should point to (default: '#') - * @param {String} title Title of action - * @param {Function} clickHandler Method to call when action is clicked on - * @param {Boolean} options.fadeTransition Boolean to determine whether to fade the alert out - * @param {Boolean} options.captureError Boolean to determine whether to send error to Sentry - * @param {Object} options.error Error to be captured in Sentry - * @deprecated Use `createAlert` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/362334. + * @param {object} options - Options to control the flash message + * @param {string} options.message - Flash message text + * @param {'alert'|'notice'|'success'|'warning'} [options.type] - Type of Flash; it defaults to 'alert' + * @param {Element|Document} [options.parent] - Reference to parent element under which Flash needs to appear + * @param {object} [options.actionConfig] - Map of config to show action on banner + * @param {string} [options.actionConfig.href] - URL to which action config should point to (default: '#') + * @param {string} [options.actionConfig.title] - Title of action + * @param {Function} [options.actionConfig.clickHandler] - Method to call when action is clicked on + * @param {boolean} [options.fadeTransition] - Boolean to determine whether to fade the alert out + * @param {boolean} [options.addBodyClass] - Adds `flash-shown` class to the `body` element + * @param {boolean} [options.captureError] - Boolean to determine whether to send error to Sentry + * @param {object} [options.error] - Error to be captured in Sentry */ const createFlash = function createFlash({ message, diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 4101b520e80..2f3cdc525a7 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -195,7 +195,11 @@ $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() { * Quick fix: Get rid of jQuery for this implementation */ const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page); -if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) { +if ( + !isBoardsPage && + !window.gon?.features?.movedMrSidebar && + (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs') +) { const $rightSidebar = $('aside.right-sidebar'); const $layoutPage = $('.layout-page'); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 61f7a079d77..e02109d1fd1 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -1,5 +1,4 @@ /* eslint-disable no-new, class-methods-use-this */ -import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import $ from 'jquery'; import Vue from 'vue'; import { getCookie, isMetaClick, parseBoolean, scrollToElement } from '~/lib/utils/common_utils'; @@ -176,6 +175,8 @@ export default class MergeRequestTabs { : null; this.navbar = document.querySelector('.navbar-gitlab'); this.peek = document.getElementById('js-peek'); + this.sidebar = document.querySelector('.js-right-sidebar'); + this.pageLayout = document.querySelector('.layout-page'); this.paddingTop = 16; this.scrollPositions = {}; @@ -282,7 +283,7 @@ export default class MergeRequestTabs { if (action === 'commits') { this.loadCommits(href); - this.expandView(); + // this.hideSidebar(); this.resetViewContainer(); this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); } else if (action === 'new') { @@ -301,13 +302,12 @@ export default class MergeRequestTabs { */ this.loadDiff(href); } - if (bp.getBreakpointSize() !== 'xl') { - this.shrinkView(); - } + // this.hideSidebar(); this.expandViewContainer(); this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); this.commitsTab.classList.remove('active'); } else if (action === 'pipelines') { + // this.hideSidebar(); this.resetViewContainer(); this.mountPipelinesView(); } else { @@ -320,9 +320,7 @@ export default class MergeRequestTabs { notesTab.classList.add('active'); } - if (bp.getBreakpointSize() !== 'xs') { - this.expandView(); - } + // this.showSidebar(); this.resetViewContainer(); this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); } @@ -509,19 +507,6 @@ export default class MergeRequestTabs { } } - shrinkView() { - const $gutterBtn = $('.js-sidebar-toggle:visible'); - const $expandSvg = $gutterBtn.find('.js-sidebar-expand'); - - // Wait until listeners are set - setTimeout(() => { - // Only when sidebar is expanded - if ($expandSvg.length && $expandSvg.hasClass('hidden')) { - $gutterBtn.trigger('click', [true]); - } - }, 0); - } - // Expand the issuable sidebar unless the user explicitly collapsed it expandView() { if (parseBoolean(getCookie('collapsed_gutter'))) { @@ -538,4 +523,24 @@ export default class MergeRequestTabs { } }, 0); } + + hideSidebar() { + if (!isInVueNoteablePage() || this.cachedPageLayoutClasses) return; + + this.cachedPageLayoutClasses = this.pageLayout.className; + this.pageLayout.classList.remove( + 'right-sidebar-collapsed', + 'right-sidebar-expanded', + 'page-with-icon-sidebar', + ); + this.sidebar.style.width = '0px'; + } + + showSidebar() { + if (!isInVueNoteablePage() || !this.cachedPageLayoutClasses) return; + + this.pageLayout.className = this.cachedPageLayoutClasses; + this.sidebar.style.width = ''; + delete this.cachedPageLayoutClasses; + } } diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 009afe03ea6..a3abc8b8e90 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -7,6 +7,18 @@ import createFlash from './flash'; import axios from './lib/utils/axios_utils'; import { sprintf, s__, __ } from './locale'; +const updateSidebarClasses = (layoutPage, rightSidebar) => { + if (window.innerWidth >= 768) { + layoutPage.classList.remove('right-sidebar-expanded', 'right-sidebar-collapsed'); + rightSidebar.classList.remove('right-sidebar-collapsed'); + rightSidebar.classList.add('right-sidebar-expanded'); + } else { + layoutPage.classList.add('right-sidebar-collapsed', 'is-merge-request'); + rightSidebar.classList.add('right-sidebar-collapsed'); + rightSidebar.classList.remove('right-sidebar-expanded'); + } +}; + function Sidebar() { this.toggleTodo = this.toggleTodo.bind(this); this.sidebar = $('aside'); @@ -42,13 +54,22 @@ Sidebar.prototype.addEventListeners = function () { this.sidebar.on('hiddenGlDropdown', this, this.onSidebarDropdownHidden); $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked); - return $(document) - .off('click', '.js-issuable-todo') - .on('click', '.js-issuable-todo', this.toggleTodo); + $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo); + + if (window.gon?.features?.movedMrSidebar) { + const layoutPage = document.querySelector('.layout-page'); + const rightSidebar = document.querySelector('.js-right-sidebar'); + + updateSidebarClasses(layoutPage, rightSidebar); + window.addEventListener('resize', () => updateSidebarClasses(layoutPage, rightSidebar)); + } }; Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { const $this = $(this); + + if ($this.hasClass('right-sidebar-merge-requests')) return; + const $collapseIcon = $('.js-sidebar-collapse'); const $expandIcon = $('.js-sidebar-expand'); const $toggleContainer = $('.js-sidebar-toggle-container'); diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue index 68deb3761a3..6e18cf36690 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; import { n__, __ } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'AssigneeTitle', @@ -8,6 +9,7 @@ export default { GlLoadingIcon, GlIcon, }, + mixins: [glFeatureFlagMixin()], props: { loading: { type: Boolean, @@ -63,6 +65,7 @@ export default { v-if="showToggle" :aria-label="__('Toggle sidebar')" class="gutter-toggle float-right js-sidebar-toggle" + :class="{ 'gl-display-block gl-md-display-none!': glFeatures.movedMrSidebar }" href="#" role="button" > diff --git a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue index 63ba5c31809..b0502202b0e 100644 --- a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue +++ b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue @@ -111,12 +111,6 @@ export default { /> <div data-testid="lock-status" class="sidebar-item-value" :class="lockStatus.class"> - <gl-icon - :size="16" - :name="lockStatus.icon" - class="sidebar-item-icon" - :class="lockStatus.iconClass" - /> {{ lockStatus.displayText }} </div> </div> diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index fbecb21ecbd..77e41648e9b 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -103,7 +103,7 @@ export default { </div> <div v-if="showParticipantLabel" - class="title hide-collapsed gl-mb-2 gl-line-height-20 gl-font-weight-bold" + class="title hide-collapsed gl-mb-2! gl-line-height-20 gl-font-weight-bold" > <gl-loading-icon v-if="loading" size="sm" :inline="true" /> {{ participantLabel }} diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue index ee3972ab3d5..2f58e11c00f 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue @@ -98,7 +98,7 @@ export default { 'gl-mb-3': index !== users.length - 1, 'attention-requests': glFeatures.mrAttentionRequests, }" - class="gl-display-grid gl-align-items-center reviewer-grid" + class="gl-display-grid gl-align-items-center reviewer-grid gl-mr-2" data-testid="reviewer" > <reviewer-avatar-link diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue index 7a10a9f3a4c..1bafa845665 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue @@ -5,6 +5,7 @@ import { IssuableType } from '~/issues/constants'; import { isLoggedIn } from '~/lib/utils/common_utils'; import { __, sprintf } from '~/locale'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { subscribedQueries, Tracking } from '~/sidebar/constants'; const ICON_ON = 'notifications'; @@ -25,6 +26,7 @@ export default { GlToggle, SidebarEditableItem, }, + mixins: [glFeatureFlagMixin()], props: { iid: { type: String, @@ -82,6 +84,9 @@ export default { }, }, computed: { + isMergeRequest() { + return this.issuableType === IssuableType.MergeRequest && this.glFeatures.movedMrSidebar; + }, isLoading() { return this.$apollo.queries?.subscribed?.loading || this.loading; }, @@ -171,7 +176,20 @@ export default { </script> <template> + <li v-if="isMergeRequest" class="gl-new-dropdown-item"> + <button type="button" class="dropdown-item" @click="toggleSubscribed"> + <span class="gl-new-dropdown-item-text-wrapper"> + <template v-if="subscribed"> + {{ __('Turn off notifications') }} + </template> + <template v-else> + {{ __('Turn on notifications') }} + </template> + </span> + </button> + </li> <sidebar-editable-item + v-else ref="editable" :title="$options.i18n.notifications" :tracking="$options.tracking" diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index e7d47634af2..057bb9f0100 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -204,7 +204,7 @@ export default { :time-estimate-human-readable="humanTimeEstimate" /> <div - class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center gl-font-weight-bold" + class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center gl-font-weight-bold gl-mr-3" > {{ __('Time tracking') }} <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" class="gl-ml-2" inline /> diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 87dac85d4cd..7c5ccd07913 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -19,13 +19,23 @@ .right-sidebar-collapsed { padding-right: 0; - @include media-breakpoint-up(sm) { - &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { - padding-right: $gutter-collapsed-width; + &:not(.is-merge-request) { + @include media-breakpoint-up(sm) { + &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { + padding-right: $gutter-collapsed-width; + } + + .merge-request-tabs-holder.affix { + right: $gutter-collapsed-width; + } } + } - .merge-request-tabs-holder.affix { - right: $gutter-collapsed-width; + &.is-merge-request { + @include media-breakpoint-up(md) { + .content-wrapper { + padding-right: $gutter-collapsed-width; + } } } @@ -49,6 +59,18 @@ padding-right: 0; z-index: $zindex-dropdown-menu; + &.right-sidebar-merge-requests { + width: 270px; + + @include media-breakpoint-up(md) { + z-index: auto; + } + + .shortcut-sidebar-dropdown-toggle { + margin-right: 0 !important; + } + } + @include media-breakpoint-only(sm) { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { padding-right: $gutter-collapsed-width; @@ -73,10 +95,18 @@ .right-sidebar { border-left: 1px solid $gray-50; - .sidebar-container, - .issuable-sidebar { - // Add 100px so that potentially visible vertical scroll bar is hidden - width: calc(100% + 100px); + &.right-sidebar-merge-requests { + @include media-breakpoint-up(md) { + border-left: 0; + } + } + + &:not(.right-sidebar-merge-requests) { + .sidebar-container, + .issuable-sidebar { + // Add 100px so that potentially visible vertical scroll bar is hidden + width: calc(100% + 100px); + } } } @@ -231,6 +261,10 @@ margin-right: -$gl-spacing-scale-2; } +.issuable-sidebar.is-merge-request .edit-link { + margin-right: 0; +} + .assignee-grid { grid-template-areas: ' attention user'; grid-template-columns: min-content 1fr; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 269ce4042fc..f04cdfba0e4 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -735,3 +735,11 @@ $tabs-holder-z-index: 250; .attention-request-sidebar-popover { z-index: 999; } + +.merge-request-overview { + @include media-breakpoint-up(md) { + display: grid; + grid-template-columns: 1fr 270px; + grid-gap: 5%; + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index a0088e5ab56..5b8366e91bf 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -114,7 +114,7 @@ } } -.right-sidebar { +@mixin right-sidebar { position: fixed; top: $header-height; // Default value for CSS var must contain a unit @@ -125,6 +125,18 @@ background-color: $white; z-index: 200; overflow: hidden; +} + +.right-sidebar { + &:not(.right-sidebar-merge-requests) { + @include right-sidebar; + } + + &.right-sidebar-merge-requests { + @include media-breakpoint-down(sm) { + @include right-sidebar; + } + } @include media-breakpoint-down(sm) { z-index: 251; @@ -135,10 +147,6 @@ &:hover { color: $blue-800; - - .avatar { - border-color: rgba($gray-normal, 0.2); - } } } @@ -168,6 +176,21 @@ } } + &.right-sidebar-merge-requests { + .block, + .sidebar-contained-width, + .issuable-sidebar-header { + width: 100%; + border-bottom: 0; + } + + .block { + @include media-breakpoint-up(md) { + padding: $gl-spacing-scale-5 0; + } + } + } + .block, .sidebar-contained-width, .issuable-sidebar-header { @@ -224,9 +247,20 @@ .issuable-sidebar { height: 100%; - overflow-y: scroll; - overflow-x: hidden; - -webkit-overflow-scrolling: touch; + + &:not(.is-merge-request) { + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + + &.is-merge-request { + @include media-breakpoint-down(sm) { + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + } } &.affix-top .issuable-sidebar { @@ -234,7 +268,9 @@ } &.right-sidebar-expanded { - width: $gutter-width; + &:not(.right-sidebar-merge-requests) { + width: $gutter-width; + } .value { line-height: 1; @@ -242,6 +278,12 @@ .issuable-sidebar { padding: 0 20px; + + &.is-merge-request { + @include media-breakpoint-up(md) { + padding: 0; + } + } } &:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) { @@ -284,8 +326,17 @@ /* Extra small devices (phones, less than 768px) */ display: none; /* Small devices (tablets, 768px and up) */ - @include media-breakpoint-up(sm) { - display: block; + + &:not(.right-sidebar-merge-requests) { + @include media-breakpoint-up(sm) { + display: block; + } + } + + &.right-sidebar-merge-requests { + @include media-breakpoint-up(md) { + display: block; + } } width: $gutter-collapsed-width; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c40871c858f..0d3ed0e7c71 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -233,8 +233,7 @@ $tabs-holder-z-index: 250; top: calc(#{$header-height} + #{$system-header-height} + #{$performance-bar-height}); } - @include media-breakpoint-up(sm) { - position: -webkit-sticky; + @include media-breakpoint-up(md) { position: sticky; } diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index be61232eace..458df40ece1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -47,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:mr_attention_requests, current_user) push_frontend_feature_flag(:updated_mr_header, project) push_frontend_feature_flag(:remove_diff_header_icons, project) + push_frontend_feature_flag(:moved_mr_sidebar, project) end before_action do diff --git a/app/finders/alert_management/alerts_finder.rb b/app/finders/alert_management/alerts_finder.rb index 1fbc1a4a258..11c0f37a79c 100644 --- a/app/finders/alert_management/alerts_finder.rb +++ b/app/finders/alert_management/alerts_finder.rb @@ -66,5 +66,3 @@ module AlertManagement end end end - -AlertManagement::AlertsFinder.prepend_mod_with('AlertManagement::AlertsFinder') diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 9c604209409..6f957d2511f 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -41,6 +41,8 @@ module Types field :architecture_name, GraphQL::Types::String, null: true, description: 'Architecture provided by the the runner.', method: :architecture + field :maintenance_note, GraphQL::Types::String, null: true, + description: 'Runner\'s maintenance notes.' field :groups, ::Types::GroupType.connection_type, null: true, description: 'Groups the runner is associated with. For group runners only.' field :id, ::Types::GlobalIDType[::Ci::Runner], null: false, diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 6ecc41151af..1bd27158be4 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -13,6 +13,8 @@ module IssuablesHelper end def sidebar_gutter_collapsed_class + return "right-sidebar-expanded" if moved_mr_sidebar_enabled? + "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 821ea403dc9..a2730c53ea2 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -256,6 +256,10 @@ module MergeRequestsHelper _('%{author} requested to merge %{source_branch} %{copy_button} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe } end + + def moved_mr_sidebar_enabled? + Feature.enabled?(:moved_mr_sidebar, @project) && defined?(@merge_request) + end end MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper') diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index d4d3fddb2cd..37cd491e19f 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -19,11 +19,13 @@ module NavHelper end def page_gutter_class + moved_sidebar_enabled = current_controller?('merge_requests') && moved_mr_sidebar_enabled? + if page_has_markdown? if cookies[:collapsed_gutter] == 'true' - %w[page-gutter right-sidebar-collapsed] + ["page-gutter", "#{'right-sidebar-collapsed' unless moved_sidebar_enabled}"] else - %w[page-gutter right-sidebar-expanded] + ["page-gutter", "#{'right-sidebar-expanded' unless moved_sidebar_enabled}"] end elsif current_path?('jobs#show') %w[page-gutter build-sidebar right-sidebar-expanded] diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index 047a4d510f9..9f05c87018d 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -81,7 +81,6 @@ module AlertManagement scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) } scope :not_resolved, -> { without_status(:resolved) } scope :with_prometheus_alert, -> { includes(:prometheus_alert) } - scope :with_threat_monitoring_alerts, -> { where(domain: :threat_monitoring ) } scope :with_operations_alerts, -> { where(domain: :operations) } scope :order_start_time, -> (sort_order) { order(started_at: sort_order) } diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 21f7e410843..d1e169a1f78 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -18,7 +18,6 @@ module Clusters default_value_for :version, VERSION scope :preload_cluster_platform, -> { preload(cluster: [:platform_kubernetes]) } - scope :with_clusters_with_cilium, -> { joins(:cluster).merge(Clusters::Cluster.with_available_cilium) } attr_encrypted :alert_manager_token, mode: :per_attribute_iv, diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 87afa9f9491..014f7530357 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -137,7 +137,6 @@ module Clusters scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) } scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) } - scope :with_available_cilium, -> { joins(:application_cilium).merge(::Clusters::Applications::Cilium.available) } scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct } scope :managed, -> { where(managed: true) } diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index 1dc8451dc09..3c0f7d91a03 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -5,7 +5,11 @@ class DeployToken < ApplicationRecord include TokenAuthenticatable include PolicyActor include Gitlab::Utils::StrongMemoize - add_authentication_token_field :token, encrypted: :optional + include IgnorableColumns + + ignore_column :token, remove_with: '15.2', remove_after: '2022-07-22' + + add_authentication_token_field :token, encrypted: :required AVAILABLE_SCOPES = %i(read_repository read_registry write_registry read_package_registry write_package_registry).freeze diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb index d6aafe45ae9..5a30f55595b 100644 --- a/app/models/integrations/prometheus.rb +++ b/app/models/integrations/prometheus.rb @@ -30,7 +30,6 @@ module Integrations after_create_commit :create_default_alerts scope :preload_project, -> { preload(:project) } - scope :with_clusters_with_cilium, -> { joins(project: [:clusters]).merge(Clusters::Cluster.with_available_cilium) } def show_active_box? false diff --git a/app/services/clusters/kubernetes.rb b/app/services/clusters/kubernetes.rb index ef549b56946..819ac4c8464 100644 --- a/app/services/clusters/kubernetes.rb +++ b/app/services/clusters/kubernetes.rb @@ -14,7 +14,5 @@ module Clusters GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding' KNATIVE_SERVING_NAMESPACE = 'knative-serving' ISTIO_SYSTEM_NAMESPACE = 'istio-system' - GITLAB_CILIUM_ROLE_NAME = 'gitlab-cilium-role' - GITLAB_CILIUM_ROLE_BINDING_NAME = 'gitlab-cilium-rolebinding' end end diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb index ecad33fc7c0..eabc428d0d2 100644 --- a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb +++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb @@ -53,8 +53,6 @@ module Clusters create_or_update_knative_serving_role_binding create_or_update_crossplane_database_role create_or_update_crossplane_database_role_binding - create_or_update_cilium_role - create_or_update_cilium_role_binding end private @@ -99,14 +97,6 @@ module Clusters kubeclient.update_role_binding(crossplane_database_role_binding_resource) end - def create_or_update_cilium_role - kubeclient.update_role(cilium_role_resource) - end - - def create_or_update_cilium_role_binding - kubeclient.update_role_binding(cilium_role_binding_resource) - end - def service_account_resource Gitlab::Kubernetes::ServiceAccount.new( service_account_name, @@ -185,28 +175,6 @@ module Clusters service_account_name: service_account_name ).generate end - - def cilium_role_resource - Gitlab::Kubernetes::Role.new( - name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, - namespace: service_account_namespace, - rules: [{ - apiGroups: %w(cilium.io), - resources: %w(ciliumnetworkpolicies), - verbs: %w(get list create update patch) - }] - ).generate - end - - def cilium_role_binding_resource - Gitlab::Kubernetes::RoleBinding.new( - name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, - role_name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, - role_kind: :Role, - namespace: service_account_namespace, - service_account_name: service_account_name - ).generate - end end end end diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml index df9ef28726a..d4e8893407c 100644 --- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml +++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml @@ -38,3 +38,7 @@ = link_to new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request)), class: 'dropdown-item' do .gl-new-dropdown-item-text-wrapper = _('Report abuse') + - if current_user && moved_mr_sidebar_enabled? + %li.gl-new-dropdown-divider + %hr.dropdown-divider + %li.gl-new-dropdown-item.js-sidebar-subscriptions-entry-point diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml index 70b0a7dc650..638c520e210 100644 --- a/app/views/projects/merge_requests/_mr_title.html.haml +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -27,7 +27,7 @@ = issuable_meta(@merge_request, @project) %div - %button.gl-button.btn.btn-default.btn-icon.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ type: 'button' } + %button.gl-button.btn.btn-default.btn-icon.float-right.gl-display-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ type: 'button', class: "#{'gl-md-display-none!' if moved_mr_sidebar_enabled? } #{'gl-sm-display-none!' unless moved_mr_sidebar_enabled?}" } = sprite_icon('chevron-double-lg-left') .detail-page-header-actions.js-issuable-actions{ class: "#{'gl-align-self-start is-merge-request' if updated_mr_header_enabled}" } diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 91f999eda36..13e5451df98 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -1,5 +1,6 @@ - @gfm_form = true -- @content_class = "merge-request-container#{' limit-container-width' unless fluid_layout}" +- unless moved_mr_sidebar_enabled? + - @content_class = "merge-request-container#{' limit-container-width' unless fluid_layout}" - add_to_breadcrumbs _("Merge requests"), project_merge_requests_path(@project) - breadcrumb_title @merge_request.to_reference - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", _("Merge requests") @@ -47,8 +48,8 @@ #js-diff-file-finder #js-code-navigation = render "projects/merge_requests/tabs/pane", id: "notes", class: "notes voting_notes" do - .row - %section.col-md-12 + %div{ class: "#{'merge-request-overview' if moved_mr_sidebar_enabled?}" } + %section .issuable-discussion.js-vue-notes-event - if @merge_request.description.present? .detail-page-description @@ -70,6 +71,8 @@ help_page_path: suggest_changes_help_path, current_user_data: @current_user_data, is_locked: @merge_request.discussion_locked.to_s } } + - if moved_mr_sidebar_enabled? + = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch = render "projects/merge_requests/tabs/pane", name: "commits", id: "commits", class: "commits" do -# This tab is always loaded via AJAX @@ -83,7 +86,8 @@ .loading.hide = gl_loading_icon(size: 'lg') -= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch +- unless moved_mr_sidebar_enabled? + = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch - if @merge_request.can_be_reverted?(current_user) = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 0af9b1cf2e3..4317c77eb89 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -8,11 +8,12 @@ - add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras" - reviewers = local_assigns.fetch(:reviewers, nil) - in_group_context_with_iterations = @project.group.present? && issuable_sidebar[:supports_iterations] +- moved_sidebar_enabled = moved_mr_sidebar_enabled? && issuable_type === 'merge_request' -%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in }, issuable_type: issuable_type }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite', 'aria-label': issuable_type } - .issuable-sidebar - .issuable-sidebar-header.gl-py-3 - %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } +%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in }, issuable_type: issuable_type }, class: "#{sidebar_gutter_collapsed_class} #{'right-sidebar-merge-requests' if moved_sidebar_enabled}", 'aria-live' => 'polite', 'aria-label': issuable_type } + .issuable-sidebar{ class: "#{'is-merge-request' if moved_sidebar_enabled}" } + .issuable-sidebar-header{ class: "#{'gl-pb-2! gl-md-display-flex gl-justify-content-end' if moved_sidebar_enabled}" } + %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", class: "#{'gl-display-block gl-md-display-none!' if moved_sidebar_enabled}", href: "#", "aria-label" => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } = sidebar_gutter_toggle_icon - if signed_in .js-issuable-todo{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } @@ -76,14 +77,14 @@ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe #js-lock-entry-point - - if signed_in + - if signed_in && !moved_sidebar_enabled .js-sidebar-subscriptions-entry-point .js-sidebar-participants-entry-point .block.with-sub-blocks #js-reference-entry-point - - if issuable_type == 'merge_request' + - if issuable_type == 'merge_request' && !moved_sidebar_enabled .sub-block.js-sidebar-source-branch .sidebar-collapsed-icon.js-dont-change-state = clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport', class: 'btn-clipboard gl-button btn-default-tertiary btn-icon btn-sm js-source-branch-copy') diff --git a/config/feature_flags/development/moved_mr_sidebar.yml b/config/feature_flags/development/moved_mr_sidebar.yml new file mode 100644 index 00000000000..b12d3023e3c --- /dev/null +++ b/config/feature_flags/development/moved_mr_sidebar.yml @@ -0,0 +1,8 @@ +--- +name: moved_mr_sidebar +introduced_by_url: +rollout_issue_url: +milestone: '14.10' +type: development +group: group::code review +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c5060028488..8d7ca8a84b1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -9567,6 +9567,7 @@ Represents the total number of issues and their weights for a particular day. | <a id="cirunneripaddress"></a>`ipAddress` | [`String`](#string) | IP address of the runner. | | <a id="cirunnerjobcount"></a>`jobCount` | [`Int`](#int) | Number of jobs processed by the runner (limited to 1000, plus one to indicate that more items exist). | | <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. | +| <a id="cirunnermaintenancenote"></a>`maintenanceNote` | [`String`](#string) | Runner's maintenance notes. | | <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. | | <a id="cirunnerpaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is paused and not available to run jobs. | | <a id="cirunnerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner. | diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index 20bde16a6bd..e7d927de2cf 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -625,6 +625,11 @@ Use lowercase for **merge requests**. If you use **MR** as the acronym, spell it Use lowercase for **milestones**. +## n/a, N/A, not applicable + +When possible, use **not applicable**. Spelling out the phrase helps non-English speaking users and avoids +capitalization inconsistencies. + ## navigate Do not use **navigate**. Use **go** instead. For example: diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md index 4f797f5c238..e2d984dbbff 100644 --- a/doc/topics/autodevops/customize.md +++ b/doc/topics/autodevops/customize.md @@ -26,7 +26,7 @@ used for the build. Specify either: -- The CI/CD variable `BUILDPACK_URL` according to [`pack`'s specifications](https://buildpacks.io/docs/app-developer-guide/specify-buildpacks/). +- The CI/CD variable `BUILDPACK_URL` with any of [`pack`'s URI specification formats](https://buildpacks.io/docs/app-developer-guide/specify-buildpacks/). - A [`project.toml` project descriptor](https://buildpacks.io/docs/app-developer-guide/using-project-descriptor/) with the buildpacks you would like to include. ### Custom buildpacks with Herokuish diff --git a/lib/gitlab/alert_management/payload.rb b/lib/gitlab/alert_management/payload.rb index 1b67b91e839..de34a0f5d47 100644 --- a/lib/gitlab/alert_management/payload.rb +++ b/lib/gitlab/alert_management/payload.rb @@ -4,8 +4,7 @@ module Gitlab module AlertManagement module Payload MONITORING_TOOLS = { - prometheus: 'Prometheus', - cilium: 'Cilium' + prometheus: 'Prometheus' }.freeze class << self @@ -48,5 +47,3 @@ module Gitlab end end end - -Gitlab::AlertManagement::Payload.prepend_mod_with('Gitlab::AlertManagement::Payload') diff --git a/lib/gitlab/ci/badge/coverage/template.rb b/lib/gitlab/ci/badge/coverage/template.rb index f12b4f2dbfb..18db4861dc9 100644 --- a/lib/gitlab/ci/badge/coverage/template.rb +++ b/lib/gitlab/ci/badge/coverage/template.rb @@ -23,13 +23,11 @@ module Gitlab::Ci MIN_MEDIUM_DEFAULT = 75 def initialize(badge) - @entity = badge.entity @status = badge.status - @key_text = badge.customization.dig(:key_text) - @key_width = badge.customization.dig(:key_width) @min_good = badge.customization.dig(:min_good) @min_acceptable = badge.customization.dig(:min_acceptable) @min_medium = badge.customization.dig(:min_medium) + super end def value_text diff --git a/lib/gitlab/ci/badge/pipeline/template.rb b/lib/gitlab/ci/badge/pipeline/template.rb index c39f96e4a34..417fff252a3 100644 --- a/lib/gitlab/ci/badge/pipeline/template.rb +++ b/lib/gitlab/ci/badge/pipeline/template.rb @@ -22,10 +22,8 @@ module Gitlab::Ci }.freeze def initialize(badge) - @entity = badge.entity @status = badge.status - @key_text = badge.customization.dig(:key_text) - @key_width = badge.customization.dig(:key_width) + super end def value_text diff --git a/lib/gitlab/ci/badge/release/template.rb b/lib/gitlab/ci/badge/release/template.rb index 65bff4371cf..354be6276fa 100644 --- a/lib/gitlab/ci/badge/release/template.rb +++ b/lib/gitlab/ci/badge/release/template.rb @@ -13,10 +13,8 @@ module Gitlab::Ci VALUE_WIDTH_DEFAULT = 54 def initialize(badge) - @entity = badge.entity @tag = badge.tag || "none" - @key_width = badge.customization.dig(:key_width) - @key_text = badge.customization.dig(:key_text) + super end def key_text diff --git a/lib/gitlab/ci/badge/template.rb b/lib/gitlab/ci/badge/template.rb index d514a8577bd..b185fadc3a2 100644 --- a/lib/gitlab/ci/badge/template.rb +++ b/lib/gitlab/ci/badge/template.rb @@ -12,7 +12,8 @@ module Gitlab::Ci def initialize(badge) @entity = badge.entity - @status = badge.status + @key_text = badge.customization.dig(:key_text) + @key_width = badge.customization.dig(:key_width) end def key_text diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb index c2f1466d945..2e31849caaa 100644 --- a/lib/gitlab/query_limiting/transaction.rb +++ b/lib/gitlab/query_limiting/transaction.rb @@ -63,12 +63,14 @@ module Gitlab GEO_NODES_LOAD = 'SELECT 1 AS one FROM "geo_nodes" LIMIT 1' LICENSES_LOAD = 'SELECT "licenses".* FROM "licenses" ORDER BY "licenses"."id"' + ATTR_INTROSPECTION = %r/SELECT .*\ba.attname\b.* (FROM|JOIN) pg_attribute a/m.freeze # queries can be safely ignored if they are amoritized in regular usage # (i.e. only requested occasionally and otherwise cached). def ignorable?(sql) return true if sql&.include?(GEO_NODES_LOAD) return true if sql&.include?(LICENSES_LOAD) + return true if ATTR_INTROSPECTION =~ sql false end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b90d5774a38..68106c97361 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -40027,9 +40027,15 @@ msgstr "" msgid "Turn off" msgstr "" +msgid "Turn off notifications" +msgstr "" + msgid "Turn on" msgstr "" +msgid "Turn on notifications" +msgstr "" + msgid "Twitter" msgstr "" diff --git a/qa/lib/gitlab/page/group/settings/billing.rb b/qa/lib/gitlab/page/group/settings/billing.rb index 24d327502f8..d0d73278890 100644 --- a/qa/lib/gitlab/page/group/settings/billing.rb +++ b/qa/lib/gitlab/page/group/settings/billing.rb @@ -5,13 +5,30 @@ module Gitlab module Group module Settings class Billing < Chemlab::Page - # TODO: Supplant with data-qa-selectors - h4 :billing_plan_header, css: 'div.billing-plan-header h4' - + h4 :billing_plan_header link :start_your_free_trial + link :upgrade_to_premium + link :upgrade_to_ultimate + + # Subscription details + strong :subscription_header + button :refresh_seats + + # Usage + p :seats_in_subscription + p :seats_currently_in_use + link :see_seats_usage + p :max_seats_used + p :seats_owed + + # Billing + p :subscription_start_date + p :subscription_end_date - link :upgrade_to_premium, css: '[data-testid="plan-card-premium"] a.billing-cta-purchase-new' - link :upgrade_to_ultimate, css: '[data-testid="plan-card-ultimate"] a.billing-cta-purchase-new' + def refresh_subscription_seats + refresh_seats + ::QA::Support::WaitForRequests.wait_for_requests + end end end end diff --git a/qa/lib/gitlab/page/group/settings/billing.stub.rb b/qa/lib/gitlab/page/group/settings/billing.stub.rb index 64176af794a..c49d744d61f 100644 --- a/qa/lib/gitlab/page/group/settings/billing.stub.rb +++ b/qa/lib/gitlab/page/group/settings/billing.stub.rb @@ -100,6 +100,222 @@ module Gitlab def upgrade_to_ultimate? # This is a stub, used for indexing. The method is dynamically generated. end + + # @note Defined as +strong :subscription_header+ + # @return [String] The text content or value of +subscription_header+ + def subscription_header + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_header_element).to exist + # end + # @return [Watir::Strong] The raw +Strong+ element + def subscription_header_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_header + # end + # @return [Boolean] true if the +subscription_header+ element is present on the page + def subscription_header? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +button :refresh_seats+ + # Clicks +refresh_seats+ + def refresh_seats + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.refresh_seats_element).to exist + # end + # @return [Watir::Button] The raw +Button+ element + def refresh_seats_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_refresh_seats + # end + # @return [Boolean] true if the +refresh_seats+ element is present on the page + def refresh_seats? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_in_subscription+ + # @return [String] The text content or value of +seats_in_subscription+ + def seats_in_subscription + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_in_subscription_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_in_subscription_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_in_subscription + # end + # @return [Boolean] true if the +seats_in_subscription+ element is present on the page + def seats_in_subscription? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_currently_in_use+ + # @return [String] The text content or value of +seats_currently_in_use+ + def seats_currently_in_use + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_currently_in_use_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_currently_in_use_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_currently_in_use + # end + # @return [Boolean] true if the +seats_currently_in_use+ element is present on the page + def seats_currently_in_use? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +link :see_seats_usage+ + # Clicks +see_seats_usage+ + def see_seats_usage + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.see_seats_usage_element).to exist + # end + # @return [Watir::Link] The raw +Link+ element + def see_seats_usage_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_see_seats_usage + # end + # @return [Boolean] true if the +see_seats_usage+ element is present on the page + def see_seats_usage? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :max_seats_used+ + # @return [String] The text content or value of +max_seats_used+ + def max_seats_used + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.max_seats_used_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def max_seats_used_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_max_seats_used + # end + # @return [Boolean] true if the +max_seats_used+ element is present on the page + def max_seats_used? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_owed+ + # @return [String] The text content or value of +seats_owed+ + def seats_owed + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_owed_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_owed_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_owed + # end + # @return [Boolean] true if the +seats_owed+ element is present on the page + def seats_owed? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :subscription_start_date+ + # @return [String] The text content or value of +subscription_start_date+ + def subscription_start_date + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_start_date_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def subscription_start_date_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_start_date + # end + # @return [Boolean] true if the +subscription_start_date+ element is present on the page + def subscription_start_date? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :subscription_end_date+ + # @return [String] The text content or value of +subscription_end_date+ + def subscription_end_date + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_end_date_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def subscription_end_date_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_end_date + # end + # @return [Boolean] true if the +subscription_end_date+ element is present on the page + def subscription_end_date? + # This is a stub, used for indexing. The method is dynamically generated. + end end end end diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index 83adb10c3a0..0061f74cec5 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -12,7 +12,8 @@ module QA QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}]) response = post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level } - response.code == QA::Support::API::HTTP_STATUS_CREATED + break true if response.code == QA::Support::API::HTTP_STATUS_CREATED + break true if response.body.include?('Member already exists') end end diff --git a/qa/qa/service/cluster_provider/k3s_cilium.rb b/qa/qa/service/cluster_provider/k3s_cilium.rb deleted file mode 100644 index 5b529caa20b..00000000000 --- a/qa/qa/service/cluster_provider/k3s_cilium.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -module QA - module Service - module ClusterProvider - class K3sCilium < K3s - def setup - @k3s = Service::DockerRun::K3s.new.tap do |k3s| - k3s.remove! - k3s.cni_enabled = true - k3s.register! - - shell "kubectl config set-cluster k3s --server https://#{k3s.host_name}:6443 --insecure-skip-tls-verify" - shell 'kubectl config set-credentials default --username=node --password=some-secret' - shell 'kubectl config set-context k3s --cluster=k3s --user=default' - shell 'kubectl config use-context k3s' - - wait_for_server(k3s.host_name) do - shell 'kubectl version' - # install local storage - shell 'kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml' - - # patch local storage - shell %(kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}') - shell 'kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.8/install/kubernetes/quick-install.yaml' - - wait_for_namespaces do - wait_for_cilium - wait_for_coredns do - shell 'kubectl create -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.31.0/deploy/static/provider/cloud/deploy.yaml' - wait_for_ingress - end - end - end - end - end - - private - - def wait_for_cilium - QA::Runtime::Logger.info 'Waiting for Cilium pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces -l k8s-app=cilium --no-headers=true | grep -o "cilium-.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'Cilium pod has not initialized correctly' - end - - def wait_for_coredns - QA::Runtime::Logger.info 'Waiting for CoreDNS pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces --no-headers=true | grep -o "coredns.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'CoreDNS pod has not been initialized correctly' - end - - def wait_for_ingress - QA::Runtime::Logger.info 'Waiting for Ingress controller pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces -l app.kubernetes.io/component=controller | grep -o "ingress-nginx-controller.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'Ingress pod has not been initialized correctly' - end - end - end - end -end diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb index ca9f137716b..d7207803d45 100644 --- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb @@ -57,7 +57,6 @@ module QA MEDIA_TYPE: 'application/vnd.docker.distribution.manifest.v2+json' before_script: - token=$(curl -u "$CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD" "https://$CI_SERVER_HOST/jwt/auth?service=container_registry&scope=repository:$CI_PROJECT_PATH:pull,push,delete" | jq -r '.token') - - echo $token script: - 'digest=$(curl -L -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/master" | jq -r ".layers[0].digest")' - 'curl -L -X DELETE -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/blobs/$digest"' diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb index 589a62a68bb..7e9e58edc1e 100644 --- a/spec/factories/alert_management/alerts.rb +++ b/spec/factories/alert_management/alerts.rb @@ -113,20 +113,6 @@ FactoryBot.define do end end - trait :cilium do - monitoring_tool { Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:cilium] } - payload do - { - annotations: { - title: 'This is a cilium alert', - summary: 'Summary of the alert', - description: 'Description of the alert' - }, - startsAt: started_at - }.with_indifferent_access - end - end - trait :all_fields do with_incident with_assignee diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb index b2c478fd3fe..a2116b738fd 100644 --- a/spec/factories/deploy_tokens.rb +++ b/spec/factories/deploy_tokens.rb @@ -2,7 +2,6 @@ FactoryBot.define do factory :deploy_token do - token { nil } token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt(SecureRandom.hex(50)) } sequence(:name) { |n| "PDT #{n}" } read_repository { true } diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 6b663445124..ef3346b9763 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -11,6 +11,10 @@ RSpec.describe 'Group issues page' do let(:project_with_issues_disabled) { create(:project, :issues_disabled, group: group) } let(:path) { issues_group_path(group) } + before do + stub_feature_flags(vue_issues_list: true) + end + context 'with shared examples', :js do let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} @@ -58,10 +62,10 @@ RSpec.describe 'Group issues page' do let(:user2) { user_outside_group } it 'filters by only group users' do - filtered_search.set('assignee:=') + select_tokens 'Assignee', '=' - expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) - expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) + expect_suggestion(user.name) + expect_no_suggestion(user2.name) end end end @@ -76,23 +80,9 @@ RSpec.describe 'Group issues page' do it 'returns all group and subgroup issues' do visit issues_group_path(group) - page.within('.issuable-list') do - expect(page).to have_selector('li.issue', count: 2) - expect(page).to have_content('root group issue') - expect(page).to have_content('subgroup issue') - end - end - - it 'truncates issue counts if over the threshold', :clean_gitlab_redis_cache do - allow(Rails.cache).to receive(:read).and_call_original - allow(Rails.cache).to receive(:read).with( - ['group', group.id, 'issues'], - { expires_in: Gitlab::IssuablesCountForState::CACHE_EXPIRES_IN } - ).and_return({ opened: 1050, closed: 500, all: 1550 }) - - visit issues_group_path(group) - - expect(page).to have_text('Open 1.1k Closed 500 All 1.6k') + expect(page).to have_selector('li.issue', count: 2) + expect(page).to have_content('root group issue') + expect(page).to have_content('subgroup issue') end context 'when project is archived' do @@ -115,7 +105,6 @@ RSpec.describe 'Group issues page' do let!(:subgroup_issue) { create(:issue, project: subgroup_project) } before do - stub_feature_flags(vue_issues_list: true) visit issues_group_path(group_with_no_issues) end @@ -135,14 +124,10 @@ RSpec.describe 'Group issues page' do end it 'shows projects only with issues feature enabled', :js do - within '.empty-state' do - click_button 'Toggle project select' - end + click_button 'Toggle project select' - page.within('.select2-results') do - expect(page).to have_content(project.full_name) - expect(page).not_to have_content(project_with_issues_disabled.full_name) - end + expect(page).to have_button project.full_name + expect(page).not_to have_button project_with_issues_disabled.full_name end end end @@ -155,15 +140,15 @@ RSpec.describe 'Group issues page' do let!(:issue3) { create(:issue, project: project, title: 'Issue #3', relative_position: 3) } before do + stub_feature_flags(vue_issues_list: false) + sign_in(user_in_group) end it 'displays all issues' do visit issues_group_path(group, sort: 'relative_position') - page.within('.issues-list') do - expect(page).to have_selector('li.issue', count: 3) - end + expect(page).to have_selector('li.issue', count: 3) end it 'has manual-ordering css applied' do @@ -218,11 +203,9 @@ RSpec.describe 'Group issues page' do end def check_issue_order - page.within('.manual-ordering') do - expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2') - expect(find('.issue:nth-child(2) .title')).to have_content('Issue #3') - expect(find('.issue:nth-child(3) .title')).to have_content('Issue #1') - end + expect(page).to have_css('.issue:nth-child(1) .title', text: 'Issue #2') + expect(page).to have_css('.issue:nth-child(2) .title', text: 'Issue #3') + expect(page).to have_css('.issue:nth-child(3) .title', text: 'Issue #1') end end @@ -239,14 +222,8 @@ RSpec.describe 'Group issues page' do end it 'shows the pagination' do - expect(page).to have_link 'Prev' - expect(page).to have_link 'Next' - end - - it 'first pagination item is active' do - page.within('.gl-pagination') do - expect(find('li.active')).to have_content('1') - end + expect(page).to have_button 'Prev', disabled: true + expect(page).to have_button 'Next' end end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 479199b72b7..ea888d4b254 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -17,6 +17,8 @@ RSpec.describe 'Labels Hierarchy', :js do let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') } before do + stub_feature_flags(vue_issues_list: true) + grandparent.add_owner(user) sign_in(user) @@ -34,8 +36,6 @@ RSpec.describe 'Labels Hierarchy', :js do click_on 'Close' end - wait_for_requests - expect(page).to have_selector('.gl-label', text: label.title) end end @@ -44,8 +44,6 @@ RSpec.describe 'Labels Hierarchy', :js do page.within('.block.labels') do click_on 'Edit' - wait_for_requests - expect(page).not_to have_text(child_group_label.title) end end @@ -54,15 +52,21 @@ RSpec.describe 'Labels Hierarchy', :js do shared_examples 'filtering by ancestor labels for projects' do |board = false| it 'filters by ancestor labels' do [grandparent_group_label, parent_group_label, project_label_1].each do |label| - select_label_on_dropdown(label.title) - - wait_for_requests - if board + select_label_on_dropdown(label.title) + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end else + within '[data-testid="filtered-search-input"]' do + click_filtered_search_bar + click_on 'Label' + click_on '= is' + click_on label.title + send_keys :enter + end + expect_issues_list_count(1) expect(page).to have_selector('.issue-title', text: labeled_issue.title) end @@ -70,9 +74,11 @@ RSpec.describe 'Labels Hierarchy', :js do end it 'does not filter by descendant group labels' do - filtered_search.set("label=") - - wait_for_requests + if board + filtered_search.set("label=") + else + select_tokens 'Label', '=' + end expect(page).not_to have_link child_group_label.title end @@ -93,11 +99,9 @@ RSpec.describe 'Labels Hierarchy', :js do it 'filters by ancestors and current group labels' do [grandparent_group_label, parent_group_label].each do |label| - select_label_on_dropdown(label.title) - - wait_for_requests - if board + select_label_on_dropdown(label.title) + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end @@ -106,6 +110,14 @@ RSpec.describe 'Labels Hierarchy', :js do expect(card).to have_selector('a', text: labeled_issue_2.title) end else + within '[data-testid="filtered-search-input"]' do + click_filtered_search_bar + click_on 'Label' + click_on '= is' + click_on label.title + send_keys :enter + end + expect_issues_list_count(3) expect(page).to have_selector('.issue-title', text: labeled_issue.title) expect(page).to have_selector('.issue-title', text: labeled_issue_2.title) @@ -115,11 +127,9 @@ RSpec.describe 'Labels Hierarchy', :js do end it 'filters by descendant group labels' do - wait_for_requests - - select_label_on_dropdown(group_label_3.title) - if board + select_label_on_dropdown(group_label_3.title) + expect(page).to have_selector('.board-card-title') do |card| expect(card).not_to have_selector('a', text: labeled_issue_2.title) end @@ -128,17 +138,23 @@ RSpec.describe 'Labels Hierarchy', :js do expect(card).to have_selector('a', text: labeled_issue_3.title) end else + select_tokens 'Label', '=', group_label_3.title, submit: true + expect_issues_list_count(1) expect(page).to have_selector('.issue-title', text: labeled_issue_3.title) end end it 'does not filter by descendant group project labels' do - filtered_search.set("label=") + if board + filtered_search.set("label=") - wait_for_requests + expect(page).not_to have_selector('.btn-link', text: project_label_3.title) + else + select_tokens 'Label', '=' - expect(page).not_to have_selector('.btn-link', text: project_label_3.title) + expect(page).not_to have_link project_label_3.title + end end end @@ -195,9 +211,7 @@ RSpec.describe 'Labels Hierarchy', :js do it_behaves_like 'filtering by ancestor labels for projects' it 'does not filter by descendant group labels' do - filtered_search.set("label=") - - wait_for_requests + select_tokens 'Label', '=' expect(page).not_to have_link child_group_label.title end diff --git a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb index 7d67cde4bbb..f5b5460769e 100644 --- a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb +++ b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb @@ -59,7 +59,6 @@ RSpec.describe 'Batch diffs', :js do # Confirm scrolled to correct UI element expect(get_first_diff.find('.discussion-notes .timeline-entry li.note[id]').obscured?).to be_falsey - expect(get_second_diff.find('.discussion-notes .timeline-entry li.note[id]').obscured?).to be_truthy end end diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb index 3cdb22000f6..c64c761b8d1 100644 --- a/spec/features/merge_request/user_manages_subscription_spec.rb +++ b/spec/features/merge_request/user_manages_subscription_spec.rb @@ -6,29 +6,60 @@ RSpec.describe 'User manages subscription', :js do let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } + let(:moved_mr_sidebar_enabled) { false } before do + stub_feature_flags(moved_mr_sidebar: moved_mr_sidebar_enabled) + project.add_maintainer(user) sign_in(user) visit(merge_request_path(merge_request)) end - it 'toggles subscription' do - page.within('[data-testid="subscription-toggle"]') do + context 'moved sidebar flag disabled' do + it 'toggles subscription' do + page.within('[data-testid="subscription-toggle"]') do + wait_for_requests + + expect(page).to have_css 'button:not(.is-checked)' + find('button:not(.is-checked)').click + + wait_for_requests + + expect(page).to have_css 'button.is-checked' + find('button.is-checked').click + + wait_for_requests + + expect(page).to have_css 'button:not(.is-checked)' + end + end + end + + context 'moved sidebar flag enabled' do + let(:moved_mr_sidebar_enabled) { true } + + it 'toggles subscription' do wait_for_requests - expect(page).to have_css 'button:not(.is-checked)' - find('button:not(.is-checked)').click + click_button 'Toggle dropdown' + + expect(page).to have_content('Turn on notifications') + click_button 'Turn on notifications' wait_for_requests - expect(page).to have_css 'button.is-checked' - find('button.is-checked').click + click_button 'Toggle dropdown' + + expect(page).to have_content('Turn off notifications') + click_button 'Turn off notifications' wait_for_requests - expect(page).to have_css 'button:not(.is-checked)' + click_button 'Toggle dropdown' + + expect(page).to have_content('Turn on notifications') end end end 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 fbb4847130c..1f4682b4a46 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 @@ -119,6 +119,8 @@ RSpec.describe 'User views an open merge request' do let(:source_branch) { "'><iframe/srcdoc=''></iframe>" } before do + stub_feature_flags(moved_mr_sidebar: false) + project.repository.create_branch(source_branch, "master") mr = create(:merge_request, source_project: project, target_project: project, source_branch: source_branch) diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report.json b/spec/fixtures/security_reports/master/gl-common-scanning-report.json index 1fb00b2ff3a..787573301bb 100644 --- a/spec/fixtures/security_reports/master/gl-common-scanning-report.json +++ b/spec/fixtures/security_reports/master/gl-common-scanning-report.json @@ -1,300 +1,422 @@ { - "vulnerabilities": [ - { - "category": "dependency_scanning", - "name": "Vulnerabilities in libxml2", - "message": "Vulnerabilities in libxml2 in nokogiri", - "description": "", - "cve": "CVE-1020", - "severity": "High", - "solution": "Upgrade to latest version.", - "scanner": { - "id": "gemnasium", - "name": "Gemnasium" - }, - "evidence": { - "source": { - "id": "assert:CORS - Bad 'Origin' value", - "name": "CORS - Bad 'Origin' value" - }, - "summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - }, - "response": { - "headers": [ - { - "name": "Server", - "value": "TwistedWeb/20.3.0" - } - ], - "reason_phrase": "OK", - "status_code": 200, - "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" - }, - "supporting_messages": [ - { - "name": "Origional", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - } - }, - { - "name": "Recorded", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - }, - "response": { - "headers": [ - { - "name": "Server", - "value": "TwistedWeb/20.3.0" - } - ], - "reason_phrase": "OK", - "status_code": 200, - "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" - } - } - ] - }, - "location": {}, - "identifiers": [ - { - "type": "GitLab", - "name": "Foo vulnerability", - "value": "foo" - } - ], - "links": [ - { - "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020" - } - ], - "details": { - "commit": { - "name": [ - { - "lang": "en", - "value": "The Commit" - } - ], - "description": [ - { - "lang": "en", - "value": "Commit where the vulnerability was identified" - } - ], - "type": "commit", - "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" - } - } + "vulnerabilities": [{ + "category": "dependency_scanning", + "name": "Vulnerability for remediation testing 1", + "message": "This vulnerability should have ONE remediation", + "description": "", + "cve": "CVE-2137", + "severity": "High", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Foo vulnerability", + "value": "foo" + }], + "links": [{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137" + }], + "details": { + "commit": { + "name": [{ + "lang": "en", + "value": "The Commit" + }], + "description": [{ + "lang": "en", + "value": "Commit where the vulnerability was identified" + }], + "type": "commit", + "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" + } + } + }, + { + "category": "dependency_scanning", + "name": "Vulnerability for remediation testing 2", + "message": "This vulnerability should have ONE remediation", + "description": "", + "cve": "CVE-2138", + "severity": "High", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Foo vulnerability", + "value": "foo" + }], + "links": [{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2138" + }], + "details": { + "commit": { + "name": [{ + "lang": "en", + "value": "The Commit" + }], + "description": [{ + "lang": "en", + "value": "Commit where the vulnerability was identified" + }], + "type": "commit", + "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" + } + } + }, + { + "category": "dependency_scanning", + "name": "Vulnerability for remediation testing 3", + "message": "Remediation for this vulnerability should remediate CVE-2140 as well", + "description": "", + "cve": "CVE-2139", + "severity": "High", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Foo vulnerability", + "value": "foo" + }], + "links": [{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2139" + }], + "details": { + "commit": { + "name": [{ + "lang": "en", + "value": "The Commit" + }], + "description": [{ + "lang": "en", + "value": "Commit where the vulnerability was identified" + }], + "type": "commit", + "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" + } + } + }, + { + "category": "dependency_scanning", + "name": "Vulnerability for remediation testing 4", + "message": "Remediation for this vulnerability should remediate CVE-2139 as well", + "description": "", + "cve": "CVE-2140", + "severity": "High", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Foo vulnerability", + "value": "foo" + }], + "links": [{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2140" + }], + "details": { + "commit": { + "name": [{ + "lang": "en", + "value": "The Commit" + }], + "description": [{ + "lang": "en", + "value": "Commit where the vulnerability was identified" + }], + "type": "commit", + "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" + } + } + }, + { + "category": "dependency_scanning", + "name": "Vulnerabilities in libxml2", + "message": "Vulnerabilities in libxml2 in nokogiri", + "description": "", + "cve": "CVE-1020", + "severity": "High", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "evidence": { + "source": { + "id": "assert:CORS - Bad 'Origin' value", + "name": "CORS - Bad 'Origin' value" }, - { - "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3", - "category": "dependency_scanning", - "name": "Regular Expression Denial of Service", - "message": "Regular Expression Denial of Service in debug", - "description": "", - "cve": "CVE-1030", - "severity": "Unknown", - "solution": "Upgrade to latest versions.", - "scanner": { - "id": "gemnasium", - "name": "Gemnasium" - }, - "evidence": { - "source": { - "id": "assert:CORS - Bad 'Origin' value", - "name": "CORS - Bad 'Origin' value" - }, - "summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - }, - "response": { - "headers": [ - { - "name": "Server", - "value": "TwistedWeb/20.3.0" - } - ], - "reason_phrase": "OK", - "status_code": 200, - "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" - }, - "supporting_messages": [ - { - "name": "Origional", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - } - }, - { - "name": "Recorded", - "request": { - "headers": [ - { - "name": "Host", - "value": "127.0.0.1:7777" - } - ], - "method": "GET", - "url": "http://127.0.0.1:7777/api/users", - "body": "" - }, - "response": { - "headers": [ - { - "name": "Server", - "value": "TwistedWeb/20.3.0" - } - ], - "reason_phrase": "OK", - "status_code": 200, - "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" - } - } - ] - }, - "location": {}, - "identifiers": [ - { - "type": "GitLab", - "name": "Bar vulnerability", - "value": "bar" - } - ], - "links": [ - { - "name": "CVE-1030", - "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030" - } - ] + "summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" }, - { - "category": "dependency_scanning", - "name": "Authentication bypass via incorrect DOM traversal and canonicalization", - "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js", - "description": "", - "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98", - "severity": "Unknown", - "solution": "Upgrade to fixed version.\r\n", - "scanner": { - "id": "gemnasium", - "name": "Gemnasium" + "response": { + "headers": [{ + "name": "Server", + "value": "TwistedWeb/20.3.0" + }], + "reason_phrase": "OK", + "status_code": 200, + "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" + }, + "supporting_messages": [{ + "name": "Origional", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" + } + }, + { + "name": "Recorded", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" }, - "location": {}, - "identifiers": [], - "links": [ - ] + "response": { + "headers": [{ + "name": "Server", + "value": "TwistedWeb/20.3.0" + }], + "reason_phrase": "OK", + "status_code": 200, + "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" + } + } + ] + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Foo vulnerability", + "value": "foo" + }], + "links": [{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020" + }], + "details": { + "commit": { + "name": [{ + "lang": "en", + "value": "The Commit" + }], + "description": [{ + "lang": "en", + "value": "Commit where the vulnerability was identified" + }], + "type": "commit", + "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" } - ], - "remediations": [ - { - "fixes": [ - { - "cve": "CVE-1020" - } - ], - "summary": "", - "diff": "" - }, - { - "fixes": [ - { - "cve": "CVE", - "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" - } - ], - "summary": "", - "diff": "" + } + }, + { + "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3", + "category": "dependency_scanning", + "name": "Regular Expression Denial of Service", + "message": "Regular Expression Denial of Service in debug", + "description": "", + "cve": "CVE-1030", + "severity": "Unknown", + "solution": "Upgrade to latest versions.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "evidence": { + "source": { + "id": "assert:CORS - Bad 'Origin' value", + "name": "CORS - Bad 'Origin' value" }, - { - "fixes": [ - { - "cve": "CVE", - "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" - } - ], - "summary": "", - "diff": "" + "summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" }, - { - "fixes": [ - { - "id": "2134", - "cve": "CVE-1" - } - ], - "summary": "", - "diff": "" - } - ], - "dependency_files": [], - "scan": { - "analyzer": { - "id": "common-analyzer", - "name": "Common Analyzer", - "url": "https://site.com/analyzer/common", - "version": "2.0.1", - "vendor": { - "name": "Common" - } + "response": { + "headers": [{ + "name": "Server", + "value": "TwistedWeb/20.3.0" + }], + "reason_phrase": "OK", + "status_code": 200, + "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" }, - "scanner": { - "id": "gemnasium", - "name": "Gemnasium", - "url": "https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven", - "vendor": { - "name": "GitLab" + "supporting_messages": [{ + "name": "Origional", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" + } + }, + { + "name": "Recorded", + "request": { + "headers": [{ + "name": "Host", + "value": "127.0.0.1:7777" + }], + "method": "GET", + "url": "http://127.0.0.1:7777/api/users", + "body": "" }, - "version": "2.18.0" - }, - "type": "dependency_scanning", - "start_time": "placeholder-value", - "end_time": "placeholder-value", - "status": "success" + "response": { + "headers": [{ + "name": "Server", + "value": "TwistedWeb/20.3.0" + }], + "reason_phrase": "OK", + "status_code": 200, + "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" + } + } + ] + }, + "location": {}, + "identifiers": [{ + "type": "GitLab", + "name": "Bar vulnerability", + "value": "bar" + }], + "links": [{ + "name": "CVE-1030", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030" + }] + }, + { + "category": "dependency_scanning", + "name": "Authentication bypass via incorrect DOM traversal and canonicalization", + "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js", + "description": "", + "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98", + "severity": "Unknown", + "solution": "Upgrade to fixed version.\r\n", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": {}, + "identifiers": [], + "links": [] + } + ], + "remediations": [{ + "fixes": [{ + "cve": "CVE-2137" + }], + "summary": "this remediates CVE-2137", + "diff": "dG90YWxseSBsZWdpdCBkaWZm" + }, + { + "fixes": [{ + "cve": "CVE-2138" + }], + "summary": "this remediates CVE-2138", + "diff": "dG90YWxseSBsZWdpdCBkaWZm" + }, + { + "fixes": [{ + "cve": "CVE-2139" + }, { + "cve": "CVE-2140" + }], + "summary": "this remediates CVE-2139 and CVE-2140", + "diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5" + }, + { + "fixes": [{ + "cve": "CVE-1020" + }], + "summary": "", + "diff": "" + }, + { + "fixes": [{ + "cve": "CVE", + "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" + }], + "summary": "", + "diff": "" + }, + { + "fixes": [{ + "cve": "CVE", + "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" + }], + "summary": "", + "diff": "" + }, + { + "fixes": [{ + "id": "2134", + "cve": "CVE-1" + }], + "summary": "", + "diff": "" + } + ], + "dependency_files": [], + "scan": { + "analyzer": { + "id": "common-analyzer", + "name": "Common Analyzer", + "url": "https://site.com/analyzer/common", + "version": "2.0.1", + "vendor": { + "name": "Common" + } + }, + "scanner": { + "id": "gemnasium", + "name": "Gemnasium", + "url": "https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven", + "vendor": { + "name": "GitLab" + }, + "version": "2.18.0" }, - "version": "14.0.2" + "type": "dependency_scanning", + "start_time": "placeholder-value", + "end_time": "placeholder-value", + "status": "success" + }, + "version": "14.0.2" } diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js index edcacb0f740..e6e587ff44b 100644 --- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import Mousetrap from 'mousetrap'; import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import waitForPromises from 'helpers/wait_for_promises'; import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm'; @@ -13,7 +12,6 @@ jest.mock('~/lib/utils/common_utils', () => ({ describe('ShortcutsIssuable', () => { const snippetShowFixtureName = 'snippets/show.html'; - const mrShowFixtureName = 'merge_requests/merge_request_of_current_user.html'; beforeAll(() => { initCopyAsGFM(); @@ -282,40 +280,4 @@ describe('ShortcutsIssuable', () => { }); }); }); - - describe('copyBranchName', () => { - let sidebarCollapsedBtn; - let sidebarExpandedBtn; - - beforeEach(() => { - loadHTMLFixture(mrShowFixtureName); - - window.shortcut = new ShortcutsIssuable(); - - [sidebarCollapsedBtn, sidebarExpandedBtn] = document.querySelectorAll( - '.js-source-branch-copy', - ); - - [sidebarCollapsedBtn, sidebarExpandedBtn].forEach((btn) => jest.spyOn(btn, 'click')); - }); - - afterEach(() => { - delete window.shortcut; - resetHTMLFixture(); - }); - - describe('when the sidebar is expanded', () => { - beforeEach(() => { - // simulate the applied CSS styles when the - // sidebar is expanded - sidebarCollapsedBtn.style.display = 'none'; - - Mousetrap.trigger('b'); - }); - - it('clicks the "expanded" version of the copy source branch button', () => { - expect(sidebarExpandedBtn.click).toHaveBeenCalled(); - }); - }); - }); }); diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb index c042f6dac19..14ebe85d80e 100644 --- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb @@ -39,8 +39,8 @@ RSpec.describe Resolvers::AlertManagement::AlertResolver do end context 'filtering by domain' do - let_it_be(:alert1) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) } - let_it_be(:alert2) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) } + let_it_be(:alert1) { create(:alert_management_alert, project: project, monitoring_tool: 'other', domain: :threat_monitoring) } + let_it_be(:alert2) { create(:alert_management_alert, project: project, monitoring_tool: 'other', domain: :threat_monitoring) } let_it_be(:alert3) { create(:alert_management_alert, project: project, monitoring_tool: 'generic') } let(:args) { { domain: 'operations' } } diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index b2fbfda898e..26ac7a4da8d 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -12,7 +12,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do id description created_at contacted_at maximum_timeout access_level active paused status version short_sha revision locked run_untagged ip_address runner_type tag_list project_count job_count admin_url edit_admin_url user_permissions executor_name architecture_name platform_name - groups projects jobs token_expires_at + maintenance_note groups projects jobs token_expires_at ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index dfc5dec1481..6495d1f654b 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -292,7 +292,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do expect(scans.map(&:status).all?('success')).to be(true) expect(scans.map(&:start_time).all?('placeholder-value')).to be(true) expect(scans.map(&:end_time).all?('placeholder-value')).to be(true) - expect(scans.size).to eq(3) + expect(scans.size).to eq(7) expect(scans.first).to be_a(::Gitlab::Ci::Reports::Security::Scan) end @@ -348,22 +348,29 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do it 'returns links object for each finding', :aggregate_failures do links = report.findings.flat_map(&:links) - expect(links.map(&:url)).to match_array(['https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030']) - expect(links.map(&:name)).to match_array([nil, 'CVE-1030']) - expect(links.size).to eq(2) + expect(links.map(&:url)).to match_array(['https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030', + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137", "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2138", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2139", "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2140"]) + expect(links.map(&:name)).to match_array([nil, nil, nil, nil, nil, 'CVE-1030']) + expect(links.size).to eq(6) expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link) end end describe 'parsing evidence' do - it 'returns evidence object for each finding', :aggregate_failures do - evidences = report.findings.map(&:evidence) + RSpec::Matchers.define_negated_matcher :have_values, :be_empty - expect(evidences.first.data).not_to be_empty - expect(evidences.first.data["summary"]).to match(/The Origin header was changed/) - expect(evidences.size).to eq(3) - expect(evidences.compact.size).to eq(2) - expect(evidences.first).to be_a(::Gitlab::Ci::Reports::Security::Evidence) + it 'returns evidence object for each finding', :aggregate_failures do + all_evidences = report.findings.map(&:evidence) + evidences = all_evidences.compact + data = evidences.map(&:data) + summaries = evidences.map { |e| e.data["summary"] } + + expect(all_evidences.size).to eq(7) + expect(evidences.size).to eq(2) + expect(evidences).to all( be_a(::Gitlab::Ci::Reports::Security::Evidence) ) + expect(data).to all( have_values ) + expect(summaries).to all( match(/The Origin header was changed/) ) end end diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb index 32a31f091bb..27da1f23556 100644 --- a/spec/lib/gitlab/query_limiting/transaction_spec.rb +++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb @@ -85,6 +85,12 @@ RSpec.describe Gitlab::QueryLimiting::Transaction do expect do transaction.increment(described_class::GEO_NODES_LOAD) transaction.increment(described_class::LICENSES_LOAD) + transaction.increment('SELECT a.attname, a.other_column FROM pg_attribute a') + transaction.increment('SELECT x.foo, a.attname FROM some_table x JOIN pg_attribute a') + transaction.increment(<<-SQL) + SELECT a.attname, a.other_column + FROM pg_attribute a + SQL end.not_to change(transaction, :count) end end diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 86eb38814ed..635326eeadc 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -73,10 +73,10 @@ RSpec.describe DeployToken do describe '#ensure_token' do it 'ensures a token' do - deploy_token.token = nil + deploy_token.token_encrypted = nil deploy_token.save! - expect(deploy_token.token).not_to be_empty + expect(deploy_token.token_encrypted).not_to be_empty end end diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 46361e49d5e..6fa455cbfca 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -11,7 +11,8 @@ RSpec.describe 'Query.runner(id)' do let_it_be(:active_instance_runner) do create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago, active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600, - access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true, executor_type: :custom) + access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true, executor_type: :custom, + maintenance_note: 'Test maintenance note') end let_it_be(:inactive_instance_runner) do @@ -64,6 +65,7 @@ RSpec.describe 'Query.runner(id)' do 'executorName' => runner.executor_type&.dasherize, 'architectureName' => runner.architecture, 'platformName' => runner.platform, + 'maintenanceNote' => runner.maintenance_note, 'jobCount' => 0, 'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything), 'projectCount' => nil, diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb index 98963f57341..90956e7b4ea 100644 --- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb @@ -39,8 +39,6 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) - stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace) - stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_get_secret( api_url, diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb index 11045dfe950..a4f018aec0c 100644 --- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb @@ -147,8 +147,6 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) - stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace) - stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace) end it 'creates a namespace object' do @@ -245,47 +243,6 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do ) ) end - - it 'creates a role granting cilium permissions to the service account' do - subject - - expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME}").with( - body: hash_including( - metadata: { - name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, - namespace: namespace - }, - rules: [{ - apiGroups: %w(cilium.io), - resources: %w(ciliumnetworkpolicies), - verbs: %w(get list create update patch) - }] - ) - ) - end - - it 'creates a role binding granting cilium permissions to the service account' do - subject - - expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME}").with( - body: hash_including( - metadata: { - name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, - namespace: namespace - }, - roleRef: { - apiGroup: 'rbac.authorization.k8s.io', - kind: 'Role', - name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME - }, - subjects: [{ - kind: 'ServiceAccount', - name: service_account_name, - namespace: namespace - }] - ) - ) - end end end end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 5b74f347a44..93122ca3d0c 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -274,6 +274,10 @@ module FilteredSearchHelpers expect(page).to have_css '.gl-filtered-search-token', text: "Milestone != %#{value}" end + def expect_epic_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Epic = #{value}" + end + def expect_search_term(value) value.split(' ').each do |term| expect(page).to have_css '.gl-filtered-search-term', text: term |