Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3ff3d897d6
commit
c19944d997
|
|
@ -0,0 +1,2 @@
|
|||
7e07fe42d34916b276a7b068f4faa8bdc0ebc984:doc/architecture/blueprints/runner_tokens/index.md:gitlab-rrt:485
|
||||
f6504b498548380198ad38295d9caa71412115f0:doc/architecture/blueprints/runner_tokens/index.md:generic-api-key:506
|
||||
|
|
@ -1 +1 @@
|
|||
65769c7a58d3339fe94a809bf6fd34f2f300a700
|
||||
998d1f6dbf9856c51d548814371cc6b8276086a6
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { WEBAUTHN_AUTHENTICATE } from './constants';
|
||||
import WebAuthnError from './error';
|
||||
import WebAuthnFlow from './flow';
|
||||
import { supported, convertGetParams, convertGetResponse } from './util';
|
||||
|
|
@ -44,7 +45,7 @@ export default class WebAuthnAuthenticate {
|
|||
this.renderAuthenticated(JSON.stringify(convertedResponse));
|
||||
})
|
||||
.catch((err) => {
|
||||
this.flow.renderError(new WebAuthnError(err, 'authenticate'));
|
||||
this.flow.renderError(new WebAuthnError(err, WEBAUTHN_AUTHENTICATE));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ import {
|
|||
STATE_UNSUPPORTED,
|
||||
STATE_WAITING,
|
||||
WEBAUTHN_DOCUMENTATION_PATH,
|
||||
WEBAUTHN_REGISTER,
|
||||
} from '~/authentication/webauthn/constants';
|
||||
import WebAuthnError from '~/authentication/webauthn/error';
|
||||
import {
|
||||
FLOW_REGISTER,
|
||||
convertCreateParams,
|
||||
convertCreateResponse,
|
||||
isHTTPS,
|
||||
|
|
@ -123,7 +123,7 @@ export default {
|
|||
this.credentials = JSON.stringify(convertCreateResponse(credentials));
|
||||
this.state = STATE_SUCCESS;
|
||||
} catch (error) {
|
||||
this.errorMessage = new WebAuthnError(error, FLOW_REGISTER).message();
|
||||
this.errorMessage = new WebAuthnError(error, WEBAUTHN_REGISTER).message();
|
||||
this.state = STATE_ERROR;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export const STATE_SUCCESS = 'success';
|
|||
export const STATE_UNSUPPORTED = 'unsupported';
|
||||
export const STATE_WAITING = 'waiting';
|
||||
|
||||
export const WEBAUTHN_AUTHENTICATE = 'authenticate';
|
||||
export const WEBAUTHN_REGISTER = 'register';
|
||||
export const WEBAUTHN_DOCUMENTATION_PATH = helpPagePath(
|
||||
'user/profile/account/two_factor_authentication',
|
||||
{ anchor: 'set-up-a-webauthn-device' },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { __ } from '~/locale';
|
||||
import { isHTTPS, FLOW_AUTHENTICATE, FLOW_REGISTER } from './util';
|
||||
import { WEBAUTHN_AUTHENTICATE, WEBAUTHN_REGISTER } from './constants';
|
||||
import { isHTTPS } from './util';
|
||||
|
||||
export default class WebAuthnError {
|
||||
constructor(error, flowType) {
|
||||
|
|
@ -13,9 +14,9 @@ export default class WebAuthnError {
|
|||
message() {
|
||||
if (this.errorName === 'NotSupportedError') {
|
||||
return __('Your device is not compatible with GitLab. Please try another device');
|
||||
} else if (this.errorName === 'InvalidStateError' && this.flowType === FLOW_AUTHENTICATE) {
|
||||
} else if (this.errorName === 'InvalidStateError' && this.flowType === WEBAUTHN_AUTHENTICATE) {
|
||||
return __('This device has not been registered with us.');
|
||||
} else if (this.errorName === 'InvalidStateError' && this.flowType === FLOW_REGISTER) {
|
||||
} else if (this.errorName === 'InvalidStateError' && this.flowType === WEBAUTHN_REGISTER) {
|
||||
return __('This device has already been registered with us.');
|
||||
} else if (this.errorName === 'SecurityError' && this.httpsDisabled) {
|
||||
return __(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { __ } from '~/locale';
|
|||
import WebAuthnError from './error';
|
||||
import WebAuthnFlow from './flow';
|
||||
import { supported, isHTTPS, convertCreateParams, convertCreateResponse } from './util';
|
||||
import { WEBAUTHN_REGISTER } from './constants';
|
||||
|
||||
// Register WebAuthn devices for users to authenticate with.
|
||||
//
|
||||
|
|
@ -40,7 +41,7 @@ export default class WebAuthnRegister {
|
|||
publicKey: this.webauthnOptions,
|
||||
})
|
||||
.then((cred) => this.renderRegistered(JSON.stringify(convertCreateResponse(cred))))
|
||||
.catch((err) => this.flow.renderError(new WebAuthnError(err, 'register')));
|
||||
.catch((err) => this.flow.renderError(new WebAuthnError(err, WEBAUTHN_REGISTER)));
|
||||
}
|
||||
|
||||
renderSetup() {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ export function isHTTPS() {
|
|||
return window.location.protocol.startsWith('https');
|
||||
}
|
||||
|
||||
export const FLOW_AUTHENTICATE = 'authenticate';
|
||||
export const FLOW_REGISTER = 'register';
|
||||
|
||||
/**
|
||||
* Converts a base64 string to an ArrayBuffer
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
import confidentialMergeRequestState from '~/confidential_merge_request/state';
|
||||
import DropLab from '~/filtered_search/droplab/drop_lab_deprecated';
|
||||
import ISetter from '~/filtered_search/droplab/plugins/input_setter';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { addDelimiter } from '~/lib/utils/text_utility';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_st
|
|||
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
|
||||
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
|
||||
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
|
||||
import { createAlert, VARIANT_INFO } from '~/flash';
|
||||
import { createAlert, VARIANT_INFO } from '~/alert';
|
||||
import { TYPENAME_USER } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Sortable from 'sortablejs';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import { getSortableDefaultOptions, sortableStart } from '~/sortable/utils';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { normalizeHeaders } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlIcon, GlBadge, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Visibility from 'visibilityjs';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import {
|
||||
IssuableStatusText,
|
||||
STATUS_CLOSED,
|
||||
|
|
@ -277,7 +277,7 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
this.flashContainer = null;
|
||||
this.alert = null;
|
||||
this.service = new Service(this.endpoint);
|
||||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
|
|
@ -395,7 +395,7 @@ export default {
|
|||
? { ...formState, issue_type: issueState.issueType }
|
||||
: formState;
|
||||
|
||||
this.clearFlash();
|
||||
this.alert?.dismiss();
|
||||
|
||||
return this.service
|
||||
.updateIssuable(issuablePayload)
|
||||
|
|
@ -431,7 +431,7 @@ export default {
|
|||
errMsg += `. ${message}`;
|
||||
}
|
||||
|
||||
this.flashContainer = createAlert({
|
||||
this.alert = createAlert({
|
||||
message: errMsg,
|
||||
});
|
||||
})
|
||||
|
|
@ -448,13 +448,6 @@ export default {
|
|||
this.isStickyHeaderShowing = true;
|
||||
},
|
||||
|
||||
clearFlash() {
|
||||
if (this.flashContainer) {
|
||||
this.flashContainer.close();
|
||||
this.flashContainer = null;
|
||||
}
|
||||
},
|
||||
|
||||
handleSaveDescription(description) {
|
||||
this.updateFormState();
|
||||
this.setFormState({ description });
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_detail
|
|||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_ISSUE, TYPENAME_WORK_ITEM } from '~/graphql_shared/constants';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { TYPE_ISSUE } from '~/issues/constants';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { getSortableDefaultOptions, isDragging } from '~/sortable/utils';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/alert';
|
||||
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
|
||||
import { STATUS_CLOSED, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { ISSUE_STATE_EVENT_CLOSE, ISSUE_STATE_EVENT_REOPEN } from '~/issues/show/constants';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { produce } from 'immer';
|
|||
import { sortBy } from 'lodash';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { sprintf } from '~/locale';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_ISSUE } from '~/graphql_shared/constants';
|
||||
import { timelineFormI18n } from './constants';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlTab, GlTabs } from '@gitlab/ui';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { trackIncidentDetailsViewsOptions } from '~/incidents/constants';
|
||||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { sprintf } from '~/locale';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export const displayAndLogError = (error) =>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ export default {
|
|||
NewNamespacePage,
|
||||
},
|
||||
props: {
|
||||
groupsUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentGroupUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -39,7 +43,10 @@ export default {
|
|||
{ text: this.parentGroupName, href: this.parentGroupUrl },
|
||||
{ text: s__('GroupsNew|New subgroup'), href: '#' },
|
||||
]
|
||||
: [{ text: s__('GroupsNew|New group'), href: '#' }];
|
||||
: [
|
||||
{ text: s__('GroupsNew|Groups'), href: this.groupsUrl },
|
||||
{ text: s__('GroupsNew|New group'), href: '#' },
|
||||
];
|
||||
},
|
||||
panels() {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ initFilePickers();
|
|||
function initNewGroupCreation(el) {
|
||||
const {
|
||||
hasErrors,
|
||||
groupsUrl,
|
||||
parentGroupUrl,
|
||||
parentGroupName,
|
||||
importExistingGroupPath,
|
||||
|
|
@ -31,6 +32,7 @@ function initNewGroupCreation(el) {
|
|||
} = el.dataset;
|
||||
|
||||
const props = {
|
||||
groupsUrl,
|
||||
parentGroupUrl,
|
||||
parentGroupName,
|
||||
importExistingGroupPath,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Vue from 'vue';
|
|||
import loadAwardsHandler from '~/awards_handler';
|
||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
import Diff from '~/diff';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import initDeprecatedNotes from '~/init_deprecated_notes';
|
||||
import { initDiffStatsDropdown } from '~/init_diff_stats_dropdown';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ export default class ActivityCalendar {
|
|||
});
|
||||
return `translate(${this.daySizeWithSpace * i + 1 + this.daySizeWithSpace}, 18)`;
|
||||
})
|
||||
.attr('data-testid', 'user-contrib-cell-group')
|
||||
.selectAll('rect')
|
||||
.data((stamp) => stamp)
|
||||
.enter()
|
||||
|
|
@ -195,6 +196,7 @@ export default class ActivityCalendar {
|
|||
.attr('data-level', (stamp) => getLevelFromContributions(stamp.count))
|
||||
.attr('title', (stamp) => formatTooltipText(stamp))
|
||||
.attr('class', 'user-contrib-cell has-tooltip')
|
||||
.attr('data-testid', 'user-contrib-cell')
|
||||
.attr('data-html', true)
|
||||
.attr('data-container', 'body')
|
||||
.on('click', this.clickDay);
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ export default class UserTabs {
|
|||
timestamps: data,
|
||||
calendarActivitiesPath,
|
||||
utcOffset,
|
||||
firstDayOfTheWeek: gon.first_day_of_week,
|
||||
firstDayOfWeek: gon.first_day_of_week,
|
||||
monthsAgo,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ export default {
|
|||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
projectsUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentGroupUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -89,9 +93,11 @@ export default {
|
|||
computed: {
|
||||
initialBreadcrumbs() {
|
||||
return [
|
||||
this.parentGroupUrl && { text: this.parentGroupName, href: this.parentGroupUrl },
|
||||
this.parentGroupUrl
|
||||
? { text: this.parentGroupName, href: this.parentGroupUrl }
|
||||
: { text: s__('ProjectsNew|Projects'), href: this.projectsUrl },
|
||||
{ text: s__('ProjectsNew|New project'), href: '#' },
|
||||
].filter(Boolean);
|
||||
];
|
||||
},
|
||||
availablePanels() {
|
||||
return this.isCiCdAvailable ? PANELS : PANELS.filter((p) => p.name !== CI_CD_PANEL);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export function initNewProjectCreation() {
|
|||
isCiCdAvailable,
|
||||
parentGroupUrl,
|
||||
parentGroupName,
|
||||
projectsUrl,
|
||||
} = el.dataset;
|
||||
|
||||
const props = {
|
||||
|
|
@ -25,6 +26,7 @@ export function initNewProjectCreation() {
|
|||
newProjectGuidelines,
|
||||
parentGroupUrl,
|
||||
parentGroupName,
|
||||
projectsUrl,
|
||||
};
|
||||
|
||||
const provide = {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper gl-p-0!">
|
||||
<li
|
||||
:id="noteAnchorId"
|
||||
class="timeline-entry note system-note note-wrapper gl-p-0!"
|
||||
data-qa-selector="alert_system_note_container"
|
||||
>
|
||||
<div class="gl-display-inline-flex gl-align-items-center gl-relative">
|
||||
<div
|
||||
class="gl-display-inline gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-box-sizing-content-box gl-p-3 gl-mt-n2 gl-mr-6"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
|||
|
||||
import { TYPENAME_CRM_CONTACT } from '~/graphql_shared/constants';
|
||||
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
|
||||
import { isPositiveInteger } from '~/lib/utils/number_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
import { OPTIONS_NONE_ANY } from '../constants';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlAvatar, GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import { compact } from 'lodash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import { OPTIONS_NONE_ANY } from '../constants';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { glEmojiTag } from '~/emoji';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { followUser, unfollowUser } from '~/rest_api';
|
||||
import { isUserBusy } from '~/set_status_modal/utils';
|
||||
import Tracking from '~/tracking';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { reportTypeToSecurityReportTypeEnum } from 'ee_else_ce/vue_shared/security_reports/constants';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { s__ } from '~/locale';
|
||||
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
|
||||
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { s__ } from '~/locale';
|
||||
import ReportSection from '~/ci/reports/components/report_section.vue';
|
||||
import { ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/ci/reports/constants';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module DesignManagement
|
||||
class Update < ::Mutations::BaseMutation
|
||||
graphql_name "DesignManagementUpdate"
|
||||
|
||||
authorize :update_design
|
||||
|
||||
argument :id, ::Types::GlobalIDType[::DesignManagement::Design],
|
||||
required: true,
|
||||
description: "ID of the design to update."
|
||||
|
||||
argument :description, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: copy_field_description(Types::DesignManagement::DesignType, :description)
|
||||
|
||||
field :design, Types::DesignManagement::DesignType,
|
||||
null: false,
|
||||
description: "Updated design."
|
||||
|
||||
def resolve(id:, description:)
|
||||
design = authorized_find!(id: id)
|
||||
design.update(description: description)
|
||||
|
||||
{
|
||||
design: design.reset,
|
||||
errors: errors_on_object(design)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object(id:)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,6 +15,11 @@ module Types
|
|||
implements(Types::CurrentUserTodos)
|
||||
implements(Types::TodoableInterface)
|
||||
|
||||
field :description,
|
||||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'Description of the design.'
|
||||
|
||||
field :web_url,
|
||||
GraphQL::Types::String,
|
||||
null: false,
|
||||
|
|
@ -25,6 +30,8 @@ module Types
|
|||
resolver: Resolvers::DesignManagement::VersionsResolver,
|
||||
description: "All versions related to this design ordered newest first."
|
||||
|
||||
markdown_field :description_html, null: true
|
||||
|
||||
# Returns a `DesignManagement::Version` for this query based on the
|
||||
# `atVersion` argument passed to a parent node if present, or otherwise
|
||||
# the most recent `Version` for the issue.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ module Types
|
|||
mount_mutation Mutations::DesignManagement::Upload, calls_gitaly: true
|
||||
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
|
||||
mount_mutation Mutations::DesignManagement::Move
|
||||
mount_mutation Mutations::DesignManagement::Update
|
||||
mount_mutation Mutations::ContainerExpirationPolicies::Update
|
||||
mount_mutation Mutations::ContainerRepositories::Destroy
|
||||
mount_mutation Mutations::ContainerRepositories::DestroyTags
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class IssuePolicy < IssuablePolicy
|
|||
rule { ~can?(:read_issue) }.policy do
|
||||
prevent :read_design
|
||||
prevent :create_design
|
||||
prevent :update_design
|
||||
prevent :destroy_design
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -464,6 +464,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_alert_management_alert
|
||||
enable :update_alert_management_alert
|
||||
enable :create_design
|
||||
enable :update_design
|
||||
enable :move_design
|
||||
enable :destroy_design
|
||||
enable :read_terraform_state
|
||||
|
|
@ -750,6 +751,7 @@ class ProjectPolicy < BasePolicy
|
|||
prevent :read_design
|
||||
prevent :read_design_activity
|
||||
prevent :create_design
|
||||
prevent :update_design
|
||||
prevent :destroy_design
|
||||
prevent :move_design
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
module ResourceAccessTokens
|
||||
class CreateService < BaseService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(current_user, resource, params = {})
|
||||
@resource_type = resource.class.name.downcase
|
||||
@resource = resource
|
||||
|
|
@ -45,6 +45,14 @@ module ResourceAccessTokens
|
|||
|
||||
attr_reader :resource_type, :resource
|
||||
|
||||
def username_and_email_generator
|
||||
Gitlab::Utils::UsernameAndEmailGenerator.new(
|
||||
username_prefix: "#{resource_type}_#{resource.id}_bot",
|
||||
email_domain: "noreply.#{Gitlab.config.gitlab.host}"
|
||||
)
|
||||
end
|
||||
strong_memoize_attr :username_and_email_generator
|
||||
|
||||
def has_permission_to_create?
|
||||
%w(project group).include?(resource_type) && can?(current_user, :create_resource_access_tokens, resource)
|
||||
end
|
||||
|
|
@ -65,25 +73,13 @@ module ResourceAccessTokens
|
|||
def default_user_params
|
||||
{
|
||||
name: params[:name] || "#{resource.name.to_s.humanize} bot",
|
||||
email: generate_email,
|
||||
username: generate_username,
|
||||
email: username_and_email_generator.email,
|
||||
username: username_and_email_generator.username,
|
||||
user_type: :project_bot,
|
||||
skip_confirmation: true # Bot users should always have their emails confirmed.
|
||||
}
|
||||
end
|
||||
|
||||
def generate_username
|
||||
username
|
||||
end
|
||||
|
||||
def generate_email
|
||||
"#{username}@noreply.#{Gitlab.config.gitlab.host}"
|
||||
end
|
||||
|
||||
def username
|
||||
@username ||= "#{resource_type}_#{resource.id}_bot_#{SecureRandom.hex(8)}"
|
||||
end
|
||||
|
||||
def create_personal_access_token(user)
|
||||
PersonalAccessTokens::CreateService.new(
|
||||
current_user: user, target_user: user, params: personal_access_token_params
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
.group-edit-container
|
||||
|
||||
.js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s }.merge(subgroup_creation_data(@group),
|
||||
.js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s, groups_url: dashboard_groups_url }.merge(subgroup_creation_data(@group),
|
||||
verification_for_group_creation_data) }
|
||||
|
||||
.row{ 'v-cloak': true }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
.project-edit-errors
|
||||
= render 'projects/errors'
|
||||
|
||||
.js-new-project-creation{ data: { is_ci_cd_available: remote_mirror_setting_enabled?.to_s, has_errors: @project.errors.any?.to_s, new_project_guidelines: brand_new_project_guidelines, push_to_create_project_command: push_to_create_project_command, working_with_projects_help_path: help_page_path("user/project/working_with_projects"), parent_group_url: @project.parent && group_url(@project.parent), parent_group_name: @project.parent&.name } }
|
||||
.js-new-project-creation{ data: {
|
||||
is_ci_cd_available: remote_mirror_setting_enabled?.to_s,
|
||||
has_errors: @project.errors.any?.to_s,
|
||||
new_project_guidelines: brand_new_project_guidelines,
|
||||
push_to_create_project_command: push_to_create_project_command,
|
||||
working_with_projects_help_path: help_page_path("user/project/working_with_projects"),
|
||||
parent_group_url: @project.parent && group_url(@project.parent),
|
||||
parent_group_name: @project.parent&.name,
|
||||
projects_url: dashboard_projects_url } }
|
||||
|
||||
.row{ 'v-cloak': true }
|
||||
#blank-project-pane.tab-pane.active
|
||||
|
|
|
|||
|
|
@ -2450,6 +2450,26 @@ Input type: `DesignManagementMoveInput`
|
|||
| <a id="mutationdesignmanagementmovedesigncollection"></a>`designCollection` | [`DesignCollection`](#designcollection) | Current state of the collection. |
|
||||
| <a id="mutationdesignmanagementmoveerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.designManagementUpdate`
|
||||
|
||||
Input type: `DesignManagementUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdesignmanagementupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdesignmanagementupdatedescription"></a>`description` | [`String`](#string) | Description of the design. |
|
||||
| <a id="mutationdesignmanagementupdateid"></a>`id` | [`DesignManagementDesignID!`](#designmanagementdesignid) | ID of the design to update. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdesignmanagementupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdesignmanagementupdatedesign"></a>`design` | [`Design!`](#design) | Updated design. |
|
||||
| <a id="mutationdesignmanagementupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.designManagementUpload`
|
||||
|
||||
Input type: `DesignManagementUploadInput`
|
||||
|
|
@ -12755,6 +12775,8 @@ A single design.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="designcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="designdescription"></a>`description` | [`String`](#string) | Description of the design. |
|
||||
| <a id="designdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="designdiffrefs"></a>`diffRefs` | [`DiffRefs!`](#diffrefs) | Diff refs for this design. |
|
||||
| <a id="designdiscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="designevent"></a>`event` | [`DesignVersionEvent!`](#designversionevent) | How this design was changed in the current version. |
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ To run a DAST authenticated scan:
|
|||
| CI/CD variable | Type | Description |
|
||||
|:-----------------------------------------------|:------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `DAST_AUTH_COOKIES` | string | Set to a comma-separated list of cookie names to specify which cookies are used for authentication. |
|
||||
| `DAST_AUTH_REPORT` | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
|
||||
| `DAST_AUTH_REPORT` | boolean | Set to `true` to generate a report detailing steps taken during the authentication process. You must also define `gl-dast-debug-auth-report.html` as a CI job artifact to be able to access the generated report. Useful for debugging when authentication fails. |
|
||||
| `DAST_AUTH_URL` <sup>1</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Example: `https://login.example.com`. |
|
||||
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` | boolean | Verifies successful authentication by checking for the absence of a login form once the login form has been submitted. |
|
||||
| `DAST_AUTH_VERIFICATION_SELECTOR` | [selector](#finding-an-elements-selector) | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo`. |
|
||||
|
|
|
|||
|
|
@ -170,13 +170,13 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_ADVERTISE_SCAN` | boolean | `true` | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. |
|
||||
| `DAST_BROWSER_ACTION_STABILITY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `800ms` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after completing an action. |
|
||||
| `DAST_BROWSER_ACTION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to complete an action. |
|
||||
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. |
|
||||
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. |
|
||||
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
|
||||
| `DAST_BROWSER_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. |
|
||||
| `DAST_BROWSER_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. |
|
||||
| `DAST_BROWSER_DEVTOOLS_LOG` | string | `Default:messageAndBody,truncate:2000` | Set to log protocol messages between DAST and the Chromium browser. | |
|
||||
| `DAST_BROWSER_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. |
|
||||
| `DAST_BROWSER_EXCLUDED_ELEMENTS` | selector | `a[href='2.html'],css:.no-follow` | Comma-separated list of selectors that are ignored when scanning. |
|
||||
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
|
||||
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
|
||||
| `DAST_BROWSER_EXTRACT_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `5s` | The maximum amount of time to allow the browser to extract newly found elements or navigations. |
|
||||
| `DAST_BROWSER_FILE_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended logging level for use in the file log. |
|
||||
| `DAST_BROWSER_FILE_LOG_PATH` | string | `/output/browserker.log` | Set to the path of the file log. |
|
||||
|
|
@ -190,8 +190,8 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_BROWSER_NAVIGATION_STABILITY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after a navigation completes. |
|
||||
| `DAST_BROWSER_NAVIGATION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `15s` | The maximum amount of time to wait for a browser to navigate from one page to another. |
|
||||
| `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com, we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but are likely to produce little benefit after five to seven instances. |
|
||||
| `DAST_BROWSER_PAGE_LOADING_SELECTOR` | selector | `css:#page-is-loading` | Selector that when is no longer visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_READY_SELECTOR` |
|
||||
| `DAST_BROWSER_PAGE_READY_SELECTOR` | selector | `css:#page-is-ready` | Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_LOADING_SELECTOR` |
|
||||
| `DAST_BROWSER_PAGE_LOADING_SELECTOR` | selector | `css:#page-is-loading` | Selector that when is no longer visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_READY_SELECTOR`. |
|
||||
| `DAST_BROWSER_PAGE_READY_SELECTOR` | selector | `css:#page-is-ready` | Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_LOADING_SELECTOR`. |
|
||||
| `DAST_BROWSER_SCAN` | boolean | `true` | Required to be `true` to run a browser-based scan. |
|
||||
| `DAST_BROWSER_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or user actions. |
|
||||
| `DAST_BROWSER_STABILITY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis. |
|
||||
|
|
@ -201,8 +201,8 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_PATHS` | string | `/page1.html,/category1/page3.html` | Set to a comma-separated list of URL paths relative to `DAST_WEBSITE` for DAST to scan. |
|
||||
| `DAST_PATHS_FILE` | string | `/builds/project/urls.txt` | Set to a file path containing a list of URL paths relative to `DAST_WEBSITE` for DAST to scan. The file must be plain text with one path per line. |
|
||||
| `DAST_PKCS12_CERTIFICATE_BASE64` | string | `ZGZkZ2p5NGd...` | The PKCS12 certificate used for sites that require Mutual TLS. Must be encoded as base64 text. |
|
||||
| `DAST_PKCS12_PASSWORD` | string | `password` | The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. Create sensitive [custom CI/CI variables](../../../ci/variables/index.md#define-a-cicd-variable-in-the-ui) using the GitLab UI. |
|
||||
| `DAST_REQUEST_HEADERS` | string | `Cache-control:no-cache` | Set to a comma-separated list of request header names and values. |
|
||||
| `DAST_PKCS12_PASSWORD` | string | `password` | The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. Create sensitive [custom CI/CI variables](../../../ci/variables/index.md#define-a-cicd-variable-in-the-ui) using the GitLab UI. |
|
||||
| `DAST_REQUEST_HEADERS` | string | `Cache-control:no-cache` | Set to a comma-separated list of request header names and values. |
|
||||
| `DAST_SKIP_TARGET_CHECK` | boolean | `true` | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. |
|
||||
| `DAST_TARGET_AVAILABILITY_TIMEOUT` | number | `60` | Time limit in seconds to wait for target availability. |
|
||||
| `DAST_WEBSITE` | URL | `https://example.com` | The URL of the website to scan. |
|
||||
|
|
@ -275,16 +275,6 @@ dast:
|
|||
NOTE:
|
||||
Adjusting these values may impact scan time because they adjust how long each browser waits for various activities to complete.
|
||||
|
||||
## Artifacts
|
||||
|
||||
Using the latest version of the DAST [template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml) these artifacts are exposed for download by default.
|
||||
|
||||
The list of artifacts includes the following files:
|
||||
|
||||
- `gl-dast-debug-auth-report.html`
|
||||
- `gl-dast-debug-crawl-report.html`
|
||||
- `gl-dast-crawl-graph.svg`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See [troubleshooting](browser_based_troubleshooting.md) for more information.
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ This method of scanning is also capable of parsing and identifying over 500 diff
|
|||
Licenses not in the SPDX list are reported as "Unknown". License information can also be extracted from packages that are dual-licensed, or have multiple different licenses that apply.
|
||||
|
||||
To enable license detection using Dependency Scanning in a project,
|
||||
include the `Jobs/Dependency-Scanning.yml` template in its CI configuration,
|
||||
but do not include the `Jobs/License-Scanning.yml` template.
|
||||
include the `Jobs/Dependency-Scanning.gitlab-ci.yml` template in its CI configuration,
|
||||
but do not include the `Jobs/License-Scanning.gitlab-ci.yml` template.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ or API. However, administrators can use a workaround:
|
|||
group = Group.find(109)
|
||||
|
||||
# Create the group bot user. For further group access tokens, the username should be `group_{group_id}_bot_{random_string}` and email address `group_{group_id}_bot_{random_string}@noreply.{Gitlab.config.gitlab.host}`.
|
||||
random_string = SecureRandom.hex(8)
|
||||
random_string = SecureRandom.hex(16)
|
||||
bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot_#{random_string}", email: "group_#{group.id}_bot_#{random_string}@noreply.#{Gitlab.config.gitlab.host}", user_type: :project_bot }).execute
|
||||
|
||||
# Confirm the group bot.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ module API
|
|||
end
|
||||
|
||||
def authorize_push_to_branch!(branch)
|
||||
authenticate!
|
||||
|
||||
unless user_access.can_push_to_branch?(branch)
|
||||
forbidden!("You are not allowed to push into this branch")
|
||||
end
|
||||
|
|
@ -127,6 +129,8 @@ module API
|
|||
tags %w[commits]
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
detail 'This feature was introduced in GitLab 8.13'
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ Example:
|
|||
spec/migrations/20230213215230_queue_my_batched_migration_spec.rb
|
||||
lib/gitlab/background_migration/my_batched_migration.rb
|
||||
spec/lib/gitlab/background_migration/my_batched_migration_spec.rb
|
||||
db/docs/batched_background_migrations/my_batched_migration.yml
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ module BatchedBackgroundMigration
|
|||
)
|
||||
end
|
||||
|
||||
def create_dictionary_file
|
||||
template(
|
||||
"batched_background_migration_dictionary.template",
|
||||
File.join("db/docs/batched_background_migrations/#{file_name}.yml")
|
||||
)
|
||||
end
|
||||
|
||||
def db_migrate_path
|
||||
super.sub("migrate", "post_migrate")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: <%= class_name %>
|
||||
description: # Please capture what <%= class_name %> does
|
||||
feature_category: <%= feature_category %>
|
||||
introduced_by_url: # URL of the MR (or issue/commit) that introduced the migration
|
||||
milestone:
|
||||
|
|
@ -239,7 +239,7 @@ module Gitlab
|
|||
sort_by = 'name' if sort_by == 'name_asc'
|
||||
|
||||
enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
|
||||
raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
|
||||
return Gitaly::FindLocalBranchesRequest::SortBy::NAME unless enum_value
|
||||
|
||||
enum_value
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
module Gitlab
|
||||
module Utils
|
||||
class UsernameAndEmailGenerator
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(username_prefix:, email_domain: Gitlab.config.gitlab.host)
|
||||
@username_prefix = username_prefix
|
||||
@email_domain = email_domain
|
||||
end
|
||||
|
||||
def username
|
||||
uniquify.string(->(counter) { Kernel.sprintf(username_pattern, counter) }) do |suggested_username|
|
||||
::Namespace.by_path(suggested_username) || ::User.find_by_any_email(email_for(suggested_username))
|
||||
end
|
||||
end
|
||||
strong_memoize_attr :username
|
||||
|
||||
def email
|
||||
email_for(username)
|
||||
end
|
||||
strong_memoize_attr :email
|
||||
|
||||
private
|
||||
|
||||
def username_pattern
|
||||
"#{@username_prefix}_#{SecureRandom.hex(16)}%s"
|
||||
end
|
||||
|
||||
def email_for(name)
|
||||
"#{name}@#{@email_domain}"
|
||||
end
|
||||
|
||||
def uniquify
|
||||
Gitlab::Utils::Uniquify.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8581,7 +8581,7 @@ msgstr ""
|
|||
msgid "Checkout|Coupon code (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Coupon has been applied to your purchase"
|
||||
msgid "Checkout|Coupon has been applied and by continuing with your purchase, you accept and agree to the %{linkStart}Coupon Terms%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Create a new group"
|
||||
|
|
@ -20538,6 +20538,9 @@ msgstr ""
|
|||
msgid "GroupsNew|GitLab source instance URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25288,9 +25291,6 @@ msgstr ""
|
|||
msgid "LearnGitlab|Contact your administrator to start a free Ultimate trial."
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitlab|Creating your onboarding experience..."
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitlab|Ok, let's go"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -34532,6 +34532,9 @@ msgstr ""
|
|||
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectsNew|Projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectsNew|Recommended if you're new to GitLab"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,18 @@ module QA
|
|||
module Page
|
||||
module Component
|
||||
module Dropdown
|
||||
def select_item(item_text)
|
||||
find('li.gl-new-dropdown-item', text: item_text, match: :prefer_exact).click
|
||||
# Find and click item using css selector and matching text
|
||||
# If item_text is not provided, select the first item that matches the given css selector
|
||||
#
|
||||
# @param [String] item_text
|
||||
# @param [String] css - css selector of the item
|
||||
# @return [void]
|
||||
def select_item(item_text, css: 'li.gl-new-dropdown-item')
|
||||
if item_text
|
||||
find(css, text: item_text, match: :prefer_exact).click
|
||||
else
|
||||
find(css, match: :first).click
|
||||
end
|
||||
end
|
||||
|
||||
def has_item?(item_text)
|
||||
|
|
@ -65,8 +75,8 @@ module QA
|
|||
find('li.gl-new-dropdown-item span:nth-child(2)', text: item_text, exact_text: true).click
|
||||
end
|
||||
|
||||
def expand_select_list
|
||||
find('.gl-new-dropdown-toggle').click
|
||||
def expand_select_list(css: '.gl-new-dropdown-toggle')
|
||||
find(css).click
|
||||
end
|
||||
|
||||
def wait_for_search_to_complete
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ module QA
|
|||
def has_alert_with_title?(title)
|
||||
has_link?(title, wait: 5)
|
||||
end
|
||||
|
||||
def go_to_alert(title)
|
||||
click_link_with_text(title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module Project
|
||||
module Monitor
|
||||
module Alerts
|
||||
class Show < Page::Base
|
||||
view 'app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue' do
|
||||
element :alert_system_note_container
|
||||
end
|
||||
|
||||
def go_to_activity_feed_tab
|
||||
click_link_with_text('Activity feed')
|
||||
end
|
||||
|
||||
def has_system_note?(text)
|
||||
has_element?(:alert_system_note_container, text: text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -31,6 +31,22 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def go_to_monitor_on_call_schedules
|
||||
hover_monitor do
|
||||
within_submenu do
|
||||
click_element(:sidebar_menu_item_link, menu_item: 'On-call Schedules')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_monitor_escalation_policies
|
||||
hover_monitor do
|
||||
within_submenu do
|
||||
click_element(:sidebar_menu_item_link, menu_item: 'Escalation Policies')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hover_monitor
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ module QA
|
|||
tag
|
||||
label
|
||||
variable
|
||||
system_note
|
||||
].each do |predicate|
|
||||
RSpec::Matchers.define "have_#{predicate}" do |*args, **kwargs|
|
||||
match do |page_object|
|
||||
|
|
|
|||
|
|
@ -170,8 +170,6 @@ class CreatePipelineFailureIncident
|
|||
Additionally, a message can be posted in `#backend_maintainers` or `#frontend_maintainers` to get a maintainer take a look at the fix ASAP.
|
||||
- Cherry picking a change that was used to fix a similar master-broken issue.
|
||||
|
||||
In both cases, make sure to add the ~"pipeline:expedite" label to speed up the `stable`-fixing pipelines.
|
||||
|
||||
### Resolution
|
||||
|
||||
Add a comment to this issue describing how this incident could have been prevented earlier in the Merge Request pipeline (rather than the merge commit pipeline).
|
||||
|
|
|
|||
|
|
@ -1231,6 +1231,19 @@ RSpec.describe ProjectsController, feature_category: :projects do
|
|||
expect(response).to have_gitlab_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sort param is invalid' do
|
||||
let(:request) { get :refs, params: { namespace_id: project.namespace, id: project, sort: 'invalid' } }
|
||||
|
||||
it 'uses default sort by name' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(json_response['Branches']).to include('master')
|
||||
expect(json_response['Tags']).to include('v1.0.0')
|
||||
expect(json_response['Commits']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #preview_markdown' do
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
|
|||
"#{get_cell_level_selector(contributions)}[title='#{contribution_text}<br /><span class=\"gl-text-gray-300\">#{date}</span>']"
|
||||
end
|
||||
|
||||
def get_days_of_week
|
||||
page.all('[data-testid="user-contrib-cell-group"]')[1]
|
||||
.all('[data-testid="user-contrib-cell"]')
|
||||
.map do |node|
|
||||
node[:title].match(/(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/)[0]
|
||||
end
|
||||
end
|
||||
|
||||
def push_code_contribution
|
||||
event = create(:push_event, project: contributed_project, author: user)
|
||||
|
||||
|
|
@ -237,6 +245,32 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
|
|||
it_behaves_like 'hidden activity calendar'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'first_day_of_week setting' do
|
||||
context 'when first day of the week is set to Monday' do
|
||||
before do
|
||||
stub_application_setting(first_day_of_week: 1)
|
||||
end
|
||||
|
||||
include_context 'when user page is visited'
|
||||
|
||||
it 'shows calendar with Monday as the first day of the week' do
|
||||
expect(get_days_of_week).to eq(%w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when first day of the week is set to Sunday' do
|
||||
before do
|
||||
stub_application_setting(first_day_of_week: 0)
|
||||
end
|
||||
|
||||
include_context 'when user page is visited'
|
||||
|
||||
it 'shows calendar with Sunday as the first day of the week' do
|
||||
expect(get_days_of_week).to eq(%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with `profile_tabs_vue` feature flag enabled' do
|
||||
|
|
@ -346,5 +380,31 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
|
|||
it_behaves_like 'hidden activity calendar'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'first_day_of_week setting' do
|
||||
context 'when first day of the week is set to Monday' do
|
||||
before do
|
||||
stub_application_setting(first_day_of_week: 1)
|
||||
end
|
||||
|
||||
include_context 'when user page is visited'
|
||||
|
||||
it 'shows calendar with Monday as the first day of the week' do
|
||||
expect(get_days_of_week).to eq(%w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when first day of the week is set to Sunday' do
|
||||
before do
|
||||
stub_application_setting(first_day_of_week: 0)
|
||||
end
|
||||
|
||||
include_context 'when user page is visited'
|
||||
|
||||
it 'shows calendar with Sunday as the first day of the week' do
|
||||
expect(get_days_of_week).to eq(%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -253,6 +253,6 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do
|
|||
# - ProjectsHelper#load_pipeline_status / Ci::CommitWithPipeline#last_pipeline
|
||||
# - Ci::Pipeline#detailed_status
|
||||
|
||||
expect { visit dashboard_projects_path }.not_to exceed_query_limit(control).with_threshold(0)
|
||||
expect { visit dashboard_projects_path }.not_to exceed_query_limit(control).with_threshold(4)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@ import {
|
|||
STATE_SUCCESS,
|
||||
STATE_UNSUPPORTED,
|
||||
STATE_WAITING,
|
||||
WEBAUTHN_REGISTER,
|
||||
} from '~/authentication/webauthn/constants';
|
||||
import * as WebAuthnUtils from '~/authentication/webauthn/util';
|
||||
import WebAuthnError from '~/authentication/webauthn/error';
|
||||
|
||||
const csrfToken = 'mock-csrf-token';
|
||||
jest.mock('~/lib/utils/csrf', () => ({ token: csrfToken }));
|
||||
jest.mock('~/authentication/webauthn/util');
|
||||
jest.mock('~/authentication/webauthn/error');
|
||||
|
||||
describe('Registration', () => {
|
||||
const initialError = null;
|
||||
|
|
@ -221,10 +224,12 @@ describe('Registration', () => {
|
|||
|
||||
it('shows an error message and a retry button', async () => {
|
||||
createComponent();
|
||||
mockCreate.mockRejectedValueOnce(new Error());
|
||||
const error = new Error();
|
||||
mockCreate.mockRejectedValueOnce(error);
|
||||
|
||||
await setupDevice();
|
||||
|
||||
expect(WebAuthnError).toHaveBeenCalledWith(error, WEBAUTHN_REGISTER);
|
||||
expect(wrapper.findComponent(GlAlert).props()).toMatchObject({
|
||||
variant: 'danger',
|
||||
secondaryButtonText: I18N_BUTTON_TRY_AGAIN,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import WebAuthnError from '~/authentication/webauthn/error';
|
||||
import { WEBAUTHN_AUTHENTICATE, WEBAUTHN_REGISTER } from '~/authentication/webauthn/constants';
|
||||
|
||||
describe('WebAuthnError', () => {
|
||||
it.each([
|
||||
[
|
||||
'NotSupportedError',
|
||||
'Your device is not compatible with GitLab. Please try another device',
|
||||
'authenticate',
|
||||
WEBAUTHN_AUTHENTICATE,
|
||||
],
|
||||
['InvalidStateError', 'This device has not been registered with us.', 'authenticate'],
|
||||
['InvalidStateError', 'This device has already been registered with us.', 'register'],
|
||||
['UnknownError', 'There was a problem communicating with your device.', 'register'],
|
||||
['InvalidStateError', 'This device has not been registered with us.', WEBAUTHN_AUTHENTICATE],
|
||||
['InvalidStateError', 'This device has already been registered with us.', WEBAUTHN_REGISTER],
|
||||
['UnknownError', 'There was a problem communicating with your device.', WEBAUTHN_REGISTER],
|
||||
])('exception %s will have message %s, flow type: %s', (exception, expectedMessage, flowType) => {
|
||||
expect(new WebAuthnError(new DOMException('', exception), flowType).message()).toEqual(
|
||||
expectedMessage,
|
||||
|
|
@ -24,7 +25,7 @@ describe('WebAuthnError', () => {
|
|||
const expectedMessage =
|
||||
'WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details.';
|
||||
expect(
|
||||
new WebAuthnError(new DOMException('', 'SecurityError'), 'authenticate').message(),
|
||||
new WebAuthnError(new DOMException('', 'SecurityError'), WEBAUTHN_AUTHENTICATE).message(),
|
||||
).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ describe('WebAuthnError', () => {
|
|||
|
||||
const expectedMessage = 'There was a problem communicating with your device.';
|
||||
expect(
|
||||
new WebAuthnError(new DOMException('', 'SecurityError'), 'authenticate').message(),
|
||||
new WebAuthnError(new DOMException('', 'SecurityError'), WEBAUTHN_AUTHENTICATE).message(),
|
||||
).toEqual(expectedMessage);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import blobBundle from '~/blob_edit/blob_bundle';
|
||||
|
||||
import SourceEditor from '~/blob_edit/edit_blob';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
|
||||
jest.mock('~/blob_edit/edit_blob');
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('BlobBundle', () => {
|
||||
it('does not load SourceEditor by default', () => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { s__ } from '~/locale';
|
|||
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/alert';
|
||||
|
||||
import AdminNewRunnerApp from '~/ci/runner/admin_new_runner/admin_new_runner_app.vue';
|
||||
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
|
||||
|
|
@ -21,7 +21,7 @@ const mockLegacyRegistrationToken = 'LEGACY_REGISTRATION_TOKEN';
|
|||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
...jest.requireActual('~/lib/utils/url_utility'),
|
||||
redirectTo: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/alert';
|
||||
import { redirectTo } from '~/lib/utils/url_utility';
|
||||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
|
@ -24,7 +24,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
|
|||
import { runnerData } from '../mock_data';
|
||||
|
||||
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import Vue, { nextTick } from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import RegistrationTokenResetDropdownItem from '~/ci/runner/components/registration/registration_token_reset_dropdown_item.vue';
|
||||
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/ci/runner/constants';
|
||||
import runnersRegistrationTokenResetMutation from '~/ci/runner/graphql/list/runners_registration_token_reset.mutation.graphql';
|
||||
import { captureException } from '~/ci/runner/sentry_utils';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Vue from 'vue';
|
|||
import { makeVar } from '@apollo/client/core';
|
||||
import { GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { s__ } from '~/locale';
|
||||
|
|
@ -15,7 +15,7 @@ import { allRunnersData } from '../mock_data';
|
|||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('RunnerBulkDelete', () => {
|
||||
let wrapper;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import runnerDeleteMutation from '~/ci/runner/graphql/shared/runner_delete.mutat
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { captureException } from '~/ci/runner/sentry_utils';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { I18N_DELETE_RUNNER } from '~/ci/runner/constants';
|
||||
|
||||
import RunnerDeleteButton from '~/ci/runner/components/runner_delete_button.vue';
|
||||
|
|
@ -21,7 +21,7 @@ const mockRunnerName = `#${mockRunnerId} (${mockRunner.shortSha})`;
|
|||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
|
||||
describe('RunnerDeleteButton', () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
|
||||
import RunnerJobsTable from '~/ci/runner/components/runner_jobs_table.vue';
|
||||
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
|
||||
|
|
@ -15,7 +15,7 @@ import runnerJobsQuery from '~/ci/runner/graphql/show/runner_jobs.query.graphql'
|
|||
|
||||
import { runnerData, runnerJobsData } from '../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
|
||||
const mockRunner = runnerData.data.runner;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_help
|
|||
import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { captureException } from '~/ci/runner/sentry_utils';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import {
|
||||
I18N_PAUSE,
|
||||
I18N_PAUSE_TOOLTIP,
|
||||
|
|
@ -22,7 +22,7 @@ const mockRunner = allRunnersData.data.runners.nodes[0];
|
|||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
|
||||
describe('RunnerPauseButton', () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { sprintf } from '~/locale';
|
||||
import {
|
||||
I18N_ASSIGNED_PROJECTS,
|
||||
|
|
@ -22,7 +22,7 @@ import runnerProjectsQuery from '~/ci/runner/graphql/show/runner_projects.query.
|
|||
|
||||
import { runnerData, runnerProjectsData } from '../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
|
||||
const mockRunner = runnerData.data.runner;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { __ } from '~/locale';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/alert';
|
||||
import { redirectTo } from '~/lib/utils/url_utility';
|
||||
import RunnerUpdateForm from '~/ci/runner/components/runner_update_form.vue';
|
||||
import {
|
||||
|
|
@ -21,7 +21,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
|
|||
import { runnerFormData } from '../mock_data';
|
||||
|
||||
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import { mount } from '@vue/test-utils';
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import TagToken, { TAG_SUGGESTIONS_PATH } from '~/ci/runner/components/search_tokens/tag_token.vue';
|
||||
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { getRecentlyUsedSuggestions } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
|
||||
...jest.requireActual('~/vue_shared/components/filtered_search_bar/filtered_search_utils'),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/alert';
|
||||
import { redirectTo } from '~/lib/utils/url_utility';
|
||||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
|
@ -24,7 +24,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
|
|||
import { runnerData } from '../mock_data';
|
||||
|
||||
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/ci/runner/sentry_utils');
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import AccessorUtilities from '~/lib/utils/accessor';
|
|||
import { showAlertFromLocalStorage } from '~/ci/runner/local_storage_alert/show_alert_from_local_storage';
|
||||
import { LOCAL_STORAGE_ALERT_KEY } from '~/ci/runner/local_storage_alert/constants';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('showAlertFromLocalStorage', () => {
|
||||
useLocalStorageSpy();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import Audio from '~/content_editor/extensions/audio';
|
|||
import Video from '~/content_editor/extensions/video';
|
||||
import Link from '~/content_editor/extensions/link';
|
||||
import Loading from '~/content_editor/extensions/loading';
|
||||
import { VARIANT_DANGER } from '~/flash';
|
||||
import { VARIANT_DANGER } from '~/alert';
|
||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import eventHubFactory from '~/helpers/event_hub_factory';
|
||||
import { createTestEditor, createDocBuilder } from '../test_utils';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight
|
|||
import Diagram from '~/content_editor/extensions/diagram';
|
||||
import Frontmatter from '~/content_editor/extensions/frontmatter';
|
||||
import Bold from '~/content_editor/extensions/bold';
|
||||
import { VARIANT_DANGER } from '~/flash';
|
||||
import { VARIANT_DANGER } from '~/alert';
|
||||
import eventHubFactory from '~/helpers/event_hub_factory';
|
||||
import { ALERT_EVENT } from '~/content_editor/constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import * as actions from '~/error_tracking/store/actions';
|
||||
import * as types from '~/error_tracking/store/mutation_types';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
let mock;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import * as actions from '~/error_tracking/store/details/actions';
|
||||
import * as types from '~/error_tracking/store/details/mutation_types';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
HTTP_STATUS_BAD_REQUEST,
|
||||
|
|
@ -14,7 +14,7 @@ import Poll from '~/lib/utils/poll';
|
|||
let mockedAdapter;
|
||||
let mockedRestart;
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
describe('Sentry error details store actions', () => {
|
||||
|
|
@ -48,7 +48,7 @@ describe('Sentry error details store actions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should show flash on API error', async () => {
|
||||
it('should show alert on API error', async () => {
|
||||
mockedAdapter.onGet().reply(HTTP_STATUS_BAD_REQUEST);
|
||||
|
||||
await testAction(
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import * as actions from '~/error_tracking/store/list/actions';
|
||||
import * as types from '~/error_tracking/store/list/mutation_types';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('error tracking actions', () => {
|
||||
let mock;
|
||||
|
|
@ -38,7 +38,7 @@ describe('error tracking actions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should show flash on API error', async () => {
|
||||
it('should show alert on API error', async () => {
|
||||
mock.onGet().reply(HTTP_STATUS_BAD_REQUEST);
|
||||
|
||||
await testAction(
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import {
|
|||
TOAST_MESSAGE_LOCALSTORAGE_KEY,
|
||||
TOAST_MESSAGE_SUCCESSFUL,
|
||||
} from '~/invite_members/constants';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
useLocalStorageSpy();
|
||||
|
||||
describe('Display Successful Invitation Alert', () => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe('App component', () => {
|
|||
let wrapper;
|
||||
|
||||
const createComponent = (propsData = {}) => {
|
||||
wrapper = shallowMount(App, { propsData });
|
||||
wrapper = shallowMount(App, { propsData: { groupsUrl: '/dashboard/groups', ...propsData } });
|
||||
};
|
||||
|
||||
const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage);
|
||||
|
|
@ -24,20 +24,27 @@ describe('App component', () => {
|
|||
createComponent();
|
||||
|
||||
expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
|
||||
{ href: '/dashboard/groups', text: 'Groups' },
|
||||
{ href: '#', text: 'New group' },
|
||||
]);
|
||||
expect(findCreateGroupPanel().title).toBe('Create group');
|
||||
});
|
||||
|
||||
it('creates correct component for subgroup creation', () => {
|
||||
const props = { parentGroupName: 'parent', importExistingGroupPath: '/path' };
|
||||
const detailProps = {
|
||||
parentGroupName: 'parent',
|
||||
importExistingGroupPath: '/path',
|
||||
};
|
||||
|
||||
const props = { ...detailProps, parentGroupUrl: '/parent' };
|
||||
|
||||
createComponent(props);
|
||||
|
||||
expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
|
||||
{ href: '#', text: 'New group' },
|
||||
{ href: '/parent', text: 'parent' },
|
||||
{ href: '#', text: 'New subgroup' },
|
||||
]);
|
||||
expect(findCreateGroupPanel().title).toBe('Create subgroup');
|
||||
expect(findCreateGroupPanel().detailProps).toEqual(props);
|
||||
expect(findCreateGroupPanel().detailProps).toEqual(detailProps);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import PersistentUserCallout from '~/persistent_user_callout';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('PersistentUserCallout', () => {
|
||||
const dismissEndpoint = '/dismiss';
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@ describe('Experimental new project creation app', () => {
|
|||
let wrapper;
|
||||
|
||||
const createComponent = (propsData) => {
|
||||
wrapper = shallowMount(App, { propsData });
|
||||
wrapper = shallowMount(App, {
|
||||
propsData: { projectsUrl: '/dashboard/projects', ...propsData },
|
||||
});
|
||||
};
|
||||
|
||||
const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
|
@ -34,11 +38,28 @@ describe('Experimental new project creation app', () => {
|
|||
|
||||
expect(
|
||||
Boolean(
|
||||
wrapper
|
||||
.findComponent(NewNamespacePage)
|
||||
findNewNamespacePage()
|
||||
.props()
|
||||
.panels.find((p) => p.name === 'cicd_for_external_repo'),
|
||||
),
|
||||
).toBe(isCiCdAvailable);
|
||||
});
|
||||
|
||||
it('creates correct breadcrumbs for top-level projects', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
|
||||
{ href: '/dashboard/projects', text: 'Projects' },
|
||||
{ href: '#', text: 'New project' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates correct breadcrumbs for projects within groups', () => {
|
||||
createComponent({ parentGroupUrl: '/parent-group', parentGroupName: 'Parent Group' });
|
||||
|
||||
expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
|
||||
{ href: '/parent-group', text: 'Parent Group' },
|
||||
{ href: '#', text: 'New project' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['DesignAtVersion'] do
|
||||
RSpec.describe GitlabSchema.types['DesignAtVersion'], feature_category: :portfolio_management do
|
||||
it_behaves_like 'a GraphQL type with design fields' do
|
||||
let(:extra_design_fields) { %i[version design] }
|
||||
let_it_be(:design) { create(:design, :with_versions) }
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['Design'] do
|
||||
RSpec.describe GitlabSchema.types['Design'], feature_category: :portfolio_management do
|
||||
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
|
||||
|
||||
specify { expect(described_class.interfaces).to include(Types::TodoableInterface) }
|
||||
|
||||
it_behaves_like 'a GraphQL type with design fields' do
|
||||
let(:extra_design_fields) { %i[notes current_user_todos discussions versions web_url commenters] }
|
||||
let(:extra_design_fields) do
|
||||
%i[notes current_user_todos discussions versions web_url commenters description descriptionHtml]
|
||||
end
|
||||
|
||||
let_it_be(:design) { create(:design, :with_versions) }
|
||||
let(:object_id) { GitlabSchema.id_from_object(design) }
|
||||
let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) }
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ RSpec.describe BatchedBackgroundMigration::BatchedBackgroundMigrationGenerator,
|
|||
let(:expected_migration_spec_file) { load_expected_file('queue_my_batched_migration_spec.txt') }
|
||||
let(:expected_migration_job_file) { load_expected_file('my_batched_migration.txt') }
|
||||
let(:expected_migration_job_spec_file) { load_expected_file('my_batched_migration_spec_matcher.txt') }
|
||||
let(:expected_migration_dictionary) { load_expected_file('my_batched_migration_dictionary.txt') }
|
||||
|
||||
it 'generates expected files' do
|
||||
run_generator %w[my_batched_migration --table_name=projects --column_name=id --feature_category=database]
|
||||
|
|
@ -45,6 +46,10 @@ RSpec.describe BatchedBackgroundMigration::BatchedBackgroundMigrationGenerator,
|
|||
# Regex is used to match the dynamic schema: <version> in the specs
|
||||
expect(migration_job_spec_file).to match(/#{expected_migration_job_spec_file}/)
|
||||
end
|
||||
|
||||
assert_file('db/docs/batched_background_migrations/my_batched_migration.yml') do |migration_dictionary|
|
||||
expect(migration_dictionary).to eq(expected_migration_dictionary)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: MyBatchedMigration
|
||||
description: # Please capture what MyBatchedMigration does
|
||||
feature_category: database
|
||||
introduced_by_url: # URL of the MR (or issue/commit) that introduced the migration
|
||||
milestone:
|
||||
|
|
@ -213,8 +213,13 @@ RSpec.describe Gitlab::GitalyClient::RefService, feature_category: :gitaly do
|
|||
client.local_branches(sort_by: 'name_asc')
|
||||
end
|
||||
|
||||
it 'raises an argument error if an invalid sort_by parameter is passed' do
|
||||
expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
|
||||
it 'uses default sort by name' do
|
||||
expect_any_instance_of(Gitaly::RefService::Stub)
|
||||
.to receive(:find_local_branches)
|
||||
.with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash))
|
||||
.and_return([])
|
||||
|
||||
client.local_branches(sort_by: 'invalid')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -270,6 +275,17 @@ RSpec.describe Gitlab::GitalyClient::RefService, feature_category: :gitaly do
|
|||
client.tags(sort_by: 'version_asc')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorting option is invalid' do
|
||||
it 'uses default sort by name' do
|
||||
expect_any_instance_of(Gitaly::RefService::Stub)
|
||||
.to receive(:find_all_tags)
|
||||
.with(gitaly_request_with_params(sort_by: nil), kind_of(Hash))
|
||||
.and_return([])
|
||||
|
||||
client.tags(sort_by: 'invalid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with pagination option' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Utils::UsernameAndEmailGenerator, feature_category: :system_access do
|
||||
let(:username_prefix) { 'username_prefix' }
|
||||
let(:email_domain) { 'example.com' }
|
||||
|
||||
subject { described_class.new(username_prefix: username_prefix, email_domain: email_domain) }
|
||||
|
||||
describe 'email domain' do
|
||||
it 'defaults to `Gitlab.config.gitlab.host`' do
|
||||
expect(described_class.new(username_prefix: username_prefix).email).to end_with("@#{Gitlab.config.gitlab.host}")
|
||||
end
|
||||
|
||||
context 'when specified' do
|
||||
it 'uses the specified email domain' do
|
||||
expect(subject.email).to end_with("@#{email_domain}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator'
|
||||
end
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe DesignManagement::DesignPolicy do
|
||||
RSpec.describe DesignManagement::DesignPolicy, feature_category: :portfolio_management do
|
||||
include DesignManagementTestHelpers
|
||||
|
||||
let(:guest_design_abilities) { %i[read_design] }
|
||||
let(:developer_design_abilities) { %i[create_design destroy_design move_design] }
|
||||
let(:developer_design_abilities) { %i[create_design destroy_design move_design update_design] }
|
||||
let(:design_abilities) { guest_design_abilities + developer_design_abilities }
|
||||
|
||||
let_it_be(:guest) { create(:user) }
|
||||
|
|
|
|||
|
|
@ -425,6 +425,27 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
|
|||
describe "POST /projects/:id/repository/commits" do
|
||||
let!(:url) { "/projects/#{project_id}/repository/commits" }
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let(:params) do
|
||||
{
|
||||
branch: 'master',
|
||||
commit_message: 'message',
|
||||
actions: [
|
||||
{
|
||||
action: 'create',
|
||||
file_path: '/test.rb',
|
||||
content: 'puts 8'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like '401 response' do
|
||||
let(:request) { post api(url), params: params }
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a 403 unauthorized for user without permissions' do
|
||||
post api(url, guest)
|
||||
|
||||
|
|
@ -1775,7 +1796,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
|
|||
context 'when unauthenticated', 'and project is public' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
it_behaves_like '401 response' do
|
||||
let(:request) { post api(route), params: { branch: 'master' } }
|
||||
end
|
||||
end
|
||||
|
|
@ -1955,7 +1976,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
|
|||
context 'when unauthenticated', 'and project is public' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
it_behaves_like '401 response' do
|
||||
let(:request) { post api(route), params: { branch: branch } }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "updating designs", feature_category: :design_management do
|
||||
include GraphqlHelpers
|
||||
include DesignManagementTestHelpers
|
||||
|
||||
let_it_be(:issue) { create(:issue) }
|
||||
let_it_be_with_reload(:design) { create(:design, description: 'old description', issue: issue) }
|
||||
let_it_be(:developer) { create(:user, developer_projects: [issue.project]) }
|
||||
|
||||
let(:user) { developer }
|
||||
let(:description) { 'new description' }
|
||||
|
||||
let(:mutation) do
|
||||
input = {
|
||||
id: design.to_global_id.to_s,
|
||||
description: description
|
||||
}.compact
|
||||
|
||||
graphql_mutation(:design_management_update, input, <<~FIELDS)
|
||||
errors
|
||||
design {
|
||||
description
|
||||
descriptionHtml
|
||||
}
|
||||
FIELDS
|
||||
end
|
||||
|
||||
let(:update_design) { post_graphql_mutation(mutation, current_user: user) }
|
||||
let(:mutation_response) { graphql_mutation_response(:design_management_update) }
|
||||
|
||||
before do
|
||||
enable_design_management
|
||||
end
|
||||
|
||||
it 'updates design' do
|
||||
update_design
|
||||
|
||||
expect(graphql_errors).not_to be_present
|
||||
expect(mutation_response).to eq(
|
||||
'errors' => [],
|
||||
'design' => {
|
||||
'description' => description,
|
||||
'descriptionHtml' => "<p data-sourcepos=\"1:1-1:15\" dir=\"auto\">#{description}</p>"
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the user is not allowed to update designs' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'returns an error' do
|
||||
update_design
|
||||
|
||||
expect(graphql_errors).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when update fails' do
|
||||
let(:description) { 'x' * 1_000_001 }
|
||||
|
||||
it 'returns an error' do
|
||||
update_design
|
||||
|
||||
expect(graphql_errors).not_to be_present
|
||||
expect(mutation_response).to eq(
|
||||
'errors' => ["Description is too long (maximum is 1000000 characters)"],
|
||||
'design' => {
|
||||
'description' => 'old description',
|
||||
'descriptionHtml' => '<p data-sourcepos="1:1-1:15" dir="auto">old description</p>'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -99,27 +99,20 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
|
|||
end
|
||||
end
|
||||
|
||||
context 'bot email' do
|
||||
it 'check email domain' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
context 'bot username and email' do
|
||||
include_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator' do
|
||||
subject do
|
||||
response = described_class.new(user, resource, params).execute
|
||||
response.payload[:access_token].user
|
||||
end
|
||||
|
||||
expect(access_token.user.email).to end_with("@noreply.#{Gitlab.config.gitlab.host}")
|
||||
end
|
||||
let(:username_prefix) do
|
||||
"#{resource.class.name.downcase}_#{resource.id}_bot"
|
||||
end
|
||||
|
||||
it 'contains SecureRandom part' do
|
||||
expect(SecureRandom).to receive(:hex).at_least(:once).and_return('randomhex')
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
|
||||
expect(access_token.user.email).to include('_randomhex@noreply')
|
||||
end
|
||||
|
||||
it 'email is the same as username' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
|
||||
expect(access_token.user.email).to include(access_token.user.username)
|
||||
let(:email_domain) do
|
||||
"noreply.#{Gitlab.config.gitlab.host}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator' do
|
||||
let(:randomhex) { 'randomhex' }
|
||||
|
||||
it 'check email domain' do
|
||||
expect(subject.email).to end_with("@#{email_domain}")
|
||||
end
|
||||
|
||||
it 'contains SecureRandom part' do
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
|
||||
expect(subject.username).to include("_#{randomhex}")
|
||||
expect(subject.email).to include("_#{randomhex}@")
|
||||
end
|
||||
|
||||
it 'email name is the same as username' do
|
||||
expect(subject.email).to include("#{subject.username}@")
|
||||
end
|
||||
|
||||
context 'when conflicts' do
|
||||
let(:reserved_username) { "#{username_prefix}_#{randomhex}" }
|
||||
let(:reserved_email) { "#{reserved_username}@#{email_domain}" }
|
||||
|
||||
shared_examples 'uniquifies username and email' do
|
||||
it 'uniquifies username and email' do
|
||||
expect(subject.username).to eq("#{reserved_username}1")
|
||||
expect(subject.email).to include("#{subject.username}@")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when username is reserved' do
|
||||
context 'when username is reserved by user' do
|
||||
before do
|
||||
create(:user, username: reserved_username)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
|
||||
context 'when it conflicts with top-level group namespace' do
|
||||
before do
|
||||
create(:group, path: reserved_username)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
|
||||
context 'when it conflicts with top-level group namespace that includes upcased characters' do
|
||||
before do
|
||||
create(:group, path: reserved_username.upcase)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email is reserved' do
|
||||
context 'when it conflicts with confirmed primary email' do
|
||||
before do
|
||||
create(:user, email: reserved_email)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
|
||||
context 'when it conflicts with unconfirmed primary email' do
|
||||
before do
|
||||
create(:user, :unconfirmed, email: reserved_email)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
|
||||
context 'when it conflicts with confirmed secondary email' do
|
||||
before do
|
||||
create(:email, :confirmed, email: reserved_email)
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
include_examples 'uniquifies username and email'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email and username is reserved' do
|
||||
before do
|
||||
create(:user, email: reserved_email)
|
||||
create(:user, username: "#{reserved_username}1")
|
||||
allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex)
|
||||
end
|
||||
|
||||
it 'uniquifies username and email' do
|
||||
expect(subject.username).to eq("#{reserved_username}2")
|
||||
|
||||
expect(subject.email).to include("#{subject.username}@")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -21,6 +21,23 @@ RSpec.shared_examples '400 response' do
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples '401 response' do
|
||||
let(:message) { nil }
|
||||
|
||||
before do
|
||||
# Fires the request
|
||||
request
|
||||
end
|
||||
|
||||
it 'returns 401' do
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
|
||||
if message.present?
|
||||
expect(json_response['message']).to eq(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples '403 response' do
|
||||
before do
|
||||
# Fires the request
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue