Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-16 21:09:09 +00:00
parent 7ff36fc6e9
commit cffe2c2c34
65 changed files with 1096 additions and 758 deletions

View File

@ -86,48 +86,43 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => {
/** /**
* Render an alert at the top of the page, or, optionally an * Render an alert at the top of the page, or, optionally an
* arbitrary existing container. * arbitrary existing container. This alert is always dismissible.
*
* This alert is always dismissible.
*
* Usage:
*
* 1. Render a new alert
* *
* @example
* // Render a new alert
* import { createAlert, VARIANT_WARNING } from '~/flash'; * import { createAlert, VARIANT_WARNING } from '~/flash';
* *
* createAlert({ message: 'My error message' }); * createAlert({ message: 'My error message' });
* createAlert({ message: 'My warning message', variant: VARIANT_WARNING }); * createAlert({ message: 'My warning message', variant: VARIANT_WARNING });
* *
* 2. Dismiss this alert programmatically * @example
* * // Dismiss this alert programmatically
* const alert = createAlert({ message: 'Message' }); * const alert = createAlert({ message: 'Message' });
* *
* // ... * // ...
* *
* alert.dismiss(); * alert.dismiss();
* *
* 3. Respond to the alert being dismissed * @example
* // Respond to the alert being dismissed
* createAlert({ message: 'Message', onDismiss: () => {} });
* *
* createAlert({ message: 'Message', onDismiss: () => { ... }}); * @param {object} options - Options to control the flash message
* * @param {string} options.message - Alert message text
* @param {Object} options Options to control the flash message * @param {VARIANT_SUCCESS|VARIANT_WARNING|VARIANT_DANGER|VARIANT_INFO|VARIANT_TIP} [options.variant] - Which GlAlert variant to use; it defaults to VARIANT_DANGER.
* @param {String} options.message Alert message text * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`.
* @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 {Function} [options.onDismiss] - Handler to call when this alert is dismissed.
* @param {Object?} options.parent Reference to parent element under which alert needs to appear. Defaults to `document`. * @param {string} [options.containerSelector] - Selector for the container of the alert
* @param {Function?} options.onDismiss Handler to call when this alert is dismissed. * @param {object} [options.primaryButton] - Object describing primary button of alert
* @param {Object?} options.containerSelector Selector for the container of the alert * @param {string} [options.primaryButton.link] - Href of primary button
* @param {Object?} options.primaryButton Object describing primary button of alert * @param {string} [options.primaryButton.text] - Text of primary button
* @param {String?} link Href 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 {String?} text Text of primary button * @param {object} [options.secondaryButton] - Object describing secondary button of alert
* @param {Function?} clickHandler Handler to call when primary button is clicked on. The click event is sent as an argument. * @param {string} [options.secondaryButton.link] - Href of secondary button
* @param {Object?} options.secondaryButton Object describing secondary button of alert * @param {string} [options.secondaryButton.text] - Text of secondary button
* @param {String?} link Href 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 {String?} text Text of secondary button * @param {boolean} [options.captureError] - Whether to send error to Sentry
* @param {Function?} clickHandler Handler to call when secondary button is clicked on. The click event is sent as an argument. * @param {object} [options.error] - Error to be captured in Sentry
* @param {Boolean?} options.captureError Whether to send error to Sentry
* @param {Object} options.error Error to be captured in Sentry
* @returns
*/ */
const createAlert = function createAlert({ const createAlert = function createAlert({
message, message,
@ -208,22 +203,24 @@ const createAlert = function createAlert({
}; };
/** /**
* @deprecated use `createAlert` instead
*
* Flash banner supports different types of Flash configurations * Flash banner supports different types of Flash configurations
* along with ability to provide actionConfig which can be used to show * along with ability to provide actionConfig which can be used to show
* additional action or link on banner next to message * additional action or link on banner next to message
* *
* @param {Object} options Options to control the flash message * @param {object} options - Options to control the flash message
* @param {String} options.message Flash message text * @param {string} options.message - Flash message text
* @param {String} options.type Type of Flash, it can be `notice`, `success`, `warning` or `alert` (default) * @param {'alert'|'notice'|'success'|'warning'} [options.type] - Type of Flash; it defaults to 'alert'
* @param {Object} options.parent Reference to parent element under which Flash needs to appear * @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 {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} [options.actionConfig.href] - URL to which action config should point to (default: '#')
* @param {String} title Title of action * @param {string} [options.actionConfig.title] - Title of action
* @param {Function} clickHandler Method to call when action is clicked on * @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.fadeTransition] - Boolean to determine whether to fade the alert out
* @param {Boolean} options.captureError Boolean to determine whether to send error to Sentry * @param {boolean} [options.addBodyClass] - Adds `flash-shown` class to the `body` element
* @param {Object} options.error Error to be captured in Sentry * @param {boolean} [options.captureError] - Boolean to determine whether to send error to Sentry
* @deprecated Use `createAlert` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/362334. * @param {object} [options.error] - Error to be captured in Sentry
*/ */
const createFlash = function createFlash({ const createFlash = function createFlash({
message, message,

View File

@ -195,7 +195,11 @@ $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
* Quick fix: Get rid of jQuery for this implementation * Quick fix: Get rid of jQuery for this implementation
*/ */
const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page); 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 $rightSidebar = $('aside.right-sidebar');
const $layoutPage = $('.layout-page'); const $layoutPage = $('.layout-page');

View File

@ -1,5 +1,4 @@
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import { getCookie, isMetaClick, parseBoolean, scrollToElement } from '~/lib/utils/common_utils'; import { getCookie, isMetaClick, parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
@ -176,6 +175,8 @@ export default class MergeRequestTabs {
: null; : null;
this.navbar = document.querySelector('.navbar-gitlab'); this.navbar = document.querySelector('.navbar-gitlab');
this.peek = document.getElementById('js-peek'); this.peek = document.getElementById('js-peek');
this.sidebar = document.querySelector('.js-right-sidebar');
this.pageLayout = document.querySelector('.layout-page');
this.paddingTop = 16; this.paddingTop = 16;
this.scrollPositions = {}; this.scrollPositions = {};
@ -282,7 +283,7 @@ export default class MergeRequestTabs {
if (action === 'commits') { if (action === 'commits') {
this.loadCommits(href); this.loadCommits(href);
this.expandView(); // this.hideSidebar();
this.resetViewContainer(); this.resetViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
} else if (action === 'new') { } else if (action === 'new') {
@ -301,13 +302,12 @@ export default class MergeRequestTabs {
*/ */
this.loadDiff(href); this.loadDiff(href);
} }
if (bp.getBreakpointSize() !== 'xl') { // this.hideSidebar();
this.shrinkView();
}
this.expandViewContainer(); this.expandViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
this.commitsTab.classList.remove('active'); this.commitsTab.classList.remove('active');
} else if (action === 'pipelines') { } else if (action === 'pipelines') {
// this.hideSidebar();
this.resetViewContainer(); this.resetViewContainer();
this.mountPipelinesView(); this.mountPipelinesView();
} else { } else {
@ -320,9 +320,7 @@ export default class MergeRequestTabs {
notesTab.classList.add('active'); notesTab.classList.add('active');
} }
if (bp.getBreakpointSize() !== 'xs') { // this.showSidebar();
this.expandView();
}
this.resetViewContainer(); this.resetViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable); 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 // Expand the issuable sidebar unless the user explicitly collapsed it
expandView() { expandView() {
if (parseBoolean(getCookie('collapsed_gutter'))) { if (parseBoolean(getCookie('collapsed_gutter'))) {
@ -538,4 +523,24 @@ export default class MergeRequestTabs {
} }
}, 0); }, 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;
}
} }

View File

@ -7,6 +7,18 @@ import createFlash from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale'; 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() { function Sidebar() {
this.toggleTodo = this.toggleTodo.bind(this); this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside'); this.sidebar = $('aside');
@ -42,13 +54,22 @@ Sidebar.prototype.addEventListeners = function () {
this.sidebar.on('hiddenGlDropdown', this, this.onSidebarDropdownHidden); this.sidebar.on('hiddenGlDropdown', this, this.onSidebarDropdownHidden);
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked); $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document) $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
.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) { Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
const $this = $(this); const $this = $(this);
if ($this.hasClass('right-sidebar-merge-requests')) return;
const $collapseIcon = $('.js-sidebar-collapse'); const $collapseIcon = $('.js-sidebar-collapse');
const $expandIcon = $('.js-sidebar-expand'); const $expandIcon = $('.js-sidebar-expand');
const $toggleContainer = $('.js-sidebar-toggle-container'); const $toggleContainer = $('.js-sidebar-toggle-container');

View File

@ -1,6 +1,7 @@
<script> <script>
import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { n__, __ } from '~/locale'; import { n__, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
name: 'AssigneeTitle', name: 'AssigneeTitle',
@ -8,6 +9,7 @@ export default {
GlLoadingIcon, GlLoadingIcon,
GlIcon, GlIcon,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
loading: { loading: {
type: Boolean, type: Boolean,
@ -63,6 +65,7 @@ export default {
v-if="showToggle" v-if="showToggle"
:aria-label="__('Toggle sidebar')" :aria-label="__('Toggle sidebar')"
class="gutter-toggle float-right js-sidebar-toggle" class="gutter-toggle float-right js-sidebar-toggle"
:class="{ 'gl-display-block gl-md-display-none!': glFeatures.movedMrSidebar }"
href="#" href="#"
role="button" role="button"
> >

View File

@ -111,12 +111,6 @@ export default {
/> />
<div data-testid="lock-status" class="sidebar-item-value" :class="lockStatus.class"> <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 }} {{ lockStatus.displayText }}
</div> </div>
</div> </div>

View File

@ -103,7 +103,7 @@ export default {
</div> </div>
<div <div
v-if="showParticipantLabel" 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" /> <gl-loading-icon v-if="loading" size="sm" :inline="true" />
{{ participantLabel }} {{ participantLabel }}

View File

@ -98,7 +98,7 @@ export default {
'gl-mb-3': index !== users.length - 1, 'gl-mb-3': index !== users.length - 1,
'attention-requests': glFeatures.mrAttentionRequests, '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" data-testid="reviewer"
> >
<reviewer-avatar-link <reviewer-avatar-link

View File

@ -5,6 +5,7 @@ import { IssuableType } from '~/issues/constants';
import { isLoggedIn } from '~/lib/utils/common_utils'; import { isLoggedIn } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; 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'; import { subscribedQueries, Tracking } from '~/sidebar/constants';
const ICON_ON = 'notifications'; const ICON_ON = 'notifications';
@ -25,6 +26,7 @@ export default {
GlToggle, GlToggle,
SidebarEditableItem, SidebarEditableItem,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
iid: { iid: {
type: String, type: String,
@ -82,6 +84,9 @@ export default {
}, },
}, },
computed: { computed: {
isMergeRequest() {
return this.issuableType === IssuableType.MergeRequest && this.glFeatures.movedMrSidebar;
},
isLoading() { isLoading() {
return this.$apollo.queries?.subscribed?.loading || this.loading; return this.$apollo.queries?.subscribed?.loading || this.loading;
}, },
@ -171,7 +176,20 @@ export default {
</script> </script>
<template> <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 <sidebar-editable-item
v-else
ref="editable" ref="editable"
:title="$options.i18n.notifications" :title="$options.i18n.notifications"
:tracking="$options.tracking" :tracking="$options.tracking"

View File

@ -204,7 +204,7 @@ export default {
:time-estimate-human-readable="humanTimeEstimate" :time-estimate-human-readable="humanTimeEstimate"
/> />
<div <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') }} {{ __('Time tracking') }}
<gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" class="gl-ml-2" inline /> <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" class="gl-ml-2" inline />

View File

@ -19,6 +19,7 @@
.right-sidebar-collapsed { .right-sidebar-collapsed {
padding-right: 0; padding-right: 0;
&:not(.is-merge-request) {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter-collapsed-width; padding-right: $gutter-collapsed-width;
@ -28,6 +29,15 @@
right: $gutter-collapsed-width; right: $gutter-collapsed-width;
} }
} }
}
&.is-merge-request {
@include media-breakpoint-up(md) {
.content-wrapper {
padding-right: $gutter-collapsed-width;
}
}
}
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
.btn { .btn {
@ -49,6 +59,18 @@
padding-right: 0; padding-right: 0;
z-index: $zindex-dropdown-menu; 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) { @include media-breakpoint-only(sm) {
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter-collapsed-width; padding-right: $gutter-collapsed-width;
@ -73,12 +95,20 @@
.right-sidebar { .right-sidebar {
border-left: 1px solid $gray-50; border-left: 1px solid $gray-50;
&.right-sidebar-merge-requests {
@include media-breakpoint-up(md) {
border-left: 0;
}
}
&:not(.right-sidebar-merge-requests) {
.sidebar-container, .sidebar-container,
.issuable-sidebar { .issuable-sidebar {
// Add 100px so that potentially visible vertical scroll bar is hidden // Add 100px so that potentially visible vertical scroll bar is hidden
width: calc(100% + 100px); width: calc(100% + 100px);
} }
} }
}
.with-performance-bar .right-sidebar.affix { .with-performance-bar .right-sidebar.affix {
top: calc(#{$header-height} + #{$performance-bar-height}); top: calc(#{$header-height} + #{$performance-bar-height});
@ -231,6 +261,10 @@
margin-right: -$gl-spacing-scale-2; margin-right: -$gl-spacing-scale-2;
} }
.issuable-sidebar.is-merge-request .edit-link {
margin-right: 0;
}
.assignee-grid { .assignee-grid {
grid-template-areas: ' attention user'; grid-template-areas: ' attention user';
grid-template-columns: min-content 1fr; grid-template-columns: min-content 1fr;

View File

@ -735,3 +735,11 @@ $tabs-holder-z-index: 250;
.attention-request-sidebar-popover { .attention-request-sidebar-popover {
z-index: 999; z-index: 999;
} }
.merge-request-overview {
@include media-breakpoint-up(md) {
display: grid;
grid-template-columns: 1fr 270px;
grid-gap: 5%;
}
}

View File

@ -114,7 +114,7 @@
} }
} }
.right-sidebar { @mixin right-sidebar {
position: fixed; position: fixed;
top: $header-height; top: $header-height;
// Default value for CSS var must contain a unit // Default value for CSS var must contain a unit
@ -125,6 +125,18 @@
background-color: $white; background-color: $white;
z-index: 200; z-index: 200;
overflow: hidden; 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) { @include media-breakpoint-down(sm) {
z-index: 251; z-index: 251;
@ -135,10 +147,6 @@
&:hover { &:hover {
color: $blue-800; 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, .block,
.sidebar-contained-width, .sidebar-contained-width,
.issuable-sidebar-header { .issuable-sidebar-header {
@ -224,17 +247,30 @@
.issuable-sidebar { .issuable-sidebar {
height: 100%; height: 100%;
&:not(.is-merge-request) {
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
-webkit-overflow-scrolling: touch; -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 { &.affix-top .issuable-sidebar {
height: 100%; height: 100%;
} }
&.right-sidebar-expanded { &.right-sidebar-expanded {
&:not(.right-sidebar-merge-requests) {
width: $gutter-width; width: $gutter-width;
}
.value { .value {
line-height: 1; line-height: 1;
@ -242,6 +278,12 @@
.issuable-sidebar { .issuable-sidebar {
padding: 0 20px; 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]) { &:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
@ -284,9 +326,18 @@
/* Extra small devices (phones, less than 768px) */ /* Extra small devices (phones, less than 768px) */
display: none; display: none;
/* Small devices (tablets, 768px and up) */ /* Small devices (tablets, 768px and up) */
&:not(.right-sidebar-merge-requests) {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
display: block; display: block;
} }
}
&.right-sidebar-merge-requests {
@include media-breakpoint-up(md) {
display: block;
}
}
width: $gutter-collapsed-width; width: $gutter-collapsed-width;
padding: 0; padding: 0;

View File

@ -233,8 +233,7 @@ $tabs-holder-z-index: 250;
top: calc(#{$header-height} + #{$system-header-height} + #{$performance-bar-height}); top: calc(#{$header-height} + #{$system-header-height} + #{$performance-bar-height});
} }
@include media-breakpoint-up(sm) { @include media-breakpoint-up(md) {
position: -webkit-sticky;
position: sticky; position: sticky;
} }

View File

@ -47,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_attention_requests, current_user) push_frontend_feature_flag(:mr_attention_requests, current_user)
push_frontend_feature_flag(:updated_mr_header, project) push_frontend_feature_flag(:updated_mr_header, project)
push_frontend_feature_flag(:remove_diff_header_icons, project) push_frontend_feature_flag(:remove_diff_header_icons, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
end end
before_action do before_action do

View File

@ -66,5 +66,3 @@ module AlertManagement
end end
end end
end end
AlertManagement::AlertsFinder.prepend_mod_with('AlertManagement::AlertsFinder')

View File

@ -41,6 +41,8 @@ module Types
field :architecture_name, GraphQL::Types::String, null: true, field :architecture_name, GraphQL::Types::String, null: true,
description: 'Architecture provided by the the runner.', description: 'Architecture provided by the the runner.',
method: :architecture method: :architecture
field :maintenance_note, GraphQL::Types::String, null: true,
description: 'Runner\'s maintenance notes.'
field :groups, ::Types::GroupType.connection_type, null: true, field :groups, ::Types::GroupType.connection_type, null: true,
description: 'Groups the runner is associated with. For group runners only.' description: 'Groups the runner is associated with. For group runners only.'
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false, field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,

View File

@ -13,6 +13,8 @@ module IssuablesHelper
end end
def sidebar_gutter_collapsed_class def sidebar_gutter_collapsed_class
return "right-sidebar-expanded" if moved_mr_sidebar_enabled?
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end end

View File

@ -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 } _('%{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 end
def moved_mr_sidebar_enabled?
Feature.enabled?(:moved_mr_sidebar, @project) && defined?(@merge_request)
end
end end
MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper') MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper')

View File

@ -19,11 +19,13 @@ module NavHelper
end end
def page_gutter_class def page_gutter_class
moved_sidebar_enabled = current_controller?('merge_requests') && moved_mr_sidebar_enabled?
if page_has_markdown? if page_has_markdown?
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
%w[page-gutter right-sidebar-collapsed] ["page-gutter", "#{'right-sidebar-collapsed' unless moved_sidebar_enabled}"]
else else
%w[page-gutter right-sidebar-expanded] ["page-gutter", "#{'right-sidebar-expanded' unless moved_sidebar_enabled}"]
end end
elsif current_path?('jobs#show') elsif current_path?('jobs#show')
%w[page-gutter build-sidebar right-sidebar-expanded] %w[page-gutter build-sidebar right-sidebar-expanded]

View File

@ -81,7 +81,6 @@ module AlertManagement
scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) } scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
scope :not_resolved, -> { without_status(:resolved) } scope :not_resolved, -> { without_status(:resolved) }
scope :with_prometheus_alert, -> { includes(:prometheus_alert) } scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
scope :with_threat_monitoring_alerts, -> { where(domain: :threat_monitoring ) }
scope :with_operations_alerts, -> { where(domain: :operations) } scope :with_operations_alerts, -> { where(domain: :operations) }
scope :order_start_time, -> (sort_order) { order(started_at: sort_order) } scope :order_start_time, -> (sort_order) { order(started_at: sort_order) }

View File

@ -18,7 +18,6 @@ module Clusters
default_value_for :version, VERSION default_value_for :version, VERSION
scope :preload_cluster_platform, -> { preload(cluster: [:platform_kubernetes]) } 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, attr_encrypted :alert_manager_token,
mode: :per_attribute_iv, mode: :per_attribute_iv,

View File

@ -137,7 +137,6 @@ module Clusters
scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) } 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_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 :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct }
scope :managed, -> { where(managed: true) } scope :managed, -> { where(managed: true) }

View File

@ -5,7 +5,11 @@ class DeployToken < ApplicationRecord
include TokenAuthenticatable include TokenAuthenticatable
include PolicyActor include PolicyActor
include Gitlab::Utils::StrongMemoize 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 AVAILABLE_SCOPES = %i(read_repository read_registry write_registry
read_package_registry write_package_registry).freeze read_package_registry write_package_registry).freeze

View File

@ -30,7 +30,6 @@ module Integrations
after_create_commit :create_default_alerts after_create_commit :create_default_alerts
scope :preload_project, -> { preload(:project) } scope :preload_project, -> { preload(:project) }
scope :with_clusters_with_cilium, -> { joins(project: [:clusters]).merge(Clusters::Cluster.with_available_cilium) }
def show_active_box? def show_active_box?
false false

View File

@ -14,7 +14,5 @@ module Clusters
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding' GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
KNATIVE_SERVING_NAMESPACE = 'knative-serving' KNATIVE_SERVING_NAMESPACE = 'knative-serving'
ISTIO_SYSTEM_NAMESPACE = 'istio-system' ISTIO_SYSTEM_NAMESPACE = 'istio-system'
GITLAB_CILIUM_ROLE_NAME = 'gitlab-cilium-role'
GITLAB_CILIUM_ROLE_BINDING_NAME = 'gitlab-cilium-rolebinding'
end end
end end

View File

@ -53,8 +53,6 @@ module Clusters
create_or_update_knative_serving_role_binding create_or_update_knative_serving_role_binding
create_or_update_crossplane_database_role create_or_update_crossplane_database_role
create_or_update_crossplane_database_role_binding create_or_update_crossplane_database_role_binding
create_or_update_cilium_role
create_or_update_cilium_role_binding
end end
private private
@ -99,14 +97,6 @@ module Clusters
kubeclient.update_role_binding(crossplane_database_role_binding_resource) kubeclient.update_role_binding(crossplane_database_role_binding_resource)
end 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 def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new( Gitlab::Kubernetes::ServiceAccount.new(
service_account_name, service_account_name,
@ -185,28 +175,6 @@ module Clusters
service_account_name: service_account_name service_account_name: service_account_name
).generate ).generate
end 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 end
end end

View File

@ -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 = 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 .gl-new-dropdown-item-text-wrapper
= _('Report abuse') = _('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

View File

@ -27,7 +27,7 @@
= issuable_meta(@merge_request, @project) = issuable_meta(@merge_request, @project)
%div %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') = 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}" } .detail-page-header-actions.js-issuable-actions{ class: "#{'gl-align-self-start is-merge-request' if updated_mr_header_enabled}" }

View File

@ -1,4 +1,5 @@
- @gfm_form = true - @gfm_form = true
- unless moved_mr_sidebar_enabled?
- @content_class = "merge-request-container#{' limit-container-width' unless fluid_layout}" - @content_class = "merge-request-container#{' limit-container-width' unless fluid_layout}"
- add_to_breadcrumbs _("Merge requests"), project_merge_requests_path(@project) - add_to_breadcrumbs _("Merge requests"), project_merge_requests_path(@project)
- breadcrumb_title @merge_request.to_reference - breadcrumb_title @merge_request.to_reference
@ -47,8 +48,8 @@
#js-diff-file-finder #js-diff-file-finder
#js-code-navigation #js-code-navigation
= render "projects/merge_requests/tabs/pane", id: "notes", class: "notes voting_notes" do = render "projects/merge_requests/tabs/pane", id: "notes", class: "notes voting_notes" do
.row %div{ class: "#{'merge-request-overview' if moved_mr_sidebar_enabled?}" }
%section.col-md-12 %section
.issuable-discussion.js-vue-notes-event .issuable-discussion.js-vue-notes-event
- if @merge_request.description.present? - if @merge_request.description.present?
.detail-page-description .detail-page-description
@ -70,6 +71,8 @@
help_page_path: suggest_changes_help_path, help_page_path: suggest_changes_help_path,
current_user_data: @current_user_data, current_user_data: @current_user_data,
is_locked: @merge_request.discussion_locked.to_s } } 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 = render "projects/merge_requests/tabs/pane", name: "commits", id: "commits", class: "commits" do
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
@ -83,6 +86,7 @@
.loading.hide .loading.hide
= gl_loading_icon(size: 'lg') = gl_loading_icon(size: 'lg')
- 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 = 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) - if @merge_request.can_be_reverted?(current_user)

View File

@ -8,11 +8,12 @@
- add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras" - add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras"
- reviewers = local_assigns.fetch(:reviewers, nil) - reviewers = local_assigns.fetch(:reviewers, nil)
- in_group_context_with_iterations = @project.group.present? && issuable_sidebar[:supports_iterations] - 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 } %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 .issuable-sidebar{ class: "#{'is-merge-request' if moved_sidebar_enabled}" }
.issuable-sidebar-header.gl-py-3 .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", href: "#", "aria-label" => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } %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 = sidebar_gutter_toggle_icon
- if signed_in - if signed_in
.js-issuable-todo{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } .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 %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 #js-lock-entry-point
- if signed_in - if signed_in && !moved_sidebar_enabled
.js-sidebar-subscriptions-entry-point .js-sidebar-subscriptions-entry-point
.js-sidebar-participants-entry-point .js-sidebar-participants-entry-point
.block.with-sub-blocks .block.with-sub-blocks
#js-reference-entry-point #js-reference-entry-point
- if issuable_type == 'merge_request' - if issuable_type == 'merge_request' && !moved_sidebar_enabled
.sub-block.js-sidebar-source-branch .sub-block.js-sidebar-source-branch
.sidebar-collapsed-icon.js-dont-change-state .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') = 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')

View File

@ -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

View File

@ -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="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="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="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="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="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. | | <a id="cirunnerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner. |

View File

@ -625,6 +625,11 @@ Use lowercase for **merge requests**. If you use **MR** as the acronym, spell it
Use lowercase for **milestones**. 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 ## navigate
Do not use **navigate**. Use **go** instead. For example: Do not use **navigate**. Use **go** instead. For example:

View File

@ -26,7 +26,7 @@ used for the build.
Specify either: 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. - 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 ### Custom buildpacks with Herokuish

View File

@ -4,8 +4,7 @@ module Gitlab
module AlertManagement module AlertManagement
module Payload module Payload
MONITORING_TOOLS = { MONITORING_TOOLS = {
prometheus: 'Prometheus', prometheus: 'Prometheus'
cilium: 'Cilium'
}.freeze }.freeze
class << self class << self
@ -48,5 +47,3 @@ module Gitlab
end end
end end
end end
Gitlab::AlertManagement::Payload.prepend_mod_with('Gitlab::AlertManagement::Payload')

View File

@ -23,13 +23,11 @@ module Gitlab::Ci
MIN_MEDIUM_DEFAULT = 75 MIN_MEDIUM_DEFAULT = 75
def initialize(badge) def initialize(badge)
@entity = badge.entity
@status = badge.status @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_good = badge.customization.dig(:min_good)
@min_acceptable = badge.customization.dig(:min_acceptable) @min_acceptable = badge.customization.dig(:min_acceptable)
@min_medium = badge.customization.dig(:min_medium) @min_medium = badge.customization.dig(:min_medium)
super
end end
def value_text def value_text

View File

@ -22,10 +22,8 @@ module Gitlab::Ci
}.freeze }.freeze
def initialize(badge) def initialize(badge)
@entity = badge.entity
@status = badge.status @status = badge.status
@key_text = badge.customization.dig(:key_text) super
@key_width = badge.customization.dig(:key_width)
end end
def value_text def value_text

View File

@ -13,10 +13,8 @@ module Gitlab::Ci
VALUE_WIDTH_DEFAULT = 54 VALUE_WIDTH_DEFAULT = 54
def initialize(badge) def initialize(badge)
@entity = badge.entity
@tag = badge.tag || "none" @tag = badge.tag || "none"
@key_width = badge.customization.dig(:key_width) super
@key_text = badge.customization.dig(:key_text)
end end
def key_text def key_text

View File

@ -12,7 +12,8 @@ module Gitlab::Ci
def initialize(badge) def initialize(badge)
@entity = badge.entity @entity = badge.entity
@status = badge.status @key_text = badge.customization.dig(:key_text)
@key_width = badge.customization.dig(:key_width)
end end
def key_text def key_text

View File

@ -63,12 +63,14 @@ module Gitlab
GEO_NODES_LOAD = 'SELECT 1 AS one FROM "geo_nodes" LIMIT 1' GEO_NODES_LOAD = 'SELECT 1 AS one FROM "geo_nodes" LIMIT 1'
LICENSES_LOAD = 'SELECT "licenses".* FROM "licenses" ORDER BY "licenses"."id"' 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 # queries can be safely ignored if they are amoritized in regular usage
# (i.e. only requested occasionally and otherwise cached). # (i.e. only requested occasionally and otherwise cached).
def ignorable?(sql) def ignorable?(sql)
return true if sql&.include?(GEO_NODES_LOAD) return true if sql&.include?(GEO_NODES_LOAD)
return true if sql&.include?(LICENSES_LOAD) return true if sql&.include?(LICENSES_LOAD)
return true if ATTR_INTROSPECTION =~ sql
false false
end end

View File

@ -40027,9 +40027,15 @@ msgstr ""
msgid "Turn off" msgid "Turn off"
msgstr "" msgstr ""
msgid "Turn off notifications"
msgstr ""
msgid "Turn on" msgid "Turn on"
msgstr "" msgstr ""
msgid "Turn on notifications"
msgstr ""
msgid "Twitter" msgid "Twitter"
msgstr "" msgstr ""

View File

@ -5,13 +5,30 @@ module Gitlab
module Group module Group
module Settings module Settings
class Billing < Chemlab::Page class Billing < Chemlab::Page
# TODO: Supplant with data-qa-selectors h4 :billing_plan_header
h4 :billing_plan_header, css: 'div.billing-plan-header h4'
link :start_your_free_trial link :start_your_free_trial
link :upgrade_to_premium
link :upgrade_to_ultimate
link :upgrade_to_premium, css: '[data-testid="plan-card-premium"] a.billing-cta-purchase-new' # Subscription details
link :upgrade_to_ultimate, css: '[data-testid="plan-card-ultimate"] a.billing-cta-purchase-new' 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
def refresh_subscription_seats
refresh_seats
::QA::Support::WaitForRequests.wait_for_requests
end
end end
end end
end end

View File

@ -100,6 +100,222 @@ module Gitlab
def upgrade_to_ultimate? def upgrade_to_ultimate?
# This is a stub, used for indexing. The method is dynamically generated. # This is a stub, used for indexing. The method is dynamically generated.
end 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 end
end end

View File

@ -12,7 +12,8 @@ module QA
QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}]) 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 = 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
end end

View File

@ -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

View File

@ -57,7 +57,6 @@ module QA
MEDIA_TYPE: 'application/vnd.docker.distribution.manifest.v2+json' MEDIA_TYPE: 'application/vnd.docker.distribution.manifest.v2+json'
before_script: 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') - 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: 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")' - '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"' - 'curl -L -X DELETE -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/blobs/$digest"'

View File

@ -113,20 +113,6 @@ FactoryBot.define do
end end
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 trait :all_fields do
with_incident with_incident
with_assignee with_assignee

View File

@ -2,7 +2,6 @@
FactoryBot.define do FactoryBot.define do
factory :deploy_token do factory :deploy_token do
token { nil }
token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt(SecureRandom.hex(50)) } token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt(SecureRandom.hex(50)) }
sequence(:name) { |n| "PDT #{n}" } sequence(:name) { |n| "PDT #{n}" }
read_repository { true } read_repository { true }

View File

@ -11,6 +11,10 @@ RSpec.describe 'Group issues page' do
let(:project_with_issues_disabled) { create(:project, :issues_disabled, group: group) } let(:project_with_issues_disabled) { create(:project, :issues_disabled, group: group) }
let(:path) { issues_group_path(group) } let(:path) { issues_group_path(group) }
before do
stub_feature_flags(vue_issues_list: true)
end
context 'with shared examples', :js do context 'with shared examples', :js do
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} 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 } let(:user2) { user_outside_group }
it 'filters by only group users' do 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_suggestion(user.name)
expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) expect_no_suggestion(user2.name)
end end
end end
end end
@ -76,24 +80,10 @@ RSpec.describe 'Group issues page' do
it 'returns all group and subgroup issues' do it 'returns all group and subgroup issues' do
visit issues_group_path(group) visit issues_group_path(group)
page.within('.issuable-list') do
expect(page).to have_selector('li.issue', count: 2) expect(page).to have_selector('li.issue', count: 2)
expect(page).to have_content('root group issue') expect(page).to have_content('root group issue')
expect(page).to have_content('subgroup issue') expect(page).to have_content('subgroup issue')
end 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')
end
context 'when project is archived' do context 'when project is archived' do
before do before do
@ -115,7 +105,6 @@ RSpec.describe 'Group issues page' do
let!(:subgroup_issue) { create(:issue, project: subgroup_project) } let!(:subgroup_issue) { create(:issue, project: subgroup_project) }
before do before do
stub_feature_flags(vue_issues_list: true)
visit issues_group_path(group_with_no_issues) visit issues_group_path(group_with_no_issues)
end end
@ -135,14 +124,10 @@ RSpec.describe 'Group issues page' do
end end
it 'shows projects only with issues feature enabled', :js do it 'shows projects only with issues feature enabled', :js do
within '.empty-state' do
click_button 'Toggle project select' click_button 'Toggle project select'
end
page.within('.select2-results') do expect(page).to have_button project.full_name
expect(page).to have_content(project.full_name) expect(page).not_to have_button project_with_issues_disabled.full_name
expect(page).not_to have_content(project_with_issues_disabled.full_name)
end
end end
end end
end end
@ -155,16 +140,16 @@ RSpec.describe 'Group issues page' do
let!(:issue3) { create(:issue, project: project, title: 'Issue #3', relative_position: 3) } let!(:issue3) { create(:issue, project: project, title: 'Issue #3', relative_position: 3) }
before do before do
stub_feature_flags(vue_issues_list: false)
sign_in(user_in_group) sign_in(user_in_group)
end end
it 'displays all issues' do it 'displays all issues' do
visit issues_group_path(group, sort: 'relative_position') visit issues_group_path(group, sort: 'relative_position')
page.within('.issues-list') do
expect(page).to have_selector('li.issue', count: 3) expect(page).to have_selector('li.issue', count: 3)
end end
end
it 'has manual-ordering css applied' do it 'has manual-ordering css applied' do
visit issues_group_path(group, sort: 'relative_position') visit issues_group_path(group, sort: 'relative_position')
@ -218,11 +203,9 @@ RSpec.describe 'Group issues page' do
end end
def check_issue_order def check_issue_order
page.within('.manual-ordering') do expect(page).to have_css('.issue:nth-child(1) .title', text: 'Issue #2')
expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2') expect(page).to have_css('.issue:nth-child(2) .title', text: 'Issue #3')
expect(find('.issue:nth-child(2) .title')).to have_content('Issue #3') expect(page).to have_css('.issue:nth-child(3) .title', text: 'Issue #1')
expect(find('.issue:nth-child(3) .title')).to have_content('Issue #1')
end
end end
end end
@ -239,14 +222,8 @@ RSpec.describe 'Group issues page' do
end end
it 'shows the pagination' do it 'shows the pagination' do
expect(page).to have_link 'Prev' expect(page).to have_button 'Prev', disabled: true
expect(page).to have_link 'Next' expect(page).to have_button 'Next'
end
it 'first pagination item is active' do
page.within('.gl-pagination') do
expect(find('li.active')).to have_content('1')
end
end end
end end
end end

View File

@ -17,6 +17,8 @@ RSpec.describe 'Labels Hierarchy', :js do
let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') } let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') }
before do before do
stub_feature_flags(vue_issues_list: true)
grandparent.add_owner(user) grandparent.add_owner(user)
sign_in(user) sign_in(user)
@ -34,8 +36,6 @@ RSpec.describe 'Labels Hierarchy', :js do
click_on 'Close' click_on 'Close'
end end
wait_for_requests
expect(page).to have_selector('.gl-label', text: label.title) expect(page).to have_selector('.gl-label', text: label.title)
end end
end end
@ -44,8 +44,6 @@ RSpec.describe 'Labels Hierarchy', :js do
page.within('.block.labels') do page.within('.block.labels') do
click_on 'Edit' click_on 'Edit'
wait_for_requests
expect(page).not_to have_text(child_group_label.title) expect(page).not_to have_text(child_group_label.title)
end end
end end
@ -54,15 +52,21 @@ RSpec.describe 'Labels Hierarchy', :js do
shared_examples 'filtering by ancestor labels for projects' do |board = false| shared_examples 'filtering by ancestor labels for projects' do |board = false|
it 'filters by ancestor labels' do it 'filters by ancestor labels' do
[grandparent_group_label, parent_group_label, project_label_1].each do |label| [grandparent_group_label, parent_group_label, project_label_1].each do |label|
if board
select_label_on_dropdown(label.title) select_label_on_dropdown(label.title)
wait_for_requests
if board
expect(page).to have_selector('.board-card-title') do |card| expect(page).to have_selector('.board-card-title') do |card|
expect(card).to have_selector('a', text: labeled_issue.title) expect(card).to have_selector('a', text: labeled_issue.title)
end end
else 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_issues_list_count(1)
expect(page).to have_selector('.issue-title', text: labeled_issue.title) expect(page).to have_selector('.issue-title', text: labeled_issue.title)
end end
@ -70,9 +74,11 @@ RSpec.describe 'Labels Hierarchy', :js do
end end
it 'does not filter by descendant group labels' do it 'does not filter by descendant group labels' do
if board
filtered_search.set("label=") filtered_search.set("label=")
else
wait_for_requests select_tokens 'Label', '='
end
expect(page).not_to have_link child_group_label.title expect(page).not_to have_link child_group_label.title
end end
@ -93,11 +99,9 @@ RSpec.describe 'Labels Hierarchy', :js do
it 'filters by ancestors and current group labels' do it 'filters by ancestors and current group labels' do
[grandparent_group_label, parent_group_label].each do |label| [grandparent_group_label, parent_group_label].each do |label|
if board
select_label_on_dropdown(label.title) select_label_on_dropdown(label.title)
wait_for_requests
if board
expect(page).to have_selector('.board-card-title') do |card| expect(page).to have_selector('.board-card-title') do |card|
expect(card).to have_selector('a', text: labeled_issue.title) expect(card).to have_selector('a', text: labeled_issue.title)
end end
@ -106,6 +110,14 @@ RSpec.describe 'Labels Hierarchy', :js do
expect(card).to have_selector('a', text: labeled_issue_2.title) expect(card).to have_selector('a', text: labeled_issue_2.title)
end end
else 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_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.title)
expect(page).to have_selector('.issue-title', text: labeled_issue_2.title) expect(page).to have_selector('.issue-title', text: labeled_issue_2.title)
@ -115,11 +127,9 @@ RSpec.describe 'Labels Hierarchy', :js do
end end
it 'filters by descendant group labels' do it 'filters by descendant group labels' do
wait_for_requests if board
select_label_on_dropdown(group_label_3.title) select_label_on_dropdown(group_label_3.title)
if board
expect(page).to have_selector('.board-card-title') do |card| expect(page).to have_selector('.board-card-title') do |card|
expect(card).not_to have_selector('a', text: labeled_issue_2.title) expect(card).not_to have_selector('a', text: labeled_issue_2.title)
end end
@ -128,17 +138,23 @@ RSpec.describe 'Labels Hierarchy', :js do
expect(card).to have_selector('a', text: labeled_issue_3.title) expect(card).to have_selector('a', text: labeled_issue_3.title)
end end
else else
select_tokens 'Label', '=', group_label_3.title, submit: true
expect_issues_list_count(1) expect_issues_list_count(1)
expect(page).to have_selector('.issue-title', text: labeled_issue_3.title) expect(page).to have_selector('.issue-title', text: labeled_issue_3.title)
end end
end end
it 'does not filter by descendant group project labels' do it 'does not filter by descendant group project labels' do
if board
filtered_search.set("label=") filtered_search.set("label=")
wait_for_requests
expect(page).not_to have_selector('.btn-link', text: project_label_3.title) expect(page).not_to have_selector('.btn-link', text: project_label_3.title)
else
select_tokens 'Label', '='
expect(page).not_to have_link project_label_3.title
end
end end
end end
@ -195,9 +211,7 @@ RSpec.describe 'Labels Hierarchy', :js do
it_behaves_like 'filtering by ancestor labels for projects' it_behaves_like 'filtering by ancestor labels for projects'
it 'does not filter by descendant group labels' do it 'does not filter by descendant group labels' do
filtered_search.set("label=") select_tokens 'Label', '='
wait_for_requests
expect(page).not_to have_link child_group_label.title expect(page).not_to have_link child_group_label.title
end end

View File

@ -59,7 +59,6 @@ RSpec.describe 'Batch diffs', :js do
# Confirm scrolled to correct UI element # Confirm scrolled to correct UI element
expect(get_first_diff.find('.discussion-notes .timeline-entry li.note[id]').obscured?).to be_falsey 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
end end

View File

@ -6,14 +6,18 @@ RSpec.describe 'User manages subscription', :js do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:moved_mr_sidebar_enabled) { false }
before do before do
stub_feature_flags(moved_mr_sidebar: moved_mr_sidebar_enabled)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
visit(merge_request_path(merge_request)) visit(merge_request_path(merge_request))
end end
context 'moved sidebar flag disabled' do
it 'toggles subscription' do it 'toggles subscription' do
page.within('[data-testid="subscription-toggle"]') do page.within('[data-testid="subscription-toggle"]') do
wait_for_requests wait_for_requests
@ -32,3 +36,30 @@ RSpec.describe 'User manages subscription', :js do
end end
end end
end end
context 'moved sidebar flag enabled' do
let(:moved_mr_sidebar_enabled) { true }
it 'toggles subscription' do
wait_for_requests
click_button 'Toggle dropdown'
expect(page).to have_content('Turn on notifications')
click_button 'Turn on notifications'
wait_for_requests
click_button 'Toggle dropdown'
expect(page).to have_content('Turn off notifications')
click_button 'Turn off notifications'
wait_for_requests
click_button 'Toggle dropdown'
expect(page).to have_content('Turn on notifications')
end
end
end

View File

@ -119,6 +119,8 @@ RSpec.describe 'User views an open merge request' do
let(:source_branch) { "&#39;&gt;&lt;iframe/srcdoc=&#39;&#39;&gt;&lt;/iframe&gt;" } let(:source_branch) { "&#39;&gt;&lt;iframe/srcdoc=&#39;&#39;&gt;&lt;/iframe&gt;" }
before do before do
stub_feature_flags(moved_mr_sidebar: false)
project.repository.create_branch(source_branch, "master") project.repository.create_branch(source_branch, "master")
mr = create(:merge_request, source_project: project, target_project: project, source_branch: source_branch) mr = create(:merge_request, source_project: project, target_project: project, source_branch: source_branch)

View File

@ -1,5 +1,148 @@
{ {
"vulnerabilities": [ "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", "category": "dependency_scanning",
"name": "Vulnerabilities in libxml2", "name": "Vulnerabilities in libxml2",
@ -19,37 +162,30 @@
}, },
"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", "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": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
}, },
"response": { "response": {
"headers": [ "headers": [{
{
"name": "Server", "name": "Server",
"value": "TwistedWeb/20.3.0" "value": "TwistedWeb/20.3.0"
} }],
],
"reason_phrase": "OK", "reason_phrase": "OK",
"status_code": 200, "status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
}, },
"supporting_messages": [ "supporting_messages": [{
{
"name": "Origional", "name": "Origional",
"request": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
@ -58,23 +194,19 @@
{ {
"name": "Recorded", "name": "Recorded",
"request": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
}, },
"response": { "response": {
"headers": [ "headers": [{
{
"name": "Server", "name": "Server",
"value": "TwistedWeb/20.3.0" "value": "TwistedWeb/20.3.0"
} }],
],
"reason_phrase": "OK", "reason_phrase": "OK",
"status_code": 200, "status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
@ -83,32 +215,24 @@
] ]
}, },
"location": {}, "location": {},
"identifiers": [ "identifiers": [{
{
"type": "GitLab", "type": "GitLab",
"name": "Foo vulnerability", "name": "Foo vulnerability",
"value": "foo" "value": "foo"
} }],
], "links": [{
"links": [
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020" "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
} }],
],
"details": { "details": {
"commit": { "commit": {
"name": [ "name": [{
{
"lang": "en", "lang": "en",
"value": "The Commit" "value": "The Commit"
} }],
], "description": [{
"description": [
{
"lang": "en", "lang": "en",
"value": "Commit where the vulnerability was identified" "value": "Commit where the vulnerability was identified"
} }],
],
"type": "commit", "type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19" "value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
} }
@ -134,37 +258,30 @@
}, },
"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", "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": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
}, },
"response": { "response": {
"headers": [ "headers": [{
{
"name": "Server", "name": "Server",
"value": "TwistedWeb/20.3.0" "value": "TwistedWeb/20.3.0"
} }],
],
"reason_phrase": "OK", "reason_phrase": "OK",
"status_code": 200, "status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
}, },
"supporting_messages": [ "supporting_messages": [{
{
"name": "Origional", "name": "Origional",
"request": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
@ -173,23 +290,19 @@
{ {
"name": "Recorded", "name": "Recorded",
"request": { "request": {
"headers": [ "headers": [{
{
"name": "Host", "name": "Host",
"value": "127.0.0.1:7777" "value": "127.0.0.1:7777"
} }],
],
"method": "GET", "method": "GET",
"url": "http://127.0.0.1:7777/api/users", "url": "http://127.0.0.1:7777/api/users",
"body": "" "body": ""
}, },
"response": { "response": {
"headers": [ "headers": [{
{
"name": "Server", "name": "Server",
"value": "TwistedWeb/20.3.0" "value": "TwistedWeb/20.3.0"
} }],
],
"reason_phrase": "OK", "reason_phrase": "OK",
"status_code": 200, "status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]" "body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
@ -198,19 +311,15 @@
] ]
}, },
"location": {}, "location": {},
"identifiers": [ "identifiers": [{
{
"type": "GitLab", "type": "GitLab",
"name": "Bar vulnerability", "name": "Bar vulnerability",
"value": "bar" "value": "bar"
} }],
], "links": [{
"links": [
{
"name": "CVE-1030", "name": "CVE-1030",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030" "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030"
} }]
]
}, },
{ {
"category": "dependency_scanning", "category": "dependency_scanning",
@ -226,47 +335,60 @@
}, },
"location": {}, "location": {},
"identifiers": [], "identifiers": [],
"links": [ "links": []
]
} }
], ],
"remediations": [ "remediations": [{
"fixes": [{
"cve": "CVE-2137"
}],
"summary": "this remediates CVE-2137",
"diff": "dG90YWxseSBsZWdpdCBkaWZm"
},
{ {
"fixes": [ "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" "cve": "CVE-1020"
} }],
],
"summary": "", "summary": "",
"diff": "" "diff": ""
}, },
{ {
"fixes": [ "fixes": [{
{
"cve": "CVE", "cve": "CVE",
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
} }],
],
"summary": "", "summary": "",
"diff": "" "diff": ""
}, },
{ {
"fixes": [ "fixes": [{
{
"cve": "CVE", "cve": "CVE",
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3" "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
} }],
],
"summary": "", "summary": "",
"diff": "" "diff": ""
}, },
{ {
"fixes": [ "fixes": [{
{
"id": "2134", "id": "2134",
"cve": "CVE-1" "cve": "CVE-1"
} }],
],
"summary": "", "summary": "",
"diff": "" "diff": ""
} }

