summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 21:13:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 21:13:36 +0000
commitc19944d9970b788d8523cee6ee05217a8afd7646 (patch)
treee2d15e8c0d541b9b2fe26b9b82f23661df120f94
parent3ff3d897d6529aabb21aa6aed54eb430a9cf0fe2 (diff)
downloadgitlab-ce-c19944d9970b788d8523cee6ee05217a8afd7646.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitleaksignore2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/authentication/webauthn/authenticate.js3
-rw-r--r--app/assets/javascripts/authentication/webauthn/components/registration.vue4
-rw-r--r--app/assets/javascripts/authentication/webauthn/constants.js2
-rw-r--r--app/assets/javascripts/authentication/webauthn/error.js7
-rw-r--r--app/assets/javascripts/authentication/webauthn/register.js3
-rw-r--r--app/assets/javascripts/authentication/webauthn/util.js3
-rw-r--r--app/assets/javascripts/issues/create_merge_request_dropdown.js2
-rw-r--r--app/assets/javascripts/issues/issue.js2
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue2
-rw-r--r--app/assets/javascripts/issues/manual_ordering.js2
-rw-r--r--app/assets/javascripts/issues/related_merge_requests/store/actions.js2
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue15
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/utils.js2
-rw-r--r--app/assets/javascripts/pages/groups/new/components/app.vue9
-rw-r--r--app/assets/javascripts/pages/groups/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js2
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js2
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/projects/new/components/app.vue10
-rw-r--r--app/assets/javascripts/projects/new/index.js2
-rw-r--r--app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue2
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue2
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue2
-rw-r--r--app/graphql/mutations/design_management/update.rb39
-rw-r--r--app/graphql/types/design_management/design_type.rb7
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/policies/issue_policy.rb1
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/services/resource_access_tokens/create_service.rb28
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/projects/new.html.haml10
-rw-r--r--doc/api/graphql/reference/index.md22
-rw-r--r--doc/user/application_security/dast/authentication.md2
-rw-r--r--doc/user/application_security/dast/browser_based.md24
-rw-r--r--doc/user/compliance/license_scanning_of_cyclonedx_files/index.md4
-rw-r--r--doc/user/group/settings/group_access_tokens.md2
-rw-r--r--lib/api/commits.rb4
-rw-r--r--lib/generators/batched_background_migration/USAGE1
-rw-r--r--lib/generators/batched_background_migration/batched_background_migration_generator.rb7
-rw-r--r--lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template6
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb2
-rw-r--r--lib/gitlab/utils/username_and_email_generator.rb42
-rw-r--r--locale/gitlab.pot11
-rw-r--r--qa/qa/page/component/dropdown.rb18
-rw-r--r--qa/qa/page/project/monitor/alerts/index.rb4
-rw-r--r--qa/qa/page/project/monitor/alerts/show.rb25
-rw-r--r--qa/qa/page/project/sub_menus/monitor.rb16
-rw-r--r--qa/qa/support/matchers/have_matcher.rb1
-rwxr-xr-xscripts/create-pipeline-failure-incident.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb13
-rw-r--r--spec/features/calendar_spec.rb60
-rw-r--r--spec/features/dashboard/projects_spec.rb2
-rw-r--r--spec/frontend/authentication/webauthn/components/registration_spec.js7
-rw-r--r--spec/frontend/authentication/webauthn/error_spec.js13
-rw-r--r--spec/frontend/blob_edit/blob_bundle_spec.js4
-rw-r--r--spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js4
-rw-r--r--spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_bulk_delete_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_delete_button_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_jobs_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_pause_button_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_projects_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_update_form_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js4
-rw-r--r--spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js4
-rw-r--r--spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js4
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js2
-rw-r--r--spec/frontend/content_editor/extensions/paste_markdown_spec.js2
-rw-r--r--spec/frontend/error_tracking/store/actions_spec.js4
-rw-r--r--spec/frontend/error_tracking/store/details/actions_spec.js6
-rw-r--r--spec/frontend/error_tracking/store/list/actions_spec.js6
-rw-r--r--spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js4
-rw-r--r--spec/frontend/pages/groups/new/components/app_spec.js15
-rw-r--r--spec/frontend/persistent_user_callout_spec.js4
-rw-r--r--spec/frontend/projects/new/components/app_spec.js27
-rw-r--r--spec/graphql/types/design_management/design_at_version_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_type_spec.rb7
-rw-r--r--spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb5
-rw-r--r--spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt6
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb20
-rw-r--r--spec/lib/gitlab/utils/username_and_email_generator_spec.rb24
-rw-r--r--spec/policies/design_management/design_policy_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb25
-rw-r--r--spec/requests/api/graphql/mutations/design_management/update_spec.rb77
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb31
-rw-r--r--spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb104
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb17
-rw-r--r--vendor/project_templates/learn_gitlab_ultimate.tar.gzbin115431 -> 0 bytes
101 files changed, 755 insertions, 179 deletions
diff --git a/.gitleaksignore b/.gitleaksignore
new file mode 100644
index 00000000000..eab7926138c
--- /dev/null
+++ b/.gitleaksignore
@@ -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
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index f60c9678b67..6d17d4c34e2 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-65769c7a58d3339fe94a809bf6fd34f2f300a700
+998d1f6dbf9856c51d548814371cc6b8276086a6
diff --git a/app/assets/javascripts/authentication/webauthn/authenticate.js b/app/assets/javascripts/authentication/webauthn/authenticate.js
index 47cb7a40f76..748945a680b 100644
--- a/app/assets/javascripts/authentication/webauthn/authenticate.js
+++ b/app/assets/javascripts/authentication/webauthn/authenticate.js
@@ -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));
});
}
diff --git a/app/assets/javascripts/authentication/webauthn/components/registration.vue b/app/assets/javascripts/authentication/webauthn/components/registration.vue
index 1cc57046562..9a3644e0325 100644
--- a/app/assets/javascripts/authentication/webauthn/components/registration.vue
+++ b/app/assets/javascripts/authentication/webauthn/components/registration.vue
@@ -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;
}
},
diff --git a/app/assets/javascripts/authentication/webauthn/constants.js b/app/assets/javascripts/authentication/webauthn/constants.js
index 6646cb2eb3f..c41e6d2bd58 100644
--- a/app/assets/javascripts/authentication/webauthn/constants.js
+++ b/app/assets/javascripts/authentication/webauthn/constants.js
@@ -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' },
diff --git a/app/assets/javascripts/authentication/webauthn/error.js b/app/assets/javascripts/authentication/webauthn/error.js
index a1a3f861c25..40dbecd8bc9 100644
--- a/app/assets/javascripts/authentication/webauthn/error.js
+++ b/app/assets/javascripts/authentication/webauthn/error.js
@@ -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 __(
diff --git a/app/assets/javascripts/authentication/webauthn/register.js b/app/assets/javascripts/authentication/webauthn/register.js
index 62ebf85abe4..c00d3ede2c1 100644
--- a/app/assets/javascripts/authentication/webauthn/register.js
+++ b/app/assets/javascripts/authentication/webauthn/register.js
@@ -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() {
diff --git a/app/assets/javascripts/authentication/webauthn/util.js b/app/assets/javascripts/authentication/webauthn/util.js
index 4e1409b9ed9..0ff0f0e6a29 100644
--- a/app/assets/javascripts/authentication/webauthn/util.js
+++ b/app/assets/javascripts/authentication/webauthn/util.js
@@ -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
*
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js
index caf82e482ea..c821c18bcb9 100644
--- a/app/assets/javascripts/issues/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js
@@ -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';
diff --git a/app/assets/javascripts/issues/issue.js b/app/assets/javascripts/issues/issue.js
index de1c689e590..b7fd99d8042 100644
--- a/app/assets/javascripts/issues/issue.js
+++ b/app/assets/javascripts/issues/issue.js
@@ -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';
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 35727566fba..f03ecdc4125 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/manual_ordering.js b/app/assets/javascripts/issues/manual_ordering.js
index 1bb53dfd50d..f22062cf048 100644
--- a/app/assets/javascripts/issues/manual_ordering.js
+++ b/app/assets/javascripts/issues/manual_ordering.js
@@ -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';
diff --git a/app/assets/javascripts/issues/related_merge_requests/store/actions.js b/app/assets/javascripts/issues/related_merge_requests/store/actions.js
index 4c81f1d9bc1..ad5b61424dc 100644
--- a/app/assets/javascripts/issues/related_merge_requests/store/actions.js
+++ b/app/assets/javascripts/issues/related_merge_requests/store/actions.js
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index 0be11c5c537..851808b628e 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -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 });
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index b36a456f41b..6e072e12bd9 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index d77cb1fa1c2..ba986bd0020 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue
index c2b7d33c14c..ac64c35bf15 100644
--- a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue
index 1c677c0d9e6..4ec64ef838d 100644
--- a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
index 10b80529a66..5aef4b1b809 100644
--- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
@@ -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';
diff --git a/app/assets/javascripts/issues/show/components/incidents/utils.js b/app/assets/javascripts/issues/show/components/incidents/utils.js
index ce33e91c3b8..2072961ce29 100644
--- a/app/assets/javascripts/issues/show/components/incidents/utils.js
+++ b/app/assets/javascripts/issues/show/components/incidents/utils.js
@@ -1,4 +1,4 @@
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { s__ } from '~/locale';
export const displayAndLogError = (error) =>
diff --git a/app/assets/javascripts/pages/groups/new/components/app.vue b/app/assets/javascripts/pages/groups/new/components/app.vue
index 68f813d9375..38fb96d40b7 100644
--- a/app/assets/javascripts/pages/groups/new/components/app.vue
+++ b/app/assets/javascripts/pages/groups/new/components/app.vue
@@ -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 [
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
index acaee097dc1..b16c5f3da9f 100644
--- a/app/assets/javascripts/pages/groups/new/index.js
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -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,
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 3a293956089..f871cd804e7 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -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';
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 6f7ce7d59c3..b52b7ed4caa 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -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);
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 3a23f9ff49c..430022f9a9b 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -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,
});
}
diff --git a/app/assets/javascripts/projects/new/components/app.vue b/app/assets/javascripts/projects/new/components/app.vue
index 8d883e7126c..251db16d8e3 100644
--- a/app/assets/javascripts/projects/new/components/app.vue
+++ b/app/assets/javascripts/projects/new/components/app.vue
@@ -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);
diff --git a/app/assets/javascripts/projects/new/index.js b/app/assets/javascripts/projects/new/index.js
index 0b190b6a88b..7330874eefe 100644
--- a/app/assets/javascripts/projects/new/index.js
+++ b/app/assets/javascripts/projects/new/index.js
@@ -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 = {
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
index 634b7da3def..93581dbbd40 100644
--- a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
@@ -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"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
index 741395b3193..fff8a95c193 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
@@ -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';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue
index fcabeab1d8a..63ffded9e8e 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_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';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
index 6d681aab3ca..a251035b683 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
@@ -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';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
index 28e65c1185f..c294c23abfc 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
@@ -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';
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index d06bc7b8f98..dd9d2ce66cd 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -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';
diff --git a/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue
index a4fb30a03a1..4c2b082242b 100644
--- a/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue
+++ b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue
@@ -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';
diff --git a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
index b739baad5d7..0cff5edf628 100644
--- a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
+++ b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
@@ -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';
diff --git a/app/graphql/mutations/design_management/update.rb b/app/graphql/mutations/design_management/update.rb
new file mode 100644
index 00000000000..5dc20730a90
--- /dev/null
+++ b/app/graphql/mutations/design_management/update.rb
@@ -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
diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb
index cc4c0e19ec7..be5edd17643 100644
--- a/app/graphql/types/design_management/design_type.rb
+++ b/app/graphql/types/design_management/design_type.rb
@@ -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.
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 108c55f1292..75235405763 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -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
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index d1e35793c64..804709ed072 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -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
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index fbe0f9bfeea..2bdd8b23c62 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -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
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index 396d6a19dea..8bddf154017 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -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
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 0878fbf9a35..a5cbc443fa4 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -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 }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index b493a8d0ce8..f4a5862b2c0 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -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
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 5ab6c40960f..36f4cfec9d4 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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. |
diff --git a/doc/user/application_security/dast/authentication.md b/doc/user/application_security/dast/authentication.md
index 77732ab532c..1205c0f0491 100644
--- a/doc/user/application_security/dast/authentication.md
+++ b/doc/user/application_security/dast/authentication.md
@@ -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`. |
diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md
index 34c0bb59f67..d9938aaa94a 100644
--- a/doc/user/application_security/dast/browser_based.md
+++ b/doc/user/application_security/dast/browser_based.md
@@ -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.
diff --git a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
index 6a41128a8d4..692cee7a27c 100644
--- a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
+++ b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
@@ -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
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index 3a1ecb36b9f..07e0fb4b616 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -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.
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index fa99a1a2bc8..2f52bf8b701 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -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'
diff --git a/lib/generators/batched_background_migration/USAGE b/lib/generators/batched_background_migration/USAGE
index 9e93385fa94..2fc2b2f7b96 100644
--- a/lib/generators/batched_background_migration/USAGE
+++ b/lib/generators/batched_background_migration/USAGE
@@ -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
diff --git a/lib/generators/batched_background_migration/batched_background_migration_generator.rb b/lib/generators/batched_background_migration/batched_background_migration_generator.rb
index 7b62cd47899..96c2b160744 100644
--- a/lib/generators/batched_background_migration/batched_background_migration_generator.rb
+++ b/lib/generators/batched_background_migration/batched_background_migration_generator.rb
@@ -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
diff --git a/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template b/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template
new file mode 100644
index 00000000000..7cc7091191d
--- /dev/null
+++ b/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template
@@ -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:
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index ac6491e8770..525d7064dae 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -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
diff --git a/lib/gitlab/utils/username_and_email_generator.rb b/lib/gitlab/utils/username_and_email_generator.rb
new file mode 100644
index 00000000000..38c9bb7050d
--- /dev/null
+++ b/lib/gitlab/utils/username_and_email_generator.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3651f944e03..d383f77df01 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/qa/qa/page/component/dropdown.rb b/qa/qa/page/component/dropdown.rb
index 01ef3533ff8..767cd40daa2 100644
--- a/qa/qa/page/component/dropdown.rb
+++ b/qa/qa/page/component/dropdown.rb
@@ -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
diff --git a/qa/qa/page/project/monitor/alerts/index.rb b/qa/qa/page/project/monitor/alerts/index.rb
index 1363fb32498..aa4d780b5a2 100644
--- a/qa/qa/page/project/monitor/alerts/index.rb
+++ b/qa/qa/page/project/monitor/alerts/index.rb
@@ -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
diff --git a/qa/qa/page/project/monitor/alerts/show.rb b/qa/qa/page/project/monitor/alerts/show.rb
new file mode 100644
index 00000000000..1f3c52d8988
--- /dev/null
+++ b/qa/qa/page/project/monitor/alerts/show.rb
@@ -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
diff --git a/qa/qa/page/project/sub_menus/monitor.rb b/qa/qa/page/project/sub_menus/monitor.rb
index 27fb58fb146..00774261467 100644
--- a/qa/qa/page/project/sub_menus/monitor.rb
+++ b/qa/qa/page/project/sub_menus/monitor.rb
@@ -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
diff --git a/qa/qa/support/matchers/have_matcher.rb b/qa/qa/support/matchers/have_matcher.rb
index 734a8890536..15daf9f5b39 100644
--- a/qa/qa/support/matchers/have_matcher.rb
+++ b/qa/qa/support/matchers/have_matcher.rb
@@ -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|
diff --git a/scripts/create-pipeline-failure-incident.rb b/scripts/create-pipeline-failure-incident.rb
index 2b86fac680d..bd57abf3740 100755
--- a/scripts/create-pipeline-failure-incident.rb
+++ b/scripts/create-pipeline-failure-incident.rb
@@ -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).
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 3ba106a7dff..cd6d3990309 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -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
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 97b0e6d5c48..67baed5dc91 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -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
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index bb48a9ab95c..8761d8291f8 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -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
diff --git a/spec/frontend/authentication/webauthn/components/registration_spec.js b/spec/frontend/authentication/webauthn/components/registration_spec.js
index 56185c59b5a..1dcf7de4bc7 100644
--- a/spec/frontend/authentication/webauthn/components/registration_spec.js
+++ b/spec/frontend/authentication/webauthn/components/registration_spec.js
@@ -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,
diff --git a/spec/frontend/authentication/webauthn/error_spec.js b/spec/frontend/authentication/webauthn/error_spec.js
index 9b71f77dde2..b979173edc6 100644
--- a/spec/frontend/authentication/webauthn/error_spec.js
+++ b/spec/frontend/authentication/webauthn/error_spec.js
@@ -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);
});
});
diff --git a/spec/frontend/blob_edit/blob_bundle_spec.js b/spec/frontend/blob_edit/blob_bundle_spec.js
index ed42322b0e6..89d507b4ec5 100644
--- a/spec/frontend/blob_edit/blob_bundle_spec.js
+++ b/spec/frontend/blob_edit/blob_bundle_spec.js
@@ -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', () => {
diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
index 37392a9f1f4..85b1d3b1b2f 100644
--- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
@@ -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(),
diff --git a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
index ed4f43c12d8..257c264aac1 100644
--- a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
+++ b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -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');
diff --git a/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js b/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
index 71e54e34372..f9e553c1a6e 100644
--- a/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
@@ -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);
diff --git a/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
index 05ceed931bd..f609c6be41a 100644
--- a/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
+++ b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
@@ -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;
diff --git a/spec/frontend/ci/runner/components/runner_delete_button_spec.js b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
index a97c9902ceb..3cb664f92ae 100644
--- a/spec/frontend/ci/runner/components/runner_delete_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
@@ -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', () => {
diff --git a/spec/frontend/ci/runner/components/runner_jobs_spec.js b/spec/frontend/ci/runner/components/runner_jobs_spec.js
index bdb8a4a31a3..b830774a967 100644
--- a/spec/frontend/ci/runner/components/runner_jobs_spec.js
+++ b/spec/frontend/ci/runner/components/runner_jobs_spec.js
@@ -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;
diff --git a/spec/frontend/ci/runner/components/runner_pause_button_spec.js b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
index dc9925f153b..14d6881dd48 100644
--- a/spec/frontend/ci/runner/components/runner_pause_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
@@ -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', () => {
diff --git a/spec/frontend/ci/runner/components/runner_projects_spec.js b/spec/frontend/ci/runner/components/runner_projects_spec.js
index 17517c4db66..e24084b8836 100644
--- a/spec/frontend/ci/runner/components/runner_projects_spec.js
+++ b/spec/frontend/ci/runner/components/runner_projects_spec.js
@@ -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;
diff --git a/spec/frontend/ci/runner/components/runner_update_form_spec.js b/spec/frontend/ci/runner/components/runner_update_form_spec.js
index a0e51ebf958..0395d538d50 100644
--- a/spec/frontend/ci/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/ci/runner/components/runner_update_form_spec.js
@@ -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');
diff --git a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
index b7d9d3ad23e..7c6b00ee3ef 100644
--- a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
+++ b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
@@ -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'),
diff --git a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
index 2ad31dea774..b1290c25fce 100644
--- a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
+++ b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
@@ -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');
diff --git a/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js b/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
index 03908891cfd..30e49fc7644 100644
--- a/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
+++ b/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
@@ -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();
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
index d02184f143f..24b75ba6805 100644
--- a/spec/frontend/content_editor/extensions/attachment_spec.js
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -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';
diff --git a/spec/frontend/content_editor/extensions/paste_markdown_spec.js b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
index 30e798e8817..8f3a4934e77 100644
--- a/spec/frontend/content_editor/extensions/paste_markdown_spec.js
+++ b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
@@ -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';
diff --git a/spec/frontend/error_tracking/store/actions_spec.js b/spec/frontend/error_tracking/store/actions_spec.js
index 31bfda3ef8b..44db4780ba9 100644
--- a/spec/frontend/error_tracking/store/actions_spec.js
+++ b/spec/frontend/error_tracking/store/actions_spec.js
@@ -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;
diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js
index 66c1eb4573b..0aeb8b19a9e 100644
--- a/spec/frontend/error_tracking/store/details/actions_spec.js
+++ b/spec/frontend/error_tracking/store/details/actions_spec.js
@@ -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(
diff --git a/spec/frontend/error_tracking/store/list/actions_spec.js b/spec/frontend/error_tracking/store/list/actions_spec.js
index e6c2d4a149d..24a26476455 100644
--- a/spec/frontend/error_tracking/store/list/actions_spec.js
+++ b/spec/frontend/error_tracking/store/list/actions_spec.js
@@ -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(
diff --git a/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js b/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
index 38b16dd0c2c..fd011658f95 100644
--- a/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
+++ b/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
@@ -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', () => {
diff --git a/spec/frontend/pages/groups/new/components/app_spec.js b/spec/frontend/pages/groups/new/components/app_spec.js
index a3aa87574b5..b5fbdd764ce 100644
--- a/spec/frontend/pages/groups/new/components/app_spec.js
+++ b/spec/frontend/pages/groups/new/components/app_spec.js
@@ -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);
});
});
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 6519989661f..376575a8acb 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -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';
diff --git a/spec/frontend/projects/new/components/app_spec.js b/spec/frontend/projects/new/components/app_spec.js
index f6edbab3cca..42ef7837fa6 100644
--- a/spec/frontend/projects/new/components/app_spec.js
+++ b/spec/frontend/projects/new/components/app_spec.js
@@ -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' },
+ ]);
+ });
});
diff --git a/spec/graphql/types/design_management/design_at_version_type_spec.rb b/spec/graphql/types/design_management/design_at_version_type_spec.rb
index 4d61ecf62cc..06aefb6fea3 100644
--- a/spec/graphql/types/design_management/design_at_version_type_spec.rb
+++ b/spec/graphql/types/design_management/design_at_version_type_spec.rb
@@ -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) }
diff --git a/spec/graphql/types/design_management/design_type_spec.rb b/spec/graphql/types/design_management/design_type_spec.rb
index 24b007a6b33..a093f785eb7 100644
--- a/spec/graphql/types/design_management/design_type_spec.rb
+++ b/spec/graphql/types/design_management/design_type_spec.rb
@@ -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)) }
diff --git a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
index 957be21f5eb..017548b6fb5 100644
--- a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
+++ b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
@@ -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
diff --git a/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt b/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt
new file mode 100644
index 00000000000..0b350961abd
--- /dev/null
+++ b/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt
@@ -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:
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 09d8ea3cc0a..7bdfa8922d3 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/utils/username_and_email_generator_spec.rb b/spec/lib/gitlab/utils/username_and_email_generator_spec.rb
new file mode 100644
index 00000000000..45df8f08055
--- /dev/null
+++ b/spec/lib/gitlab/utils/username_and_email_generator_spec.rb
@@ -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
diff --git a/spec/policies/design_management/design_policy_spec.rb b/spec/policies/design_management/design_policy_spec.rb
index c62e97dcdb9..1c0270a969e 100644
--- a/spec/policies/design_management/design_policy_spec.rb
+++ b/spec/policies/design_management/design_policy_spec.rb
@@ -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) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 57050c39083..542b92e7eae 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/mutations/design_management/update_spec.rb b/spec/requests/api/graphql/mutations/design_management/update_spec.rb
new file mode 100644
index 00000000000..9558f2538f1
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/design_management/update_spec.rb
@@ -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
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index d81bbc30318..cc1397a7c42 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -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]
-
- expect(access_token.user.email).to end_with("@noreply.#{Gitlab.config.gitlab.host}")
- 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
+ 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
- it 'email is the same as username' do
- response = subject
- access_token = response.payload[:access_token]
+ let(:username_prefix) do
+ "#{resource.class.name.downcase}_#{resource.id}_bot"
+ end
- expect(access_token.user.email).to include(access_token.user.username)
+ let(:email_domain) do
+ "noreply.#{Gitlab.config.gitlab.host}"
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb
new file mode 100644
index 00000000000..a42d1450e4d
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index 40843ccbd15..fad5211fc59 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -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
diff --git a/vendor/project_templates/learn_gitlab_ultimate.tar.gz b/vendor/project_templates/learn_gitlab_ultimate.tar.gz
deleted file mode 100644
index b134ac89815..00000000000
--- a/vendor/project_templates/learn_gitlab_ultimate.tar.gz
+++ /dev/null
Binary files differ