Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-18 09:11:27 +00:00
parent ad3b511ba3
commit 31522c5182
233 changed files with 944 additions and 718 deletions

View File

@ -29,27 +29,6 @@ Layout/HashAlignment:
- 'ee/lib/gitlab/ci/parsers/security/formatters/dependency_list.rb'
- 'ee/lib/gitlab/elastic/helper.rb'
- 'ee/lib/gitlab/elastic/indexer.rb'
- 'ee/spec/controllers/ee/projects/variables_controller_spec.rb'
- 'ee/spec/controllers/groups/epic_boards_controller_spec.rb'
- 'ee/spec/controllers/groups/issues_controller_spec.rb'
- 'ee/spec/controllers/projects/settings/operations_controller_spec.rb'
- 'ee/spec/controllers/trials_controller_spec.rb'
- 'ee/spec/factories/dependencies.rb'
- 'ee/spec/factories/projects.rb'
- 'ee/spec/features/billings/billing_plans_spec.rb'
- 'ee/spec/features/groups/settings/protected_environments_spec.rb'
- 'ee/spec/features/projects/environments/environments_spec.rb'
- 'ee/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb'
- 'ee/spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb'
- 'ee/spec/finders/epics_finder_spec.rb'
- 'ee/spec/finders/merge_requests_finder_spec.rb'
- 'ee/spec/frontend/fixtures/dast_profiles.rb'
- 'ee/spec/graphql/ee/mutations/ci/runner/update_spec.rb'
- 'ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/path_locks_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/security_orchestration/scan_execution_policy_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/security_report_summary_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/vulnerabilities/issue_links_resolver_spec.rb'
- 'ee/spec/helpers/billing_plans_helper_spec.rb'
- 'ee/spec/helpers/routing/pseudonymization_helper_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/access_levels_spec.rb'

View File