View File

@ -1,5 +1,4 @@
import $ from 'jquery'; import $ from 'jquery';
import Mousetrap from 'mousetrap';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm'; import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
@ -13,7 +12,6 @@ jest.mock('~/lib/utils/common_utils', () => ({
describe('ShortcutsIssuable', () => { describe('ShortcutsIssuable', () => {
const snippetShowFixtureName = 'snippets/show.html'; const snippetShowFixtureName = 'snippets/show.html';
const mrShowFixtureName = 'merge_requests/merge_request_of_current_user.html';
beforeAll(() => { beforeAll(() => {
initCopyAsGFM(); 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();
});
});
});
}); });

View File

@ -39,8 +39,8 @@ RSpec.describe Resolvers::AlertManagement::AlertResolver do
end end
context 'filtering by domain' do context 'filtering by domain' do
let_it_be(:alert1) { 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: 'Cilium', 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_it_be(:alert3) { create(:alert_management_alert, project: project, monitoring_tool: 'generic') }
let(:args) { { domain: 'operations' } } let(:args) { { domain: 'operations' } }

View File

@ -12,7 +12,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
id description created_at contacted_at maximum_timeout access_level active paused status 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 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 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) expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -292,7 +292,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
expect(scans.map(&:status).all?('success')).to be(true) expect(scans.map(&:status).all?('success')).to be(true)
expect(scans.map(&:start_time).all?('placeholder-value')).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.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) expect(scans.first).to be_a(::Gitlab::Ci::Reports::Security::Scan)
end end
@ -348,22 +348,29 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
it 'returns links object for each finding', :aggregate_failures do it 'returns links object for each finding', :aggregate_failures do
links = report.findings.flat_map(&:links) 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(&: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']) "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137", "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2138",
expect(links.size).to eq(2) "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) expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link)
end end
end end
describe 'parsing evidence' do describe 'parsing evidence' do
it 'returns evidence object for each finding', :aggregate_failures do RSpec::Matchers.define_negated_matcher :have_values, :be_empty
evidences = report.findings.map(&:evidence)
expect(evidences.first.data).not_to be_empty it 'returns evidence object for each finding', :aggregate_failures do
expect(evidences.first.data["summary"]).to match(/The Origin header was changed/) all_evidences = report.findings.map(&:evidence)
expect(evidences.size).to eq(3) evidences = all_evidences.compact
expect(evidences.compact.size).to eq(2) data = evidences.map(&:data)
expect(evidences.first).to be_a(::Gitlab::Ci::Reports::Security::Evidence) 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
end end

View File

@ -85,6 +85,12 @@ RSpec.describe Gitlab::QueryLimiting::Transaction do
expect do expect do
transaction.increment(described_class::GEO_NODES_LOAD) transaction.increment(described_class::GEO_NODES_LOAD)
transaction.increment(described_class::LICENSES_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.not_to change(transaction, :count)
end end
end end

View File

@ -73,10 +73,10 @@ RSpec.describe DeployToken do
describe '#ensure_token' do describe '#ensure_token' do
it 'ensures a token' do it 'ensures a token' do
deploy_token.token = nil deploy_token.token_encrypted = nil
deploy_token.save! deploy_token.save!
expect(deploy_token.token).not_to be_empty expect(deploy_token.token_encrypted).not_to be_empty
end end
end end

View File

@ -11,7 +11,8 @@ RSpec.describe 'Query.runner(id)' do
let_it_be(:active_instance_runner) do let_it_be(:active_instance_runner) do
create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago, 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, 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 end
let_it_be(:inactive_instance_runner) do let_it_be(:inactive_instance_runner) do
@ -64,6 +65,7 @@ RSpec.describe 'Query.runner(id)' do
'executorName' => runner.executor_type&.dasherize, 'executorName' => runner.executor_type&.dasherize,
'architectureName' => runner.architecture, 'architectureName' => runner.architecture,
'platformName' => runner.platform, 'platformName' => runner.platform,
'maintenanceNote' => runner.maintenance_note,
'jobCount' => 0, 'jobCount' => 0,
'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything), 'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything),
'projectCount' => nil, 'projectCount' => nil,

View File

@ -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_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(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_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( stub_kubeclient_get_secret(
api_url, api_url,

View File

@ -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_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(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_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 end
it 'creates a namespace object' do it 'creates a namespace object' do
@ -245,47 +243,6 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
) )
) )
end 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 end
end end

View File

@ -274,6 +274,10 @@ module FilteredSearchHelpers
expect(page).to have_css '.gl-filtered-search-token', text: "Milestone != %#{value}" expect(page).to have_css '.gl-filtered-search-token', text: "Milestone != %#{value}"
end end
def expect_epic_token(value)
expect(page).to have_css '.gl-filtered-search-token', text: "Epic = #{value}"
end
def expect_search_term(value) def expect_search_term(value)
value.split(' ').each do |term| value.split(' ').each do |term|
expect(page).to have_css '.gl-filtered-search-term', text: term expect(page).to have_css '.gl-filtered-search-term', text: term