Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-10-02 15:07:48 +00:00
parent 4d8b6311a9
commit f9adc222bc
63 changed files with 624 additions and 411 deletions

View File

@ -98,7 +98,7 @@ export default {
:file-name="blob.name"
:type="activeViewer.fileType"
:hide-line-numbers="hideLineNumbers"
data-qa-selector="blob_viewer_file_content"
data-testid="blob-viewer-file-content"
@richContentLoaded="richContentLoaded = true"
/>
</template>

View File

@ -96,7 +96,7 @@ export default {
};
</script>
<template>
<gl-button-group data-qa-selector="default_actions_container">
<gl-button-group data-testid="default-actions-container">
<gl-button
v-if="showCopyButton"
v-gl-tooltip.hover
@ -104,8 +104,7 @@ export default {
:title="$options.BTN_COPY_CONTENTS_TITLE"
:disabled="copyDisabled"
:data-clipboard-target="getBlobHashTarget"
data-testid="copyContentsButton"
data-qa-selector="copy_contents_button"
data-testid="copy-contents-button"
icon="copy-to-clipboard"
category="primary"
variant="default"

View File

@ -49,7 +49,7 @@ export default {
<file-icon :file-name="fileName" :size="16" aria-hidden="true" css-classes="gl-mr-3" />
<strong
class="file-title-name mr-1 js-blob-header-filepath"
data-qa-selector="file_title_content"
data-testid="file-title-content"
>{{ fileName }}</strong
>
</template>

View File

@ -1,5 +1,5 @@
<script>
import { GlForm, GlFormInput, GlButton } from '@gitlab/ui';
import { GlForm, GlFormInput, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
@ -12,6 +12,9 @@ export default {
GlButton,
GlFormInput,
},
directives: {
GlTooltip: GlTooltipDirective,
},
data() {
return {
inputEnabled: false,
@ -37,7 +40,8 @@ export default {
<div id="peek-view-add-request" class="view gl-display-flex">
<gl-form class="gl-display-flex gl-align-items-center" @submit.prevent>
<gl-button
class="gl-text-blue-300! gl-mr-2"
v-gl-tooltip.viewport
class="gl-text-white! gl-mr-2"
category="tertiary"
variant="link"
icon="plus"
@ -52,7 +56,7 @@ export default {
type="text"
:placeholder="$options.i18n.inputLabel"
:aria-label="$options.i18n.inputLabel"
class="gl-ml-2"
class="gl-ml-2 gl-px-3! gl-py-2!"
@keyup.enter="addRequest"
@keyup.esc="clearForm"
/>

View File

@ -1,5 +1,11 @@
<script>
import { GlButton, GlModal, GlModalDirective, GlCollapsibleListbox } from '@gitlab/ui';
import {
GlButton,
GlTooltipDirective,
GlModal,
GlModalDirective,
GlCollapsibleListbox,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { sortOrders, sortOrderOptions } from '../constants';
@ -13,6 +19,7 @@ export default {
GlCollapsibleListbox,
},
directives: {
GlTooltip: GlTooltipDirective,
'gl-modal': GlModalDirective,
},
props: {
@ -133,14 +140,17 @@ export default {
<div
v-if="currentRequest.details && metricDetails"
:id="`peek-view-${metric}`"
class="gl-display-flex gl-align-items-center view"
class="gl-display-flex gl-align-items-baseline view"
data-qa-selector="detailed_metric_content"
>
<gl-button v-gl-modal="modalId" class="gl-mr-2" type="button" variant="link">
<span
class="gl-text-blue-200 gl-font-weight-bold"
data-testid="performance-bar-details-label"
>
<gl-button
v-gl-tooltip.viewport
v-gl-modal="modalId"
class="gl-text-white! gl-mr-2"
:title="header"
variant="link"
>
<span class="gl-font-sm gl-font-weight-semibold" data-testid="performance-bar-details-label">
{{ metricDetailsLabel }}
</span>
</gl-button>
@ -150,7 +160,7 @@ export default {
<div v-for="(value, name) in metricDetailsSummary" :key="name" class="gl-pr-8">
<div v-if="value" data-testid="performance-bar-summary-item">
<div>{{ name }}</div>
<div class="gl-font-size-h1 gl-font-weight-bold">{{ value }}</div>
<div class="gl-font-size-h1 gl-font-weight-semibold">{{ value }}</div>
</div>
</div>
</div>
@ -178,7 +188,7 @@ export default {
v-for="(key, keyIndex) in keys"
:key="key"
class="text-break-word"
:class="{ 'mb-3 bold': keyIndex == 0 }"
:class="{ 'mb-3 gl-font-weight-semibold': keyIndex == 0 }"
>
{{ item[key] }}
<gl-button
@ -214,7 +224,7 @@ export default {
<div></div>
</template>
</gl-modal>
{{ actualTitle }}
<span class="gl-opacity-7">{{ actualTitle }}</span>
<request-warning :html-id="htmlId" :warnings="warnings" />
</div>
</template>

View File

@ -1,7 +1,5 @@
<script>
import { GlLink, GlPopover } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { glEmojiTag } from '~/emoji';
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
@ -11,14 +9,13 @@ import RequestSelector from './request_selector.vue';
export default {
components: {
GlPopover,
AddRequest,
DetailedMetric,
GlLink,
RequestSelector,
},
directives: {
SafeHtml,
GlTooltip: GlTooltipDirective,
},
props: {
store: {
@ -123,11 +120,8 @@ export default {
hasHost() {
return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host;
},
birdEmoji() {
if (this.hasHost && this.currentRequest.details.host.canary) {
return glEmojiTag('baby_chick');
}
return '';
isCanary() {
return Boolean(this.currentRequest.details.host.canary);
},
downloadPath() {
const data = JSON.stringify(this.requests);
@ -165,7 +159,6 @@ export default {
this.currentRequest = this.requestId;
},
methods: {
glEmojiTag,
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
this.$emit('change-request', newRequestId);
@ -180,96 +173,112 @@ export default {
return this.store.findRequest(requestId)?.method?.toUpperCase() === 'GET';
},
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
</script>
<template>
<div id="js-peek" :class="env">
<div
v-if="currentRequest"
class="d-flex container-fluid container-limited justify-content-center gl-align-items-center"
class="gl-display-flex container-fluid gl-overflow-x-auto"
data-qa-selector="performance_bar"
>
<div id="peek-view-host" class="view">
<span
v-if="hasHost"
class="current-host"
:class="{ canary: currentRequest.details.host.canary }"
<div class="gl-display-flex gl-flex-shrink-0 view-performance-container">
<div v-if="hasHost" id="peek-view-host" class="gl-display-flex gl-gap-2 view">
<span class="current-host" :class="{ canary: isCanary }">
<gl-emoji
v-if="isCanary"
id="canary-emoji"
v-gl-tooltip.viewport="'Canary'"
data-name="baby_chick"
/>
<gl-emoji
id="host-emoji"
v-gl-tooltip.viewport="currentRequest.details.host.hostname"
data-name="computer"
/>
</span>
</div>
<detailed-metric
v-for="metric in $options.detailedMetrics"
:key="metric.metric"
:current-request="currentRequest"
:metric="metric.metric"
:title="metric.title"
:header="metric.header"
:keys="metric.keys"
/>
<div
v-if="currentRequest.details && currentRequest.details.tracing"
id="peek-view-trace"
class="view"
>
<span id="canary-emoji" v-safe-html:[$options.safeHtmlConfig]="birdEmoji"></span>
<gl-popover placement="bottom" target="canary-emoji" content="Canary" />
<span
id="host-emoji"
v-safe-html:[$options.safeHtmlConfig]="glEmojiTag('computer')"
></span>
<gl-popover
placement="bottom"
target="host-emoji"
:content="currentRequest.details.host.hostname"
/>
</span>
<gl-link
class="gl-text-white! gl-text-decoration-underline"
:href="currentRequest.details.tracing.tracing_url"
>{{ s__('PerformanceBar|Trace') }}</gl-link
>
</div>
<div v-if="showFlamegraphButtons" id="peek-flamegraph" class="view">
<gl-link
v-gl-tooltip.viewport
class="gl-font-sm gl-text-white!"
:href="flamegraphPath('wall', currentRequestId)"
:title="s__('PerformanceBar|Wall flamegraph')"
>{{ s__('PerformanceBar|Wall') }}</gl-link
>
/
<gl-link
v-gl-tooltip.viewport
class="gl-font-sm gl-text-white!"
:href="flamegraphPath('cpu', currentRequestId)"
:title="s__('PerformanceBar|CPU flamegraph')"
>{{ s__('PerformanceBar|CPU') }}</gl-link
>
/
<gl-link
v-gl-tooltip.viewport
class="gl-font-sm gl-text-white!"
:href="flamegraphPath('object', currentRequestId)"
:title="s__('PerformanceBar|Object flamegraph')"
>{{ s__('PerformanceBar|Object') }}</gl-link
>
<span class="gl-opacity-7">{{ s__('PerformanceBar|flamegraph') }}</span>
</div>
</div>
<detailed-metric
v-for="metric in $options.detailedMetrics"
:key="metric.metric"
:current-request="currentRequest"
:metric="metric.metric"
:title="metric.title"
:header="metric.header"
:keys="metric.keys"
/>
<div
v-if="currentRequest.details && currentRequest.details.tracing"
id="peek-view-trace"
class="view"
>
<gl-link class="gl-text-blue-200" :href="currentRequest.details.tracing.tracing_url">{{
s__('PerformanceBar|Trace')
<div class="gl-display-flex gl-flex-shrink-0 gl-ml-auto">
<div class="gl-display-flex view-reports-container">
<div v-if="currentRequest.details" id="peek-download" class="view">
<gl-link
v-gl-tooltip.viewport
class="gl-font-sm gl-text-white!"
is-unsafe-link
:download="downloadName"
:href="downloadPath"
:title="s__('PerformanceBar|Download report')"
>{{ s__('PerformanceBar|Download') }}</gl-link
>
</div>
<div v-if="showMemoryReportButton" id="peek-memory-report" class="view">
<gl-link
v-gl-tooltip.viewport
class="gl-font-sm gl-text-white!"
:href="memoryReportPath"
:title="s__('PerformanceBar|Download memory report')"
>{{ s__('PerformanceBar|Memory report') }}</gl-link
>
</div>
</div>
<gl-link v-if="statsUrl" class="gl-text-white! view" :href="statsUrl">{{
s__('PerformanceBar|Stats')
}}</gl-link>
<request-selector
v-if="currentRequest"
:current-request="currentRequest"
:requests="requests"
@change-current-request="changeCurrentRequest"
/>
<add-request v-on="$listeners" />
</div>
<div v-if="currentRequest.details" id="peek-download" class="view">
<gl-link
class="gl-text-blue-200"
is-unsafe-link
:download="downloadName"
:href="downloadPath"
>{{ s__('PerformanceBar|Download') }}</gl-link
>
</div>
<div v-if="showMemoryReportButton" id="peek-memory-report" class="view">
<gl-link class="gl-text-blue-200" :href="memoryReportPath">{{
s__('PerformanceBar|Memory report')
}}</gl-link>
</div>
<div v-if="showFlamegraphButtons" id="peek-flamegraph" class="view">
<span id="flamegraph-emoji" class="gl-text-white-200">
<span v-safe-html:[$options.safeHtmlConfig]="glEmojiTag('fire')"></span>
<span v-safe-html:[$options.safeHtmlConfig]="glEmojiTag('bar_chart')"></span>
</span>
<gl-popover placement="bottom" target="flamegraph-emoji" content="Flamegraph" />
<gl-link class="gl-text-blue-200" :href="flamegraphPath('wall', currentRequestId)">{{
s__('PerformanceBar|wall')
}}</gl-link>
/
<gl-link class="gl-text-blue-200" :href="flamegraphPath('cpu', currentRequestId)">{{
s__('PerformanceBar|cpu')
}}</gl-link>
/
<gl-link class="gl-text-blue-200" :href="flamegraphPath('object', currentRequestId)">{{
s__('PerformanceBar|object')
}}</gl-link>
</div>
<gl-link v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{
s__('PerformanceBar|Stats')
}}</gl-link>
<request-selector
v-if="currentRequest"
:current-request="currentRequest"
:requests="requests"
class="gl-ml-auto"
@change-current-request="changeCurrentRequest"
/>
<add-request v-on="$listeners" />
</div>
</div>
</template>

View File

@ -1,5 +1,10 @@
<script>
import { GlFormSelect } from '@gitlab/ui';
export default {
components: {
GlFormSelect,
},
props: {
currentRequest: {
type: Object,
@ -23,8 +28,8 @@ export default {
};
</script>
<template>
<div id="peek-request-selector" data-qa-selector="request_dropdown" class="view">
<select v-model="currentRequestId">
<div id="peek-request-selector" data-qa-selector="request_dropdown" class="view gl-mr-5">
<gl-form-select v-model="currentRequestId" class="gl-px-3! gl-py-2!">
<option
v-for="request in requests"
:key="request.id"
@ -33,6 +38,6 @@ export default {
>
{{ request.displayName }}
</option>
</select>
</gl-form-select>
</div>
</template>

View File

@ -1,14 +1,9 @@
<script>
import { GlPopover } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { glEmojiTag } from '~/emoji';
import { GlTooltipDirective } from '@gitlab/ui';
export default {
components: {
GlPopover,
},
directives: {
SafeHtml,
GlTooltip: GlTooltipDirective,
},
props: {
htmlId: {
@ -32,15 +27,17 @@ export default {
return this.warnings.join('\n');
},
},
methods: {
glEmojiTag,
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
</script>
<template>
<span v-if="hasWarnings" class="gl-cursor-default">
<span :id="htmlId" v-safe-html:[$options.safeHtmlConfig]="glEmojiTag('warning')"></span>
<gl-popover placement="bottom" :target="htmlId" :content="warningMessage" />
<gl-emoji
v-if="hasWarnings"
:id="htmlId"
v-gl-tooltip.viewport="warningMessage"
data-name="warning"
class="gl-ml-2"
/>
</span>
</template>

View File

@ -53,7 +53,7 @@ export default {
:aria-label="$options.MSG_COPY"
:data-clipboard-text="value"
icon="copy-to-clipboard"
data-qa-selector="copy_button"
data-testid="copy-button"
:data-qa-action="name"
/>
</template>

View File

@ -68,14 +68,14 @@ export default {
<embed-dropdown
v-if="embeddable"
:url="snippet.webUrl"
data-qa-selector="snippet_embed_dropdown"
data-testid="snippet-embed-dropdown"
/>
<clone-dropdown-button
v-if="canBeCloned"
class="gl-ml-3"
:ssh-link="snippet.sshUrlToRepo"
:http-link="snippet.httpUrlToRepo"
data-qa-selector="clone_button"
data-testid="clone-button"
/>
</div>
<gl-alert v-if="hasUnretrievableBlobs" variant="danger" class="gl-mb-3" :dismissible="false">

View File

@ -20,7 +20,7 @@ export default {
};
</script>
<template>
<markdown-field-view class="snippet-description" data-qa-selector="snippet_description_content">
<markdown-field-view class="snippet-description" data-testid="snippet-description-content">
<div
v-safe-html:[$options.safeHtmlConfig]="description"
class="md js-snippet-description"

View File

@ -216,7 +216,7 @@ export default {
<div class="detail-page-header-body">
<div
class="snippet-box has-tooltip d-flex align-items-center gl-mr-2 mb-1"
data-qa-selector="snippet_container"
data-testid="snippet-container"
:title="snippetVisibilityLevelDescription"
data-container="body"
>
@ -267,7 +267,7 @@ export default {
:category="action.category"
:class="action.cssClass"
:href="action.href"
data-qa-selector="snippet_action_button"
data-testid="snippet-action-button"
:data-qa-action="action.text"
@click="action.click ? action.click() : undefined"
>{{ action.text }}</gl-button
@ -321,8 +321,7 @@ export default {
variant="danger"
category="primary"
:disabled="isLoading"
data-qa-selector="delete_snippet_button"
data-testid="delete-snippet"
data-testid="delete-snippet-button"
@click="deleteSnippet"
>
<gl-loading-icon v-if="isLoading" size="sm" inline />

View File

@ -20,7 +20,7 @@ export default {
</script>
<template>
<div class="snippet-header limited-header-width">
<h2 class="snippet-title gl-mt-0 mb-3" data-qa-selector="snippet_title_content">
<h2 class="snippet-title gl-mt-0 mb-3" data-testid="snippet-title-content">
{{ snippet.title }}
</h2>

View File

@ -47,13 +47,13 @@ export default {
v-if="sshLink"
:label="$options.labels.ssh"
:link="sshLink"
qa-selector="copy_ssh_url_button"
test-id="copy-ssh-url-button"
/>
<clone-dropdown-item
v-if="httpLink"
:label="httpLabel"
:link="httpLink"
qa-selector="copy_http_url_button"
test-id="copy-http-url-button"
/>
</gl-disclosure-dropdown>
</template>

View File

@ -27,7 +27,7 @@ export default {
type: String,
required: true,
},
qaSelector: {
testId: {
type: String,
required: true,
},
@ -45,7 +45,7 @@ export default {
:title="$options.copyURLTooltip"
:aria-label="$options.copyURLTooltip"
:data-clipboard-text="link"
:data-qa-selector="qaSelector"
:data-testid="testId"
icon="copy-to-clipboard"
class="gl-display-inline-flex"
/>

View File

@ -258,7 +258,7 @@ export default {
:class="$options.userColorScheme"
data-type="simple"
:data-path="blob.path"
data-qa-selector="blob_viewer_file_content"
data-testid="blob-viewer-file-content"
>
<codeowners-validation
v-if="isCodeownersFile"

View File

@ -481,7 +481,7 @@ $count-arrow-border: #dce0e5;
$general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
$performance-bar-height: 40px;
$system-header-height: 16px;
$system-footer-height: $system-header-height;
$mr-sticky-header-height: 72px;
@ -834,7 +834,7 @@ Performance Bar
*/
$perf-bar-production: $gray-950;
$perf-bar-staging: $indigo-950;
$perf-bar-development: $red-950;
$perf-bar-development: $red-900;
$perf-bar-bucket-bg: $black;
$perf-bar-bucket-box-shadow-from: rgba($white, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);

View File

@ -10,8 +10,9 @@
height: $performance-bar-height;
background: $black;
font-size: $gl-font-size-small;
line-height: $performance-bar-height;
color: $gray-100;
color: $gray-50;
select {
width: 200px;
@ -24,7 +25,11 @@
select,
input {
color: inherit;
background-color: inherit;
background-color: rgba($white, 0.2);
&::placeholder {
color: rgba($white, 0.7);
}
}
option {
@ -88,7 +93,6 @@
}
.view {
margin-right: 15px;
flex-shrink: 0;
&:last-child {
@ -96,6 +100,22 @@
}
}
.view-performance-container,
.view-reports-container {
margin-right: $gl-padding-24;
.view:not(:first-child) {
margin-right: 0;
&::before {
content: '';
opacity: .5;
display: inline-block;
margin: 0 $gl-padding-8;
}
}
}
.css-truncate {
&.css-truncate-target,
.css-truncate-target {

View File

@ -255,6 +255,12 @@ class GraphqlController < ApplicationController
end
def authorize_access_api!
if current_user.nil? &&
request_authenticator.authentication_token_present? &&
Feature.enabled?(:invalid_graphql_auth_401)
render_error('Invalid token', status: :unauthorized)
end
return if can?(current_user, :access_api)
render_error('API not accessible for user', status: :forbidden)

View File

@ -95,7 +95,7 @@ class Namespace < ApplicationRecord
length: { maximum: 255 }
validates :name, uniqueness: { scope: [:type, :parent_id] }, if: -> { parent_id.present? }
validates :description, length: { maximum: 2000 }
validates :description, length: { maximum: 255 }
validates :path,
presence: true,

View File

@ -5,6 +5,9 @@
= render_if_exists 'dashboard/todos/saml_reauth_notice'
- add_page_specific_style 'page_bundles/todos'
- add_page_specific_style 'page_bundles/issuable'
- filter_by_done = params[:state] == 'done'
- open_todo_count = !filter_by_done ? @allowed_todos.count : todos_pending_count
- done_todo_count = filter_by_done ? @allowed_todos.count : todos_done_count
.page-title-holder.d-flex.align-items-center
%h1.page-title.gl-font-size-h-display= _("To-Do List")
@ -14,10 +17,10 @@
= gl_tabs_nav({ class: 'gl-flex-grow-1 gl-border-0' }) do
= gl_tab_link_to todos_filter_path(state: 'pending'), item_active: params[:state].blank? || params[:state] == 'pending', class: "js-todos-pending" do
= _("To Do")
= gl_tab_counter_badge(number_with_delimiter(todos_pending_count), { class: 'js-todos-badge' })
= gl_tab_link_to todos_filter_path(state: 'done'), item_active: params[:state] == 'done', class: "js-todos-done" do
= gl_tab_counter_badge(number_with_delimiter(open_todo_count), { class: 'js-todos-badge' })
= gl_tab_link_to todos_filter_path(state: 'done'), item_active: filter_by_done, class: "js-todos-done" do
= _("Done")
= gl_tab_counter_badge(number_with_delimiter(todos_done_count), { class: 'js-todos-badge' })
= gl_tab_counter_badge(number_with_delimiter(done_todo_count), { class: 'js-todos-badge' })
.nav-controls
- if @allowed_todos.any?(&:pending?)

View File

@ -19,6 +19,6 @@
.note-actions-item.gl-ml-0
= render Pajamas::ButtonComponent.new(category: :tertiary,
icon: 'pencil',
button_options: { class: 'note-action-button js-note-edit has-tooltip', data: { container: 'body', qa_selector: 'edit_comment_button' }, title: _('Edit comment'), 'aria-label': _('Edit comment') })
button_options: { class: 'note-action-button js-note-edit has-tooltip', data: { container: 'body', testid: 'edit-comment-button' }, title: _('Edit comment'), 'aria-label': _('Edit comment') })
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable

View File

@ -2,7 +2,7 @@
- if note_editable || !is_current_user
%div{ class: "dropdown more-actions note-actions-item gl-ml-0!" }
= render Pajamas::ButtonComponent.new(icon: 'ellipsis_v', category: :tertiary, button_options: { class: 'note-action-button more-actions-toggle has-tooltip', data: { title: 'More actions', toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' }})
= render Pajamas::ButtonComponent.new(icon: 'ellipsis_v', category: :tertiary, button_options: { class: 'note-action-button more-actions-toggle has-tooltip', data: { title: 'More actions', toggle: 'dropdown', container: 'body', testid: 'more-actions-dropdown' }})
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
%li
= deprecated_clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'btn-clipboard', hide_tooltip: true, hide_button_icon: true)
@ -11,6 +11,6 @@
.js-report-abuse-dropdown-item{ data: { report_abuse_path: add_category_abuse_reports_path, reported_user_id: note.author.id, reported_from_url: noteable_note_url(note) } }
- if note_editable
%li
= link_to note_url(note), method: :delete, data: { confirm: _('Are you sure you want to delete this comment?'), confirm_btn_variant: 'danger', qa_selector: 'delete_comment_button' }, aria: { label: _('Delete comment') }, remote: true, class: 'js-note-delete' do
= link_to note_url(note), method: :delete, data: { confirm: _('Are you sure you want to delete this comment?'), confirm_btn_variant: 'danger', testid: 'delete-comment-button' }, aria: { label: _('Delete comment') }, remote: true, class: 'js-note-delete' do
%span.text-danger
= _('Delete comment')

View File

@ -2,7 +2,7 @@
- current_text ||= nil
- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true)
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
- qa_selector = local_assigns.fetch(:qa_selector, '')
- testid = local_assigns.fetch(:testid, '')
- autofocus = local_assigns.fetch(:autofocus, false)
.zen-backdrop
@ -14,9 +14,9 @@
dir: 'auto',
data: { supports_quick_actions: supports_quick_actions,
supports_autocomplete: supports_autocomplete,
qa_selector: qa_selector,
testid: testid,
autofocus: autofocus }
- else
= text_area_tag attr, current_text, data: { qa_selector: qa_selector }, class: classes, placeholder: placeholder
= text_area_tag attr, current_text, data: { testid: testid }, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave.gl-text-gray-500{ href: "#" }
= sprite_icon('minimize')

View File

@ -1,5 +1,5 @@
- noteable_name = @note.noteable.human_class_name
.js-comment-type-dropdown.float-left.gl-sm-mr-3{ data: { noteable_name: noteable_name } }
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'js-comment-button js-comment-submit-button', value: _('Comment'), data: { qa_selector: 'comment_button' }}) do
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'js-comment-button js-comment-submit-button', value: _('Comment'), data: { testid: 'comment-button' }}) do
= _('Comment')

View File

@ -4,13 +4,13 @@
= hidden_field_tag :target_type, '', class: 'js-form-target-type'
.flash-container
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do
= render 'shared/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', qa_selector: 'edit_note_field', placeholder: _("Write a comment or drag your files here…")
= render 'shared/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', testid: 'edit-note-field', placeholder: _("Write a comment or drag your files here…")
= render 'shared/notes/hints'
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
= _("Finish editing this message first!")
= render Pajamas::ButtonComponent.new(type: 'submit', variant: :confirm, button_options: { class: 'js-comment-save-button', data: { qa_selector: 'save_comment_button' } }) do
= render Pajamas::ButtonComponent.new(type: 'submit', variant: :confirm, button_options: { class: 'js-comment-save-button', data: { testid: 'save-comment-button' } }) do
= _("Save comment")
= render Pajamas::ButtonComponent.new(button_options: { class: 'note-edit-cancel' }) do
= _("Cancel")

View File

@ -26,7 +26,7 @@
.discussion-form-container.discussion-with-resolve-btn.flex-column.p-0
= render layout: 'shared/md_preview', locals: { url: preview_url, referenced_users: true, supports_quick_actions: supports_quick_actions } do
= render 'shared/zen', f: f, qa_selector: 'note_field',
= render 'shared/zen', f: f, testid: 'note-field',
attr: :note,
classes: 'note-textarea js-note-text',
placeholder: _("Write a comment or drag your files here…"),

View File

@ -35,7 +35,7 @@
%span.note-header-author-name.bold
= note.author.name
= user_status(note.author)
%spannote-headline-light{ data: { qa_selector: 'note_author_content' } }
%spannote-headline-light{ data: { testid: 'note-author-content' } }
= note.author.to_reference
%span.note-headline-ligh.note-headline-meta
- if note.system
@ -52,7 +52,7 @@
- else
= render 'projects/notes/actions', note: note, note_editable: note_editable
.note-body{ class: note_editable ? 'js-task-list-container' : '' }
.note-text.md{ data: { qa_selector: 'note_content' } }
.note-text.md{ data: { testid: 'note-content' } }
= markdown_field(note, :note)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }

View File

@ -1,7 +1,7 @@
- issuable = @issue || @merge_request
- discussion_locked = issuable&.discussion_locked?
%ul#notes-list.notes.main-notes-list.timeline{ data: { 'qa_selector': 'notes_list' } }
%ul#notes-list.notes.main-notes-list.timeline{ data: { 'testid': 'notes-list' } }
= render "shared/notes/notes"
= render 'shared/notes/edit_form', project: @project

View File

@ -8,6 +8,6 @@
- if note_editable
.note-actions-item.gl-ml-0
= render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'pencil', button_options: { title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip', data: { container: 'body', qa_selector: 'edit_comment_button' } })
= render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'pencil', button_options: { title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip', data: { container: 'body', testid: 'edit-comment-button' } })
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable

View File

@ -1,8 +1,8 @@
---
name: use_primary_store_as_default_for_queues_metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131736
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/425508
name: invalid_graphql_auth_401
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132149
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426196
milestone: '16.5'
type: development
group: group::scalability
group: group::import and integrate
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: use_primary_and_secondary_stores_for_queues_metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131736
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/425508
milestone: '16.5'
type: development
group: group::scalability
default_enabled: false

View File

@ -894,6 +894,10 @@ Gitlab.ee do
Settings.cron_jobs['click_house_events_sync_worker'] ||= {}
Settings.cron_jobs['click_house_events_sync_worker']['cron'] ||= "*/3 * * * *"
Settings.cron_jobs['click_house_events_sync_worker']['job_class'] = 'ClickHouse::EventsSyncWorker'
Settings.cron_jobs['click_house_ci_finished_builds_sync_worker'] ||= {}
Settings.cron_jobs['click_house_ci_finished_builds_sync_worker']['cron'] ||= '*/3 * * * *'
Settings.cron_jobs['click_house_ci_finished_builds_sync_worker']['args'] ||= [0, 1]
Settings.cron_jobs['click_house_ci_finished_builds_sync_worker']['job_class'] = 'ClickHouse::CiFinishedBuildsSyncCronWorker'
end
end

View File

@ -0,0 +1,10 @@
---
table_name: analytics_cycle_analytics_value_stream_settings
classes:
- Analytics::CycleAnalytics::ValueStreamSetting
feature_categories:
- value_stream_management
description: Stores settings for each value stream.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132650
milestone: '16.5'
gitlab_schema: gitlab_main

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class CreateValueStreamAnalyticsSettings < Gitlab::Database::Migration[2.1]
def change
create_table :analytics_cycle_analytics_value_stream_settings, id: false do |t|
t.references(
:value_stream,
primary_key: true,
default: nil,
type: :bigint,
index: false,
foreign_key: {
to_table: :analytics_cycle_analytics_group_value_streams,
column: :analytics_cycle_analytics_group_value_stream_id,
on_delete: :cascade
}
)
t.bigint :project_ids_filter, array: true, default: []
t.check_constraint 'CARDINALITY(project_ids_filter) <= 100'
end
end
end

View File

@ -3,7 +3,6 @@
class CleanupProjectPipelineStatusKey < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
# TODO: to remove after feature-flag in duplicate-jobs client middleware is removed
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION_WORKER_CLASS = 'BackfillProjectPipelineStatusTtl'

View File

@ -0,0 +1 @@
03edad77c4b9ca8754a6365f42abe3e1ae139b934603085fd88d01c0a3e0acbc

View File

@ -11179,6 +11179,12 @@ CREATE SEQUENCE analytics_cycle_analytics_stage_event_hashes_id_seq
ALTER SEQUENCE analytics_cycle_analytics_stage_event_hashes_id_seq OWNED BY analytics_cycle_analytics_stage_event_hashes.id;
CREATE TABLE analytics_cycle_analytics_value_stream_settings (
value_stream_id bigint NOT NULL,
project_ids_filter bigint[] DEFAULT '{}'::bigint[],
CONSTRAINT chk_rails_a91b547c97 CHECK ((cardinality(project_ids_filter) <= 100))
);
CREATE TABLE analytics_dashboards_pointers (
id bigint NOT NULL,
namespace_id bigint,
@ -27605,6 +27611,9 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_value_streams
ALTER TABLE ONLY analytics_cycle_analytics_stage_event_hashes
ADD CONSTRAINT analytics_cycle_analytics_stage_event_hashes_pkey PRIMARY KEY (id);
ALTER TABLE ONLY analytics_cycle_analytics_value_stream_settings
ADD CONSTRAINT analytics_cycle_analytics_value_stream_settings_pkey PRIMARY KEY (value_stream_id);
ALTER TABLE ONLY analytics_dashboards_pointers
ADD CONSTRAINT analytics_dashboards_pointers_pkey PRIMARY KEY (id);
@ -38166,6 +38175,9 @@ ALTER TABLE ONLY batched_background_migration_jobs
ALTER TABLE ONLY operations_strategies_user_lists
ADD CONSTRAINT fk_rails_43241e8d29 FOREIGN KEY (strategy_id) REFERENCES operations_strategies(id) ON DELETE CASCADE;
ALTER TABLE ONLY analytics_cycle_analytics_value_stream_settings
ADD CONSTRAINT fk_rails_4360d37256 FOREIGN KEY (value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_request_assignment_events
ADD CONSTRAINT fk_rails_4378a2e8d7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;

View File

@ -66,25 +66,25 @@ All Work Item types share the same pool of predefined widgets and are customized
### Work Item widget types (updating)
| Widget | Description | feature flag |
|---|---|---|
| [WorkItemWidgetAssignees](../../../api/graphql/reference/index.md#workitemwidgetassignees) | List of work item assignees | |
| [WorkItemWidgetAwardEmoji](../../../api/graphql/reference/index.md#workitemwidgetawardemoji) | Emoji reactions added to work item, including support for upvote/downvote counts | |
| [WorkItemWidgetCurrentUserTodos](../../../api/graphql/reference/index.md#workitemwidgetcurrentusertodos) | User todo state of work item | |
| [WorkItemWidgetDescription](../../../api/graphql/reference/index.md#workitemwidgetdescription) | Description of work item, including support for edited state, timestamp, and author | |
| [WorkItemWidgetHealthStatus](../../../api/graphql/reference/index.md#workitemwidgethealthstatus) | Health status assignment support for work item | |
| [WorkItemWidgetHierarchy](../../../api/graphql/reference/index.md#workitemwidgethierarchy) | Hierarchy of work items, including support for boolean representing presence of children. **Note:** Hierarchy is currently available only for OKRs. | `okrs_mvc` |
| [WorkItemWidgetIteration](../../../api/graphql/reference/index.md#workitemwidgetiteration) | Iteration assignment support for work item | |
| [WorkItemWidgetLabels](../../../api/graphql/reference/index.md#workitemwidgetlabels) | List of labels added to work items, including support for checking whether scoped labels are supported |
| [WorkItemWidgetLinkedItems](../../../api/graphql/reference/index.md#workitemwidgetlinkeditems) | List of work items added as related to a given work item, with possible relationship types being `relates_to`, `blocks`, and `blocked_by`. Includes support for individual counts of blocked status, blocked by, blocking, and related to. | `linked_work_items` |
| [WorkItemWidgetMilestone](../../../api/graphql/reference/index.md#workitemwidgetmilestone) | Milestone assignment support for work item | |
| [WorkItemWidgetNotes](../../../api/graphql/reference/index.md#workitemwidgetnotes) | List of discussions within a work item | |
| [WorkItemWidgetNotifications](../../../api/graphql/reference/index.md#workitemwidgetnotifications) | Notifications subscription status of a work item for current user | |
| [WorkItemWidgetProgress](../../../api/graphql/reference/index.md#workitemwidgetprogress) | Progress value of a work item. **Note:** Progress is currently available only for OKRs. | `okrs_mvc` |
| [WorkItemWidgetStartAndDueDate](../../../api/graphql/reference/index.md#workitemwidgetstartandduedate) | Set start and due dates for a work item | |
| [WorkItemWidgetStatus](../../../api/graphql/reference/index.md#workitemwidgetstatus) | Status of a work item when type is Requirement, with possible status types being `unverified`, `satisfied`, or `failed` | |
| [WorkItemWidgetTestReports](../../../api/graphql/reference/index.md#workitemwidgettestreports) | Test reports associated with a work item | |
| [WorkItemWidgetWeight](../../../api/graphql/reference/index.md#workitemwidgetweight) | Set weight of a work item | |
| Widget | Description | Feature flag | Write permission | GraphQL Subscription Support |
|---|---|---|---|---|
| [WorkItemWidgetAssignees](../../../api/graphql/reference/index.md#workitemwidgetassignees) | List of work item assignees | |`Guest`|Yes|
| [WorkItemWidgetAwardEmoji](../../../api/graphql/reference/index.md#workitemwidgetawardemoji) | Emoji reactions added to work item, including support for upvote/downvote counts | |Anyone who can view|No|
| [WorkItemWidgetCurrentUserTodos](../../../api/graphql/reference/index.md#workitemwidgetcurrentusertodos) | User todo state of work item | |Anyone who can view|No|
| [WorkItemWidgetDescription](../../../api/graphql/reference/index.md#workitemwidgetdescription) | Description of work item, including support for edited state, timestamp, and author | |`Reporter`|No|
| [WorkItemWidgetHealthStatus](../../../api/graphql/reference/index.md#workitemwidgethealthstatus) | Health status assignment support for work item | |`Reporter`|No|
| [WorkItemWidgetHierarchy](../../../api/graphql/reference/index.md#workitemwidgethierarchy) | Hierarchy of work items, including support for boolean representing presence of children. **Note:** Hierarchy is currently available only for OKRs. | `okrs_mvc` |`Guest`|No|
| [WorkItemWidgetIteration](../../../api/graphql/reference/index.md#workitemwidgetiteration) | Iteration assignment support for work item | |`Reporter`|No|
| [WorkItemWidgetLabels](../../../api/graphql/reference/index.md#workitemwidgetlabels) | List of labels added to work items, including support for checking whether scoped labels are supported | |`Reporter`|Yes|
| [WorkItemWidgetLinkedItems](../../../api/graphql/reference/index.md#workitemwidgetlinkeditems) | List of work items added as related to a given work item, with possible relationship types being `relates_to`, `blocks`, and `blocked_by`. Includes support for individual counts of blocked status, blocked by, blocking, and related to. | `linked_work_items`|`Guest`|No|
| [WorkItemWidgetMilestone](../../../api/graphql/reference/index.md#workitemwidgetmilestone) | Milestone assignment support for work item | |`Reporter`|No|
| [WorkItemWidgetNotes](../../../api/graphql/reference/index.md#workitemwidgetnotes) | List of discussions within a work item | |`Guest`|Yes|
| [WorkItemWidgetNotifications](../../../api/graphql/reference/index.md#workitemwidgetnotifications) | Notifications subscription status of a work item for current user | |Anyone who can view|No|
| [WorkItemWidgetProgress](../../../api/graphql/reference/index.md#workitemwidgetprogress) | Progress value of a work item. **Note:** Progress is currently available only for OKRs. | `okrs_mvc` |`Reporter`|No|
| [WorkItemWidgetStartAndDueDate](../../../api/graphql/reference/index.md#workitemwidgetstartandduedate) | Set start and due dates for a work item | |`Reporter`|No|
| [WorkItemWidgetStatus](../../../api/graphql/reference/index.md#workitemwidgetstatus) | Status of a work item when type is Requirement, with possible status types being `unverified`, `satisfied`, or `failed` | | |No|
| [WorkItemWidgetTestReports](../../../api/graphql/reference/index.md#workitemwidgettestreports) | Test reports associated with a work item | | | |
| [WorkItemWidgetWeight](../../../api/graphql/reference/index.md#workitemwidgetweight) | Set weight of a work item | |`Reporter`|No|
### Work item relationships

View File

@ -222,7 +222,7 @@ coverage-jdk11:
# The `visualize` stage does not exist by default.
# Please define it first, or choose an existing stage like `deploy`.
stage: visualize
image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.9
script:
# convert report from jacoco to cobertura, using relative project path
- python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > target/site/cobertura.xml

View File

@ -32,6 +32,17 @@ module Gitlab
RUNNER_JOB_TOKEN_PARAM = :token
PATH_DEPENDENT_FEED_TOKEN_REGEX = /\A#{User::FEED_TOKEN_PREFIX}(\h{64})-(\d+)\z/
PARAM_TOKEN_KEYS = [
PRIVATE_TOKEN_PARAM,
JOB_TOKEN_PARAM,
RUNNER_JOB_TOKEN_PARAM
].map(&:to_s).freeze
HEADER_TOKEN_KEYS = [
PRIVATE_TOKEN_HEADER,
JOB_TOKEN_HEADER,
DEPLOY_TOKEN_HEADER
].freeze
# Check the Rails session for valid authentication details
def find_user_from_warden
current_request.env['warden']&.authenticate if verified_request?
@ -204,6 +215,12 @@ module Gitlab
end
end
def authentication_token_present?
PARAM_TOKEN_KEYS.intersection(current_request.params.keys).any? ||
HEADER_TOKEN_KEYS.intersection(current_request.env.keys).any? ||
parsed_oauth_token.present?
end
private
def find_user_from_job_bearer_token

View File

@ -7,15 +7,6 @@ module Gitlab
def config_fallback
Queues
end
private
def redis
primary_store = ::Redis.new(params)
secondary_store = ::Redis.new(config_fallback.params)
MultiStore.new(primary_store, secondary_store, name.demodulize)
end
end
end
end

View File

@ -257,12 +257,7 @@ module Gitlab
end
def with_redis(&block)
if Feature.enabled?(:use_primary_and_secondary_stores_for_queues_metadata) ||
Feature.enabled?(:use_primary_store_as_default_for_queues_metadata)
Gitlab::Redis::QueuesMetadata.with(&block) # rubocop:disable CodeReuse/ActiveRecord
else
Gitlab::Redis::Queues.with(&block) # rubocop:disable Cop/RedisQueueUsage, CodeReuse/ActiveRecord
end
Gitlab::Redis::QueuesMetadata.with(&block) # rubocop:disable CodeReuse/ActiveRecord
end
end
end

View File

@ -28931,6 +28931,9 @@ msgstr ""
msgid "Maximum project import requests per minute"
msgstr ""
msgid "Maximum projects allowed in the filter is %{count}"
msgstr ""
msgid "Maximum push size"
msgstr ""
@ -33936,6 +33939,12 @@ msgstr ""
msgid "PerformanceBar|Bullet notifications"
msgstr ""
msgid "PerformanceBar|CPU"
msgstr ""
msgid "PerformanceBar|CPU flamegraph"
msgstr ""
msgid "PerformanceBar|ClickHouse queries"
msgstr ""
@ -33945,6 +33954,12 @@ msgstr ""
msgid "PerformanceBar|Download"
msgstr ""
msgid "PerformanceBar|Download memory report"
msgstr ""
msgid "PerformanceBar|Download report"
msgstr ""
msgid "PerformanceBar|Elasticsearch calls"
msgstr ""
@ -33966,6 +33981,12 @@ msgstr ""
msgid "PerformanceBar|Memory report"
msgstr ""
msgid "PerformanceBar|Object"
msgstr ""
msgid "PerformanceBar|Object flamegraph"
msgstr ""
msgid "PerformanceBar|Redis calls"
msgstr ""
@ -33990,16 +34011,16 @@ msgstr ""
msgid "PerformanceBar|Trace"
msgstr ""
msgid "PerformanceBar|Wall"
msgstr ""
msgid "PerformanceBar|Wall flamegraph"
msgstr ""
msgid "PerformanceBar|Zoekt calls"
msgstr ""
msgid "PerformanceBar|cpu"
msgstr ""
msgid "PerformanceBar|object"
msgstr ""
msgid "PerformanceBar|wall"
msgid "PerformanceBar|flamegraph"
msgstr ""
msgid "Period in seconds"

View File

@ -10,20 +10,20 @@ module QA
super
base.view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do
element :file_title_content
element 'file-title-content'
end
base.view 'app/assets/javascripts/blob/components/blob_content.vue' do
element :blob_viewer_file_content
element 'blob-viewer-file-content'
end
base.view 'app/assets/javascripts/blob/components/blob_header_default_actions.vue' do
element :default_actions_container
element :copy_contents_button
element 'default-actions-container'
element 'copy-contents-button'
end
base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue' do
element :blob_viewer_file_content
element 'blob-viewer-file-content'
end
end
@ -36,30 +36,30 @@ module QA
end
def has_file_name?(file_name, file_number = nil)
within_file_by_number(:file_title_content, file_number) { has_text?(file_name) }
within_file_by_number('file-title-content', file_number) { has_text?(file_name) }
end
def has_no_file_name?(file_name)
within_element(:file_title_content) do
within_element('file-title-content') do
has_no_text?(file_name)
end
end
def has_file_content?(file_content, file_number = nil)
within_file_by_number(:blob_viewer_file_content, file_number) { has_text?(file_content) }
within_file_by_number('blob-viewer-file-content', file_number) { has_text?(file_content) }
end
def has_no_file_content?(file_content)
within_element(:blob_viewer_file_content) do
within_element('blob-viewer-file-content') do
has_no_text?(file_content)
end
end
def has_normalized_ws_text?(text, wait: Capybara.default_max_wait_time)
if has_element?(:blob_viewer_file_content, wait: 1)
if has_element?('blob-viewer-file-content', wait: 1)
# The blob viewer renders line numbers and whitespace in a way that doesn't match the source file
# This isn't a visual validation test, so we ignore line numbers and whitespace
find_element(:blob_viewer_file_content, wait: 0).text.gsub(/^\d+\s|\s*/, '')
find_element('blob-viewer-file-content', wait: 0).text.gsub(/^\d+\s|\s*/, '')
.start_with?(text.gsub(/\s*/, ''))
else
has_text?(text.gsub(/\s+/, " "), wait: wait)
@ -67,7 +67,7 @@ module QA
end
def click_copy_file_contents(file_number = nil)
within_file_by_number(:default_actions_container, file_number) { click_element(:copy_contents_button) }
within_file_by_number('default-actions-container', file_number) { click_element('copy-contents-button') }
end
private

View File

@ -30,7 +30,7 @@ module QA
base.view 'app/views/shared/_zen.html.haml' do
# This 'element' is here only to ensure the changes in the view source aren't mistakenly changed
element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
element :_, "testid = local_assigns.fetch(:testid, '')" # rubocop:disable QA/ElementWithPattern
end
base.view 'app/assets/javascripts/snippets/components/snippet_visibility_edit.vue' do

View File

@ -14,78 +14,75 @@ module QA
end
base.view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
element :snippet_title_content
element 'snippet-title-content'
end
base.view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do
element :snippet_description_content
element 'snippet-description-content'
end
base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
element :snippet_container
element 'snippet-container'
element 'snippet-action-button'
element 'delete-snippet-button'
end
base.view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do
element :file_title_content
element 'file-title-content'
end
base.view 'app/assets/javascripts/blob/components/blob_content.vue' do
element :blob_viewer_file_content
end
base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
element :snippet_action_button
element :delete_snippet_button
element 'blob-viewer-file-content'
end
base.view 'app/assets/javascripts/snippets/components/show.vue' do
element :clone_button
element :snippet_embed_dropdown
element 'clone-button'
element 'snippet-embed-dropdown'
end
base.view 'app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue' do
element :copy_http_url_button
element :copy_ssh_url_button
element 'copy-http-url-button'
element 'copy-ssh-url-button'
end
base.view 'app/views/shared/notes/_comment_button.html.haml' do
element :comment_button
element 'comment-button'
end
base.view 'app/views/shared/notes/_form.html.haml' do
element :note_field
element 'note-field'
end
base.view 'app/views/snippets/notes/_actions.html.haml' do
element :edit_comment_button
base.view 'app/views/projects/notes/_actions.html.haml' do
element 'edit-comment-button'
end
base.view 'app/views/shared/notes/_edit_form.html.haml' do
element :edit_note_field
element :save_comment_button
element 'edit-note-field'
element 'save-comment-button'
end
base.view 'app/views/shared/notes/_note.html.haml' do
element :note_content
element :note_author_content
element 'note-content'
element 'note-author-content'
end
base.view 'app/views/shared/notes/_notes_with_form.html.haml' do
element :notes_list
element 'notes-list'
end
base.view 'app/views/projects/notes/_more_actions_dropdown.html.haml' do
element :more_actions_dropdown
element :delete_comment_button
element 'more-actions-dropdown'
element 'delete-comment-button'
end
base.view 'app/assets/javascripts/snippets/components/embed_dropdown.vue' do
element :copy_button
element 'copy-button'
end
base.view 'app/assets/javascripts/blob/components/blob_header_default_actions.vue' do
element :default_actions_container
element :copy_contents_button
element 'default-actions-container'
element 'copy-contents-button'
end
base.view 'app/views/layouts/nav/breadcrumbs/_breadcrumbs.html.haml' do
@ -95,30 +92,30 @@ module QA
end
def has_snippet_title?(snippet_title)
has_element?(:snippet_title_content, text: snippet_title, wait: 10)
has_element?('snippet-title-content', text: snippet_title, wait: 10)
end
def has_snippet_description?(snippet_description)
has_element? :snippet_description_content, text: snippet_description
has_element? 'snippet-description-content', text: snippet_description
end
def has_no_snippet_description?
has_no_element?(:snippet_description_field)
has_no_element?('snippet-description-content')
end
def has_visibility_type?(visibility_type)
within_element(:snippet_container) do
within_element('snippet-container') do
has_text?(visibility_type)
end
end
def has_file_name?(file_name, file_number = nil)
if file_number
within_element_by_index(:file_title_content, file_number - 1) do
within_element_by_index('file-title-content', file_number - 1) do
has_text?(file_name)
end
else
within_element(:file_title_content) do
within_element('file-title-content') do
has_text?(file_name)
end
end
@ -126,11 +123,11 @@ module QA
def has_no_file_name?(file_name, file_number = nil)
if file_number
within_element_by_index(:file_title_content, file_number - 1) do
within_element_by_index('file-title-content', file_number - 1) do
has_no_text?(file_name)
end
else
within_element(:file_title_content) do
within_element('file-title-content') do
has_no_text?(file_name)
end
end
@ -138,11 +135,11 @@ module QA
def has_file_content?(file_content, file_number = nil)
if file_number
within_element_by_index(:blob_viewer_file_content, file_number - 1) do
within_element_by_index('blob-viewer-file-content', file_number - 1) do
has_text?(file_content)
end
else
within_element(:blob_viewer_file_content) do
within_element('blob-viewer-file-content') do
has_text?(file_content)
end
end
@ -150,11 +147,11 @@ module QA
def has_no_file_content?(file_content, file_number = nil)
if file_number
within_element_by_index(:blob_viewer_file_content, file_number - 1) do
within_element_by_index('blob-viewer-file-content', file_number - 1) do
has_no_text?(file_content)
end
else
within_element(:blob_viewer_file_content) do
within_element('blob-viewer-file-content') do
has_no_text?(file_content)
end
end
@ -162,112 +159,112 @@ module QA
RSpec::Matchers.define :have_embed_dropdown do
match do |page|
page.has_element?(:snippet_embed_dropdown)
page.has_element?('snippet-embed-dropdown')
end
match_when_negated do |page|
page.has_no_element?(:snippet_embed_dropdown)
page.has_no_element?('snippet-embed-dropdown')
end
end
def click_edit_button
click_element(:snippet_action_button, Page::Dashboard::Snippet::Edit, action: 'Edit')
click_element('snippet-action-button', Page::Dashboard::Snippet::Edit, action: 'Edit')
end
def click_delete_button
click_element(:snippet_action_button, action: 'Delete')
click_element(:delete_snippet_button)
click_element('snippet-action-button', action: 'Delete')
click_element('delete-snippet-button')
# wait for the page to reload after deletion
wait_until(reload: false) do
has_no_element?(:delete_snippet_button) &&
has_no_element?(:snippet_action_button, action: 'Delete')
has_no_element?('delete-snippet-button') &&
has_no_element?('snippet-action-button', action: 'Delete')
end
end
def get_repository_uri_http
click_element(:clone_button)
Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s
click_element('clone-button')
Git::Location.new(find_element('copy-http-url-button')['data-clipboard-text']).uri.to_s
end
def get_repository_uri_ssh
click_element(:clone_button)
Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s
click_element('clone-button')
Git::Location.new(find_element('copy-ssh-url-button')['data-clipboard-text']).uri.to_s
end
def get_sharing_link
click_element(:snippet_embed_dropdown)
find_element(:copy_button, action: 'Share')['data-clipboard-text']
click_element('snippet-embed-dropdown')
find_element('copy-button', action: 'Share')['data-clipboard-text']
end
def add_comment(comment)
fill_element(:note_field, comment)
click_element(:comment_button)
fill_element('note-field', comment)
click_element('comment-button')
unless has_element?(:note_author_content)
unless has_element?('note-author-content')
raise ElementNotFound, "Comment did not appear as expected"
end
end
def has_comment_author?(author_username)
within_element(:note_author_content) do
within_element('note-author-content') do
has_text?('@' + author_username)
end
end
def has_comment_content?(comment_content)
within_element(:note_content) do
within_element('note-content') do
has_text?(comment_content)
end
end
def within_notes_list(&block)
within_element :notes_list, &block
within_element 'notes-list', &block
end
def has_syntax_highlighting?(language)
within_element(:blob_viewer_file_content) do
within_element('blob-viewer-file-content') do
find('.line')['lang'].to_s == language
end
end
def edit_comment(comment)
click_element(:edit_comment_button)
fill_element(:edit_note_field, comment)
click_element(:save_comment_button)
click_element('edit-comment-button')
fill_element('edit-note-field', comment)
click_element('save-comment-button')
unless has_element?(:note_author_content)
unless has_element?('note-author-content')
raise ElementNotFound, "Comment did not appear as expected"
end
end
def delete_comment(comment)
click_element(:more_actions_dropdown)
click_element(:delete_comment_button)
click_element('more-actions-dropdown')
click_element('delete-comment-button')
click_confirmation_ok_button
unless has_no_element?(:note_content, text: comment)
unless has_no_element?('note-content', text: comment)
raise ElementNotFound, "Comment was not removed as expected"
end
end
def click_copy_file_contents(file_number = nil)
if file_number
within_element_by_index(:default_actions_container, file_number - 1) do
click_element(:copy_contents_button)
within_element_by_index('default-actions-container', file_number - 1) do
click_element('copy-contents-button')
end
else
within_element(:default_actions_container) do
click_element(:copy_contents_button)
within_element('default-actions-container') do
click_element('copy-contents-button')
end
end
end
def copy_file_contents_to_comment(file_number = nil)
click_copy_file_contents(file_number)
send_keys_to_element(:note_field, [:shift, :insert])
click_element(:comment_button)
send_keys_to_element('note-field', [:shift, :insert])
click_element('comment-button')
unless has_element?(:note_author_content)
unless has_element?('note-author-content')
raise ElementNotFound, "Comment did not appear as expected"
end
end

View File

@ -9,7 +9,7 @@ module QA
include Page::Component::BlobContent
view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
element :snippet_title_content
element 'snippet-title-content'
end
end
end

View File

@ -9,7 +9,7 @@ module QA
include Page::Component::BlobContent
view 'app/views/projects/notes/_actions.html.haml' do
element :edit_comment_button
element 'edit-comment-button'
end
end
end

View File

@ -317,6 +317,73 @@ RSpec.describe GraphqlController, feature_category: :integrations do
subject { post :execute, params: { query: query, access_token: token.token } }
shared_examples 'invalid token' do
it 'returns 401 with invalid token message' do
subject
expect(response).to have_gitlab_http_status(:unauthorized)
expect_graphql_errors_to_include('Invalid token')
end
end
context 'with an invalid token' do
context 'with auth header' do
subject do
request.headers[header] = 'invalid'
post :execute, params: { query: query, user: nil }
end
context 'with private-token' do
let(:header) { 'Private-Token' }
it_behaves_like 'invalid token'
end
context 'with job-token' do
let(:header) { 'Job-Token' }
it_behaves_like 'invalid token'
end
context 'with deploy-token' do
let(:header) { 'Deploy-Token' }
it_behaves_like 'invalid token'
end
end
context 'with authorization bearer (oauth token)' do
subject do
request.headers['Authorization'] = 'Bearer invalid'
post :execute, params: { query: query, user: nil }
end
it_behaves_like 'invalid token'
end
context 'with auth param' do
subject { post :execute, params: { query: query, user: nil }.merge(header) }
context 'with private_token' do
let(:header) { { private_token: 'invalid' } }
it_behaves_like 'invalid token'
end
context 'with job_token' do
let(:header) { { job_token: 'invalid' } }
it_behaves_like 'invalid token'
end
context 'with token' do
let(:header) { { token: 'invalid' } }
it_behaves_like 'invalid token'
end
end
end
context 'when the user is a project bot' do
let(:user) { create(:user, :project_bot, last_activity_on: last_activity_on) }

View File

@ -188,4 +188,47 @@ RSpec.describe 'Dashboard > User filters todos', :js, feature_category: :team_pl
end
end
end
describe 'todos tab count' do
context 'when filtering by open todos' do
it 'includes all open todos' do
expect(find('.js-todos-pending .gl-badge')).to have_content('3')
end
it 'only counts open todos that match when filtered by project' do
click_button 'Project'
within '.dropdown-menu-project' do
fill_in 'Search projects', with: project_1.full_name
click_link project_1.full_name
end
expect(find('.js-todos-pending .gl-badge')).to have_content('1')
end
end
context 'when filtering by done todos' do
before do
create(:todo, user: user_1, author: user_2, project: project_1, target: issue1, action: 1, state: :done)
create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2, state: :done)
visit dashboard_todos_path(state: 'done')
end
it 'includes all done todos' do
expect(find('.js-todos-done .gl-badge')).to have_content('2')
end
it 'only counts done todos that match when filtered by project' do
click_button 'Project'
within '.dropdown-menu-project' do
fill_in 'Search projects', with: project_1.full_name
click_link project_1.full_name
end
expect(find('.js-todos-done .gl-badge')).to have_content('1')
end
end
end
end

View File

@ -13,7 +13,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
/>
<strong
class="file-title-name js-blob-header-filepath mr-1"
data-qa-selector="file_title_content"
data-testid="file-title-content"
>
foo/bar/dummy.md
</strong>

View File

@ -35,7 +35,7 @@ describe('Blob Header Default Actions', () => {
});
describe('renders', () => {
const findCopyButton = () => wrapper.findByTestId('copyContentsButton');
const findCopyButton = () => wrapper.findByTestId('copy-contents-button');
const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
it('gl-button-group component', () => {

View File

@ -1,6 +1,9 @@
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import RequestWarning from '~/performance_bar/components/request_warning.vue';
Vue.config.ignoredElements = ['gl-emoji'];
describe('request warning', () => {
let wrapper;
const htmlId = 'request-123';
@ -16,8 +19,8 @@ describe('request warning', () => {
});
it('adds a warning emoji with the correct ID', () => {
expect(wrapper.find('span[id]').attributes('id')).toEqual(htmlId);
expect(wrapper.find('span[id] gl-emoji').element.dataset.name).toEqual('warning');
expect(wrapper.find('span gl-emoji[id]').attributes('id')).toEqual(htmlId);
expect(wrapper.find('span gl-emoji[id]').element.dataset.name).toEqual('warning');
});
});

View File

@ -1,3 +1,4 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
@ -6,6 +7,8 @@ import '~/performance_bar/components/performance_bar_app.vue';
import performanceBar from '~/performance_bar';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
Vue.config.ignoredElements = ['gl-emoji'];
jest.mock('~/performance_bar/performance_bar_log');
describe('performance bar wrapper', () => {

View File

@ -3,7 +3,7 @@
exports[`Snippet Description component matches the snapshot 1`] = `
<markdown-field-view-stub
class="snippet-description"
data-qa-selector="snippet_description_content"
data-testid="snippet-description-content"
>
<div
class="js-snippet-description md"

View File

@ -331,7 +331,7 @@ describe('Snippet header component', () => {
expect(findDeleteModal().props().visible).toBe(true);
// Click delete button in delete modal
document.querySelector('[data-testid="delete-snippet"').click();
document.querySelector('[data-testid="delete-snippet-button"').click();
await waitForPromises();
};

View File

@ -6,11 +6,11 @@ describe('Clone Dropdown Button', () => {
let wrapper;
const link = 'ssh://foo.bar';
const label = 'SSH';
const qaSelector = 'some-selector';
const testId = 'some-selector';
const defaultPropsData = {
link,
label,
qaSelector,
testId,
};
const findCopyButton = () => wrapper.findComponent(GlButton);
@ -46,7 +46,7 @@ describe('Clone Dropdown Button', () => {
});
it('sets the qa selector', () => {
expect(findCopyButton().attributes('data-qa-selector')).toBe(qaSelector);
expect(findCopyButton().attributes('data-testid')).toBe(testId);
});
});
});

View File

@ -1149,4 +1149,75 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
end
end
end
describe '#authentication_token_present?' do
subject { authentication_token_present? }
context 'no auth header/param/oauth' do
before do
request.headers['Random'] = 'Something'
set_param(:random, 'something')
end
it { is_expected.to be(false) }
end
context 'with auth header' do
before do
request.headers[header] = 'invalid'
end
context 'with private-token' do
let(:header) { 'Private-Token' }
it { is_expected.to be(true) }
end
context 'with job-token' do
let(:header) { 'Job-Token' }
it { is_expected.to be(true) }
end
context 'with deploy-token' do
let(:header) { 'Deploy-Token' }
it { is_expected.to be(true) }
end
end
context 'with authorization bearer (oauth token)' do
before do
request.headers['Authorization'] = 'Bearer invalid'
end
it { is_expected.to be(true) }
end
context 'with auth param' do
context 'with private_token' do
it 'returns true' do
set_param(:private_token, 'invalid')
expect(subject).to be(true)
end
end
context 'with job_token' do
it 'returns true' do
set_param(:job_token, 'invalid')
expect(subject).to be(true)
end
end
context 'with token' do
it 'returns true' do
set_param(:token, 'invalid')
expect(subject).to be(true)
end
end
end
end
end

View File

@ -5,39 +5,4 @@ require 'spec_helper'
RSpec.describe Gitlab::Redis::QueuesMetadata, feature_category: :redis do
include_examples "redis_new_instance_shared_examples", 'queues_metadata', Gitlab::Redis::Queues
include_examples "redis_shared_examples"
describe '#pool' do
let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
subject { described_class.pool }
around do |example|
clear_pool
example.run
ensure
clear_pool
end
before do
allow(described_class).to receive(:config_file_name).and_return(config_new_format_host)
allow(described_class).to receive(:config_file_name).and_return(config_new_format_host)
allow(Gitlab::Redis::Queues).to receive(:config_file_name).and_return(config_new_format_socket)
end
it 'instantiates an instance of MultiStore' do
subject.with do |redis_instance|
expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99")
expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
expect(redis_instance.instance_name).to eq('QueuesMetadata')
end
end
it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_queues_metadata,
:use_primary_store_as_default_for_queues_metadata
end
end

View File

@ -3,8 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
:clean_gitlab_redis_queues, :clean_gitlab_redis_shared_state, :clean_gitlab_redis_queues_metadata,
feature_category: :shared do
:clean_gitlab_redis_queues_metadata, feature_category: :shared do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do
@ -79,7 +78,11 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
end
end
shared_examples 'with Redis cookies' do
context 'with Redis cookies' do
def with_redis(&block)
Gitlab::Redis::QueuesMetadata.with(&block)
end
let(:cookie_key) { "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:#{idempotency_key}:cookie:v2" }
let(:cookie) { get_redis_msgpack(cookie_key) }
@ -413,62 +416,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
end
end
context 'with multi-store feature flags turned on' do
def with_redis(&block)
Gitlab::Redis::QueuesMetadata.with(&block)
end
shared_examples 'uses QueuesMetadata' do
it 'use Gitlab::Redis::QueuesMetadata.with' do
expect(Gitlab::Redis::QueuesMetadata).to receive(:with).and_call_original
expect(Gitlab::Redis::Queues).not_to receive(:with)
duplicate_job.check!
end
end
context 'when migration is ongoing with double-write' do
before do
stub_feature_flags(use_primary_store_as_default_for_queues_metadata: false)
end
it_behaves_like 'uses QueuesMetadata'
it_behaves_like 'with Redis cookies'
end
context 'when migration is completed' do
before do
stub_feature_flags(use_primary_and_secondary_stores_for_queues_metadata: false)
end
it_behaves_like 'uses QueuesMetadata'
it_behaves_like 'with Redis cookies'
end
it_behaves_like 'uses QueuesMetadata'
it_behaves_like 'with Redis cookies'
end
context 'when both multi-store feature flags are off' do
def with_redis(&block)
Gitlab::Redis::Queues.with(&block)
end
before do
stub_feature_flags(use_primary_and_secondary_stores_for_queues_metadata: false)
stub_feature_flags(use_primary_store_as_default_for_queues_metadata: false)
end
it 'use Gitlab::Redis::Queues' do
expect(Gitlab::Redis::Queues).to receive(:with).and_call_original
expect(Gitlab::Redis::QueuesMetadata).not_to receive(:with)
duplicate_job.check!
end
it_behaves_like 'with Redis cookies'
end
describe '#scheduled?' do
it 'returns false for non-scheduled jobs' do
expect(duplicate_job.scheduled?).to be(false)

View File

@ -82,7 +82,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
describe 'validations' do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
it { is_expected.to validate_length_of(:description).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_length_of(:path).is_at_most(255) }
it { is_expected.to validate_presence_of(:owner) }

View File

@ -282,9 +282,9 @@ RSpec.describe 'GraphQL', feature_category: :shared do
it 'does not authenticate user' do
post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
expect(response).to have_gitlab_http_status(:ok)
expect(response).to have_gitlab_http_status(:unauthorized)
expect(graphql_data['echo']).to eq('nil says: Hello world')
expect_graphql_errors_to_include('Invalid token')
end
end
@ -308,9 +308,9 @@ RSpec.describe 'GraphQL', feature_category: :shared do
post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
expect(response).to have_gitlab_http_status(:ok)
expect(response).to have_gitlab_http_status(:unauthorized)
expect(graphql_data['echo']).to eq('nil says: Hello world')
expect_graphql_errors_to_include('Invalid token')
end
end
end