@ -1,5 +1,5 @@
import JSZip from 'jszip';
import JSZipUtils from 'jszip-utils';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
export default class SketchLoader {
@ -7,37 +7,30 @@ export default class SketchLoader {
this.container = container;
this.loadingIcon = this.container.querySelector('.js-loading-icon');
this.load();
}
load() {
return this.getZipFile()
.then((data) => JSZip.loadAsync(data))
.then((asyncResult) => asyncResult.files['previews/preview.png'].async('uint8array'))
.then((content) => {
const url = window.URL || window.webkitURL;
const blob = new Blob([new Uint8Array(content)], {
type: 'image/png',
});
const previewUrl = url.createObjectURL(blob);
this.render(previewUrl);
})
.catch(this.error.bind(this));
}
getZipFile() {
return new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(this.container.dataset.endpoint, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
this.load().catch(() => {
this.error();
});
}
async load() {
const zipContents = await this.getZipContents();
const previewContents = await zipContents.files['previews/preview.png'].async('uint8array');
const blob = new Blob([previewContents], {
type: 'image/png',
});
this.render(window.URL.createObjectURL(blob));
}
async getZipContents() {
const { data } = await axios.get(this.container.dataset.endpoint, {
responseType: 'arraybuffer',
});
return JSZip.loadAsync(data);
}
render(previewUrl) {
const previewLink = document.createElement('a');
const previewImage = document.createElement('img');

View File

@ -102,7 +102,7 @@ export default {
data-qa-selector="board_add_new_list"
>
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-gray-50"
>
<h3 class="gl-font-size-h2 gl-px-5 gl-py-5 gl-m-0" data-testid="board-add-column-form-title">
{{ $options.i18n.newList }}

View File

@ -154,7 +154,7 @@ export default {
:id="glIconId"
ref="icon"
name="issue-block"
class="issue-blocked-icon gl-mr-2 gl-cursor-pointer"
class="issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-text-red-500"
data-testid="issue-blocked-icon"
@mouseenter="handleMouseEnter"
/>

View File

@ -81,10 +81,10 @@ export default {
data-qa-selector="board_card"
:class="[
{
'multi-select': multiSelectVisible,
'multi-select gl-bg-blue-50 gl-border-blue-200': multiSelectVisible,
'gl-cursor-grab': isDraggable,
'is-disabled': isDisabled,
'is-active': isActive,
'is-active gl-bg-blue-50': isActive,
'gl-cursor-not-allowed gl-bg-gray-10': item.isLoading,
},
colorClass,
@ -95,7 +95,7 @@ export default {
:data-item-path="item.referencePath"
:style="cardStyle"
data-testid="board_card"
class="board-card gl-p-5 gl-rounded-base"
class="board-card gl-p-5 gl-rounded-base gl-line-height-normal gl-relative gl-mb-3"
@click="toggleIssue($event)"
>
<board-card-inner :list="list" :item="item" :update-filters="true" :index="index" />

View File

@ -208,7 +208,7 @@ export default {
<template>
<div>
<div class="gl-display-flex" dir="auto">
<h4 class="board-card-title gl-mb-0 gl-mt-0 gl-mr-3">
<h4 class="board-card-title gl-mb-0 gl-mt-0 gl-mr-3 gl-font-base gl-overflow-break-word">
<board-blocked-icon
v-if="item.blocked"
:item="item"
@ -221,7 +221,7 @@ export default {
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
class="confidential-icon gl-mr-2"
class="confidential-icon gl-mr-2 gl-text-orange-500 gl-cursor-help"
:aria-label="__('Confidential')"
/>
<gl-icon
@ -229,14 +229,14 @@ export default {
v-gl-tooltip
name="spam"
:title="__('This issue is hidden because its author has been banned')"
class="gl-mr-2 hidden-icon"
class="gl-mr-2 hidden-icon gl-text-orange-500 gl-cursor-help"
data-testid="hidden-icon"
/>
<a
:href="item.path || item.webUrl || ''"
:title="item.title"
:class="{ 'gl-text-gray-400!': item.isLoading }"
class="js-no-trigger"
class="js-no-trigger gl-text-body gl-hover-text-gray-900"
@mousemove.stop
>{{ item.title }}</a
>
@ -247,7 +247,7 @@ export default {
<template v-for="label in orderedLabels">
<gl-label
:key="label.id"
class="js-no-trigger"
class="js-no-trigger gl-mt-2 gl-mr-2"
:background-color="label.color"
:title="label.title"
:description="label.description"
@ -267,7 +267,7 @@ export default {
<gl-loading-icon v-if="item.isLoading" size="lg" class="gl-mt-5" />
<span
v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3 gl-text-secondary"
:class="{ 'gl-font-base': isEpicBoard }"
>
<tooltip-on-truncate
@ -328,7 +328,10 @@ export default {
</p>
</gl-tooltip>
<span ref="countBadge" class="board-card-info gl-mr-0 gl-pr-0 gl-pl-3">
<span
ref="countBadge"
class="board-card-info gl-mr-0 gl-pr-0 gl-pl-3 gl-text-secondary gl-cursor-help"
>
<span v-if="allowSubEpics" class="gl-mr-3">
<gl-icon name="epic" />
{{ totalEpicsCount }}
@ -346,7 +349,7 @@ export default {
<span
v-if="shouldRenderEpicProgress"
ref="progressBadge"
class="board-card-info gl-pl-0"
class="board-card-info gl-pl-0 gl-text-secondary gl-cursor-help"
>
<span class="gl-mr-3" data-testid="epic-progress">
<gl-icon name="progress" />
@ -369,7 +372,7 @@ export default {
</span>
</span>
</div>
<div class="board-card-assignee gl-display-flex gl-gap-3">
<div class="board-card-assignee gl-display-flex gl-gap-3 gl-mb-n2">
<user-avatar-link
v-for="assignee in cappedAssignees"
:key="assignee.id"
@ -377,7 +380,7 @@ export default {
:img-alt="avatarUrlTitle(assignee)"
:img-src="avatarUrl(assignee)"
:img-size="avatarSize"
class="js-no-trigger"
class="js-no-trigger user-avatar-link"
tooltip-placement="bottom"
:enforce-gl-avatar="true"
>
@ -391,7 +394,7 @@ export default {
v-if="shouldRenderCounter"
v-gl-tooltip
:title="assigneeCounterTooltip"
class="avatar-counter"
class="avatar-counter gl-bg-gray-400 gl-cursor-help gl-font-weight-bold gl-ml-n4 gl-border-0 gl-line-height-24"
data-placement="bottom"
>{{ assigneeCounterLabel }}</span
>

View File

@ -76,7 +76,7 @@ export default {
<div
:class="{
'is-draggable': isListDraggable,
'is-collapsed': list.collapsed,
'is-collapsed gl-w-10': list.collapsed,
'board-type-assignee': list.listType === 'assignee',
}"
:data-list-id="list.id"
@ -84,7 +84,7 @@ export default {
data-qa-selector="board_list"
>
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-gray-50"
:class="{ 'board-column-highlighted': highlighted }"
>
<board-list-header :list="list" :disabled="disabled" />

View File

@ -75,7 +75,7 @@ export default {
v-if="!isSwimlanesOn"
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-pr-3 gl-white-space-nowrap"
class="boards-list gl-w-full gl-py-5 gl-pr-3 gl-white-space-nowrap gl-overflow-x-scroll"
@end="moveList"
>
<board-column

View File

@ -265,7 +265,7 @@ export default {
<template>
<div
v-show="!list.collapsed"
class="board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column"
class="board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column gl-min-h-0"
data-qa-selector="board_list_cards_area"
>
<div
@ -287,7 +287,7 @@ export default {
:data-board-type="list.listType"
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
draggable=".board-card"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-3 gl-pt-0"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-3 gl-pt-0 gl-overflow-y-auto gl-overflow-x-hidden"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
@ -303,7 +303,11 @@ export default {
:disabled="disabled"
/>
<gl-intersection-observer @appear="onReachingListBottom">
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
<li
v-if="showCount"
class="board-list-count gl-text-center gl-text-secondary gl-py-4"
data-issue-id="-1"
>
<gl-loading-icon
v-if="loadingMore"
size="sm"

View File

@ -252,7 +252,7 @@ export default {
<header
:class="{
'gl-h-full': list.collapsed,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base gl-bg-gray-50': isSwimlanesHeader,
}"
:style="headerStyle"
class="board-header gl-relative"
@ -267,14 +267,15 @@ export default {
'gl-py-2': list.collapsed && isSwimlanesHeader,
'gl-flex-direction-column': list.collapsed,
}"
class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3"
class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 gl-h-9"
>
<gl-button
v-gl-tooltip.hover
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
class="board-title-caret no-drag gl-cursor-pointer"
class="board-title-caret no-drag gl-cursor-pointer gl-hover-bg-gray-50"
:class="{ 'gl-mt-1': list.collapsed, 'gl-mr-2': !list.collapsed }"
category="tertiary"
size="small"
data-testid="board-title-caret"
@ -307,6 +308,7 @@ export default {
'gl-display-none': list.collapsed && isSwimlanesHeader,
'gl-flex-grow-0 gl-my-3 gl-mx-0': list.collapsed,
'gl-flex-grow-1': !list.collapsed,
'gl-rotate-90': list.collapsed,
}"
>
<!-- EE start -->
@ -324,7 +326,7 @@ export default {
<span
v-if="listType === 'assignee'"
v-show="!list.collapsed"
class="gl-ml-2 gl-font-weight-normal gl-text-gray-500"
class="gl-ml-2 gl-font-weight-normal gl-text-secondary"
>
@{{ listAssignee }}
</span>
@ -345,7 +347,7 @@ export default {
v-if="isSwimlanesHeader && list.collapsed"
ref="collapsedInfo"
aria-hidden="true"
class="board-header-collapsed-info-icon gl-cursor-pointer gl-text-gray-500"
class="board-header-collapsed-info-icon gl-cursor-pointer gl-text-secondary gl-hover-text-gray-900"
>
<gl-icon name="information" />
</span>
@ -369,14 +371,14 @@ export default {
<!-- EE end -->
<div
class="issue-count-badge gl-display-inline-flex gl-pr-2 no-drag gl-text-gray-500"
class="issue-count-badge gl-display-inline-flex gl-pr-2 no-drag gl-text-secondary"
data-testid="issue-count-badge"
:class="{
'gl-display-none!': list.collapsed && isSwimlanesHeader,
'gl-p-0': list.collapsed,
}"
>
<span class="gl-display-inline-flex">
<span class="gl-display-inline-flex" :class="{ 'gl-rotate-90': list.collapsed }">
<gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" />
<span ref="itemCount" class="gl-display-inline-flex gl-align-items-center">
<gl-icon class="gl-mr-2" :name="countIcon" :size="16" />

View File

@ -69,7 +69,7 @@ export default {
</script>
<template>
<div class="board-new-issue-form">
<div class="board-new-issue-form gl-z-index-3 gl-m-3">
<div class="board-card position-relative gl-p-5 rounded">
<gl-form @submit.prevent="handleFormSubmit" @reset="handleFormCancel">
<label :for="inputFieldId" class="gl-font-weight-bold">{{ __('Title') }}</label>

View File

@ -85,7 +85,11 @@ export default {
<template>
<span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
<span
ref="issueDueDate"
:class="cssClass"
class="board-card-info gl-mr-3 gl-text-secondary gl-cursor-help"
>
<gl-icon
:class="{ 'text-danger': isPastDue }"
class="board-card-info-icon gl-mr-2"

View File

@ -36,7 +36,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
<span ref="issueTimeEstimate" class="board-card-info gl-mr-3 gl-text-secondary gl-cursor-help">
<gl-icon name="hourglass" class="board-card-info-icon gl-mr-2" />
<time class="board-card-info-text">{{ timeEstimate }}</time>
</span>

View File

@ -30,7 +30,9 @@ export default {
{{ itemsSize }}
</span>
<span v-if="isMaxLimitSet" class="max-issue-size">
{{ maxIssueCount }}
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
{{ `/ ${maxIssueCount}` }}
<!-- eslint-enable @gitlab/vue-require-i18n-strings -->
</span>
</div>
</template>

View File

@ -63,6 +63,12 @@ export default {
return btn.tooltipText;
},
actionButtonQaSelector(btn) {
if (btn.dataQaSelector) {
return btn.dataQaSelector;
}
return 'mr_widget_extension_actions_button';
},
},
};
</script>
@ -105,7 +111,7 @@ export default {
:target="btn.target"
:class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]"
:data-clipboard-text="btn.dataClipboardText"
:data-qa-selector="btn.dataQaSelector"
:data-qa-selector="actionButtonQaSelector(btn)"
:data-method="btn.dataMethod"
:icon="btn.icon"
:data-testid="btn.testId || 'extension-actions-button'"

View File

@ -307,7 +307,11 @@ export default {
</script>
<template>
<section class="media-section" data-testid="widget-extension">
<section
class="media-section"
data-testid="widget-extension"
data-qa-selector="mr_widget_extension"
>
<div
:class="{ 'gl-cursor-pointer': isCollapsible }"
class="media gl-p-5"
@ -352,6 +356,7 @@ export default {
:icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
category="tertiary"
data-testid="toggle-button"
data-qa-selector="toggle_button"
size="small"
@click="toggleCollapsed"
/>

View File

@ -109,6 +109,7 @@ export default {
:modal-id="modalId"
:level="3"
data-testid="child-content"
data-qa-selector="child_content"
@clickedAction="onClickedAction"
/>
</li>

View File

@ -60,6 +60,7 @@ export default {
:name="$options.EXTENSION_ICON_NAMES[iconName]"
:size="size"
:aria-label="iconAriaLabel"
:data-qa-selector="`status_${iconName}_icon`"
class="gl-display-block"
/>
</div>

View File

@ -1,7 +1,8 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { normalizeHeaders } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import { sprintf, __ } from '~/locale';
import Poll from '~/lib/utils/poll';
import StatusIcon from '../extensions/status_icon.vue';
import { EXTENSION_ICON_NAMES } from '../../constants';
@ -11,6 +12,10 @@ const FETCH_TYPE_COLLAPSED = 'collapsed';
export default {
components: {
StatusIcon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
/**
@ -63,6 +68,10 @@ export default {
required: false,
validator: (value) => Object.keys(EXTENSION_ICON_NAMES).indexOf(value) > -1,
},
isCollapsible: {
type: Boolean,
required: true,
},
widgetName: {
type: String,
required: true,
@ -70,6 +79,7 @@ export default {
},
data() {
return {
isCollapsed: true,
isLoading: false,
error: null,
};
@ -91,6 +101,12 @@ export default {
this.isLoading = false;
},
methods: {
collapseButtonLabel() {
return sprintf(this.isCollapsed ? __('Show details') : __('Hide details'));
},
toggleCollapsed() {
this.isCollapsed = !this.isCollapsed;
},
fetch(handler, dataType) {
const requests = this.multiPolling ? handler() : [handler];
@ -145,10 +161,26 @@ export default {
<slot name="summary">{{ isLoading ? loadingText : summary }}</slot>
</div>
<!-- actions will go here -->
<!-- toggle button will go here -->
<div
v-if="isCollapsible"
class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6"
>
<gl-button
v-gl-tooltip
:title="collapseButtonLabel"
:aria-expanded="`${!isCollapsed}`"
:aria-label="collapseButtonLabel"
:icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
category="tertiary"
data-testid="toggle-button"
size="small"
@click="toggleCollapsed"
/>
</div>
</div>
</div>
<div
v-if="!isCollapsed"
class="mr-widget-grouped-section gl-relative"
data-testid="widget-extension-collapsed-section"
>

View File

@ -32,7 +32,7 @@ export default {
});
});
return fileNames.join(' ');
return fileNames.join(' ').trim();
},
summary(data) {
if (data.parsingInProgress) {

View File

@ -5,34 +5,6 @@
pointer-events: none;
}
.dropdown-projects {
.dropdown-content {
max-height: 200px;
}
}
.issue-board-dropdown-content {
margin: 0;
padding: $gl-padding-4 $gl-padding $gl-padding;
border-bottom: 0;
color: var(--gray-500, $gray-500);
}
[data-page$='epic_boards:index'],
[data-page$='epic_boards:show'],
.issue-boards-page {
.content-wrapper {
padding-bottom: 0;
}
}
[data-page$='epic_boards:index'],
[data-page$='epic_boards:show'] {
.filtered-search-wrapper {
display: none !important;
}
}
.boards-app {
@include media-breakpoint-up(sm) {
transition: width $gl-transition-duration-medium;
@ -87,33 +59,7 @@
width: 400px;
}
.board-title-caret {
border-radius: $border-radius-default;
line-height: $gl-spacing-scale-5;
&.btn svg {
top: 0;
}
&:hover {
background-color: var(--gray-50, $gray-50);
transition: background-color 0.1s linear;
}
}
&:not(.is-collapsed) {
.board-title-caret {
margin-right: $gl-padding-4;
}
}
&.is-collapsed {
width: 50px;
.board-title-caret {
margin-top: 1px;
}
.board-title-text > span,
.issue-count-badge > span {
height: 16px;
@ -124,17 +70,11 @@
// rotated element has square dimensions so it won't overlap with its siblings.
margin: calc(50% - 8px) 0;
transform: rotate(90deg);
transform-origin: center;
}
}
}
.board-inner {
font-size: $issue-boards-font-size;
background: var(--gray-50, $gray-50);
}
// to highlight columns we have animated pulse of box-shadow
// we don't want to actually animate the box-shadow property
// because that causes costly repaints. Instead we can add a
@ -169,42 +109,12 @@
}
}
.board-title {
height: 3rem;
.max-issue-size::before {
content: '/';
}
}
.board-list-component {
min-height: 0; // firefox fix
}
.board-list {
overflow-y: auto;
overflow-x: hidden;
}
.board-list-loading {
margin-top: 10px;
font-size: (26px / $issue-boards-font-size) * 1em;
}
.board-card {
background: var(--gray-10, $white);
box-shadow: 0 1px 2px rgba(var(--black, $black), 0.1);
line-height: $gl-padding;
list-style: none;
position: relative;
&:not(:last-child) {
margin-bottom: $gl-padding-8;
}
&.is-active,
&.is-active .board-card-assignee:hover a {
background-color: var(--blue-50, $blue-50);
&:last-child {
@include gl-mb-0;
}
.move-to-position {
@ -215,34 +125,6 @@
visibility: visible;
}
&.multi-select {
border-color: var(--blue-200, $blue-200);
background-color: var(--blue-50, $blue-50);
}
&.sortable-chosen {
box-shadow: 0 2px 4px 0 rgba($black, 0.16);
}
.gl-label {
margin-top: 4px;
margin-right: 4px;
}
.confidential-icon,
.hidden-icon {
color: var(--orange-500, $orange-500);
cursor: help;
}
.issue-blocked-icon {
color: var(--red-500, $red-500);
}
@include media-breakpoint-down(md) {
padding: $gl-padding-8;
}
@include media-breakpoint-down(sm) {
.move-to-position {
visibility: visible;
@ -251,36 +133,21 @@
}
.board-card-title {
@include overflow-break-word();
font-size: 1em;
width: 95%;
a {
color: var(--gray-900, $gray-900);
}
@include media-breakpoint-down(md) {
font-size: $label-font-size;
@include media-breakpoint-down(md) {
font-size: $gl-font-size-sm;
}
}
}
.board-card-assignee {
margin-top: -$gl-padding-4;
margin-bottom: -$gl-padding-4;
.avatar-counter {
vertical-align: middle;
line-height: $gl-padding-24;
min-width: $gl-padding-24;
height: $gl-padding-24;
border-radius: $gl-padding-24;
background-color: var(--gray-400, $gray-400);
font-size: $gl-font-size-xs;
cursor: help;
font-weight: $gl-font-weight-bold;
margin-left: -$gl-padding-4;
border: 0;
padding: 0 $gl-padding-4;
@include media-breakpoint-down(md) {
min-width: auto;
@ -290,12 +157,8 @@
}
}
img {
vertical-align: top;
}
.user-avatar-link:not(:only-child) {
margin-left: -$gl-padding-4;
margin-left: -$gl-padding;
&:nth-of-type(1) {
z-index: 2;
@ -314,14 +177,12 @@
}
@include media-breakpoint-down(md) {
margin-top: 0;
margin-bottom: 0;
margin-bottom: 0 !important;
}
}
.board-card-number {
font-size: $gl-font-size-xs;
color: var(--gray-500, $gray-500);
@include media-breakpoint-up(md) {
font-size: $label-font-size;
@ -329,74 +190,15 @@
}
.board-list-count {
padding: 10px 0;
color: var(--gray-500, $gray-500);
font-size: 13px;
}
.board-new-issue-form {
z-index: 4;
margin: 5px;
}
.right-sidebar.boards-sidebar {
.gutter-toggle {
bottom: 15px;
width: 22px;
padding-left: $gl-padding-32;
svg {
position: absolute;
top: 50%;
right: 0;
margin-top: (-11px / 2);
height: $gl-font-size-12;
width: $gl-font-size-12;
}
}
.issuable-header-text {
@include overflow-break-word();
padding-right: 35px;
}
}
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
transition: width $gl-transition-duration-medium, padding $gl-transition-duration-medium;
}
&.boards-sidebar-slide-enter,
&.boards-sidebar-slide-leave-active {
width: 0;
padding-left: 0;
padding-right: 0;
}
}
.board-card-info {
color: var(--gray-500, $gray-500);
white-space: nowrap;
margin-right: $gl-padding-8;
&:not(.board-card-weight) {
cursor: help;
}
&.board-card-weight {
color: var(--gray-500, $gray-500);
cursor: pointer;
&:hover {
color: initial;
text-decoration: underline;
}
&.board-card-weight:hover {
color: initial;
}
.board-card-info-icon {
color: var(--gray-500, $gray-500);
margin-right: $gl-padding-4;
vertical-align: text-top;
}
@ -409,15 +211,6 @@
cursor: help;
}
.board-labels-toggle-wrapper,
.board-swimlanes-toggle-wrapper {
/**
* Make the wrapper the same height as a button so it aligns properly when the
* filtered-search-box input element increases in size on Linux smaller breakpoints
*/
height: $input-height;
}
.issue-boards-content:not(.breadcrumbs) {
isolation: isolate;
}
@ -437,7 +230,6 @@
.boards-list {
height: calc(100vh - #{$issue-boards-filter-height});
overflow-x: scroll;
}
.boards-sidebar {
@ -448,15 +240,7 @@
.boards-sidebar {
.sidebar-collapsed-icon {
display: none;
}
.gl-drawer-header {
align-items: flex-start;
}
.labels-select-wrapper.is-embedded .labels-select-wrapper.is-embedded {
width: auto;
@include gl-display-none;
}
.show.dropdown .dropdown-menu {
@ -464,10 +248,6 @@
}
}
.board-header-collapsed-info-icon:hover {
color: var(--gray-900, $gray-900);
}
.board-card-skeleton {
height: 110px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);

View File

@ -7,9 +7,6 @@ module VerifiesWithEmail
extend ActiveSupport::Concern
include ActionView::Helpers::DateHelper
TOKEN_LENGTH = 6
TOKEN_VALID_FOR_MINUTES = 60
included do
prepend_before_action :verify_with_email, only: :create, unless: -> { two_factor_enabled? }
end
@ -76,7 +73,8 @@ module VerifiesWithEmail
def send_verification_instructions(user)
return if send_rate_limited?(user)
raw_token, encrypted_token = generate_token
service = Users::EmailVerification::GenerateTokenService.new(attr: :unlock_token)
raw_token, encrypted_token = service.execute
user.unlock_token = encrypted_token
user.lock_access!({ send_instructions: false })
send_verification_instructions_email(user, raw_token)
@ -88,27 +86,20 @@ module VerifiesWithEmail
Notify.verification_instructions_email(
user.id,
token: token,
expires_in: TOKEN_VALID_FOR_MINUTES).deliver_later
expires_in: Users::EmailVerification::ValidateTokenService::TOKEN_VALID_FOR_MINUTES).deliver_later
log_verification(user, :instructions_sent)
end
def verify_token(user, token)
return handle_verification_failure(user, :rate_limited) if verification_rate_limited?(user)
return handle_verification_failure(user, :invalid) unless valid_token?(user, token)
return handle_verification_failure(user, :expired) if expired_token?(user)
service = Users::EmailVerification::ValidateTokenService.new(attr: :unlock_token, user: user, token: token)
result = service.execute
handle_verification_success(user)
end
def generate_token
raw_token = SecureRandom.random_number(10**TOKEN_LENGTH).to_s.rjust(TOKEN_LENGTH, '0')
encrypted_token = digest_token(raw_token)
[raw_token, encrypted_token]
end
def digest_token(token)
Devise.token_generator.digest(User, :unlock_token, token)
if result[:status] == :success
handle_verification_success(user)
else
handle_verification_failure(user, result[:reason], result[:message])
end
end
def render_sign_in_rate_limited
@ -122,44 +113,17 @@ module VerifiesWithEmail
distance_of_time_in_words(interval_in_seconds)
end
def verification_rate_limited?(user)
Gitlab::ApplicationRateLimiter.throttled?(:email_verification, scope: user.unlock_token)
end
def send_rate_limited?(user)
Gitlab::ApplicationRateLimiter.throttled?(:email_verification_code_send, scope: user)
end
def expired_token?(user)
user.locked_at < (Time.current - TOKEN_VALID_FOR_MINUTES.minutes)
end
def valid_token?(user, token)
user.unlock_token == digest_token(token)
end
def handle_verification_failure(user, reason)
message = case reason
when :rate_limited
s_("IdentityVerification|You've reached the maximum amount of tries. "\
'Wait %{interval} or resend a new code and try again.') % { interval: email_verification_interval }
when :expired
s_('IdentityVerification|The code has expired. Resend a new code and try again.')
when :invalid
s_('IdentityVerification|The code is incorrect. Enter it again, or resend a new code.')
end
def handle_verification_failure(user, reason, message)
user.errors.add(:base, message)
log_verification(user, :failed_attempt, reason)
prompt_for_email_verification(user)
end
def email_verification_interval
interval_in_seconds = Gitlab::ApplicationRateLimiter.rate_limits[:email_verification][:interval]
distance_of_time_in_words(interval_in_seconds)
end
def handle_verification_success(user)
user.unlock_access!
log_verification(user, :successful)

View File

@ -5,8 +5,9 @@ module Groups
class RepositoryController < Groups::ApplicationController
layout 'group_settings'
skip_cross_project_access_check :show
before_action :authorize_create_deploy_token!
before_action :define_deploy_token_variables
before_action :authorize_create_deploy_token!, only: :create_deploy_token
before_action :authorize_access!, only: :show
before_action :define_deploy_token_variables, if: -> { can?(current_user, :create_deploy_token, @group) }
before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @group)
end
@ -43,6 +44,10 @@ module Groups
private
def authorize_access!
authorize_admin_group!
end
def define_deploy_token_variables
@deploy_tokens = @group.deploy_tokens.active
@ -55,3 +60,5 @@ module Groups
end
end
end
Groups::Settings::RepositoryController.prepend_mod

View File

@ -22,8 +22,7 @@ module Ci
accessibility: %w[accessibility],
coverage: %w[cobertura],
codequality: %w[codequality],
terraform: %w[terraform],
sbom: %w[cyclonedx]
terraform: %w[terraform]
}.freeze
DEFAULT_FILE_NAMES = {

View File

@ -34,7 +34,8 @@ class ResourceTimeboxEvent < ResourceEvent
case self
when ResourceMilestoneEvent
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_milestone_changed_action(author: user)
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_milestone_changed_action(author: user,
project: issue.project)
else
# no-op
end

View File

@ -3,7 +3,7 @@
class UsersStarProject < ApplicationRecord
include Sortable
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :project, counter_cache: :star_count
belongs_to :user
validates :user, presence: true

View File

@ -29,7 +29,10 @@ module ResourceEvents
resource.expire_note_etag_cache
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue)
return unless resource.is_a?(Issue)
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user,
project: resource.project)
end
private

View File

@ -495,7 +495,9 @@ module SystemNotes
end
def track_cross_reference_action
issue_activity_counter.track_issue_cross_referenced_action(author: author) if noteable.is_a?(Issue)
return unless noteable.is_a?(Issue)
issue_activity_counter.track_issue_cross_referenced_action(author: author, project: project || noteable.project)
end
def hierarchy_note_params(action, parent, child)

View File

@ -21,7 +21,7 @@ module SystemNotes
# Using instance_of because WorkItem < Issue. We don't want to track work item updates as issue updates
if noteable.instance_of?(Issue) && changed_dates.key?('due_date')
issue_activity_counter.track_issue_due_date_changed_action(author: author)
issue_activity_counter.track_issue_due_date_changed_action(author: author, project: project)
end
work_item_activity_counter.track_work_item_date_changed_action(author: author) if noteable.is_a?(WorkItem)
@ -50,7 +50,9 @@ module SystemNotes
"changed time estimate to #{parsed_time}"
end
issue_activity_counter.track_issue_time_estimate_changed_action(author: author) if noteable.is_a?(Issue)
if noteable.is_a?(Issue)
issue_activity_counter.track_issue_time_estimate_changed_action(author: author, project: project)
end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end
@ -81,7 +83,9 @@ module SystemNotes
body = text_parts.join(' ')
end
issue_activity_counter.track_issue_time_spent_changed_action(author: author) if noteable.is_a?(Issue)
if noteable.is_a?(Issue)
issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project)
end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end
@ -107,7 +111,9 @@ module SystemNotes
text_parts << "at #{spent_at}" if spent_at && spent_at != DateTime.current.to_date
body = text_parts.join(' ')
issue_activity_counter.track_issue_time_spent_changed_action(author: author) if noteable.is_a?(Issue)
if noteable.is_a?(Issue)
issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project)
end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Users
module EmailVerification
class BaseService
VALID_ATTRS = %i[unlock_token confirmation_token].freeze
def initialize(attr:)
@attr = attr
validate_attr!
end
protected
attr_reader :attr, :token
def validate_attr!
raise ArgumentError, 'Invalid attribute' unless attr.in?(VALID_ATTRS)
end
def digest
Devise.token_generator.digest(User, attr, token)
end
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Users
module EmailVerification
class GenerateTokenService < EmailVerification::BaseService
TOKEN_LENGTH = 6
def execute
@token = generate_token
[token, digest]
end
private
def generate_token
SecureRandom.random_number(10**TOKEN_LENGTH).to_s.rjust(TOKEN_LENGTH, '0')
end
end
end
end

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
module Users
module EmailVerification
class ValidateTokenService < EmailVerification::BaseService
include ActionView::Helpers::DateHelper
TOKEN_VALID_FOR_MINUTES = 60
def initialize(attr:, user:, token:)
super(attr: attr)
@user = user
@token = token
end
def execute
return failure(:rate_limited) if verification_rate_limited?
return failure(:invalid) unless valid?
return failure(:expired) if expired_token?
success
end
private
attr_reader :user
def verification_rate_limited?
Gitlab::ApplicationRateLimiter.throttled?(:email_verification, scope: token)
end
def valid?
Devise.secure_compare(user[attr], digest)
end
def expired_token?
generated_at = case attr
when :unlock_token then user.locked_at
when :confirmation_token then user.confirmation_sent_at
end
generated_at < TOKEN_VALID_FOR_MINUTES.minutes.ago
end
def success
{ status: :success }
end
def failure(reason)
{
status: :failure,
reason: reason,
message: failure_message(reason)
}
end
def failure_message(reason)
case reason
when :rate_limited
format(s_("IdentityVerification|You've reached the maximum amount of tries. "\
'Wait %{interval} or resend a new code and try again.'), interval: email_verification_interval)
when :expired
s_('IdentityVerification|The code has expired. Resend a new code and try again.')
when :invalid
s_('IdentityVerification|The code is incorrect. Enter it again, or resend a new code.')
end
end
def email_verification_interval
interval_in_seconds = Gitlab::ApplicationRateLimiter.rate_limits[:email_verification][:interval]
distance_of_time_in_words(interval_in_seconds)
end
end
end
end

View File

@ -2,7 +2,11 @@
- page_title _('Repository')
- @content_class = "limit-container-width" unless fluid_layout
- deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.')
- if can?(current_user, :admin_group, @group)
- deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.')
= render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description
= render "default_branch", group: @group
= render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description
= render "default_branch", group: @group
- if can?(current_user, :change_push_rules, @group)
= render "push_rules"

View File

@ -1,7 +1,7 @@
- board = local_assigns.fetch(:board, nil)
- @no_breadcrumb_container = true
- @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative gl-pb-0"
- @content_class = "issue-boards-content js-focus-mode-board"
- is_epic_board = board.to_type == "EpicBoard"
- if is_epic_board

View File

@ -144,12 +144,6 @@
:why: https://github.com/Stuk/jszip/blob/master/LICENSE.markdown
:versions: []
:when: 2017-04-05 10:38:46.275721000 Z
- - :approve
- jszip-utils
- :who: Phil Hughes
:why: https://github.com/Stuk/jszip-utils/blob/master/LICENSE.markdown
:versions: []
:when: 2017-04-05 10:39:32.676232000 Z
- - :approve
- pako
- :who: Phil Hughes

View File

@ -8,10 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
From October 19, 2022, namespaces in GitLab.com on the Free tier
will be limited to five (5) members per [namespace](namespace/index.md).
This limit applies to top-level groups and personal namespaces.
In a personal namespace, the limit applies across all projects in your personal
namespace.
This limit applies to top-level private groups.
On the transition date, if your namespace has six or more unique members:
@ -45,12 +42,6 @@ Prerequisite:
1. To view all members, select the **Seats** tab.
1. To remove a member, select **Remove user**.
NOTE:
The **Usage Quotas** page is not available for personal namespaces. You can
view and [remove members](project/members/index.md#remove-a-member-from-a-project)
in each project instead. The five user limit includes all
unique members across all projects in your personal namespace.
If you need more time to manage your members, or to try GitLab features
with a team of more than five members, you can [start a trial](https://about.gitlab.com/free-trial/).
A trial lasts for 30 days and includes an unlimited number of members.

View File

@ -74,7 +74,6 @@ Slack user on GitLab.com.
The only difference with the [manually configurable Slack slash commands](slack_slash_commands.md)
is that all the commands should be prefixed with the `/gitlab` keyword.
We are working on making this configurable in the future.
For example, to show the issue number `1001` under the `gitlab-org/gitlab`
project, you would do:

View File

@ -42,7 +42,9 @@ After the Harbor integration is activated:
- The global variables `$HARBOR_USERNAME`, `$HARBOR_HOST`, `$HARBOR_OCI`, `$HARBOR_PASSWORD`, `$HARBOR_URL`, and `$HARBOR_PROJECT` are created for CI/CD use.
- The project-level integration settings override the group-level integration settings.
## Secure your requests to the Harbor APIs
## Security considerations
### Secure your requests to the Harbor APIs
For each API request through the Harbor integration, the credentials for your connection to the Harbor API use
the `username:password` combination. The following are suggestions for safe use:
@ -51,6 +53,12 @@ the `username:password` combination. The following are suggestions for safe use:
- Follow the principle of least privilege (for access on Harbor) with your credentials.
- Have a rotation policy on your credentials.
### CI/CD variable security
Malicious code pushed to your `.gitlab-ci.yml` file could compromise your variables, including
`$HARBOR_PASSWORD`, and send them to a third-party server. For more details, see
[CI/CD variable security](../../../ci/variables/index.md#cicd-variable-security).
## Examples of Harbor variables in CI/CD
### Push a Docker image with kaniko

View File

@ -27,6 +27,29 @@ included_attributes:
- :name
namespace_settings:
- :prevent_sharing_groups_outside_hierarchy
iterations_cadence: &iterations_cadence_definition
- :group_id
- :created_at
- :updated_at
- :start_date
- :last_run_date
- :duration_in_weeks
- :iterations_in_advance
- :active
- :automatic
- :roll_over
- :title
- :description
iterations_cadences: *iterations_cadence_definition
iteration: &iteration_definition
- :iid
- :created_at
- :updated_at
- :start_date
- :due_date
- :group_id
- :description
iterations: *iteration_definition
excluded_attributes:
group:
@ -44,6 +67,19 @@ excluded_attributes:
- :max_pages_size
epics:
- :state_id
iterations_cadence: &iterations_cadence_definition
- :id
iterations_cadences: *iterations_cadence_definition
iteration: &iteration_excluded_definition
- :id
- :title
- :title_html
- :project_id
- :description_html
- :cached_markdown_version
- :iterations_cadence_id
- :sequence
iterations: *iteration_excluded_definition
methods:
labels:
@ -92,3 +128,5 @@ ee:
- milestone:
- events:
- :push_event_payload
- iterations_cadences:
- :iterations

View File

@ -24,6 +24,29 @@ included_attributes:
- :username
author:
- :name
iterations_cadence: &iterations_cadence_definition
- :group_id
- :created_at
- :updated_at
- :start_date
- :last_run_date
- :duration_in_weeks
- :iterations_in_advance
- :active
- :automatic
- :roll_over
- :title
- :description
iterations_cadences: *iterations_cadence_definition
iteration: &iteration_definition
- :iid
- :created_at
- :updated_at
- :start_date
- :due_date
- :group_id
- :description
iterations: *iteration_definition
excluded_attributes:
group:
@ -41,6 +64,18 @@ excluded_attributes:
- :extra_shared_runners_minutes_limit
epics:
- :state_id
iterations_cadence: &iterations_cadence_definition
- :id
iterations_cadences: *iterations_cadence_definition
iteration: &iteration_excluded_definition
- :id
- :title
- :title_html
- :project_id
- :description_html
- :cached_markdown_version
- :iterations_cadence_id
iterations: *iteration_excluded_definition
methods:
labels:
@ -88,3 +123,5 @@ ee:
- milestone:
- events:
- :push_event_payload
- iterations_cadences:
- :iterations

View File

@ -8,7 +8,8 @@ module Gitlab
labels: :group_labels,
priorities: :label_priorities,
label: :group_label,
parent: :epic
parent: :epic,
iterations_cadences: 'Iterations::Cadence'
}.freeze
EXISTING_OBJECT_RELATIONS = %i[

View File

@ -68,15 +68,18 @@ module Gitlab
track_unique_action(ISSUE_REOPENED, author)
end
def track_issue_label_changed_action(author:)
def track_issue_label_changed_action(author:, project:)
track_snowplow_action(ISSUE_LABEL_CHANGED, author, project)
track_unique_action(ISSUE_LABEL_CHANGED, author)
end
def track_issue_milestone_changed_action(author:)
def track_issue_milestone_changed_action(author:, project:)
track_snowplow_action(ISSUE_MILESTONE_CHANGED, author, project)
track_unique_action(ISSUE_MILESTONE_CHANGED, author)
end
def track_issue_cross_referenced_action(author:)
def track_issue_cross_referenced_action(author:, project:)
track_snowplow_action(ISSUE_CROSS_REFERENCED, author, project)
track_unique_action(ISSUE_CROSS_REFERENCED, author)
end
@ -116,15 +119,18 @@ module Gitlab
track_unique_action(ISSUE_DESIGNS_REMOVED, author)
end
def track_issue_due_date_changed_action(author:)
def track_issue_due_date_changed_action(author:, project:)
track_snowplow_action(ISSUE_DUE_DATE_CHANGED, author, project)
track_unique_action(ISSUE_DUE_DATE_CHANGED, author)
end
def track_issue_time_estimate_changed_action(author:)
def track_issue_time_estimate_changed_action(author:, project:)
track_snowplow_action(ISSUE_TIME_ESTIMATE_CHANGED, author, project)
track_unique_action(ISSUE_TIME_ESTIMATE_CHANGED, author)
end
def track_issue_time_spent_changed_action(author:)
def track_issue_time_spent_changed_action(author:, project:)
track_snowplow_action(ISSUE_TIME_SPENT_CHANGED, author, project)
track_unique_action(ISSUE_TIME_SPENT_CHANGED, author)
end

View File

@ -6,18 +6,23 @@ module Sidebars
class SettingsMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
return false unless can?(context.current_user, :admin_group, context.group)
if can?(context.current_user, :admin_group, context.group)
add_item(general_menu_item)
add_item(integrations_menu_item)
add_item(access_tokens_menu_item)
add_item(group_projects_menu_item)
add_item(repository_menu_item)
add_item(ci_cd_menu_item)
add_item(applications_menu_item)
add_item(packages_and_registries_menu_item)
return true
elsif Gitlab.ee? && can?(context.current_user, :change_push_rules, context.group)
# Push Rules are the only group setting that can also be edited by maintainers.
# Create an empty sub-menu here and EE adds Repository menu item (with only Push Rules).
return true
end
add_item(general_menu_item)
add_item(integrations_menu_item)
add_item(access_tokens_menu_item)
add_item(group_projects_menu_item)
add_item(repository_menu_item)
add_item(ci_cd_menu_item)
add_item(applications_menu_item)
add_item(packages_and_registries_menu_item)
true
false
end
override :title

View File

@ -31980,9 +31980,6 @@ msgstr ""
msgid "Push project from command line"
msgstr ""
msgid "Push rules"
msgstr ""
msgid "Push the target branch up to GitLab."
msgstr ""

View File

@ -129,7 +129,6 @@
"js-cookie": "^3.0.0",
"js-yaml": "^3.13.1",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.13.2",
"lodash": "^4.17.20",
"lowlight": "^2.6.1",

View File

@ -14,6 +14,7 @@ RSpec.describe 'Group navbar' do
before do
insert_package_nav(_('Kubernetes'))
insert_after_nav_item(_('Analytics'), new_nav_item: settings_for_maintainer_nav_item) if Gitlab.ee?
stub_config(dependency_proxy: { enabled: false })
stub_config(registry: { enabled: false })

View File

@ -150,7 +150,7 @@ RSpec.describe 'Email Verification On Login', :clean_gitlab_redis_rate_limiting
code = expect_instructions_email_and_extract_code
# Wait for the code to expire before verifying
travel VerifiesWithEmail::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
travel Users::EmailVerification::ValidateTokenService::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
verify_code(code)
# Expect an error message
@ -268,7 +268,7 @@ RSpec.describe 'Email Verification On Login', :clean_gitlab_redis_rate_limiting
verify_code(code)
expect(page).to have_content('The code is incorrect. Enter it again, or resend a new code.')
travel VerifiesWithEmail::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
travel Users::EmailVerification::ValidateTokenService::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
verify_code(new_code)
expect(page).to have_content('The code has expired. Resend a new code and try again.')
@ -335,7 +335,7 @@ RSpec.describe 'Email Verification On Login', :clean_gitlab_redis_rate_limiting
mail = find_email_for(user)
expect(mail.to).to match_array([user.email])
expect(mail.subject).to eq('Verify your identity')
code = mail.body.parts.first.to_s[/\d{#{VerifiesWithEmail::TOKEN_LENGTH}}/o]
code = mail.body.parts.first.to_s[/\d{#{Users::EmailVerification::GenerateTokenService::TOKEN_LENGTH}}/o]
reset_delivered_emails!
code
end

View File

@ -2,18 +2,6 @@ import SketchLoader from '~/blob/sketch';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('jszip', () => {
return {
loadAsync: jest.fn().mockResolvedValue({
files: {
'previews/preview.png': {
async: jest.fn().mockResolvedValue('foo'),
},
},
}),
};
});
describe('Sketch viewer', () => {
beforeEach(() => {
loadHTMLFixture('static/sketch_viewer.html');
@ -25,7 +13,7 @@ describe('Sketch viewer', () => {
describe('with error message', () => {
beforeEach(() => {
jest.spyOn(SketchLoader.prototype, 'getZipFile').mockImplementation(
jest.spyOn(SketchLoader.prototype, 'getZipContents').mockImplementation(
() =>
new Promise((resolve, reject) => {
reject();
@ -50,7 +38,13 @@ describe('Sketch viewer', () => {
describe('success', () => {
beforeEach(() => {
jest.spyOn(SketchLoader.prototype, 'getZipFile').mockResolvedValue();
jest.spyOn(SketchLoader.prototype, 'getZipContents').mockResolvedValue({
files: {
'previews/preview.png': {
async: jest.fn().mockResolvedValue('foo'),
},
},
});
// eslint-disable-next-line no-new
new SketchLoader(document.getElementById('js-sketch-viewer'));

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BoardBlockedIcon on mouseenter on blocked icon with more than three blocking issues matches the snapshot 1`] = `
"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-icon s16\\" id=\\"blocked-icon-uniqueId\\">
"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-text-red-500 gl-icon s16\\" id=\\"blocked-icon-uniqueId\\">
<use href=\\"#issue-block\\"></use>
</svg>
<div class=\\"gl-popover\\">

View File

@ -50,7 +50,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount));
});
it('does not have text-danger class when issueSize is less than maxIssueCount', () => {
@ -75,7 +75,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount));
});
it('has text-danger class', () => {

View File

@ -0,0 +1,40 @@
{
"summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 2 },
"suites": [
{
"name": "rspec:pg",
"summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 2 },
"new_failures": [
{
"result": "failure",
"name": "Test#sum when a is 1 and b is 2 returns summary",
"file": null,
"execution_time": 0.009411,
"system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'"
},
{
"result": "failure",
"name": "Test#sum when a is 100 and b is 200 returns summary",
"file": null,
"execution_time": 0.000162,
"system_output": "Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'"
}
],
"resolved_failures": [],
"existing_failures": [],
"new_errors": [],
"resolved_errors": [],
"existing_errors": []
},
{
"name": "java ant",
"summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 },
"new_failures": [],
"resolved_failures": [],
"existing_failures": [],
"new_errors": [],
"resolved_errors": [],
"existing_errors": []
}
]
}

View File

@ -9,10 +9,13 @@ describe('MR Widget', () => {
let wrapper;
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
const findToggleButton = () => wrapper.findByTestId('toggle-button');
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(Widget, {
propsData: {
isCollapsible: false,
loadingText: 'Loading widget',
widgetName: 'MyWidget',
value: {
@ -111,7 +114,7 @@ describe('MR Widget', () => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
createComponent({
propsData: {
fetchCollapsedData: async () => Promise.reject(error),
fetchCollapsedData: () => Promise.reject(error),
},
});
await waitForPromises();
@ -125,7 +128,7 @@ describe('MR Widget', () => {
createComponent({
propsData: {
summary: 'Hello world',
fetchCollapsedData: async () => Promise.resolve(),
fetchCollapsedData: () => Promise.resolve(),
},
});
@ -137,7 +140,7 @@ describe('MR Widget', () => {
it('displays the summary slot when provided', () => {
createComponent({
propsData: {
fetchCollapsedData: async () => Promise.resolve(),
fetchCollapsedData: () => Promise.resolve(),
},
slots: {
summary: '<b>More complex summary</b>',
@ -148,20 +151,35 @@ describe('MR Widget', () => {
'More complex summary',
);
});
});
it('displays the content slot when provided', () => {
describe('handle collapse toggle', () => {
it('does not display the content slot until toggle is clicked', async () => {
createComponent({
propsData: {
fetchCollapsedData: async () => Promise.resolve(),
isCollapsible: true,
fetchCollapsedData: () => Promise.resolve(),
},
slots: {
content: '<b>More complex content</b>',
},
});
expect(wrapper.findByTestId('widget-extension-collapsed-section').text()).toBe(
'More complex content',
);
expect(findExpandedSection().exists()).toBe(false);
findToggleButton().vm.$emit('click');
await nextTick();
expect(findExpandedSection().text()).toBe('More complex content');
});
it('does not display the toggle button if isCollapsible is false', () => {
createComponent({
propsData: {
isCollapsible: false,
fetchCollapsedData: () => Promise.resolve(),
},
});
expect(findToggleButton().exists()).toBe(false);
});
});
});

View File

@ -15,6 +15,7 @@ import { failedReport } from 'jest/reports/mock_data/mock_data';
import mixedResultsTestReports from 'jest/reports/mock_data/new_and_fixed_failures_report.json';
import newErrorsTestReports from 'jest/reports/mock_data/new_errors_report.json';
import newFailedTestReports from 'jest/reports/mock_data/new_failures_report.json';
import newFailedTestWithNullFilesReport from 'jest/reports/mock_data/new_failures_with_null_files_report.json';
import successTestReports from 'jest/reports/mock_data/no_failures_report.json';
import resolvedFailures from 'jest/reports/mock_data/resolved_failures.json';
import recentFailures from 'jest/reports/mock_data/recent_failures_report.json';
@ -157,6 +158,15 @@ describe('Test report extension', () => {
);
});
it('hides copy failed tests button when endpoint returns null files', async () => {
mockApi(httpStatusCodes.OK, newFailedTestWithNullFilesReport);
createComponent();
await waitForPromises();
expect(findCopyFailedSpecsBtn().exists()).toBe(false);
});
it('copy failed tests button updates tooltip text when clicked', async () => {
mockApi(httpStatusCodes.OK, newFailedTestReports);
createComponent();

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe API::Helpers::Pagination do
subject { Class.new.include(described_class).new }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Backup::DatabaseBackupError do
let(:config) do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Backup::Task do
let(:progress) { StringIO.new }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Banzai::ColorParser do
describe '.parse' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Banzai::Filter::OutputSafety do
subject do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Banzai::FilterArray do
describe '#insert_after' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Banzai::Pipeline do
describe '.[]' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Banzai::Querying do
describe '.css' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
# Emulates paginator. It returns 2 pages with results
class TestPaginator

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Page do
let(:response) { { 'values' => [{ 'username' => 'Ben' }], 'pagelen' => 2, 'next' => '' } }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Paginator do
let(:last_page) { double(:page, next?: false, items: ['item_2']) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::Comment do
describe '#author' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::Issue do
describe '#iid' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::PullRequestComment do
describe '#iid' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::PullRequest do
describe '#iid' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::Repo do
describe '#has_wiki?' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Bitbucket::Representation::User do
describe '#username' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe BitbucketServer::Page do
let(:response) { { 'values' => [{ 'description' => 'Test' }], 'isLastPage' => false, 'nextPageStart' => 2 } }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe BulkImports::Pipeline::ExtractedData do
let(:data) { 'data' }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe BulkImports::Pipeline do
let(:context) { instance_double(BulkImports::Pipeline::Context, tracker: nil) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe BulkImports::RetryPipelineError do
describe '#retry_delay' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Constraints::JiraEncodedUrlConstrainer do
let(:namespace_id) { 'group' }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe DeclarativeEnum do
let(:enum_module) do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe ErrorTracking::SentryClient do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
let(:instance) { described_class.new({}) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::ApplicationRateLimiter::BaseStrategy do
describe '#increment' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Asciidoc::Html5Converter do
describe 'convert AsciiDoc to HTML5' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Audit::NullTarget do
subject { described_class.new }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require "spec_helper"
require 'fast_spec_helper'
RSpec.describe Gitlab::ChangesList do
let(:valid_changes_string) { "\n000000 570e7b2 refs/heads/my_branch\nd14d6c 6fd24d refs/heads/master" }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Chat::Responder::Base do
let(:project) { double(:project) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
# The rest of the specs for this class are covered in style_spec.rb
RSpec.describe Gitlab::Ci::Ansi2json::Parser do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Ansi2json::Result do
let(:stream) { StringIO.new('hello') }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Build::Artifacts::Path do
describe '#valid?' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Build::Policy do
let(:policy) { spy('policy specification') }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Build::Port do
subject { described_class.new(port) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
require_dependency 'active_model'
RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::MaskSecret do
subject { described_class }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do
subject(:parse_source) { described_class.parse_source(properties) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do
subject { described_class.source(property_data) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Parsers do
describe '.fabricate!' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Duration do
let(:calculated_duration) { calculate(data) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
describe '.build' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
describe '.build' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
describe '.build' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe ::Gitlab::Ci::Pipeline::Metrics do
describe '.pipeline_creation_step_duration_histogram' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
let(:comparer) { described_class.new(base_report, head_report) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do
let(:accessibility_report) { described_class.new }

Some files were not shown because too many files have changed in this diff Show More