Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-16 06:12:24 +00:00
parent f5c9eb81b0
commit 8858979899
61 changed files with 849 additions and 99 deletions

View File

@ -24,6 +24,7 @@ export default () => {
return new Vue({
el,
name: 'DesignRoot',
router,
apolloProvider,
provide: {

View File

@ -12,6 +12,7 @@ export default (el) => {
return new Vue({
el,
name: 'AwardsListRoot',
store: createstore(),
computed: {
...mapState(['currentUserId', 'canAwardEmoji', 'awards']),

View File

@ -28,6 +28,7 @@ export default function initInviteMembersModal() {
return new Vue({
el,
name: 'InviteMembersModalRoot',
provide: {
newProjectPath: el.dataset.newProjectPath,
},

View File

@ -11,6 +11,7 @@ export default function initInviteMembersTrigger() {
return triggers.forEach((el) => {
return new Vue({
el,
name: 'InviteMembersTriggerRoot',
render: (createElement) =>
createElement(InviteMembersTrigger, {
props: {

View File

@ -97,6 +97,7 @@ export function initIssuableHeaderWarnings(store) {
return new Vue({
el,
name: 'IssuableHeaderWarningsRoot',
store,
provide: { hidden: parseBoolean(hidden) },
render: (createElement) => createElement(IssuableHeaderWarnings),

View File

@ -13,6 +13,7 @@ export function initRelatedMergeRequests() {
return new Vue({
el,
name: 'RelatedMergeRequestsRoot',
store: createStore(),
render: (createElement) =>
createElement(RelatedMergeRequests, {

View File

@ -44,6 +44,7 @@ export function initIncidentApp(issueData = {}) {
return new Vue({
el,
name: 'DescriptionRoot',
apolloProvider,
provide: {
issueType: INCIDENT_TYPE,
@ -86,6 +87,7 @@ export function initIssueApp(issueData, store) {
return new Vue({
el,
name: 'DescriptionRoot',
apolloProvider,
store,
provide: {
@ -123,6 +125,7 @@ export function initHeaderActions(store, type = '') {
return new Vue({
el,
name: 'HeaderActionsRoot',
apolloProvider,
store,
provide: {

View File

@ -1,13 +1,21 @@
<script>
import { GlModal } from '@gitlab/ui';
import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
cancelAction: { text: __('Cancel') },
directives: {
SafeHtml: GlSafeHtmlDirective,
},
components: {
GlModal,
},
props: {
title: {
type: String,
required: false,
default: '',
},
primaryText: {
type: String,
required: false,
@ -18,11 +26,27 @@ export default {
required: false,
default: 'confirm',
},
modalHtmlMessage: {
type: String,
required: false,
default: '',
},
hideCancel: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
primaryAction() {
return { text: this.primaryText, attributes: { variant: this.primaryVariant } };
},
cancelAction() {
return this.hideCancel ? null : this.$options.cancelAction;
},
shouldShowHeader() {
return Boolean(this.title?.length);
},
},
mounted() {
this.$refs.modal.show();
@ -36,12 +60,14 @@ export default {
size="sm"
modal-id="confirmationModal"
body-class="gl-display-flex"
:title="title"
:action-primary="primaryAction"
:action-cancel="$options.cancelAction"
hide-header
:action-cancel="cancelAction"
:hide-header="!shouldShowHeader"
@primary="$emit('confirmed')"
@hidden="$emit('closed')"
>
<div class="gl-align-self-center"><slot></slot></div>
<div v-if="!modalHtmlMessage" class="gl-align-self-center"><slot></slot></div>
<div v-else v-safe-html="modalHtmlMessage" class="gl-align-self-center"></div>
</gl-modal>
</template>

View File

@ -1,6 +1,9 @@
import Vue from 'vue';
export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {}) {
export function confirmAction(
message,
{ primaryBtnVariant, primaryBtnText, modalHtmlMessage, title, hideCancel } = {},
) {
return new Promise((resolve) => {
let confirmed = false;
@ -15,6 +18,9 @@ export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {
props: {
primaryVariant: primaryBtnVariant,
primaryText: primaryBtnText,
title,
modalHtmlMessage,
hideCancel,
},
on: {
confirmed() {

View File

@ -5,6 +5,12 @@ import { insertText } from '~/lib/utils/common_utils';
const LINK_TAG_PATTERN = '[{text}](url)';
// at the start of a line, find any amount of whitespace followed by
// a bullet point character (*+-) and an optional checkbox ([ ] [x])
// OR a number with a . after it and an optional checkbox ([ ] [x])
// followed by one or more whitespace characters
const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isOl>[*+-])|(?<isUl>\d+\.))( \[([x ])\])?\s)(?<content>.)?/;
function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
@ -13,8 +19,15 @@ function addBlockTags(blockTag, selected) {
return `${blockTag}\n${selected}\n${blockTag}`;
}
function lineBefore(text, textarea) {
const split = text.substring(0, textarea.selectionStart).trim().split('\n');
function lineBefore(text, textarea, trimNewlines = true) {
let split = text.substring(0, textarea.selectionStart);
if (trimNewlines) {
split = split.trim();
}
split = split.split('\n');
return split[split.length - 1];
}
@ -284,9 +297,9 @@ function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagCo
}
/* eslint-disable @gitlab/require-i18n-strings */
export function keypressNoteText(e) {
function handleSurroundSelectedText(e, textArea) {
if (!gon.markdown_surround_selection) return;
if (this.selectionStart === this.selectionEnd) return;
if (textArea.selectionStart === textArea.selectionEnd) return;
const keys = {
'*': '**{text}**', // wraps with bold character
@ -306,7 +319,7 @@ export function keypressNoteText(e) {
updateText({
tag,
textArea: this,
textArea,
blockTag: '',
wrap: true,
select: '',
@ -316,6 +329,48 @@ export function keypressNoteText(e) {
}
/* eslint-enable @gitlab/require-i18n-strings */
function handleContinueList(e, textArea) {
if (!gon.features?.markdownContinueLists) return;
if (!(e.key === 'Enter')) return;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (textArea.selectionStart !== textArea.selectionEnd) return;
const currentLine = lineBefore(textArea.value, textArea, false);
const result = currentLine.match(LIST_LINE_HEAD_PATTERN);
if (result) {
const { indent, content, leader } = result.groups;
const prevLineEmpty = !content;
if (prevLineEmpty) {
// erase previous empty list item - select the text and allow the
// natural line feed erase the text
textArea.selectionStart = textArea.selectionStart - result[0].length;
return;
}
const itemInsert = `${indent}${leader}`;
e.preventDefault();
updateText({
tag: itemInsert,
textArea,
blockTag: '',
wrap: false,
select: '',
tagContent: '',
});
}
}
export function keypressNoteText(e) {
const textArea = this;
handleContinueList(e, textArea);
handleSurroundSelectedText(e, textArea);
}
export function updateTextForToolbarBtn($toolbarBtn) {
return updateText({
textArea: $toolbarBtn.closest('.md-area').find('textarea'),

View File

@ -12,6 +12,7 @@ const mount = (el, Component) => {
return new Vue({
el,
name: 'TopNavRoot',
store,
render(h) {
return h(Component, {

View File

@ -19,7 +19,7 @@ export default (store) => {
return new Vue({
el: discussionFilterEl,
name: 'DiscussionFilter',
name: 'DiscussionFilterRoot',
components: {
DiscussionFilter,
},

View File

@ -14,6 +14,7 @@ export default () => {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'NotesRoot',
components: {
notesApp,
},

View File

@ -8,6 +8,7 @@ export default (store) => {
return new Vue({
el,
name: 'SortDiscussionRoot',
store,
render(createElement) {
return createElement(SortDiscussion);

View File

@ -20,6 +20,7 @@ const initPerformanceBar = (el) => {
return new Vue({
el,
name: 'PerformanceBarRoot',
components: {
PerformanceBarApp: () => import('./components/performance_bar_app.vue'),
},

View File

@ -13,7 +13,7 @@ const getPopoversApp = () => {
document.body.appendChild(container);
const Popovers = Vue.extend(PopoversComponent);
app = new Popovers();
app = new Popovers({ name: 'PopoversRoot' });
app.$mount(`#${APP_ELEMENT_ID}`);
}

View File

@ -1,6 +1,7 @@
import $ from 'jquery';
import { debounce } from 'lodash';
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
import axios from '../lib/utils/axios_utils';
import {
@ -105,6 +106,21 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => {
};
const bindHowToImport = () => {
const importLinks = document.querySelectorAll('.js-how-to-import-link');
importLinks.forEach((link) => {
const { modalTitle: title, modalMessage: modalHtmlMessage } = link.dataset;
link.addEventListener('click', (e) => {
e.preventDefault();
confirmAction('', {
modalHtmlMessage,
title,
hideCancel: true,
});
});
});
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
$(e.currentTarget).next('.modal').show();

View File

@ -8,7 +8,7 @@ export default function initRelatedIssues() {
// eslint-disable-next-line no-new
new Vue({
el: relatedIssuesRootElement,
name: 'RelatedIssuesApp',
name: 'RelatedIssuesRoot',
components: {
relatedIssuesRoot: RelatedIssuesRoot,
},

View File

@ -57,6 +57,7 @@ function mountSidebarToDoWidget() {
return new Vue({
el,
name: 'SidebarTodoRoot',
apolloProvider,
components: {
SidebarTodoWidget,
@ -103,6 +104,7 @@ function mountAssigneesComponentDeprecated(mediator) {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarAssigneesRoot',
apolloProvider,
components: {
SidebarAssignees,
@ -135,6 +137,7 @@ function mountAssigneesComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarAssigneesRoot',
apolloProvider,
components: {
SidebarAssigneesWidget,
@ -185,6 +188,7 @@ function mountReviewersComponent(mediator) {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarReviewersRoot',
apolloProvider,
components: {
SidebarReviewers,
@ -218,6 +222,7 @@ function mountCrmContactsComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarCrmContactsRoot',
apolloProvider,
components: {
CrmContacts,
@ -242,6 +247,7 @@ function mountMilestoneSelect() {
return new Vue({
el,
name: 'SidebarMilestoneRoot',
apolloProvider,
components: {
SidebarDropdownWidget,
@ -274,6 +280,7 @@ export function mountSidebarLabels() {
return new Vue({
el,
name: 'SidebarLabelsRoot',
apolloProvider,
components: {
@ -328,6 +335,7 @@ function mountConfidentialComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarConfidentialRoot',
apolloProvider,
components: {
SidebarConfidentialityWidget,
@ -362,6 +370,7 @@ function mountDueDateComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarDueDateRoot',
apolloProvider,
components: {
SidebarDueDateWidget,
@ -392,6 +401,7 @@ function mountReferenceComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarReferenceRoot',
apolloProvider,
components: {
SidebarReferenceWidget,
@ -428,6 +438,7 @@ function mountLockComponent(store) {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarLockRoot',
store,
provide: {
fullPath,
@ -451,6 +462,7 @@ function mountParticipantsComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarParticipantsRoot',
apolloProvider,
components: {
SidebarParticipantsWidget,
@ -479,6 +491,7 @@ function mountSubscriptionsComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarSubscriptionsRoot',
apolloProvider,
components: {
SidebarSubscriptionsWidget,
@ -509,6 +522,7 @@ function mountTimeTrackingComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarTimeTrackingRoot',
apolloProvider,
provide: { issuableType },
render: (createElement) =>
@ -534,6 +548,7 @@ function mountSeverityComponent() {
return new Vue({
el: severityContainerEl,
name: 'SidebarSeverityRoot',
apolloProvider,
components: {
SidebarSeverity,
@ -562,6 +577,7 @@ function mountCopyEmailComponent() {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'SidebarCopyEmailRoot',
render: (createElement) =>
createElement(CopyEmailToClipboard, { props: { issueEmailAddress: createNoteEmail } }),
});

View File

@ -21,6 +21,7 @@ const tooltipsApp = () => {
document.body.appendChild(container);
app = new Vue({
name: 'TooltipsRoot',
render(h) {
return h(Tooltips, {
props: {

View File

@ -200,6 +200,12 @@
}
}
.gl-xl-ml-3 {
@include media-breakpoint-up(lg) {
margin-left: $gl-spacing-scale-3;
}
}
.gl-mb-n3 {
margin-bottom: -$gl-spacing-scale-3;
}

View File

@ -46,14 +46,15 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:vue_issues_list, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:contacts_autocomplete, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:markdown_continue_lists, project, default_enabled: :yaml)
end
before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, @project, default_enabled: :yaml)
push_frontend_feature_flag(:real_time_issue_sidebar, project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:fix_comment_scroll, @project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml)
push_frontend_feature_flag(:fix_comment_scroll, project, default_enabled: :yaml)
push_frontend_feature_flag(:work_items, project, default_enabled: :yaml)
end

View File

@ -36,20 +36,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action only: [:show] do
push_frontend_feature_flag(:file_identifier_hash)
push_frontend_feature_flag(:merge_request_widget_graphql, @project, default_enabled: :yaml)
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:merge_request_widget_graphql, project, default_enabled: :yaml)
push_frontend_feature_flag(:default_merge_ref_for_diffs, project, default_enabled: :yaml)
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:paginated_notes, project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, project, default_enabled: :yaml)
push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml)
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, project, default_enabled: :yaml)
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
push_frontend_feature_flag(:markdown_continue_lists, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:users_expanding_widgets_usage_data, project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
push_frontend_feature_flag(:usage_data_diff_searches, @project, default_enabled: :yaml)
push_frontend_feature_flag(:usage_data_diff_searches, project, default_enabled: :yaml)
end
before_action do

View File

@ -144,6 +144,7 @@ class SearchController < ApplicationController
payload[:metadata]['meta.search.filters.state'] = params[:state]
payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results]
payload[:metadata]['meta.search.project_ids'] = params[:project_ids]
payload[:metadata]['meta.search.search_level'] = params[:search_level]
if search_service.abuse_detected?
payload[:metadata]['abuse.confidence'] = Gitlab::Abuse.confidence(:certain)

View File

@ -430,6 +430,18 @@ module ProjectsHelper
end
end
def import_from_bitbucket_message
link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("integration/bitbucket") }
str = if current_user.admin?
'ImportProjects|To enable importing projects from Bitbucket, as administrator you need to configure %{link_start}OAuth integration%{link_end}'
else
'ImportProjects|To enable importing projects from Bitbucket, ask your GitLab administrator to configure %{link_start}OAuth integration%{link_end}'
end
s_(str).html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
end
private
def tab_ability_map

View File

@ -50,8 +50,9 @@ class WebHook < ApplicationRecord
end
# rubocop: disable CodeReuse/ServiceClass
def execute(data, hook_name)
WebHookService.new(self, data, hook_name).execute if executable?
def execute(data, hook_name, force: false)
# hook.executable? is checked in WebHookService#execute
WebHookService.new(self, data, hook_name, force: force).execute
end
# rubocop: enable CodeReuse/ServiceClass

View File

@ -18,7 +18,7 @@ module TestHooks
return error('Testing not available for this hook') if trigger_key.nil? || data.blank?
return error(data[:error]) if data[:error].present?
hook.execute(data, trigger_key)
hook.execute(data, trigger_key, force: true)
end
end
end

View File

@ -34,11 +34,12 @@ class WebHookService
hook_name.to_s.singularize.titleize
end
def initialize(hook, data, hook_name, uniqueness_token = nil)
def initialize(hook, data, hook_name, uniqueness_token = nil, force: false)
@hook = hook
@data = data
@hook_name = hook_name.to_s
@uniqueness_token = uniqueness_token
@force = force
@request_options = {
timeout: Gitlab.config.gitlab.webhook_timeout,
use_read_total_timeout: true,
@ -46,8 +47,12 @@ class WebHookService
}
end
def disabled?
!@force && !hook.executable?
end
def execute
return { status: :error, message: 'Hook disabled' } unless hook.executable?
return { status: :error, message: 'Hook disabled' } if disabled?
if recursion_blocked?
log_recursion_blocked
@ -148,8 +153,12 @@ class WebHookService
internal_error_message: error_message
}
::WebHooks::LogExecutionWorker
.perform_async(hook.id, log_data, category, uniqueness_token)
if @force # executed as part of test - run log-execution inline.
::WebHooks::LogExecutionService.new(hook: hook, log_data: log_data, response_category: category).execute
else
::WebHooks::LogExecutionWorker
.perform_async(hook.id, log_data, category, uniqueness_token)
end
end
def response_category(response)

View File

@ -1,14 +0,0 @@
#bitbucket_import_modal.modal
.modal-dialog
.modal-content
.modal-header
%h3.modal-title Import projects from Bitbucket
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": "true" } &times;
.modal-body
To enable importing projects from Bitbucket,
- if current_user.admin?
as administrator you need to configure
- else
ask your GitLab administrator to configure
= link_to 'OAuth integration', help_page_path("integration/bitbucket")

View File

@ -22,13 +22,11 @@
- if bitbucket_import_enabled?
%div
= link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'how_to_import_link' unless bitbucket_import_configured?}",
data: { platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
= link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'js-how-to-import-link' unless bitbucket_import_configured?}",
data: { modal_title: _("Import projects from Bitbucket"), modal_message: import_from_bitbucket_message, platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
.gl-button-icon
= sprite_icon('bitbucket')
Bitbucket Cloud
- unless bitbucket_import_configured?
= render 'projects/bitbucket_import_modal'
- if bitbucket_server_import_enabled?
%div
= link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn", data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } do

View File

@ -1,8 +0,0 @@
---
name: block_external_fork_network_mirrors
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60735
rollout_issue_url:
milestone: '14.0'
type: development
group: group::source code
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: markdown_continue_lists
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79161
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351386
milestone: '14.8'
type: development
group: group::project management
default_enabled: false

View File

@ -0,0 +1,66 @@
- name: "GraphQL ID and GlobalID compatibility"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-04-22"
breaking_change: true
reporter: alexkalderimis
body: | # Do not modify this line, instead modify the lines below.
We are removing a non-standard extension to our GraphQL processor, which we added for backwards compatibility. This extension modifies the validation of GraphQL queries, allowing the use of the `ID` type for arguments where it would normally be rejected.
Some arguments originally had the type `ID`. These were changed to specific
kinds of `ID`. This change may be a breaking change if you:
- Use GraphQL.
- Use the `ID` type for any argument in your query signatures.
Some field arguments still have the `ID` type. These are typically for
IID values, or namespace paths. An example is `Query.project(fullPath: ID!)`.
For a list of affected and unaffected field arguments,
see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352832).
You can test if this change affects you by validating
your queries locally, using schema data fetched from a GitLab server.
You can do this by using the GraphQL explorer tool for the relevant GitLab
instance. For example: `https://gitlab.com/-/graphql-explorer`.
For example, the following query illustrates the breaking change:
```graphql
# a query using the deprecated type of Query.issue(id:)
# WARNING: This will not work after GitLab 15.0
query($id: ID!) {
deprecated: issue(id: $id) {
title, description
}
}
```
The query above will not work after GitLab 15.0 is released, because the type
of `Query.issue(id:)` is actually `IssueID!`.
Instead, you should use one of the following two forms:
```graphql
# This will continue to work
query($id: IssueID!) {
a: issue(id: $id) {
title, description
}
b: issue(id: "gid://gitlab/Issue/12345") {
title, description
}
}
```
This query works now, and will continue to work after GitLab 15.0.
You should convert any queries in the first form (using `ID` as a named type in the signature)
to one of the other two forms (using the correct appropriate type in the signature, or using
an inline argument expression).
# The following items are not published on the docs page, but may be used in the future.
stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257883'
documentation_url: # (optional) This is a link to the current documentation page
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View File

@ -0,0 +1,32 @@
- name: "SAST analyzer consolidation and CI/CD template changes"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
reporter: connorgilbert
body: | # Do not modify this line, instead modify the lines below.
GitLab SAST uses various [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) to scan code for vulnerabilities.
We are reducing the number of analyzers used in GitLab SAST as part of our long-term strategy to deliver a better and more consistent user experience.
Streamlining the set of analyzers will also enable faster [iteration](https://about.gitlab.com/handbook/values/#iteration), better [results](https://about.gitlab.com/handbook/values/#results), and greater [efficiency](https://about.gitlab.com/handbook/values/#results) (including a reduction in CI runner usage in most cases).
In GitLab 15.0, GitLab SAST will no longer use the following analyzers:
- [ESLint](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (JavaScript, TypeScript, React)
- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Go)
- [Bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Python)
These analyzers will be removed from the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml) and replaced with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
They will no longer receive routine updates, except for security issues.
We will not delete container images previously published for these analyzers; any such change would be announced as a [deprecation, removal, or breaking change announcement](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes).
We will also remove Java from the scope of the [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) analyzer and replace it with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
This change will make it simpler to scan Java code; compilation will no longer be required.
This change will be reflected in the automatic language detection portion of the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml).
If you applied customizations to any of the affected analyzers, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352554#breaking-change).
# The following items are not published on the docs page, but may be used in the future.
stage: Secure
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352554

View File

@ -0,0 +1,31 @@
- name: "SAST support for .NET 2.1"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
reporter: connorgilbert
body: | # Do not modify this line, instead modify the lines below.
The GitLab SAST Security Code Scan analyzer scans .NET code for security vulnerabilities.
For technical reasons, the analyzer must first build the code to scan it.
In GitLab versions prior to 15.0, the default analyzer image (version 2) includes support for:
- .NET 2.1
- .NET 3.0 and .NET Core 3.0
- .NET Core 3.1
- .NET 5.0
In GitLab 15.0, we will change the default major version for this analyzer from version 2 to version 3. This change:
- Adds [severity values for vulnerabilities](https://gitlab.com/gitlab-org/gitlab/-/issues/350408) along with [other new features and improvements](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/blob/master/CHANGELOG.md).
- Removes .NET 2.1 support.
- Adds support for .NET 6.0, Visual Studio 2019, and Visual Studio 2022.
Version 3 was [announced in GitLab 14.6](https://about.gitlab.com/releases/2021/12/22/gitlab-14-6-released/#sast-support-for-net-6) and made available as an optional upgrade.
If you rely on .NET 2.1 support being present in the analyzer image by default, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352553#breaking-change).
# The following items are not published on the docs page, but may be used in the future.
stage: Secure
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352553

View File

@ -0,0 +1,28 @@
- name: "Secret Detection configuration variables deprecated"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: false
reporter: connorgilbert
body: | # Do not modify this line, instead modify the lines below.
To make it simpler and more reliable to [customize GitLab Secret Detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings), we're deprecating some of the variables that you could previously set in your CI/CD configuration.
The following variables currently allow you to customize the options for historical scanning, but interact poorly with the [GitLab-managed CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml) and are now deprecated:
- `SECRET_DETECTION_COMMIT_FROM`
- `SECRET_DETECTION_COMMIT_TO`
- `SECRET_DETECTION_COMMITS`
- `SECRET_DETECTION_COMMITS_FILE`
The `SECRET_DETECTION_ENTROPY_LEVEL` previously allowed you to configure rules that only considered the entropy level of strings in your codebase, and is now deprecated.
This type of entropy-only rule created an unacceptable number of incorrect results (false positives) and is no longer supported.
In GitLab 15.0, we'll update the Secret Detection [analyzer](https://docs.gitlab.com/ee/user/application_security/terminology/#analyzer) to ignore these deprecated options.
You'll still be able to configure historical scanning of your commit history by setting the [`SECRET_DETECTION_HISTORIC_SCAN` CI/CD variable](https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-cicd-variables).
For further details, see [the deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352565).
# The following items are not published on the docs page, but may be used in the future.
stage: Secure
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352565

View File

@ -867,6 +867,68 @@ To align with this change, API calls to list external status checks will also re
**Planned removal milestone: 15.0 (2022-05-22)**
### GraphQL ID and GlobalID compatibility
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
We are removing a non-standard extension to our GraphQL processor, which we added for backwards compatibility. This extension modifies the validation of GraphQL queries, allowing the use of the `ID` type for arguments where it would normally be rejected.
Some arguments originally had the type `ID`. These were changed to specific
kinds of `ID`. This change may be a breaking change if you:
- Use GraphQL.
- Use the `ID` type for any argument in your query signatures.
Some field arguments still have the `ID` type. These are typically for
IID values, or namespace paths. An example is `Query.project(fullPath: ID!)`.
For a list of affected and unaffected field arguments,
see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352832).
You can test if this change affects you by validating
your queries locally, using schema data fetched from a GitLab server.
You can do this by using the GraphQL explorer tool for the relevant GitLab
instance. For example: `https://gitlab.com/-/graphql-explorer`.
For example, the following query illustrates the breaking change:
```graphql
# a query using the deprecated type of Query.issue(id:)
# WARNING: This will not work after GitLab 15.0
query($id: ID!) {
deprecated: issue(id: $id) {
title, description
}
}
```
The query above will not work after GitLab 15.0 is released, because the type
of `Query.issue(id:)` is actually `IssueID!`.
Instead, you should use one of the following two forms:
```graphql
# This will continue to work
query($id: IssueID!) {
a: issue(id: $id) {
title, description
}
b: issue(id: "gid://gitlab/Issue/12345") {
title, description
}
}
```
This query works now, and will continue to work after GitLab 15.0.
You should convert any queries in the first form (using `ID` as a named type in the signature)
to one of the other two forms (using the correct appropriate type in the signature, or using
an inline argument expression).
**Planned removal milestone: 15.0 (2022-04-22)**
### OAuth tokens without expiration
WARNING:
@ -1082,6 +1144,88 @@ If you have explicitly excluded retire.js using DS_EXCLUDED_ANALYZERS you will n
**Planned removal milestone: 15.0 (2022-05-22)**
### SAST analyzer consolidation and CI/CD template changes
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
GitLab SAST uses various [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) to scan code for vulnerabilities.
We are reducing the number of analyzers used in GitLab SAST as part of our long-term strategy to deliver a better and more consistent user experience.
Streamlining the set of analyzers will also enable faster [iteration](https://about.gitlab.com/handbook/values/#iteration), better [results](https://about.gitlab.com/handbook/values/#results), and greater [efficiency](https://about.gitlab.com/handbook/values/#results) (including a reduction in CI runner usage in most cases).
In GitLab 15.0, GitLab SAST will no longer use the following analyzers:
- [ESLint](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (JavaScript, TypeScript, React)
- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Go)
- [Bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Python)
These analyzers will be removed from the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml) and replaced with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
They will no longer receive routine updates, except for security issues.
We will not delete container images previously published for these analyzers; any such change would be announced as a [deprecation, removal, or breaking change announcement](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes).
We will also remove Java from the scope of the [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) analyzer and replace it with the [Semgrep-based analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep).
This change will make it simpler to scan Java code; compilation will no longer be required.
This change will be reflected in the automatic language detection portion of the [GitLab-managed SAST CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml).
If you applied customizations to any of the affected analyzers, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352554#breaking-change).
**Planned removal milestone: 15.0 (2022-05-22)**
### SAST support for .NET 2.1
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The GitLab SAST Security Code Scan analyzer scans .NET code for security vulnerabilities.
For technical reasons, the analyzer must first build the code to scan it.
In GitLab versions prior to 15.0, the default analyzer image (version 2) includes support for:
- .NET 2.1
- .NET 3.0 and .NET Core 3.0
- .NET Core 3.1
- .NET 5.0
In GitLab 15.0, we will change the default major version for this analyzer from version 2 to version 3. This change:
- Adds [severity values for vulnerabilities](https://gitlab.com/gitlab-org/gitlab/-/issues/350408) along with [other new features and improvements](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/blob/master/CHANGELOG.md).
- Removes .NET 2.1 support.
- Adds support for .NET 6.0, Visual Studio 2019, and Visual Studio 2022.
Version 3 was [announced in GitLab 14.6](https://about.gitlab.com/releases/2021/12/22/gitlab-14-6-released/#sast-support-for-net-6) and made available as an optional upgrade.
If you rely on .NET 2.1 support being present in the analyzer image by default, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352553#breaking-change).
**Planned removal milestone: 15.0 (2022-05-22)**
### Secret Detection configuration variables deprecated
To make it simpler and more reliable to [customize GitLab Secret Detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings), we're deprecating some of the variables that you could previously set in your CI/CD configuration.
The following variables currently allow you to customize the options for historical scanning, but interact poorly with the [GitLab-managed CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml) and are now deprecated:
- `SECRET_DETECTION_COMMIT_FROM`
- `SECRET_DETECTION_COMMIT_TO`
- `SECRET_DETECTION_COMMITS`
- `SECRET_DETECTION_COMMITS_FILE`
The `SECRET_DETECTION_ENTROPY_LEVEL` previously allowed you to configure rules that only considered the entropy level of strings in your codebase, and is now deprecated.
This type of entropy-only rule created an unacceptable number of incorrect results (false positives) and is no longer supported.
In GitLab 15.0, we'll update the Secret Detection [analyzer](https://docs.gitlab.com/ee/user/application_security/terminology/#analyzer) to ignore these deprecated options.
You'll still be able to configure historical scanning of your commit history by setting the [`SECRET_DETECTION_HISTORIC_SCAN` CI/CD variable](https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-cicd-variables).
For further details, see [the deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/352565).
**Planned removal milestone: 15.0 (2022-05-22)**
### Support for gRPC-aware proxy deployed between Gitaly and rest of GitLab
WARNING:

View File

@ -150,6 +150,7 @@ The following table lists project permissions available for each role:
| [Projects](project/index.md):<br>Export project | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Manage [project access tokens](project/settings/project_access_tokens.md) **(FREE SELF)** **(PREMIUM SAAS)** (*11*) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Manage [Project Operations](../operations/index.md) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Rename project | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Share (invite) projects with groups | | | | ✓ (*7*) | ✓ (*7*) |
| [Projects](project/index.md):<br>View 2FA status of members | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Administer project compliance frameworks | | | | | ✓ |
@ -157,7 +158,6 @@ The following table lists project permissions available for each role:
| [Projects](project/index.md):<br>Change project visibility level | | | | | ✓ |
| [Projects](project/index.md):<br>Delete project | | | | | ✓ |
| [Projects](project/index.md):<br>Disable notification emails | | | | | ✓ |
| [Projects](project/index.md):<br>Rename project | | | | | ✓ |
| [Projects](project/index.md):<br>Transfer project to another namespace | | | | | ✓ |
| [Repository](project/repository/index.md):<br>Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| [Repository](project/repository/index.md):<br>View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |

View File

@ -129,17 +129,7 @@ module Gitlab
def discussion_id
strong_memoize(:discussion_id) do
if in_reply_to_id.present?
current_discussion_id
else
Discussion.discussion_id(
Struct
.new(:noteable_id, :noteable_type)
.new(merge_request.id, NOTEABLE_TYPE)
).tap do |discussion_id|
cache_discussion_id(discussion_id)
end
end
(in_reply_to_id.present? && current_discussion_id) || generate_discussion_id
end
end
@ -160,6 +150,16 @@ module Gitlab
side == 'RIGHT'
end
def generate_discussion_id
Discussion.discussion_id(
Struct
.new(:noteable_id, :noteable_type)
.new(merge_request.id, NOTEABLE_TYPE)
).tap do |discussion_id|
cache_discussion_id(discussion_id)
end
end
def cache_discussion_id(discussion_id)
Gitlab::Cache::Import::Caching.write(discussion_id_cache_key(note_id), discussion_id)
end

View File

@ -12869,6 +12869,9 @@ msgstr ""
msgid "Display name"
msgstr ""
msgid "Display progress of child issues"
msgstr ""
msgid "Display rendered file"
msgstr ""

View File

@ -32,6 +32,9 @@ module QA
# Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/
Runtime::Scenario.define(:about_address, URI(-> { gitlab_address.host = "about.#{gitlab_address.host}"; gitlab_address }.call).to_s) # rubocop:disable Style/Semicolon
# Save the scenario class name
Runtime::Scenario.define(:klass, self.class.name)
##
# Setup knapsack and download latest report
#

View File

@ -7,6 +7,7 @@ module QA
module Specs
class Runner < Scenario::Template
attr_accessor :tty, :tags, :options
RegexMismatchError = Class.new(StandardError)
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
@ -72,16 +73,48 @@ module QA
elsif Runtime::Scenario.attributes[:count_examples_only]
args.unshift('--dry-run')
out = StringIO.new
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
abort if status.nonzero?
end
$stdout.puts out.string.match(/(\d+) examples,/)[1]
begin
total_examples = out.string.match(/(\d+) examples?,/)[1]
rescue StandardError
raise RegexMismatchError, 'Rspec output did not match regex'
end
filename = build_filename
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
$stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''}"
else
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status|
abort if status.nonzero?
end
end
end
private
def build_filename
filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase
tags = []
options.reduce do |before, after|
tags << after if %w[--tag -t].include?(before)
after
end
tags = tags.compact.join('_')
filename.concat("_#{tags}") unless tags.empty?
filename.concat('.txt')
FileUtils.mkdir_p('no_of_examples')
File.join('no_of_examples', filename)
end
end
end
end

View File

@ -14,6 +14,7 @@ RSpec.describe QA::Specs::Runner do
allow(QA::Runtime::Browser).to receive(:configure!)
QA::Runtime::Scenario.define(:gitlab_address, "http://gitlab.test")
QA::Runtime::Scenario.define(:klass, "QA::Scenario::Test::Instance::All")
end
it_behaves_like 'excludes orchestrated, transient, and geo'
@ -43,6 +44,43 @@ RSpec.describe QA::Specs::Runner do
subject.perform
end
it 'writes to file when examples are more than zero' do
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect(File).to receive(:open).with('no_of_examples/test_instance_all.txt', 'w') { '22' }
subject.perform
end
it 'does not write to file when zero examples' do
out.string = '0 examples,'
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect(File).not_to receive(:open)
subject.perform
end
it 'raises error when Rspec output does not match regex' do
out.string = '0'
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect { subject.perform }
.to raise_error(QA::Specs::Runner::RegexMismatchError, 'Rspec output did not match regex')
end
context 'when --tag is specified as an option' do
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
it 'includes the option value in the file name' do
expect_rspec_runner_arguments(['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
subject.perform
end
end
after do
QA::Runtime::Scenario.attributes.delete(:count_examples_only)
end

View File

@ -397,9 +397,10 @@ RSpec.describe SearchController do
expect(payload[:metadata]['meta.search.filters.confidential']).to eq('true')
expect(payload[:metadata]['meta.search.filters.state']).to eq('true')
expect(payload[:metadata]['meta.search.project_ids']).to eq(%w(456 789))
expect(payload[:metadata]['meta.search.search_level']).to eq('multi-project')
end
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', project_ids: %w(456 789), confidential: true, state: true, force_search_results: true }
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', project_ids: %w(456 789), search_level: 'multi-project', confidential: true, state: true, force_search_results: true }
end
it 'appends the default scope in meta.search.scope' do

View File

@ -404,4 +404,47 @@ RSpec.describe 'New project', :js do
end
end
end
context 'from Bitbucket', :js do
shared_examples 'has a link to bitbucket cloud' do
context 'when bitbucket is not configured' do
before do
allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).and_call_original
allow(Gitlab::Auth::OAuth::Provider)
.to receive(:enabled?).with(:bitbucket)
.and_return(false)
visit new_project_path
click_link 'Import project'
click_link 'Bitbucket Cloud'
end
it 'shows import instructions' do
expect(find('.modal-body')).to have_content(bitbucket_link_content)
end
end
end
context 'as a user' do
let(:user) { create(:user) }
let(:bitbucket_link_content) { 'To enable importing projects from Bitbucket, ask your GitLab administrator to configure OAuth integration' }
before do
sign_in(user)
end
it_behaves_like 'has a link to bitbucket cloud'
end
context 'as an admin' do
let(:user) { create(:admin) }
let(:bitbucket_link_content) { 'To enable importing projects from Bitbucket, as administrator you need to configure OAuth integration' }
before do
sign_in(user)
end
it_behaves_like 'has a link to bitbucket cloud'
end
end
end

View File

@ -31,6 +31,10 @@ describe('Design reply form component', () => {
});
}
beforeEach(() => {
gon.features = { markdownContinueLists: true };
});
afterEach(() => {
wrapper.destroy();
});

View File

@ -25,6 +25,7 @@ describe('Description field component', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit');
gon.features = { markdownContinueLists: true };
});
afterEach(() => {

View File

@ -6,11 +6,13 @@ describe('Confirm Modal', () => {
let wrapper;
let modal;
const createComponent = ({ primaryText, primaryVariant } = {}) => {
const createComponent = ({ primaryText, primaryVariant, title, hideCancel = false } = {}) => {
wrapper = mount(ConfirmModal, {
propsData: {
primaryText,
primaryVariant,
hideCancel,
title,
},
});
};
@ -55,5 +57,19 @@ describe('Confirm Modal', () => {
expect(customProps.text).toBe('OK');
expect(customProps.attributes.variant).toBe('confirm');
});
it('should hide the cancel button if `hideCancel` is set', () => {
createComponent({ hideCancel: true });
const props = findGlModal().props();
expect(props.actionCancel).toBeNull();
});
it('should set the modal title when the `title` prop is set', () => {
const title = 'Modal title';
createComponent({ title });
expect(findGlModal().props().title).toBe(title);
});
});
});

View File

@ -165,6 +165,80 @@ describe('init markdown', () => {
// cursor placement should be between tags
expect(textArea.selectionStart).toBe(start.length + tag.length);
});
describe('Continuing markdown lists', () => {
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
beforeEach(() => {
gon.features = { markdownContinueLists: true };
});
it.each`
text | expected
${'- item'} | ${'- item\n- '}
${'- [ ] item'} | ${'- [ ] item\n- [ ] '}
${'- [x] item'} | ${'- [x] item\n- [x] '}
${'- item\n - second'} | ${'- item\n - second\n - '}
${'1. item'} | ${'1. item\n1. '}
${'1. [ ] item'} | ${'1. [ ] item\n1. [ ] '}
${'1. [x] item'} | ${'1. [x] item\n1. [x] '}
${'108. item'} | ${'108. item\n108. '}
${'108. item\n - second'} | ${'108. item\n - second\n - '}
${'108. item\n 1. second'} | ${'108. item\n 1. second\n 1. '}
`('adds correct list continuation characters', ({ text, expected }) => {
textArea.value = text;
textArea.setSelectionRange(text.length, text.length);
textArea.addEventListener('keydown', keypressNoteText);
textArea.dispatchEvent(enterEvent);
expect(textArea.value).toEqual(expected);
expect(textArea.selectionStart).toBe(expected.length);
});
// test that when pressing Enter on an empty list item, the empty
// list item text is selected, so that when the Enter propagates,
// it's removed
it.each`
text | expected
${'- item\n- '} | ${'- item\n'}
${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'}
${'- [x] item\n- [x] '} | ${'- [x] item\n'}
${'- item\n - second\n - '} | ${'- item\n - second\n'}
${'1. item\n1. '} | ${'1. item\n'}
${'1. [ ] item\n1. [ ] '} | ${'1. [ ] item\n'}
${'1. [x] item\n1. [x] '} | ${'1. [x] item\n'}
${'108. item\n108. '} | ${'108. item\n'}
${'108. item\n - second\n - '} | ${'108. item\n - second\n'}
${'108. item\n 1. second\n 1. '} | ${'108. item\n 1. second\n'}
`('adds correct list continuation characters', ({ text, expected }) => {
textArea.value = text;
textArea.setSelectionRange(text.length, text.length);
textArea.addEventListener('keydown', keypressNoteText);
textArea.dispatchEvent(enterEvent);
expect(textArea.value.substr(0, textArea.selectionStart)).toEqual(expected);
expect(textArea.selectionStart).toBe(expected.length);
expect(textArea.selectionEnd).toBe(text.length);
});
it('does nothing if feature flag disabled', () => {
gon.features = { markdownContinueLists: false };
const text = '- item';
const expected = '- item';
textArea.value = text;
textArea.setSelectionRange(text.length, text.length);
textArea.addEventListener('keydown', keypressNoteText);
textArea.dispatchEvent(enterEvent);
expect(textArea.value).toEqual(expected);
expect(textArea.selectionStart).toBe(expected.length);
});
});
});
describe('with selection', () => {

View File

@ -45,6 +45,8 @@ describe('issue_note_form component', () => {
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
noteId: '545',
};
gon.features = { markdownContinueLists: true };
});
afterEach(() => {

View File

@ -36,6 +36,7 @@ describe('IssuableEditForm', () => {
beforeEach(() => {
wrapper = createComponent();
gon.features = { markdownContinueLists: true };
});
afterEach(() => {

View File

@ -45,6 +45,8 @@ describe('ZenMode', () => {
// Set this manually because we can't actually scroll the window
zen.scroll_position = 456;
gon.features = { markdownContinueLists: true };
});
describe('enabling dropzone', () => {

View File

@ -1026,4 +1026,26 @@ RSpec.describe ProjectsHelper do
end
end
end
describe '#import_from_bitbucket_message' do
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'as a user' do
it 'returns a link to contact an administrator' do
allow(user).to receive(:admin?).and_return(false)
expect(helper.import_from_bitbucket_message).to have_text('To enable importing projects from Bitbucket, ask your GitLab administrator to configure OAuth integration')
end
end
context 'as an administrator' do
it 'returns a link to configure bitbucket' do
allow(user).to receive(:admin?).and_return(true)
expect(helper.import_from_bitbucket_message).to have_text('To enable importing projects from Bitbucket, as administrator you need to configure OAuth integration')
end
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_shared_state do
RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_cache do
let(:hunk) do
'@@ -1 +1 @@
-Hello
@ -166,6 +166,23 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_red
expect(new_discussion_note.discussion_id)
.to eq('SECOND_DISCUSSION_ID')
end
context 'when cached value does not exist' do
it 'falls back to generating a new discussion_id' do
expect(Discussion)
.to receive(:discussion_id)
.and_return('NEW_DISCUSSION_ID')
reply_note = described_class.from_json_hash(
'note_id' => note.note_id + 1,
'in_reply_to_id' => note.note_id
)
reply_note.project = project
reply_note.merge_request = merge_request
expect(reply_note.discussion_id).to eq('NEW_DISCUSSION_ID')
end
end
end
end

View File

@ -16,7 +16,7 @@ RSpec.describe ServiceHook do
let(:data) { { key: 'value' } }
it '#execute' do
expect(WebHookService).to receive(:new).with(hook, data, 'service_hook').and_call_original
expect(WebHookService).to receive(:new).with(hook, data, 'service_hook', force: false).and_call_original
expect_any_instance_of(WebHookService).to receive(:execute)
hook.execute(data)

View File

@ -168,17 +168,17 @@ RSpec.describe SystemHook do
let(:data) { { key: 'value' } }
let(:hook_name) { 'system_hook' }
before do
expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
end
it '#execute' do
expect(WebHookService).to receive(:new).with(hook, data, hook_name, force: false).and_call_original
expect_any_instance_of(WebHookService).to receive(:execute)
hook.execute(data, hook_name)
end
it '#async_execute' do
expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
expect_any_instance_of(WebHookService).to receive(:async_execute)
hook.async_execute(data, hook_name)

View File

@ -100,12 +100,18 @@ RSpec.describe WebHook do
hook.execute(data, hook_name)
end
it 'does not execute non-executable hooks' do
hook.update!(disabled_until: 1.day.from_now)
it 'passes force: false to the web hook service by default' do
expect(WebHookService)
.to receive(:new).with(hook, data, hook_name, force: false).and_return(double(execute: :done))
expect(WebHookService).not_to receive(:new)
expect(hook.execute(data, hook_name)).to eq :done
end
hook.execute(data, hook_name)
it 'passes force: true to the web hook service if required' do
expect(WebHookService)
.to receive(:new).with(hook, data, hook_name, force: true).and_return(double(execute: :forced))
expect(hook.execute(data, hook_name, force: true)).to eq :forced
end
it '#async_execute' do

View File

@ -37,7 +37,7 @@ RSpec.describe TestHooks::ProjectService do
it 'executes hook' do
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -49,7 +49,7 @@ RSpec.describe TestHooks::ProjectService do
it 'executes hook' do
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -69,7 +69,7 @@ RSpec.describe TestHooks::ProjectService do
allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data)
allow_next(NotesFinder).to receive(:execute).and_return(Note.all)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -86,7 +86,7 @@ RSpec.describe TestHooks::ProjectService do
allow(issue).to receive(:to_hook_data).and_return(sample_data)
allow_next(IssuesFinder).to receive(:execute).and_return([issue])
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -119,7 +119,7 @@ RSpec.describe TestHooks::ProjectService do
allow(merge_request).to receive(:to_hook_data).and_return(sample_data)
allow_next(MergeRequestsFinder).to receive(:execute).and_return([merge_request])
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -138,7 +138,7 @@ RSpec.describe TestHooks::ProjectService do
allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data)
allow_next(Ci::JobsFinder).to receive(:execute).and_return([ci_job])
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -157,7 +157,7 @@ RSpec.describe TestHooks::ProjectService do
allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data)
allow_next(Ci::PipelinesFinder).to receive(:execute).and_return([pipeline])
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -184,7 +184,7 @@ RSpec.describe TestHooks::ProjectService do
create(:wiki_page, wiki: project.wiki)
allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -203,7 +203,7 @@ RSpec.describe TestHooks::ProjectService do
allow(release).to receive(:to_hook_data).and_return(sample_data)
allow_next(ReleasesFinder).to receive(:execute).and_return([release])
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end

View File

@ -32,7 +32,7 @@ RSpec.describe TestHooks::SystemService do
it 'executes hook' do
expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -45,7 +45,7 @@ RSpec.describe TestHooks::SystemService do
allow(project.repository).to receive(:tags).and_return(['tag'])
expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -57,7 +57,7 @@ RSpec.describe TestHooks::SystemService do
it 'executes hook' do
expect(Gitlab::DataBuilder::Repository).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
@ -76,7 +76,7 @@ RSpec.describe TestHooks::SystemService do
it 'executes hook' do
expect(MergeRequest).to receive(:of_projects).and_return([merge_request])
expect(merge_request).to receive(:to_hook_data).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
expect(service.execute).to include(success_result)
end
end

View File

@ -52,6 +52,25 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
end
end
describe '#disabled?' do
using RSpec::Parameterized::TableSyntax
subject { described_class.new(hook, data, :push_hooks, force: forced) }
let(:hook) { double(executable?: executable, allow_local_requests?: false) }
where(:forced, :executable, :disabled) do
false | true | false
false | false | true
true | true | false
true | false | false
end
with_them do
it { is_expected.to have_attributes(disabled?: disabled) }
end
end
describe '#execute' do
let!(:uuid) { SecureRandom.uuid }
let(:headers) do
@ -129,7 +148,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
end
it 'does not execute disabled hooks' do
project_hook.update!(recent_failures: 4)
allow(service_instance).to receive(:disabled?).and_return(true)
expect(service_instance.execute).to eq({ status: :error, message: 'Hook disabled' })
end
@ -251,6 +270,20 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
end
context 'when forced' do
let(:service_instance) { described_class.new(project_hook, data, :push_hooks, force: true) }
it 'logs execution inline' do
expect(::WebHooks::LogExecutionWorker).not_to receive(:perform_async)
expect(::WebHooks::LogExecutionService)
.to receive(:new)
.with(hook: project_hook, log_data: Hash, response_category: :ok)
.and_return(double(execute: nil))
run_service
end
end
it 'log successful execution' do
run_service

View File

@ -85,7 +85,9 @@ RSpec.shared_examples 'User previews wiki changes' do
end
it 'renders content with CommonMark' do
fill_in :wiki_content, with: "1. one\n - sublist\n"
# using two `\n` ensures we're sublist to it's own line due
# to list auto-continue
fill_in :wiki_content, with: "1. one\n\n - sublist\n"
click_on "Preview"
# the above generates two separate lists (not embedded) in CommonMark