Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-30 21:10:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-30 21:10:47 +0300
commit1abf48c10ca3802db57bad00de3355586a6cc40e (patch)
treed027173e342f44e67528b6429c8f4b0b222efec6
parent2cba3ab8e7b2d6b32be6910d15b53860f2c2140e (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js2
-rw-r--r--app/assets/javascripts/blob/components/blob_content.vue7
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue2
-rw-r--r--app/assets/javascripts/projects/commit/components/form_modal.vue1
-rw-r--r--app/assets/javascripts/releases/components/app_edit_new.vue2
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue7
-rw-r--r--app/assets/javascripts/repository/pages/blob.vue13
-rw-r--r--app/assets/javascripts/sidebar/components/reference/sidebar_reference_widget.vue43
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue53
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss3
-rw-r--r--app/controllers/invites_controller.rb26
-rw-r--r--app/graphql/mutations/base_mutation.rb8
-rw-r--r--app/graphql/resolvers/ci/config_resolver.rb6
-rw-r--r--app/graphql/resolvers/ci/runner_setup_resolver.rb36
-rw-r--r--app/graphql/resolvers/echo_resolver.rb6
-rw-r--r--app/graphql/resolvers/group_milestones_resolver.rb1
-rw-r--r--app/graphql/resolvers/user_starred_projects_resolver.rb4
-rw-r--r--app/graphql/types/boards/board_issue_input_type.rb5
-rw-r--r--app/graphql/types/group_type.rb63
-rw-r--r--app/graphql/types/jira_users_mapping_input_type.rb12
-rw-r--r--app/graphql/types/query_type.rb23
-rw-r--r--app/graphql/types/user_type.rb66
-rw-r--r--app/services/ci/register_job_service.rb4
-rw-r--r--app/views/devise/shared/_sign_in_link.html.haml4
-rw-r--r--app/views/notify/_successful_pipeline.text.erb2
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml4
-rw-r--r--app/views/notify/merge_request_status_email.html.haml2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml4
-rw-r--r--app/views/notify/merge_request_unmergeable_email.html.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.text.haml4
-rw-r--r--app/views/notify/merge_when_pipeline_succeeds_email.text.haml4
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml4
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.html.haml2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.text.erb2
-rw-r--r--app/views/notify/pipeline_failed_email.text.erb2
-rw-r--r--app/views/notify/push_to_merge_request_email.text.haml2
-rw-r--r--app/views/notify/reassigned_merge_request_email.text.erb2
-rw-r--r--app/views/notify/resolved_all_discussions_email.html.haml2
-rw-r--r--app/views/notify/resolved_all_discussions_email.text.erb2
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml4
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml8
-rw-r--r--changelogs/unreleased/232811-remove-deprecated-repo-archive-routes.yml5
-rw-r--r--changelogs/unreleased/325288-send-invited-users-to-correct-page-sign-in-or-sign-up.yml5
-rw-r--r--changelogs/unreleased/fix-mr-source-branch-styling.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-bulk-import-badges.yml5
-rw-r--r--changelogs/unreleased/issue-list-view-deprecated-button.yml5
-rw-r--r--config/known_invalid_graphql_queries.yml1
-rw-r--r--config/routes/repository.rb10
-rw-r--r--doc/api/graphql/reference/index.md13
-rw-r--r--doc/development/testing_guide/end_to_end/index.md77
-rw-r--r--doc/development/usage_ping/index.md24
-rw-r--r--doc/operations/incident_management/img/custom_alert_mapping_v13_10.pngbin38042 -> 0 bytes
-rw-r--r--doc/operations/incident_management/img/custom_alert_mapping_v13_11.pngbin0 -> 16700 bytes
-rw-r--r--doc/operations/incident_management/integrations.md30
-rw-r--r--doc/operations/incident_management/oncall_schedules.md3
-rw-r--r--doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_11.pngbin0 -> 11654 bytes
-rw-r--r--doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_9.pngbin10558 -> 0 bytes
-rw-r--r--doc/user/application_security/threat_monitoring/index.md2
-rw-r--r--doc/user/group/import/index.md4
-rw-r--r--doc/user/group/index.md12
-rw-r--r--doc/user/group/saml_sso/index.md4
-rw-r--r--doc/user/project/integrations/hipchat.md7
-rw-r--r--lib/bulk_imports/clients/http.rb2
-rw-r--r--lib/bulk_imports/common/extractors/rest_extractor.rb45
-rw-r--r--lib/bulk_imports/groups/pipelines/badges_pipeline.rb32
-rw-r--r--lib/bulk_imports/groups/rest/get_badges_query.rb22
-rw-r--r--lib/bulk_imports/importers/group_importer.rb3
-rw-r--r--lib/gitlab/ci/queue/metrics.rb6
-rw-r--r--locale/gitlab.pot20
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/component/commit_modal.rb13
-rw-r--r--qa/qa/page/merge_request/show.rb9
-rw-r--r--qa/qa/resource/merge_request.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick_spec.rb39
-rw-r--r--rubocop/cop/graphql/descriptions.rb5
-rw-r--r--spec/controllers/invites_controller_spec.rb81
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb22
-rw-r--r--spec/features/invites_spec.rb17
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb248
-rw-r--r--spec/features/projects/releases/user_views_edit_release_spec.rb2
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js2
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js1
-rw-r--r--spec/frontend/repository/pages/blob_spec.js25
-rw-r--r--spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js70
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap142
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js19
-rw-r--r--spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb31
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb116
-rw-r--r--spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb22
-rw-r--r--spec/lib/bulk_imports/importers/group_importer_spec.rb1
-rw-r--r--spec/mailers/emails/merge_requests_spec.rb2
-rw-r--r--spec/routing/project_routing_spec.rb20
-rw-r--r--spec/rubocop/cop/graphql/descriptions_spec.rb16
-rw-r--r--spec/services/ci/register_job_service_spec.rb14
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb4
104 files changed, 1173 insertions, 571 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index a55bdf231c0..c2908133fd0 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -120,7 +120,7 @@ export default class ShortcutsIssuable extends Shortcuts {
static copyBranchName() {
// There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed.
- const allCopyBtns = Array.from(document.querySelectorAll('.sidebar-source-branch button'));
+ const allCopyBtns = Array.from(document.querySelectorAll('.js-sidebar-source-branch button'));
// Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated.
diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue
index eb7f45cba6f..f5f06436bcc 100644
--- a/app/assets/javascripts/blob/components/blob_content.vue
+++ b/app/assets/javascripts/blob/components/blob_content.vue
@@ -21,6 +21,11 @@ export default {
default: '',
required: false,
},
+ isRawContent: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
loading: {
type: Boolean,
default: true,
@@ -65,6 +70,8 @@ export default {
v-else
ref="contentViewer"
:content="content"
+ :is-raw-content="isRawContent"
+ :file-name="blob.name"
:type="activeViewer.fileType"
data-qa-selector="file_content"
/>
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 3f249567daa..51da1966630 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -126,7 +126,7 @@ export default {
const diffLines = this.diffFile[INLINE_DIFF_LINES_KEY];
let isAdding = false;
- for (let i = 0, diffLinesLength = diffLines.length - 1; i < diffLinesLength; i += 1) {
+ for (let i = 0, diffLinesLength = diffLines.length - 1; i <= diffLinesLength; i += 1) {
const line = diffLines[i];
if (start.line_code === line.line_code) {
diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue
index 30968d29cde..a5f26416828 100644
--- a/app/assets/javascripts/projects/commit/components/form_modal.vue
+++ b/app/assets/javascripts/projects/commit/components/form_modal.vue
@@ -47,6 +47,7 @@ export default {
{ variant: 'success' },
{ category: 'primary' },
{ 'data-testid': 'submit-commit' },
+ { 'data-qa-selector': 'submit_commit_button' },
],
},
actionCancel: {
diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue
index 93ddbde0dc4..aecd0d6371e 100644
--- a/app/assets/javascripts/releases/components/app_edit_new.vue
+++ b/app/assets/javascripts/releases/components/app_edit_new.vue
@@ -114,7 +114,7 @@ export default {
<gl-sprintf
:message="
__(
- 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}.',
+ 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0.0%{codeEnd}, %{codeStart}v2.1.0-pre%{codeEnd}.',
)
"
>
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index a77c1a41787..58b42fb7859 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -36,6 +36,12 @@ export default {
blobHash: uniqueId(),
};
},
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ },
data() {
return {
projectPath: '',
@@ -85,6 +91,7 @@ export default {
<blob-content
:blob="blobInfo"
:content="blobInfo.rawBlob"
+ :is-raw-content="true"
:active-viewer="viewer"
:loading="false"
/>
diff --git a/app/assets/javascripts/repository/pages/blob.vue b/app/assets/javascripts/repository/pages/blob.vue
index c492f966364..27af398be09 100644
--- a/app/assets/javascripts/repository/pages/blob.vue
+++ b/app/assets/javascripts/repository/pages/blob.vue
@@ -2,16 +2,21 @@
// This file is in progress and behind a feature flag, please see the following issue for more:
// https://gitlab.com/gitlab-org/gitlab/-/issues/323200
-// TODO (follow-up MR): import BlobContentViewer from '../components/blob_content_viewer.vue';
+import BlobContentViewer from '../components/blob_content_viewer.vue';
export default {
components: {
- // TODO (follow-up MR): BlobContentViewer,
+ BlobContentViewer,
+ },
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
},
};
</script>
<template>
- <div></div>
- <!-- TODO (follow-up MR): <blob-content-viewer/> -->
+ <blob-content-viewer :path="path" />
</template>
diff --git a/app/assets/javascripts/sidebar/components/reference/sidebar_reference_widget.vue b/app/assets/javascripts/sidebar/components/reference/sidebar_reference_widget.vue
index d34f0989c88..d07c6e0cbd2 100644
--- a/app/assets/javascripts/sidebar/components/reference/sidebar_reference_widget.vue
+++ b/app/assets/javascripts/sidebar/components/reference/sidebar_reference_widget.vue
@@ -1,17 +1,11 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { referenceQueries } from '~/sidebar/constants';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
export default {
- i18n: {
- copyReference: __('Copy reference'),
- text: __('Reference'),
- },
components: {
- ClipboardButton,
- GlLoadingIcon,
+ CopyableField,
},
inject: ['fullPath', 'iid'],
props: {
@@ -56,31 +50,10 @@ export default {
</script>
<template>
- <div class="sub-block">
- <clipboard-button
- v-if="!isLoading"
- :title="$options.i18n.copyReference"
- :text="reference"
- category="tertiary"
- css-class="sidebar-collapsed-icon dont-change-state"
- tooltip-placement="left"
- />
- <div
- class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mb-2 hide-collapsed"
- >
- <span class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap">
- {{ $options.i18n.text }}: {{ reference }}
- <gl-loading-icon v-if="isLoading" inline :label="$options.i18n.text" />
- </span>
- <clipboard-button
- v-if="!isLoading"
- :title="$options.i18n.copyReference"
- :text="reference"
- size="small"
- category="tertiary"
- css-class="gl-mr-1"
- tooltip-placement="left"
- />
- </div>
- </div>
+ <copyable-field
+ class="sub-block"
+ :is-loading="isLoading"
+ :name="__('Reference')"
+ :value="reference"
+ />
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 043d14e32a2..ae127fa66db 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -151,6 +151,7 @@ export default {
v-gl-tooltip.hover
:title="cherryPickTitle"
size="small"
+ data-qa-selector="cherry_pick_button"
@click="openCherryPickModal"
>
{{ cherryPickLabel }}
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
index db61d0f6b05..9c2ed5abf04 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
@@ -11,6 +11,16 @@ export default {
type: String,
required: true,
},
+ isRawContent: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ fileName: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
mounted() {
eventHub.$emit(SNIPPET_MEASURE_BLOBS_CONTENT);
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
index 5bb31f55e6c..535b659cc5f 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
@@ -1,12 +1,14 @@
<script>
/* eslint-disable vue/no-v-html */
import { GlIcon } from '@gitlab/ui';
+import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { HIGHLIGHT_CLASS_NAME } from './constants';
import ViewerMixin from './mixins';
export default {
components: {
GlIcon,
+ EditorLite,
},
mixins: [ViewerMixin],
inject: ['blobHash'],
@@ -45,27 +47,36 @@ export default {
};
</script>
<template>
- <div
- class="file-content code js-syntax-highlight"
- data-qa-selector="file_content"
- :class="$options.userColorScheme"
- >
- <div class="line-numbers">
- <a
- v-for="line in lineNumbers"
- :id="`L${line}`"
- :key="line"
- class="diff-line-num js-line-number"
- :href="`#LC${line}`"
- :data-line-number="line"
- @click="scrollToLine(`#LC${line}`)"
- >
- <gl-icon :size="12" name="link" />
- {{ line }}
- </a>
- </div>
- <div class="blob-content">
- <pre class="code highlight"><code :data-blob-hash="blobHash" v-html="content"></code></pre>
+ <div>
+ <editor-lite
+ v-if="isRawContent"
+ :value="content"
+ :file-name="fileName"
+ :editor-options="{ readOnly: true }"
+ />
+ <div
+ v-else
+ class="file-content code js-syntax-highlight"
+ data-qa-selector="file_content"
+ :class="$options.userColorScheme"
+ >
+ <div class="line-numbers">
+ <a
+ v-for="line in lineNumbers"
+ :id="`L${line}`"
+ :key="line"
+ class="diff-line-num js-line-number"
+ :href="`#LC${line}`"
+ :data-line-number="line"
+ @click="scrollToLine(`#LC${line}`)"
+ >
+ <gl-icon :size="12" name="link" />
+ {{ line }}
+ </a>
+ </div>
+ <div class="blob-content">
+ <pre class="code highlight"><code :data-blob-hash="blobHash" v-html="content"></code></pre>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 2ab23064353..80b7a9b7d05 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -168,6 +168,9 @@ export default {
false,
);
},
+ suggestionsStartIndex() {
+ return Math.max(this.lines.length - 1, 0);
+ },
},
watch: {
isSubmitting(isSubmitting) {
@@ -260,7 +263,7 @@ export default {
:line-content="lineContent"
:can-suggest="canSuggest"
:show-suggest-popover="showSuggestPopover"
- :suggestion-start-index="lines.length - 1"
+ :suggestion-start-index="suggestionsStartIndex"
@preview-markdown="showPreviewTab"
@write-markdown="showWriteTab"
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
index 833bfdae1ab..bbc7e6e7a6e 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
@@ -67,7 +67,7 @@ export default {
<div>
<clipboard-button
v-if="!isLoading"
- css-class="sidebar-collapsed-icon dont-change-state"
+ css-class="sidebar-collapsed-icon dont-change-state gl-rounded-0! gl-hover-bg-transparent"
v-bind="clipboardProps"
/>
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 0750f0f3703..ee3ecf46f60 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -225,8 +225,7 @@
}
}
- .cross-project-reference,
- .sidebar-mr-source-branch {
+ .cross-project-reference {
color: inherit;
span {
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 08a23dc8927..0eb08d2d0ad 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -91,15 +91,29 @@ class InvitesController < ApplicationController
def authenticate_user!
return if current_user
- notice = ["To accept this invitation, sign in"]
- notice << "or create an account" if Gitlab::CurrentSettings.allow_signup?
- notice = notice.join(' ') + "."
+ store_location_for :user, request.fullpath
- redirect_params = member ? { invite_email: member.invite_email } : {}
+ if user_sign_up?
+ redirect_to new_user_registration_path(invite_email: member.invite_email), notice: _("To accept this invitation, create an account or sign in.")
+ else
+ redirect_to new_user_session_path(sign_in_redirect_params), notice: sign_in_notice
+ end
+ end
- store_location_for :user, request.fullpath
+ def sign_in_redirect_params
+ member ? { invite_email: member.invite_email } : {}
+ end
+
+ def user_sign_up?
+ Gitlab::CurrentSettings.allow_signup? && member && !User.find_by_any_email(member.invite_email)
+ end
- redirect_to new_user_session_path(redirect_params), notice: notice
+ def sign_in_notice
+ if Gitlab::CurrentSettings.allow_signup?
+ _("To accept this invitation, sign in or create an account.")
+ else
+ _("To accept this invitation, sign in.")
+ end
end
def invite_details
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index 6fa1aa943cc..1f18a37fcb9 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -29,11 +29,9 @@ module Mutations
end
def ready?(**args)
- if Gitlab::Database.read_only?
- raise_resource_not_available_error! ERROR_MESSAGE
- else
- true
- end
+ raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.read_only?
+
+ true
end
def load_application_object(argument, lookup_as_type, id, context)
diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb
index f8670649e48..252c9d3acf0 100644
--- a/app/graphql/resolvers/ci/config_resolver.rb
+++ b/app/graphql/resolvers/ci/config_resolver.rb
@@ -7,6 +7,10 @@ module Resolvers
include ResolvesProject
type Types::Ci::Config::ConfigType, null: true
+ description <<~MD
+ Linted and processed contents of a CI config.
+ Should not be requested more than once per request.
+ MD
authorize :read_pipeline
@@ -55,7 +59,7 @@ module Resolvers
name: job[:name],
stage: job[:stage],
group_name: CommitStatus.new(name: job[:name]).group_name,
- needs: job.dig(:needs) || [],
+ needs: job[:needs] || [],
allow_failure: job[:allow_failure],
before_script: job[:before_script],
script: job[:script],
diff --git a/app/graphql/resolvers/ci/runner_setup_resolver.rb b/app/graphql/resolvers/ci/runner_setup_resolver.rb
index 7a7663ea63d..4189a081e3c 100644
--- a/app/graphql/resolvers/ci/runner_setup_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_setup_resolver.rb
@@ -3,24 +3,30 @@
module Resolvers
module Ci
class RunnerSetupResolver < BaseResolver
+ ACCESS_DENIED = 'User is not authorized to register a runner for the specified resource!'
+
type Types::Ci::RunnerSetupType, null: true
description 'Runner setup instructions.'
- argument :platform, GraphQL::STRING_TYPE,
- required: true,
- description: 'Platform to generate the instructions for.'
+ argument :platform,
+ type: GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Platform to generate the instructions for.'
- argument :architecture, GraphQL::STRING_TYPE,
- required: true,
- description: 'Architecture to generate the instructions for.'
+ argument :architecture,
+ type: GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Architecture to generate the instructions for.'
- argument :project_id, ::Types::GlobalIDType[::Project],
- required: false,
- description: 'Project to register the runner for.'
+ argument :project_id,
+ type: ::Types::GlobalIDType[::Project],
+ required: false,
+ description: 'Project to register the runner for.'
- argument :group_id, ::Types::GlobalIDType[::Group],
- required: false,
- description: 'Group to register the runner for.'
+ argument :group_id,
+ type: ::Types::GlobalIDType[::Group],
+ required: false,
+ description: 'Group to register the runner for.'
def resolve(platform:, architecture:, **args)
instructions = Gitlab::Ci::RunnerInstructions.new(
@@ -35,11 +41,15 @@ module Resolvers
register_instructions: instructions.register_command
}
ensure
- raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'User is not authorized to register a runner for the specified resource!' if instructions.errors.include?('Gitlab::Access::AccessDeniedError')
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, ACCESS_DENIED if access_denied?(instructions)
end
private
+ def access_denied?(instructions)
+ instructions.errors.include?('Gitlab::Access::AccessDeniedError')
+ end
+
def other_install_instructions(platform)
Gitlab::Ci::RunnerInstructions::OTHER_ENVIRONMENTS[platform.to_sym][:installation_instructions_url]
end
diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb
index 0c7dad622cf..a09b0a1fd87 100644
--- a/app/graphql/resolvers/echo_resolver.rb
+++ b/app/graphql/resolvers/echo_resolver.rb
@@ -5,8 +5,10 @@ module Resolvers
type ::GraphQL::STRING_TYPE, null: false
description 'Testing endpoint to validate the API with'
- argument :text, GraphQL::STRING_TYPE, required: true,
- description: 'Text to echo back.'
+ argument :text,
+ type: GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Text to echo back.'
def resolve(text:)
username = current_user&.username
diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb
index 640c320dcfa..31280b36278 100644
--- a/app/graphql/resolvers/group_milestones_resolver.rb
+++ b/app/graphql/resolvers/group_milestones_resolver.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver)
module Resolvers
class GroupMilestonesResolver < MilestonesResolver
diff --git a/app/graphql/resolvers/user_starred_projects_resolver.rb b/app/graphql/resolvers/user_starred_projects_resolver.rb
index ff5e4ca82e5..a8abe759f27 100644
--- a/app/graphql/resolvers/user_starred_projects_resolver.rb
+++ b/app/graphql/resolvers/user_starred_projects_resolver.rb
@@ -5,8 +5,8 @@ module Resolvers
type Types::ProjectType.connection_type, null: true
argument :search, GraphQL::STRING_TYPE,
- required: false,
- description: 'Search query.'
+ required: false,
+ description: 'Search query.'
alias_method :user, :object
diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb
index 9cc0f484a16..3404f24ea12 100644
--- a/app/graphql/types/boards/board_issue_input_type.rb
+++ b/app/graphql/types/boards/board_issue_input_type.rb
@@ -10,7 +10,10 @@ module Types
argument :not, NegatedBoardIssueInputType,
required: false,
- description: 'List of negated params. Warning: this argument is experimental and a subject to change in future.'
+ description: <<~MD
+ List of negated arguments.
+ Warning: this argument is experimental and a subject to change in future.
+ MD
argument :search, GraphQL::STRING_TYPE,
required: false,
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index e1f45a2f2bd..a44281b2bdf 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -8,39 +8,65 @@ module Types
expose_permissions Types::PermissionTypes::Group
- field :web_url, GraphQL::STRING_TYPE, null: false,
+ field :web_url,
+ type: GraphQL::STRING_TYPE,
+ null: false,
description: 'Web URL of the group.'
- field :avatar_url, GraphQL::STRING_TYPE, null: true,
+ field :avatar_url,
+ type: GraphQL::STRING_TYPE,
+ null: true,
description: 'Avatar URL of the group.'
- field :custom_emoji, Types::CustomEmojiType.connection_type, null: true,
+ field :custom_emoji,
+ type: Types::CustomEmojiType.connection_type,
+ null: true,
description: 'Custom emoji within this namespace.',
feature_flag: :custom_emoji
- field :share_with_group_lock, GraphQL::BOOLEAN_TYPE, null: true,
+ field :share_with_group_lock,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: true,
description: 'Indicates if sharing a project with another group within this group is prevented.'
- field :project_creation_level, GraphQL::STRING_TYPE, null: true, method: :project_creation_level_str,
+ field :project_creation_level,
+ type: GraphQL::STRING_TYPE,
+ null: true,
+ method: :project_creation_level_str,
description: 'The permission level required to create projects in the group.'
- field :subgroup_creation_level, GraphQL::STRING_TYPE, null: true, method: :subgroup_creation_level_str,
+ field :subgroup_creation_level,
+ type: GraphQL::STRING_TYPE,
+ null: true,
+ method: :subgroup_creation_level_str,
description: 'The permission level required to create subgroups within the group.'
- field :require_two_factor_authentication, GraphQL::BOOLEAN_TYPE, null: true,
+ field :require_two_factor_authentication,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: true,
description: 'Indicates if all users in this group are required to set up two-factor authentication.'
- field :two_factor_grace_period, GraphQL::INT_TYPE, null: true,
+ field :two_factor_grace_period,
+ type: GraphQL::INT_TYPE,
+ null: true,
description: 'Time before two-factor authentication is enforced.'
- field :auto_devops_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ field :auto_devops_enabled,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: true,
description: 'Indicates whether Auto DevOps is enabled for all projects within this group.'
- field :emails_disabled, GraphQL::BOOLEAN_TYPE, null: true,
+ field :emails_disabled,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: true,
description: 'Indicates if a group has email notifications disabled.'
- field :mentions_disabled, GraphQL::BOOLEAN_TYPE, null: true,
+ field :mentions_disabled,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: true,
description: 'Indicates if a group is disabled from getting mentioned.'
- field :parent, GroupType, null: true,
+ field :parent,
+ type: GroupType,
+ null: true,
description: 'Parent group.'
field :issues,
@@ -55,7 +81,7 @@ module Types
description: 'Merge requests for projects in this group.',
resolver: Resolvers::GroupMergeRequestsResolver
- field :milestones, Types::MilestoneType.connection_type, null: true,
+ field :milestones,
description: 'Milestones of the group.',
resolver: Resolvers::GroupMilestonesResolver
@@ -76,9 +102,10 @@ module Types
Types::LabelType,
null: true,
description: 'A label available on this group.' do
- argument :title, GraphQL::STRING_TYPE,
- required: true,
- description: 'Title of the label.'
+ argument :title,
+ type: GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Title of the label.'
end
field :group_members,
@@ -92,7 +119,9 @@ module Types
resolver: Resolvers::ContainerRepositoriesResolver,
authorize: :read_container_image
- field :container_repositories_count, GraphQL::INT_TYPE, null: false,
+ field :container_repositories_count,
+ type: GraphQL::INT_TYPE,
+ null: false,
description: 'Number of container repositories in the group.'
field :packages,
diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb
index 61e3240ecf3..32640b9cb17 100644
--- a/app/graphql/types/jira_users_mapping_input_type.rb
+++ b/app/graphql/types/jira_users_mapping_input_type.rb
@@ -5,12 +5,12 @@ module Types
graphql_name 'JiraUsersMappingInputType'
argument :jira_account_id,
- GraphQL::STRING_TYPE,
- required: true,
- description: 'Jira account ID of the user.'
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Jira account ID of the user.'
argument :gitlab_id,
- GraphQL::INT_TYPE,
- required: false,
- description: 'Id of the GitLab user.'
+ GraphQL::INT_TYPE,
+ required: false,
+ description: 'ID of the GitLab user.'
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 84dc0e11fbc..8af0db644dd 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -55,7 +55,10 @@ module Types
field :container_repository, Types::ContainerRepositoryDetailsType,
null: true,
description: 'Find a container repository.' do
- argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository.'
+ argument :id,
+ type: ::Types::GlobalIDType[::ContainerRepository],
+ required: true,
+ description: 'The global ID of the container repository.'
end
field :package,
@@ -72,9 +75,7 @@ module Types
description: 'Find users.',
resolver: Resolvers::UsersResolver
- field :echo, GraphQL::STRING_TYPE, null: false,
- description: 'Text to echo back.',
- resolver: Resolvers::EchoResolver
+ field :echo, resolver: Resolvers::EchoResolver
field :issue, Types::IssueType,
null: true,
@@ -102,18 +103,10 @@ module Types
null: true,
description: 'CI related settings that apply to the entire instance.'
- field :runner_platforms, Types::Ci::RunnerPlatformType.connection_type,
- null: true, description: 'Supported runner platforms.',
- resolver: Resolvers::Ci::RunnerPlatformsResolver
+ field :runner_platforms, resolver: Resolvers::Ci::RunnerPlatformsResolver
+ field :runner_setup, resolver: Resolvers::Ci::RunnerSetupResolver
- field :runner_setup, Types::Ci::RunnerSetupType, null: true,
- description: 'Get runner setup instructions.',
- resolver: Resolvers::Ci::RunnerSetupResolver
-
- field :ci_config, Types::Ci::Config::ConfigType, null: true,
- description: 'Get linted and processed contents of a CI config. Should not be requested more than once per request.',
- resolver: Resolvers::Ci::ConfigResolver,
- complexity: 126 # AUTHENTICATED_COMPLEXITY / 2 + 1
+ field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_COMPLEXITY / 2 + 1
def design_management
DesignManagementObject.new(nil)
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 4e080e814c0..372bbeb4143 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -11,44 +11,72 @@ module Types
expose_permissions Types::PermissionTypes::User
- field :id, GraphQL::ID_TYPE, null: false,
+ field :id,
+ type: GraphQL::ID_TYPE,
+ null: false,
description: 'ID of the user.'
- field :bot, GraphQL::BOOLEAN_TYPE, null: false,
+ field :bot,
+ type: GraphQL::BOOLEAN_TYPE,
+ null: false,
description: 'Indicates if the user is a bot.',
method: :bot?
- field :username, GraphQL::STRING_TYPE, null: false,
+ field :username,
+ type: GraphQL::STRING_TYPE,
+ null: false,
description: 'Username of the user. Unique within this instance of GitLab.'
- field :name, GraphQL::STRING_TYPE, null: false,
+ field :name,
+ type: GraphQL::STRING_TYPE,
+ null: false,
description: 'Human-readable name of the user.'
- field :state, Types::UserStateEnum, null: false,
+ field :state,
+ type: Types::UserStateEnum,
+ null: false,
description: 'State of the user.'
- field :email, GraphQL::STRING_TYPE, null: true,
+ field :email,
+ type: GraphQL::STRING_TYPE,
+ null: true,
description: 'User email.', method: :public_email,
deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' }
- field :public_email, GraphQL::STRING_TYPE, null: true,
+ field :public_email,
+ type: GraphQL::STRING_TYPE,
+ null: true,
description: "User's public email."
- field :avatar_url, GraphQL::STRING_TYPE, null: true,
+ field :avatar_url,
+ type: GraphQL::STRING_TYPE,
+ null: true,
description: "URL of the user's avatar."
- field :web_url, GraphQL::STRING_TYPE, null: false,
+ field :web_url,
+ type: GraphQL::STRING_TYPE,
+ null: false,
description: 'Web URL of the user.'
- field :web_path, GraphQL::STRING_TYPE, null: false,
+ field :web_path,
+ type: GraphQL::STRING_TYPE,
+ null: false,
description: 'Web path of the user.'
- field :todos, Types::TodoType.connection_type, null: false,
+ field :todos,
resolver: Resolvers::TodoResolver,
description: 'To-do items of the user.'
- field :group_memberships, Types::GroupMemberType.connection_type, null: true,
+ field :group_memberships,
+ type: Types::GroupMemberType.connection_type,
+ null: true,
description: 'Group memberships of the user.'
- field :group_count, GraphQL::INT_TYPE, null: true,
+ field :group_count,
resolver: Resolvers::Users::GroupCountResolver,
description: 'Group count for the user.',
feature_flag: :user_group_counts
- field :status, Types::UserStatusType, null: true,
- description: 'User status.'
- field :location, ::GraphQL::STRING_TYPE, null: true,
+ field :status,
+ type: Types::UserStatusType,
+ null: true,
+ description: 'User status.'
+ field :location,
+ type: ::GraphQL::STRING_TYPE,
+ null: true,
description: 'The location of the user.'
- field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
+ field :project_memberships,
+ type: Types::ProjectMemberType.connection_type,
+ null: true,
description: 'Project memberships of the user.'
- field :starred_projects, Types::ProjectType.connection_type, null: true,
+ field :starred_projects,
description: 'Projects starred by the user.',
resolver: Resolvers::UserStarredProjectsResolver
@@ -64,8 +92,6 @@ module Types
description: 'Merge Requests assigned to the user for review.'
field :snippets,
- Types::SnippetType.connection_type,
- null: true,
description: 'Snippets authored by the user.',
resolver: Resolvers::Users::SnippetsResolver
field :callouts,
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index e7fb20631e3..90341b26fd6 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -24,7 +24,7 @@ module Ci
def execute(params = {})
@metrics.increment_queue_operation(:queue_attempt)
- @metrics.observe_queue_time(:process) do
+ @metrics.observe_queue_time(:process, @runner.runner_type) do
process_queue(params)
end
end
@@ -128,7 +128,7 @@ module Ci
# rubocop: enable CodeReuse/ActiveRecord
def retrieve_queue(queue_query_proc)
- @metrics.observe_queue_time(:retrieve) do
+ @metrics.observe_queue_time(:retrieve, @runner.runner_type) do
queue_query_proc.call
end
end
diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml
index 9a7d8a0a160..41a67485f14 100644
--- a/app/views/devise/shared/_sign_in_link.html.haml
+++ b/app/views/devise/shared/_sign_in_link.html.haml
@@ -1,4 +1,6 @@
%p.text-center
%span.light
Already have login and password?
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ - path_params = { redirect_to_referer: 'yes' }
+ - path_params[:invite_email] = @invite_email if @invite_email.present?
+ = link_to "Sign in", new_session_path(:user, path_params)
diff --git a/app/views/notify/_successful_pipeline.text.erb b/app/views/notify/_successful_pipeline.text.erb
index 628976e2dda..5798a2346fa 100644
--- a/app/views/notify/_successful_pipeline.text.erb
+++ b/app/views/notify/_successful_pipeline.text.erb
@@ -3,7 +3,7 @@
Project: <%= @project.name %> ( <%= project_url(@project) %> )
Branch: <%= @pipeline.source_ref %> ( <%= commits_url(@pipeline) %> )
<% if @merge_request -%>
-Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> )
+Merge request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> )
<% end -%>
Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> )
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 6caa0e59e8f..749584a7044 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,3 +1,3 @@
%p
- Merge Request #{merge_request_reference_link(@merge_request)}
+ Merge request #{merge_request_reference_link(@merge_request)}
was closed by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 28766f861d9..942e771261a 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
-Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
+Merge request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
-Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index a15c5a752d4..49f2366c594 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,3 +1,3 @@
%p
- Merge Request #{merge_request_reference_link(@merge_request)}
+ Merge request #{merge_request_reference_link(@merge_request)}
was #{@mr_status} by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index ab663b65199..1a8f848218c 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
-Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
+Merge request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
-Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_unmergeable_email.html.haml b/app/views/notify/merge_request_unmergeable_email.html.haml
index ee459a26551..fddf9eaf95a 100644
--- a/app/views/notify/merge_request_unmergeable_email.html.haml
+++ b/app/views/notify/merge_request_unmergeable_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{merge_request_reference_link(@merge_request)} can no longer be merged due to conflict.
+ Merge request #{merge_request_reference_link(@merge_request)} can no longer be merged due to conflict.
diff --git a/app/views/notify/merge_request_unmergeable_email.text.haml b/app/views/notify/merge_request_unmergeable_email.text.haml
index a23d083747c..3db5f21e6c2 100644
--- a/app/views/notify/merge_request_unmergeable_email.text.haml
+++ b/app/views/notify/merge_request_unmergeable_email.text.haml
@@ -1,6 +1,6 @@
-Merge Request #{@merge_request.to_reference} can no longer be merged due to conflict.
+Merge request #{@merge_request.to_reference} can no longer be merged due to conflict.
-Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_when_pipeline_succeeds_email.text.haml b/app/views/notify/merge_when_pipeline_succeeds_email.text.haml
index de29dda6c71..568ca995e04 100644
--- a/app/views/notify/merge_when_pipeline_succeeds_email.text.haml
+++ b/app/views/notify/merge_when_pipeline_succeeds_email.text.haml
@@ -1,6 +1,6 @@
-Merge Request #{@merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{sanitize_name(@mwps_set_by.name)}
+Merge request #{@merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{sanitize_name(@mwps_set_by.name)}
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index c84c0d1d14b..f0dadd9ce91 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{merge_request_reference_link(@merge_request)} was merged
+ Merge request #{merge_request_reference_link(@merge_request)} was merged
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index a8e07fa8d1c..91f920dec21 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
-Merge Request #{@merge_request.to_reference} was merged
+Merge request #{@merge_request.to_reference} was merged
-Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml
index ddcf287e501..a28d944529f 100644
--- a/app/views/notify/new_mention_in_merge_request_email.html.haml
+++ b/app/views/notify/new_mention_in_merge_request_email.html.haml
@@ -1,4 +1,4 @@
%p
- You have been mentioned in Merge Request #{merge_request_reference_link(@merge_request)}
+ You have been mentioned in merge request #{merge_request_reference_link(@merge_request)}
= render template: 'notify/new_merge_request_email'
diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb
index 0121006852c..9ba86f17ef6 100644
--- a/app/views/notify/new_mention_in_merge_request_email.text.erb
+++ b/app/views/notify/new_mention_in_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-You have been mentioned in Merge Request <%= @merge_request.to_reference %>
+You have been mentioned in merge request <%= @merge_request.to_reference %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb
index 2deca375db1..1fe7d554bc3 100644
--- a/app/views/notify/pipeline_failed_email.text.erb
+++ b/app/views/notify/pipeline_failed_email.text.erb
@@ -3,7 +3,7 @@ Pipeline #<%= @pipeline.id %> has failed!
Project: <%= @project.name %> ( <%= project_url(@project) %> )
Branch: <%= @pipeline.source_ref %> ( <%= commits_url(@pipeline) %> )
<% if @merge_request -%>
-Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> )
+Merge request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> )
<% end -%>
Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> )
diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml
index 5c2005a47e5..8ab9cb0fb8d 100644
--- a/app/views/notify/push_to_merge_request_email.text.haml
+++ b/app/views/notify/push_to_merge_request_email.text.haml
@@ -1,6 +1,6 @@
#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference}
-Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
\
- if @existing_commits.any?
- count = @existing_commits.size
diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb
index 2b51f48db3a..888b995b67c 100644
--- a/app/views/notify/reassigned_merge_request_email.text.erb
+++ b/app/views/notify/reassigned_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-Reassigned Merge Request <%= @merge_request.iid %>
+Reassigned merge request <%= @merge_request.iid %>
<%= url_for([@merge_request.project, @merge_request, { only_path: false }]) %>
diff --git a/app/views/notify/resolved_all_discussions_email.html.haml b/app/views/notify/resolved_all_discussions_email.html.haml
index 0b3c56c9bd1..209415e0aee 100644
--- a/app/views/notify/resolved_all_discussions_email.html.haml
+++ b/app/views/notify/resolved_all_discussions_email.html.haml
@@ -1,3 +1,3 @@
%p
- All discussions on Merge Request #{merge_request_reference_link(@merge_request)}
+ All discussions on merge request #{merge_request_reference_link(@merge_request)}
were resolved by #{sanitize_name(@resolved_by.name)}
diff --git a/app/views/notify/resolved_all_discussions_email.text.erb b/app/views/notify/resolved_all_discussions_email.text.erb
index c4b36bfe1a8..226ea3fb445 100644
--- a/app/views/notify/resolved_all_discussions_email.text.erb
+++ b/app/views/notify/resolved_all_discussions_email.text.erb
@@ -1,3 +1,3 @@
-All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= sanitize_name(@resolved_by.name) %>
+All discussions on merge request <%= @merge_request.to_reference %> were resolved by <%= sanitize_name(@resolved_by.name) %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 0bddffa881a..917ef666e85 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -18,14 +18,14 @@
- elsif @project && can?(current_user, :read_issue, @project)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
- - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn gl-button btn-confirm', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4
= messages.dig(:issuable, :title)
%p.text-left
= messages.dig(:issuable, :body).html_safe % { issues_link: issues_link }
- = new_issue_link
+ - if show_new_issue_link?(@project)
+ = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn gl-button btn-confirm', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
- else
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index b6fc1f4955b..bbbb728d048 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -6,7 +6,7 @@
= form_tag [:bulk_update, @project, type], method: :post, class: "bulk-update" do
.block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.float-left
- = button_tag _('Update all'), class: "gl-button btn update-selected-issues btn-info", disabled: true
+ = button_tag _('Update all'), class: "gl-button btn update-selected-issues btn-confirm", disabled: true
= button_tag _('Cancel'), class: "gl-button btn btn-default js-bulk-update-menu-hide float-right"
- if params[:state] != 'merged'
.block
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 7a1bb9dd3ca..6a90698f1de 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -133,12 +133,12 @@
.block.with-sub-blocks
#js-reference-entry-point
- if issuable_type == 'merge_request'
- .sidebar-source-branch.sub-block
+ .sub-block.js-sidebar-source-branch
.sidebar-collapsed-icon.dont-change-state
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
- .sidebar-mr-source-branch.hide-collapsed
- %span
- = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<cite class='ref-name' title='#{source_branch}'>".html_safe, source_branch_close: "</cite>".html_safe, source_branch: source_branch }
+ .gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed
+ %span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-white-space-nowrap
+ = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' title='#{source_branch}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: source_branch }
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
- if show_forwarding_email
diff --git a/changelogs/unreleased/232811-remove-deprecated-repo-archive-routes.yml b/changelogs/unreleased/232811-remove-deprecated-repo-archive-routes.yml
new file mode 100644
index 00000000000..87d30f99cfb
--- /dev/null
+++ b/changelogs/unreleased/232811-remove-deprecated-repo-archive-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecated repository archive routes
+merge_request: 57236
+author:
+type: removed
diff --git a/changelogs/unreleased/325288-send-invited-users-to-correct-page-sign-in-or-sign-up.yml b/changelogs/unreleased/325288-send-invited-users-to-correct-page-sign-in-or-sign-up.yml
new file mode 100644
index 00000000000..f8727a680e3
--- /dev/null
+++ b/changelogs/unreleased/325288-send-invited-users-to-correct-page-sign-in-or-sign-up.yml
@@ -0,0 +1,5 @@
+---
+title: Send invited users to sign up instead of sign in when possible
+merge_request: 57240
+author:
+type: other
diff --git a/changelogs/unreleased/fix-mr-source-branch-styling.yml b/changelogs/unreleased/fix-mr-source-branch-styling.yml
new file mode 100644
index 00000000000..258a3be1262
--- /dev/null
+++ b/changelogs/unreleased/fix-mr-source-branch-styling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix MR Source Branch styling
+merge_request: 57662
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-bulk-import-badges.yml b/changelogs/unreleased/georgekoltsov-bulk-import-badges.yml
new file mode 100644
index 00000000000..6086e6f62a7
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-bulk-import-badges.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate group badges when using Bulk Import
+merge_request: 56357
+author:
+type: added
diff --git a/changelogs/unreleased/issue-list-view-deprecated-button.yml b/changelogs/unreleased/issue-list-view-deprecated-button.yml
new file mode 100644
index 00000000000..f37c935d4af
--- /dev/null
+++ b/changelogs/unreleased/issue-list-view-deprecated-button.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecated info button from issue list view
+merge_request: 57762
+author:
+type: other
diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml
index 26188d6068f..f9929418a3d 100644
--- a/config/known_invalid_graphql_queries.yml
+++ b/config/known_invalid_graphql_queries.yml
@@ -5,3 +5,4 @@ filenames:
- ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/create_api_fuzzing_configuration.mutation.graphql
- ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
- app/assets/javascripts/repository/queries/blob_info.query.graphql
+ - ee/app/assets/javascripts/security_configuration/graphql/configure_dependency_scanning.mutation.graphql
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 61a407d5a35..58de3d29bb0 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -2,15 +2,7 @@
# All routing related to repository browsing
-resource :repository, only: [:create] do
- member do
- # deprecated since GitLab 9.5
- get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative', defaults: { append_sha: true }
-
- # deprecated since GitLab 10.7
- get ':id/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+/ }, action: 'archive', as: 'archive_deprecated', defaults: { append_sha: true }
- end
-end
+resource :repository, only: [:create]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ab809e28acb..263cd4391a7 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -38,7 +38,8 @@ Returns [`CiApplicationSettings`](#ciapplicationsettings).
### `ciConfig`
-Get linted and processed contents of a CI config. Should not be requested more than once per request.
+Linted and processed contents of a CI config.
+Should not be requested more than once per request.
Returns [`CiConfig`](#ciconfig).
@@ -93,7 +94,7 @@ Returns [`DevopsAdoptionSegmentConnection`](#devopsadoptionsegmentconnection).
### `echo`
-Text to echo back.
+Testing endpoint to validate the API with.
Returns [`String!`](#string).
@@ -271,7 +272,7 @@ Returns [`RunnerPlatformConnection`](#runnerplatformconnection).
### `runnerSetup`
-Get runner setup instructions.
+Runner setup instructions.
Returns [`RunnerSetup`](#runnersetup).
@@ -398,7 +399,9 @@ Returns [`VulnerabilitiesCountByDayConnection`](#vulnerabilitiescountbydayconnec
### `vulnerabilitiesCountByDayAndSeverity`
-Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.
+Number of vulnerabilities per severity level, per day, for the projects on the
+current user's instance security dashboard.
+.
WARNING:
**Deprecated** in 13.3.
@@ -6509,7 +6512,7 @@ Representation of a GitLab user.
| `starredProjects` | [`ProjectConnection`](#projectconnection) | Projects starred by the user. |
| `state` | [`UserState!`](#userstate) | State of the user. |
| `status` | [`UserStatus`](#userstatus) | User status. |
-| `todos` | [`TodoConnection!`](#todoconnection) | To-do items of the user. |
+| `todos` | [`TodoConnection`](#todoconnection) | To-do items of the user. |
| `userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| `username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
| `webPath` | [`String!`](#string) | Web path of the user. |
diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
index d981f9bcbba..6ac2f8dfa7b 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -22,13 +22,13 @@ a black-box testing framework for the API and the UI.
### Testing nightly builds
We run scheduled pipelines each night to test nightly builds created by Omnibus.
-You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/nightly/pipelines`
+You can find these pipelines at <https://gitlab.com/gitlab-org/quality/nightly/pipelines>
(need Developer access permissions). Results are reported in the `#qa-nightly` Slack channel.
### Testing staging
We run scheduled pipelines each night to test staging.
-You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/staging/pipelines`
+You can find these pipelines at <https://gitlab.com/gitlab-org/quality/staging/pipelines>
(need Developer access permissions). Results are reported in the `#qa-staging` Slack channel.
### Testing code in merge requests
@@ -36,64 +36,63 @@ You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/s
#### Using the `package-and-qa` job
It is possible to run end-to-end tests for a merge request, eventually being run in
-a pipeline in the [`gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror/) project,
-by triggering the `package-and-qa` manual action in the `test` stage (not
+a pipeline in the [`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror) project,
+by triggering the `package-and-qa` manual action in the `qa` stage (not
available for forks).
-**This runs end-to-end tests against a custom CE and EE (with an Ultimate license)
-Omnibus package built from your merge request's changes.**
+**This runs end-to-end tests against a custom EE (with an Ultimate license)
+Docker image built from your merge request's changes.**
-Manual action that starts end-to-end tests is also available in merge requests
-in [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab).
-
-Below you can read more about how to use it and how does it work.
+Manual action that starts end-to-end tests is also available
+in [`gitlab-org/omnibus-gitlab` merge requests](https://docs.gitlab.com/omnibus/build/team_member_docs.html#i-have-an-mr-in-the-omnibus-gitlab-project-and-want-a-package-or-docker-image-to-test-it).
#### How does it work?
-Currently, we are using _multi-project pipeline_-like approach to run QA
+Currently, we are using _multi-project pipeline_-like approach to run end-to-end
pipelines.
```mermaid
-graph LR
- A1 -.->|1. Triggers an omnibus-gitlab-mirror pipeline and wait for it to be done| A2
- B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa-mirror pipeline and wait for it to be done| A3
-
-subgraph "gitlab-foss/gitlab pipeline"
- A1[`test` stage<br>`package-and-qa` job]
+graph TB
+ A1 -.->|once done, can be triggered| A2
+ A2 -.->|1. Triggers an `omnibus-gitlab-mirror` pipeline<br>and wait for it to be done| B1
+ B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a `gitlab-qa-mirror` pipeline<br>and wait for it to be done| C1
+
+subgraph "`gitlab-org/gitlab` pipeline"
+ A1[`build-images` stage<br>`build-qa-image` and `build-assets-image` jobs]
+ A2[`qa` stage<br>`package-and-qa` job]
end
-subgraph "omnibus-gitlab pipeline"
- A2[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2
+subgraph "`gitlab-org/build/omnibus-gitlab-mirror` pipeline"
+ B1[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2
end
-subgraph "gitlab-qa-mirror pipeline"
- A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br>and post the result on the original commit tested| A1
+subgraph "`gitlab-org/gitlab-qa-mirror` pipeline"
+ C1>End-to-end jobs run]
end
```
-1. Developer triggers a manual action, that can be found in GitLab merge
- requests. This starts a chain of pipelines in multiple projects.
-
-1. The script being executed triggers a pipeline in
- [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror)
- and waits for the resulting status. We call this a _status attribution_.
-
-1. GitLab packages are being built in the [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror)
- pipeline. Packages are then pushed to its Container Registry.
+1. In the [`gitlab-org/gitlab` pipeline](https://gitlab.com/gitlab-org/gitlab):
+ 1. Developer triggers the `package-and-qa` manual action (available once the `build-qa-image` and
+ `build-assets-image` jobs are done), that can be found in GitLab merge
+ requests. This starts a chain of pipelines in multiple projects.
+ 1. The script being executed triggers a pipeline in
+ [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror)
+ and polls for the resulting status. We call this a _status attribution_.
-1. When packages are ready, and available in the registry, a final step in the
- [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) pipeline, triggers a new
- GitLab QA pipeline (those with access can view them at `https://gitlab.com/gitlab-org/gitlab-qa-mirror/pipelines`). It also waits for a resulting status.
+1. In the [`gitlab-org/build/omnibus-gitlab-mirror` pipeline](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror):
+ 1. Docker image is being built and pushed to its Container Registry.
+ 1. Finally, the `Trigger:qa-test` job triggers a new end-to-end pipeline in
+ [`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror/pipelines) and polls for the resulting status.
-1. GitLab QA pulls images from the registry, spins-up containers and runs tests
- against a test environment that has been just orchestrated by the `gitlab-qa`
- tool.
+1. In the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror):
+ 1. Container for the Docker image stored in the [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) registry is spun-up.
+ 1. End-to-end tests are run with the `gitlab-qa` executable, which spin up a container for the end-to-end image from the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) registry.
-1. The result of the GitLab QA pipeline is being
- propagated upstream, through Omnibus, back to the GitLab merge request.
+1. The result of the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror) is being
+ propagated upstream (through polling from upstream pipelines), through [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror), back to the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) merge request.
Please note, we plan to [add more specific information](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/156)
-about the tests included in each job/scenario that runs in `gitlab-qa-mirror`.
+about the tests included in each job/scenario that runs in `gitlab-org/gitlab-qa-mirror`.
#### With Pipeline for Merged Results
diff --git a/doc/development/usage_ping/index.md b/doc/development/usage_ping/index.md
index f70eb1a7cbf..91d5898d97f 100644
--- a/doc/development/usage_ping/index.md
+++ b/doc/development/usage_ping/index.md
@@ -978,7 +978,7 @@ Example aggregated metric entries:
```yaml
- name: example_metrics_union
operator: OR
- events:
+ events:
- 'i_search_total'
- 'i_search_advanced'
- 'i_search_paid'
@@ -1362,4 +1362,24 @@ bin/rake gitlab:usage_data:dump_sql_in_yaml > ~/Desktop/usage-metrics-2020-09-02
## Generating and troubleshooting usage ping
-To get a usage ping, or to troubleshoot caching issues on your GitLab instance, please follow [instructions to generate usage ping](../../administration/troubleshooting/gitlab_rails_cheat_sheet.md#generate-usage-ping).
+This activity is to be done via a detached screen session on a remote server.
+
+Before you begin these steps, make sure the key is added to the SSH agent locally
+with the `ssh-add` command.
+
+### Triggering
+
+1. Connect to bastion with agent forwarding: `$ ssh -A lb-bastion.gprd.gitlab.com`
+1. Create named screen: `$ screen -S <username>_usage_ping_<date>`
+1. Connect to console host: `$ ssh $USER-rails@console-01-sv-gprd.c.gitlab-production.internal`
+1. Run `SubmitUsagePingService.new.execute`
+1. Detach from screen: `ctrl + a, ctrl + d`
+1. Exit from bastion: `$ exit`
+
+### Verification (After approx 30 hours)
+
+1. Reconnect to bastion: `$ ssh -A lb-bastion.gprd.gitlab.com`
+1. Find your screen session: `$ screen -ls`
+1. Attach to your screen session: `$ screen -x 14226.mwawrzyniak_usage_ping_2021_01_22`
+1. Check the last payload in `raw_usage_data` table: `RawUsageData.last.payload`
+1. Check the when the payload was sent: `RawUsageData.last.sent_at`
diff --git a/doc/operations/incident_management/img/custom_alert_mapping_v13_10.png b/doc/operations/incident_management/img/custom_alert_mapping_v13_10.png
deleted file mode 100644
index 1eeb96ce3a9..00000000000
--- a/doc/operations/incident_management/img/custom_alert_mapping_v13_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/incident_management/img/custom_alert_mapping_v13_11.png b/doc/operations/incident_management/img/custom_alert_mapping_v13_11.png
new file mode 100644
index 00000000000..ea097af878f
--- /dev/null
+++ b/doc/operations/incident_management/img/custom_alert_mapping_v13_11.png
Binary files differ
diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md
index bdb16b1f88a..c675d995444 100644
--- a/doc/operations/incident_management/integrations.md
+++ b/doc/operations/incident_management/integrations.md
@@ -18,10 +18,10 @@ to use this endpoint.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in GitLab Free 13.5.
-With Maintainer or higher [permissions](../../user/permissions.md), you can view
-the list of configured alerts integrations by navigating to
-**Settings > Operations** in your project's sidebar menu, and expanding **Alerts** section.
-The list displays the integration name, type, and status (enabled or disabled):
+With Maintainer or higher [permissions](../../user/permissions.md),
+you can view the list of configured alerts integrations by navigating to **Settings > Operations**
+in your project's sidebar menu, and expanding the **Alert integrations** section. The list displays
+the integration name, type, and status (enabled or disabled):
![Current Integrations](img/integrations_list_v13_5.png)
@@ -39,9 +39,11 @@ receive alert payloads in JSON format. You can always
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
-1. Expand the **Alerts** section, and in the **Integration** dropdown menu, select **Generic**.
-1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
- for the webhook configuration.
+1. Expand the **Alert integrations** section, and in the **Select integration type** dropdown menu,
+ select **HTTP Endpoint**.
+1. Toggle the **Active** alert setting. The URL and Authorization Key for the webhook configuration
+ are available in the **View credentials** tab after you save the integration. You must also input
+ the URL and Authorization Key in your external service.
### HTTP Endpoints **(PREMIUM)**
@@ -54,11 +56,11 @@ and you can [customize the payload](#customize-the-alert-payload-outside-of-gitl
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
-1. Expand the **Alerts** section.
+1. Expand the **Alert integrations** section.
1. For each endpoint you want to create:
1. Click the **Add new integration** button.
- 1. In the **Integration** dropdown menu, select **HTTP Endpoint**.
+ 1. In the **Select integration type** dropdown menu, select **HTTP Endpoint**.
1. Name the integration.
1. Toggle the **Active** alert setting. The **URL** and **Authorization Key** for the webhook
configuration are available in the **View credentials** tab after you save the integration.
@@ -85,7 +87,7 @@ correct information in the [Alert list](alerts.md) and the
[Alert Details page](alerts.md#alert-details-page), map your alert's fields to
GitLab fields when you [create an HTTP endpoint](#http-endpoints):
-![Alert Management List](img/custom_alert_mapping_v13_10.png)
+![Alert Management List](img/custom_alert_mapping_v13_11.png)
### External Prometheus integration
@@ -165,9 +167,11 @@ alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
-1. Click **Alerts endpoint** to expand the section.
-1. Enter a sample payload in **Alert test payload** (valid JSON is required).
-1. Click **Test alert payload**.
+1. Click **Alert integrations** to expand the section.
+1. Click the **{settings}** settings icon on the right side of the integration in [the list](#integrations-list).
+1. Select the **Send test alert** tab to open it.
+1. Enter a test payload in the payload field (valid JSON is required).
+1. Click **Send**.
GitLab displays an error or success message, depending on the outcome of your test.
diff --git a/doc/operations/incident_management/oncall_schedules.md b/doc/operations/incident_management/oncall_schedules.md
index 78959dd5c2d..5a5564e3ce9 100644
--- a/doc/operations/incident_management/oncall_schedules.md
+++ b/doc/operations/incident_management/oncall_schedules.md
@@ -13,7 +13,8 @@ responsibilities. Maintain the availability of your software services by putting
With an on-call schedule, your team is notified immediately when things go wrong so they can quickly
respond to service outages and disruptions.
-To use on-call schedules, you must do the following:
+To use on-call schedules, users with Maintainer [permissions](../../user/permissions.md)
+must do the following:
1. [Create a schedule](#schedules).
1. [Add a rotation to the schedule](#rotations).
diff --git a/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_11.png b/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_11.png
new file mode 100644
index 00000000000..05df41483c4
--- /dev/null
+++ b/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_11.png
Binary files differ
diff --git a/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_9.png b/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_9.png
deleted file mode 100644
index a668ce1a3b8..00000000000
--- a/doc/user/application_security/threat_monitoring/img/threat_monitoring_policy_alert_list_v13_9.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/threat_monitoring/index.md b/doc/user/application_security/threat_monitoring/index.md
index 747d31df356..ced4669e241 100644
--- a/doc/user/application_security/threat_monitoring/index.md
+++ b/doc/user/application_security/threat_monitoring/index.md
@@ -221,7 +221,7 @@ to set the status for each alert:
By default, the list doesn't display resolved or dismissed alerts. To show these alerts, clear the
checkbox **Hide dismissed alerts**.
-![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_9.png)
+![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_11.png)
Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page).
diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md
index 302f12273cb..f28732064ad 100644
--- a/doc/user/group/import/index.md
+++ b/doc/user/group/import/index.md
@@ -66,6 +66,10 @@ The following resources are migrated to the target instance:
- due date
- created at
- updated at
+- Badges ([Introduced in 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/292431))
+ - name
+ - link URL
+ - image URL
Any other items are **not** migrated.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 8d64ef6b1db..180bd589ac5 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -653,3 +653,15 @@ The group's new subgroups have push rules set for them based on either:
- [Lock the sharing with group feature](#prevent-a-project-from-being-shared-with-groups).
- [Enforce two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group): Enforce 2FA
for all group members.
+
+## Troubleshooting
+
+### Verify if access is blocked by IP restriction
+
+If a user sees a 404 when they would normally expect access, and the problem is limited to a specific group, search the `auth.log` rails log for one or more of the following:
+
+- `json.message`: `'Attempting to access IP restricted group'`
+- `json.allowed`: `false`
+
+In viewing the log entries, compare the `remote.ip` with the list of
+[allowed IPs](#restrict-group-access-by-ip-address) for the group.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 6046258ee29..6078de0f9e1 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -96,7 +96,9 @@ Please note that the certificate [fingerprint algorithm](../../../integration/sa
- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/292811) in GitLab 13.8, with an updated timeout experience.
- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/211962) in GitLab 13.8 with allowing group owners to not go through SSO.
-With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users can't be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
+With this option enabled, users must go through your group's GitLab single sign-on URL if they wish to access group resources through the UI. Users can't be manually added as members.
+
+SSO enforcement does not affect sign in or access to any resources outside of the group. Users can view which groups and projects they are a member of without SSO sign in.
However, users are not prompted to sign in through SSO on each visit. GitLab checks whether a user
has authenticated through SSO. If it's been more than 1 day since the last sign-in, GitLab
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
new file mode 100644
index 00000000000..63772936fd4
--- /dev/null
+++ b/doc/user/project/integrations/hipchat.md
@@ -0,0 +1,7 @@
+---
+redirect_to: 'index.md'
+---
+
+This document was moved to [another location](index.md).
+<!-- This redirect file can be deleted after 2021-06-30. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/lib/bulk_imports/clients/http.rb b/lib/bulk_imports/clients/http.rb
index dcd6830bf67..ef99122cdfd 100644
--- a/lib/bulk_imports/clients/http.rb
+++ b/lib/bulk_imports/clients/http.rb
@@ -23,7 +23,7 @@ module BulkImports
resource_url(resource),
headers: request_headers,
follow_redirects: false,
- query: query.merge(request_query)
+ query: query.reverse_merge(request_query)
)
end
end
diff --git a/lib/bulk_imports/common/extractors/rest_extractor.rb b/lib/bulk_imports/common/extractors/rest_extractor.rb
new file mode 100644
index 00000000000..b18e27fd475
--- /dev/null
+++ b/lib/bulk_imports/common/extractors/rest_extractor.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Common
+ module Extractors
+ class RestExtractor
+ def initialize(options = {})
+ @query = options[:query]
+ end
+
+ def extract(context)
+ client = http_client(context.configuration)
+ params = query.to_h(context)
+ response = client.get(params[:resource], params[:query])
+
+ BulkImports::Pipeline::ExtractedData.new(
+ data: response.parsed_response,
+ page_info: page_info(response.headers)
+ )
+ end
+
+ private
+
+ attr_reader :query
+
+ def http_client(configuration)
+ @http_client ||= BulkImports::Clients::Http.new(
+ uri: configuration.url,
+ token: configuration.access_token,
+ per_page: 100
+ )
+ end
+
+ def page_info(headers)
+ next_page = headers['x-next-page']
+
+ {
+ 'has_next_page' => next_page.present?,
+ 'next_page' => next_page
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/groups/pipelines/badges_pipeline.rb b/lib/bulk_imports/groups/pipelines/badges_pipeline.rb
new file mode 100644
index 00000000000..8569ff3f77a
--- /dev/null
+++ b/lib/bulk_imports/groups/pipelines/badges_pipeline.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Groups
+ module Pipelines
+ class BadgesPipeline
+ include Pipeline
+
+ extractor BulkImports::Common::Extractors::RestExtractor,
+ query: BulkImports::Groups::Rest::GetBadgesQuery
+
+ transformer Common::Transformers::ProhibitedAttributesTransformer
+
+ def transform(_, data)
+ return if data.blank?
+
+ {
+ name: data['name'],
+ link_url: data['link_url'],
+ image_url: data['image_url']
+ }
+ end
+
+ def load(context, data)
+ return if data.blank?
+
+ context.group.badges.create!(data)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/groups/rest/get_badges_query.rb b/lib/bulk_imports/groups/rest/get_badges_query.rb
new file mode 100644
index 00000000000..79ffdd9a1f6
--- /dev/null
+++ b/lib/bulk_imports/groups/rest/get_badges_query.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Groups
+ module Rest
+ module GetBadgesQuery
+ extend self
+
+ def to_h(context)
+ encoded_full_path = ERB::Util.url_encode(context.entity.source_full_path)
+
+ {
+ resource: ['groups', encoded_full_path, 'badges'].join('/'),
+ query: {
+ page: context.tracker.next_page
+ }
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/importers/group_importer.rb b/lib/bulk_imports/importers/group_importer.rb
index ccc61ee787d..2cd97c687e0 100644
--- a/lib/bulk_imports/importers/group_importer.rb
+++ b/lib/bulk_imports/importers/group_importer.rb
@@ -34,7 +34,8 @@ module BulkImports
BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline,
BulkImports::Groups::Pipelines::MembersPipeline,
BulkImports::Groups::Pipelines::LabelsPipeline,
- BulkImports::Groups::Pipelines::MilestonesPipeline
+ BulkImports::Groups::Pipelines::MilestonesPipeline,
+ BulkImports::Groups::Pipelines::BadgesPipeline
]
end
end
diff --git a/lib/gitlab/ci/queue/metrics.rb b/lib/gitlab/ci/queue/metrics.rb
index fd02c998d84..7ecb9a1db16 100644
--- a/lib/gitlab/ci/queue/metrics.rb
+++ b/lib/gitlab/ci/queue/metrics.rb
@@ -100,7 +100,7 @@ module Gitlab
self.class.queue_size_total.observe({ runner_type: runner_type }, size_proc.call.to_f)
end
- def observe_queue_time(metric)
+ def observe_queue_time(metric, runner_type)
start_time = ::Gitlab::Metrics::System.monotonic_time
result = yield
@@ -111,9 +111,9 @@ module Gitlab
case metric
when :process
- self.class.queue_iteration_duration_seconds.observe({}, seconds.to_f)
+ self.class.queue_iteration_duration_seconds.observe({ runner_type: runner_type }, seconds.to_f)
when :retrieve
- self.class.queue_retrieval_duration_seconds.observe({}, seconds.to_f)
+ self.class.queue_retrieval_duration_seconds.observe({ runner_type: runner_type }, seconds.to_f)
else
raise ArgumentError unless Rails.env.production?
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 216c81f3514..c4c2ed72614 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1074,6 +1074,9 @@ msgstr[1] ""
msgid "+%{approvers} more approvers"
msgstr ""
+msgid "+%{extra} more"
+msgstr ""
+
msgid "+%{more_assignees_count}"
msgstr ""
@@ -20546,6 +20549,9 @@ msgstr ""
msgid "NetworkPolicy|Search by policy name"
msgstr ""
+msgid "NetworkPolicy|Status"
+msgstr ""
+
msgid "Never"
msgstr ""
@@ -25470,7 +25476,7 @@ msgstr ""
msgid "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software."
msgstr ""
-msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
+msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0.0%{codeEnd}, %{codeStart}v2.1.0-pre%{codeEnd}."
msgstr ""
msgid "Releases documentation"
@@ -27056,6 +27062,9 @@ msgstr ""
msgid "SecurityApprovals|Requires approval for vulnerabilities of Critical, High, or Unknown severity. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
+msgid "SecurityConfiguration|%{featureName} merge request creation mutation failed"
+msgstr ""
+
msgid "SecurityConfiguration|An error occurred while creating the merge request."
msgstr ""
@@ -31819,6 +31828,15 @@ msgstr ""
msgid "To GitLab"
msgstr ""
+msgid "To accept this invitation, create an account or sign in."
+msgstr ""
+
+msgid "To accept this invitation, sign in or create an account."
+msgstr ""
+
+msgid "To accept this invitation, sign in."
+msgstr ""
+
msgid "To access this domain create a new DNS record"
msgstr ""
diff --git a/qa/qa.rb b/qa/qa.rb
index be7c0de137e..6acb9263a7a 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -500,6 +500,7 @@ module QA
autoload :Wiki, 'qa/page/component/wiki'
autoload :WikiSidebar, 'qa/page/component/wiki_sidebar'
autoload :WikiPageForm, 'qa/page/component/wiki_page_form'
+ autoload :CommitModal, 'qa/page/component/commit_modal'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
diff --git a/qa/qa/page/component/commit_modal.rb b/qa/qa/page/component/commit_modal.rb
new file mode 100644
index 00000000000..7192e8bafb5
--- /dev/null
+++ b/qa/qa/page/component/commit_modal.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ class CommitModal < Page::Base
+ view 'app/assets/javascripts/projects/commit/components/form_modal.vue' do
+ element :submit_commit_button, required: true
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 648434af4f7..4cf9e404bbf 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -107,6 +107,10 @@ module QA
element :suggestion_button
end
+ view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
+ element :cherry_pick_button
+ end
+
def start_review
click_element(:start_review_button)
@@ -359,6 +363,11 @@ module QA
def apply_suggestions_batch
all_elements(:apply_suggestions_batch_button, minimum: 1).first.click
end
+
+ def cherry_pick!
+ click_element(:cherry_pick_button, Page::Component::CommitModal)
+ click_element(:submit_commit_button)
+ end
end
end
end
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index fb450a61c9a..6d1aa5b90b0 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -35,7 +35,7 @@ module QA
attribute :target do
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
- resource.branch_name = project.default_branch
+ resource.branch_name = target_branch
resource.new_branch = @target_new_branch
resource.remote_branch = target_branch
end
@@ -62,6 +62,7 @@ module QA
@labels = []
@file_name = "added_file-#{SecureRandom.hex(8)}.txt"
@file_content = "File Added"
+ @target_branch = "master"
@target_new_branch = true
@no_preparation = false
@wait_for_merge = true
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick_spec.rb
new file mode 100644
index 00000000000..85eaf5b4c5a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Cherry picking from a merge request' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project'
+ project.initialize_with_readme = true
+ end
+ end
+
+ let(:feature_mr) do
+ Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.project = project
+ merge_request.target_branch = 'development'
+ merge_request.target_new_branch = true
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'cherry picks a basic merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1616' do
+ feature_mr.visit!
+
+ Page::MergeRequest::Show.perform do |merge_request|
+ merge_request.merge!
+ merge_request.cherry_pick!
+ end
+
+ Page::MergeRequest::New.perform do |merge_request|
+ expect(merge_request).to have_content('The merge request has been successfully cherry-picked')
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/graphql/descriptions.rb b/rubocop/cop/graphql/descriptions.rb
index ec233c65874..520e34dcd16 100644
--- a/rubocop/cop/graphql/descriptions.rb
+++ b/rubocop/cop/graphql/descriptions.rb
@@ -54,6 +54,10 @@ module RuboCop
(send nil? :value ...)
PATTERN
+ def_node_matcher :resolver_kwarg, <<~PATTERN
+ (... (hash <(pair (sym :resolver) $_) ...>))
+ PATTERN
+
def_node_matcher :description_kwarg, <<~PATTERN
(... (hash <(pair (sym :description) $_) ...>))
PATTERN
@@ -64,6 +68,7 @@ module RuboCop
def on_send(node)
return unless graphql_describable?(node)
+ return if resolver_kwarg(node) # Fields may inherit the description from their resolvers.
description = locate_description(node)
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index a8d38d12f23..5195f482084 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe InvitesController do
let_it_be(:user) { create(:user) }
- let(:member) { create(:project_member, :invited, invite_email: user.email) }
+ let_it_be(:member, reload: true) { create(:project_member, :invited, invite_email: user.email) }
let(:raw_invite_token) { member.raw_invite_token }
let(:project_members) { member.source.users }
let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) }
@@ -77,10 +77,83 @@ RSpec.describe InvitesController do
context 'when not logged in' do
context 'when inviter is a member' do
- it 'is redirected to a new session with invite email param' do
- request
+ context 'when instance allows sign up' do
+ it 'indicates an account can be created in notice' do
+ request
+
+ expect(flash[:notice]).to include('or create an account')
+ end
+
+ context 'when user exists with the invited email' do
+ it 'is redirected to a new session with invite email param' do
+ request
+
+ expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ end
+ end
+
+ context 'when user exists with the invited email as secondary email' do
+ before do
+ secondary_email = create(:email, user: user, email: 'foo@example.com')
+ member.update!(invite_email: secondary_email.email)
+ end
+
+ it 'is redirected to a new session with invite email param' do
+ request
+
+ expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ end
+ end
+
+ context 'when user does not exist with the invited email' do
+ before do
+ member.update!(invite_email: 'bogus_email@example.com')
+ end
+
+ it 'indicates an account can be created in notice' do
+ request
+
+ expect(flash[:notice]).to include('create an account or sign in')
+ end
+
+ it 'is redirected to a new registration with invite email param' do
+ request
+
+ expect(response).to redirect_to(new_user_registration_path(invite_email: member.invite_email))
+ end
+ end
+ end
+
+ context 'when instance does not allow sign up' do
+ before do
+ stub_application_setting(allow_signup?: false)
+ end
+
+ it 'does not indicate an account can be created in notice' do
+ request
+
+ expect(flash[:notice]).not_to include('or create an account')
+ end
+
+ context 'when user exists with the invited email' do
+ it 'is redirected to a new session with invite email param' do
+ request
+
+ expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ end
+ end
+
+ context 'when user does not exist with the invited email' do
+ before do
+ member.update!(invite_email: 'bogus_email@example.com')
+ end
+
+ it 'is redirected to a new session with invite email param' do
+ request
- expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ end
+ end
end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index e6327a72a68..cb2579b800a 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -56,28 +56,6 @@ RSpec.describe Projects::RepositoriesController do
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
- it 'handles legacy queries with no ref' do
- get :archive, params: { namespace_id: project.namespace, project_id: project }, format: "zip"
-
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
- end
-
- it 'handles legacy queries with the ref specified as ref in params' do
- get :archive, params: { namespace_id: project.namespace, project_id: project, ref: 'feature' }, format: 'zip'
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:ref)).to eq('feature')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
- end
-
- it 'handles legacy queries with the ref specified as id in params' do
- get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature' }, format: 'zip'
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:ref)).to eq('feature')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
- end
-
it 'prioritizes the id param over the ref param when both are specified' do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict' }, format: 'zip'
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index 2ceffa896eb..e9960802378 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -50,21 +50,23 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
it 'renders sign in page with sign in notice' do
- expect(current_path).to eq(new_user_session_path)
- expect(page).to have_content('To accept this invitation, sign in')
+ expect(current_path).to eq(new_user_registration_path)
+ expect(page).to have_content('To accept this invitation, create an account or sign in')
end
it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do
+ click_link 'Sign in'
+
expect(find_field('Username or email').value).to eq(group_invite.invite_email)
end
it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do
- click_link 'Register now'
-
expect(find_field('Email').value).to eq(group_invite.invite_email)
end
it 'sign in, grants access and redirects to group page' do
+ click_link 'Sign in'
+
fill_in_sign_in_form(user)
expect(current_path).to eq(group_path(group))
@@ -85,20 +87,19 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
end
- context 'when inviting a user' do
+ context 'when inviting an unregistered user' do
let(:new_user) { build_stubbed(:user) }
let(:invite_email) { new_user.email }
let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email, created_by: owner) }
let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) }
- context 'when user has not signed in yet' do
+ context 'when registering using invitation email' do
before do
stub_application_setting(send_user_confirmation_email: send_email_confirmation)
visit invite_path(group_invite.raw_invite_token)
- click_link 'Register now'
end
- context 'with admin appoval required enabled' do
+ context 'with admin approval required enabled' do
before do
stub_application_setting(require_admin_approval_after_user_signup: true)
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index ac412d19974..b63d3aaee5a 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -117,12 +117,6 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
- it 'opens autocomplete menu when field starts with text' do
- fill_in 'Comment', with: '@'
-
- expect(find_autocomplete_menu).to be_visible
- end
-
it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;'
create(:issue, project: project, title: issue_xss_title)
@@ -153,74 +147,87 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find_autocomplete_menu).to have_text('alert milestone')
end
- it 'doesnt select the first item for non-assignee dropdowns' do
- fill_in 'Comment', with: ':'
+ describe 'autocomplete highlighting' do
+ it 'auto-selects the first item when there is a query, and only for assignees with no query', :aggregate_failures do
+ fill_in 'Comment', with: ':'
+ wait_for_requests
+ expect(find_autocomplete_menu).not_to have_css('.cur')
- wait_for_requests
+ fill_in 'Comment', with: ':1'
+ wait_for_requests
+ expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
- expect(find_autocomplete_menu).not_to have_css('.cur')
+ fill_in 'Comment', with: '@'
+ wait_for_requests
+ expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
+ end
end
- it 'selects the first item for assignee dropdowns' do
- fill_in 'Comment', with: '@'
+ describe 'assignees' do
+ it 'does not wrap with quotes for assignee values' do
+ fill_in 'Comment', with: "@#{user.username[0]}"
- wait_for_requests
+ find_highlighted_autocomplete_item.click
- expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
- end
+ expect(find_field('Comment').value).to have_text("@#{user.username}")
+ end
- it 'includes items for assignee dropdowns with non-ASCII characters in name' do
- fill_in 'Comment', with: "@#{user.name[0...8]}"
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ fill_in 'Comment', with: "@#{user.name[0...8]}"
- wait_for_requests
+ wait_for_requests
- expect(find_autocomplete_menu).to have_text(user.name)
- end
+ expect(find_autocomplete_menu).to have_text(user.name)
+ end
- it 'searches across full name for assignees' do
- fill_in 'Comment', with: '@speciÄ…lsome'
+ it 'searches across full name for assignees' do
+ fill_in 'Comment', with: '@speciÄ…lsome'
- wait_for_requests
+ wait_for_requests
- expect(find_highlighted_autocomplete_item).to have_text(user.name)
- end
+ expect(find_highlighted_autocomplete_item).to have_text(user.name)
+ end
- it 'shows names that start with the query as the top result' do
- fill_in 'Comment', with: '@mar'
+ it 'shows names that start with the query as the top result' do
+ fill_in 'Comment', with: '@mar'
- wait_for_requests
+ wait_for_requests
- expect(find_highlighted_autocomplete_item).to have_text(user2.name)
- end
+ expect(find_highlighted_autocomplete_item).to have_text(user2.name)
+ end
- it 'shows usernames that start with the query as the top result' do
- fill_in 'Comment', with: '@msi'
+ it 'shows usernames that start with the query as the top result' do
+ fill_in 'Comment', with: '@msi'
- wait_for_requests
+ wait_for_requests
- expect(find_highlighted_autocomplete_item).to have_text(user2.name)
- end
+ expect(find_highlighted_autocomplete_item).to have_text(user2.name)
+ end
- # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925
- it 'shows username when pasting then pressing Enter' do
- fill_in 'Comment', with: "@#{user.username}\n"
+ # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925
+ it 'shows username when pasting then pressing Enter' do
+ fill_in 'Comment', with: "@#{user.username}\n"
- expect(find_field('Comment').value).to have_text "@#{user.username}"
- end
+ expect(find_field('Comment').value).to have_text "@#{user.username}"
+ end
- it 'does not show `@undefined` when pressing `@` then Enter' do
- fill_in 'Comment', with: "@\n"
+ it 'does not show `@undefined` when pressing `@` then Enter' do
+ fill_in 'Comment', with: "@\n"
- expect(find_field('Comment').value).to have_text '@'
- expect(find_field('Comment').value).not_to have_text '@undefined'
- end
+ expect(find_field('Comment').value).to have_text '@'
+ expect(find_field('Comment').value).not_to have_text '@undefined'
+ end
- it 'selects the first item for non-assignee dropdowns if a query is entered' do
- fill_in 'Comment', with: ':1'
+ context 'when /assign quick action is selected' do
+ it 'triggers user autocomplete and lists users who are currently not assigned to the issue' do
+ fill_in 'Comment', with: '/as'
- wait_for_requests
+ find_highlighted_autocomplete_item.click
- expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
+ expect(find_autocomplete_menu).not_to have_text(user.username)
+ expect(find_autocomplete_menu).to have_text(user2.username)
+ end
+ end
end
context 'if a selected value has special characters' do
@@ -232,14 +239,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find_field('Comment').value).to have_text("~\"#{label.title}\"")
end
- it 'doesn\'t wrap for assignee values' do
- fill_in 'Comment', with: "@#{user.username[0]}"
-
- find_highlighted_autocomplete_item.click
-
- expect(find_field('Comment').value).to have_text("@#{user.username}")
- end
-
it 'doesn\'t wrap for emoji values' do
fill_in 'Comment', with: ':cartwheel_'
@@ -263,17 +262,6 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
- context 'assignees' do
- it 'lists users who are currently not assigned to the issue when using /assign' do
- fill_in 'Comment', with: '/as'
-
- find_highlighted_autocomplete_item.click
-
- expect(find_autocomplete_menu).not_to have_text(user.username)
- expect(find_autocomplete_menu).to have_text(user2.username)
- end
- end
-
context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
@@ -498,12 +486,6 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
- it 'opens autocomplete menu when field starts with text' do
- fill_in 'Comment', with: '@'
-
- expect(find_tribute_autocomplete_menu).to be_visible
- end
-
it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;'
create(:issue, project: project, title: issue_xss_title)
@@ -534,41 +516,77 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find_tribute_autocomplete_menu).to have_text('alert milestone')
end
- it 'selects the first item for assignee dropdowns' do
- fill_in 'Comment', with: '@'
-
- wait_for_requests
+ describe 'autocomplete highlighting' do
+ it 'auto-selects the first item with query', :aggregate_failures do
+ fill_in 'Comment', with: ':1'
+ wait_for_requests
+ expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
- expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
+ fill_in 'Comment', with: '@'
+ wait_for_requests
+ expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
+ end
end
- it 'includes items for assignee dropdowns with non-ASCII characters in name' do
- fill_in 'Comment', with: "@#{user.name[0...8]}"
+ describe 'assignees' do
+ it 'does not wrap with quotes for assignee values' do
+ fill_in 'Comment', with: "@#{user.username[0..2]}"
- wait_for_requests
+ find_highlighted_tribute_autocomplete_menu.click
- expect(find_tribute_autocomplete_menu).to have_text(user.name)
- end
+ expect(find_field('Comment').value).to have_text("@#{user.username}")
+ end
- it 'selects the first item for non-assignee dropdowns if a query is entered' do
- fill_in 'Comment', with: ':1'
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ fill_in 'Comment', with: "@#{user.name[0...8]}"
- wait_for_requests
+ wait_for_requests
- expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
- end
+ expect(find_tribute_autocomplete_menu).to have_text(user.name)
+ end
+
+ context 'when autocompleting for groups' do
+ it 'shows the group when searching for the name of the group' do
+ fill_in 'Comment', with: '@mygroup'
- context 'when autocompleting for groups' do
- it 'shows the group when searching for the name of the group' do
- fill_in 'Comment', with: '@mygroup'
+ expect(find_tribute_autocomplete_menu).to have_text('My group')
+ end
+
+ it 'does not show the group when searching for the name of the parent of the group' do
+ fill_in 'Comment', with: '@ancestor'
- expect(find_tribute_autocomplete_menu).to have_text('My group')
+ expect(find_tribute_autocomplete_menu).not_to have_text('My group')
+ end
end
- it 'does not show the group when searching for the name of the parent of the group' do
- fill_in 'Comment', with: '@ancestor'
+ context 'when /assign quick action is selected' do
+ it 'lists users who are currently not assigned to the issue' do
+ note = find_field('Comment')
+ note.native.send_keys('/assign ')
+ # The `/assign` ajax response might replace the one by `@` below causing a failed test
+ # so we need to wait for the `/assign` ajax request to finish first
+ wait_for_requests
+ note.native.send_keys('@')
+ wait_for_requests
- expect(find_tribute_autocomplete_menu).not_to have_text('My group')
+ expect(find_tribute_autocomplete_menu).not_to have_text(user.username)
+ expect(find_tribute_autocomplete_menu).to have_text(user2.username)
+ end
+
+ it 'lists users who are currently not assigned to the issue when using /assign on the second line' do
+ note = find_field('Comment')
+ note.native.send_keys('/assign @user2')
+ note.native.send_keys(:enter)
+ note.native.send_keys('/assign ')
+ # The `/assign` ajax response might replace the one by `@` below causing a failed test
+ # so we need to wait for the `/assign` ajax request to finish first
+ wait_for_requests
+ note.native.send_keys('@')
+ wait_for_requests
+
+ expect(find_tribute_autocomplete_menu).not_to have_text(user.username)
+ expect(find_tribute_autocomplete_menu).to have_text(user2.username)
+ end
end
end
@@ -581,14 +599,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find_field('Comment').value).to have_text("~\"#{label.title}\"")
end
- it 'doesn\'t wrap for assignee values' do
- fill_in 'Comment', with: "@#{user.username[0..2]}"
-
- find_highlighted_tribute_autocomplete_menu.click
-
- expect(find_field('Comment').value).to have_text("@#{user.username}")
- end
-
it 'does not wrap for emoji values' do
fill_in 'Comment', with: ':cartwheel_'
@@ -606,36 +616,6 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
- context 'assignees' do
- it 'lists users who are currently not assigned to the issue when using /assign' do
- note = find_field('Comment')
- note.native.send_keys('/assign ')
- # The `/assign` ajax response might replace the one by `@` below causing a failed test
- # so we need to wait for the `/assign` ajax request to finish first
- wait_for_requests
- note.native.send_keys('@')
- wait_for_requests
-
- expect(find_tribute_autocomplete_menu).not_to have_text(user.username)
- expect(find_tribute_autocomplete_menu).to have_text(user2.username)
- end
-
- it 'lists users who are currently not assigned to the issue when using /assign on the second line' do
- note = find_field('Comment')
- note.native.send_keys('/assign @user2')
- note.native.send_keys(:enter)
- note.native.send_keys('/assign ')
- # The `/assign` ajax response might replace the one by `@` below causing a failed test
- # so we need to wait for the `/assign` ajax request to finish first
- wait_for_requests
- note.native.send_keys('@')
- wait_for_requests
-
- expect(find_tribute_autocomplete_menu).not_to have_text(user.username)
- expect(find_tribute_autocomplete_menu).to have_text(user2.username)
- end
- end
-
context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb
index bb54b6be9c4..024c0a227c5 100644
--- a/spec/features/projects/releases/user_views_edit_release_spec.rb
+++ b/spec/features/projects/releases/user_views_edit_release_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe 'User edits Release', :js do
end
it 'renders the edit Release form' do
- expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0, v2.0-pre.')
+ expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0.0, v2.1.0-pre.')
expect(find_field('Tag name', disabled: true).value).to eq(release.tag)
expect(find_field('Release title').value).to eq(release.name)
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index 26d38b115b6..bb3b16b4c7a 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -329,7 +329,7 @@ describe('ShortcutsIssuable', () => {
window.shortcut = new ShortcutsIssuable();
[sidebarCollapsedBtn, sidebarExpandedBtn] = document.querySelectorAll(
- '.sidebar-source-branch button',
+ '.js-sidebar-source-branch button',
);
[sidebarCollapsedBtn, sidebarExpandedBtn].forEach((btn) => jest.spyOn(btn, 'click'));
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index 106e6a3a012..65ed6d6166f 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -112,7 +112,7 @@ describe('Release edit/new component', () => {
it('renders the description text at the top of the page', () => {
expect(wrapper.find('.js-subtitle-text').text()).toBe(
- 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0, v2.0-pre.',
+ 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0.0, v2.1.0-pre.',
);
});
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index a0d00ff9a7d..b662a1d20a9 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -76,6 +76,7 @@ describe('Blob content viewer component', () => {
expect(findBlobContent().props('loading')).toEqual(false);
expect(findBlobContent().props('content')).toEqual('raw content');
+ expect(findBlobContent().props('isRawContent')).toBe(true);
expect(findBlobContent().props('activeViewer')).toEqual({
fileType: 'text',
tooLarge: false,
diff --git a/spec/frontend/repository/pages/blob_spec.js b/spec/frontend/repository/pages/blob_spec.js
new file mode 100644
index 00000000000..3e7ead4ad00
--- /dev/null
+++ b/spec/frontend/repository/pages/blob_spec.js
@@ -0,0 +1,25 @@
+import { shallowMount } from '@vue/test-utils';
+import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
+import BlobPage from '~/repository/pages/blob.vue';
+
+jest.mock('~/repository/utils/dom');
+
+describe('Repository blob page component', () => {
+ let wrapper;
+
+ const findBlobContentViewer = () => wrapper.find(BlobContentViewer);
+ const path = 'file.js';
+
+ beforeEach(() => {
+ wrapper = shallowMount(BlobPage, { propsData: { path } });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has a Blob Content Viewer component', () => {
+ expect(findBlobContentViewer().exists()).toBe(true);
+ expect(findBlobContentViewer().props('path')).toBe(path);
+ });
+});
diff --git a/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js
index 1dbb7702a15..cc428693930 100644
--- a/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js
+++ b/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js
@@ -1,4 +1,3 @@
-import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
@@ -8,18 +7,21 @@ import { IssuableType } from '~/issue_show/constants';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
import { issueReferenceResponse } from '../../mock_data';
describe('Sidebar Reference Widget', () => {
let wrapper;
let fakeApollo;
- const referenceText = 'reference';
+
+ const mockReferenceValue = 'reference-1234';
+
+ const findCopyableField = () => wrapper.findComponent(CopyableField);
const createComponent = ({
- issuableType,
+ issuableType = IssuableType.Issue,
referenceQuery = issueReferenceQuery,
- referenceQueryHandler = jest.fn().mockResolvedValue(issueReferenceResponse(referenceText)),
+ referenceQueryHandler = jest.fn().mockResolvedValue(issueReferenceResponse(mockReferenceValue)),
} = {}) => {
Vue.use(VueApollo);
@@ -39,14 +41,20 @@ describe('Sidebar Reference Widget', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
+ });
+
+ describe('when reference is loading', () => {
+ it('sets CopyableField `is-loading` prop to `true`', () => {
+ createComponent({ referenceQueryHandler: jest.fn().mockReturnValue(new Promise(() => {})) });
+ expect(findCopyableField().props('isLoading')).toBe(true);
+ });
});
describe.each([
[IssuableType.Issue, issueReferenceQuery],
[IssuableType.MergeRequest, mergeRequestReferenceQuery],
])('when issuableType is %s', (issuableType, referenceQuery) => {
- it('displays the reference text', async () => {
+ it('sets CopyableField `value` prop to reference value', async () => {
createComponent({
issuableType,
referenceQuery,
@@ -54,40 +62,32 @@ describe('Sidebar Reference Widget', () => {
await waitForPromises();
- expect(wrapper.text()).toContain(referenceText);
+ expect(findCopyableField().props('value')).toBe(mockReferenceValue);
});
- it('displays loading icon while fetching and hides clipboard icon', async () => {
- createComponent({
- issuableType,
- referenceQuery,
- });
+ describe('when error occurs', () => {
+ it('calls createFlash with correct parameters', async () => {
+ const mockError = new Error('mayday');
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
- expect(wrapper.find(ClipboardButton).exists()).toBe(false);
- });
+ createComponent({
+ issuableType,
+ referenceQuery,
+ referenceQueryHandler: jest.fn().mockRejectedValue(mockError),
+ });
- it('calls createFlash with correct parameters', async () => {
- const mockError = new Error('mayday');
+ await waitForPromises();
- createComponent({
- issuableType,
- referenceQuery,
- referenceQueryHandler: jest.fn().mockRejectedValue(mockError),
+ const [
+ [
+ {
+ message,
+ error: { networkError },
+ },
+ ],
+ ] = wrapper.emitted('fetch-error');
+ expect(message).toBe('An error occurred while fetching reference');
+ expect(networkError).toEqual(mockError);
});
-
- await waitForPromises();
-
- const [
- [
- {
- message,
- error: { networkError },
- },
- ],
- ] = wrapper.emitted('fetch-error');
- expect(message).toBe('An error occurred while fetching reference');
- expect(networkError).toEqual(mockError);
});
});
});
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
index 67b721b2ab0..22e206bb483 100644
--- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
+++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
@@ -29,7 +29,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
>
<markdown-header-stub
linecontent=""
- suggestionstartindex="-1"
+ suggestionstartindex="0"
/>
<div
diff --git a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
index 023895099b1..0eae69c483e 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
+++ b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
@@ -1,87 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = `
-<div
- class="file-content code js-syntax-highlight"
- data-qa-selector="file_content"
->
+<div>
<div
- class="line-numbers"
+ class="file-content code js-syntax-highlight"
+ data-qa-selector="file_content"
>
- <a
- class="diff-line-num js-line-number"
- data-line-number="1"
- href="#LC1"
- id="L1"
+ <div
+ class="line-numbers"
>
- <gl-icon-stub
- name="link"
- size="12"
- />
+ <a
+ class="diff-line-num js-line-number"
+ data-line-number="1"
+ href="#LC1"
+ id="L1"
+ >
+ <gl-icon-stub
+ name="link"
+ size="12"
+ />
+
+ 1
- 1
-
- </a>
- <a
- class="diff-line-num js-line-number"
- data-line-number="2"
- href="#LC2"
- id="L2"
- >
- <gl-icon-stub
- name="link"
- size="12"
- />
+ </a>
+ <a
+ class="diff-line-num js-line-number"
+ data-line-number="2"
+ href="#LC2"
+ id="L2"
+ >
+ <gl-icon-stub
+ name="link"
+ size="12"
+ />
+
+ 2
- 2
-
- </a>
- <a
- class="diff-line-num js-line-number"
- data-line-number="3"
- href="#LC3"
- id="L3"
- >
- <gl-icon-stub
- name="link"
- size="12"
- />
+ </a>
+ <a
+ class="diff-line-num js-line-number"
+ data-line-number="3"
+ href="#LC3"
+ id="L3"
+ >
+ <gl-icon-stub
+ name="link"
+ size="12"
+ />
+
+ 3
- 3
-
- </a>
- </div>
-
- <div
- class="blob-content"
- >
- <pre
- class="code highlight"
+ </a>
+ </div>
+
+ <div
+ class="blob-content"
>
- <code
- data-blob-hash="foo-bar"
+ <pre
+ class="code highlight"
>
- <span
- id="LC1"
+ <code
+ data-blob-hash="foo-bar"
>
- First
- </span>
-
+ <span
+ id="LC1"
+ >
+ First
+ </span>
+
- <span
- id="LC2"
- >
- Second
- </span>
-
+ <span
+ id="LC2"
+ >
+ Second
+ </span>
+
- <span
- id="LC3"
- >
- Third
- </span>
- </code>
- </pre>
+ <span
+ id="LC3"
+ >
+ Third
+ </span>
+ </code>
+ </pre>
+ </div>
</div>
</div>
`;
diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
index 9a0616343fe..92a326efd83 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
@@ -1,13 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
+import EditorLite from '~/vue_shared/components/editor_lite.vue';
describe('Blob Simple Viewer component', () => {
let wrapper;
const contentMock = `<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`;
const blobHash = 'foo-bar';
- function createComponent(content = contentMock) {
+ function createComponent(content = contentMock, isRawContent = false) {
wrapper = shallowMount(SimpleViewer, {
provide: {
blobHash,
@@ -15,6 +16,8 @@ describe('Blob Simple Viewer component', () => {
propsData: {
content,
type: 'text',
+ fileName: 'test.js',
+ isRawContent,
},
});
}
@@ -83,4 +86,18 @@ describe('Blob Simple Viewer component', () => {
});
});
});
+
+ describe('raw content', () => {
+ const findEditorLite = () => wrapper.find(EditorLite);
+ const isRawContent = true;
+
+ it('uses the Editor Lite component in readonly mode when viewing raw content', () => {
+ createComponent('raw content', isRawContent);
+
+ expect(findEditorLite().exists()).toBe(true);
+ expect(findEditorLite().props('value')).toBe('raw content');
+ expect(findEditorLite().props('fileName')).toBe('test.js');
+ expect(findEditorLite().props('editorOptions')).toEqual({ readOnly: true });
+ });
+ });
});
diff --git a/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb
new file mode 100644
index 00000000000..721dacbe3f4
--- /dev/null
+++ b/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Extractors::RestExtractor do
+ let(:http_client) { instance_double(BulkImports::Clients::Http) }
+ let(:options) { { query: double(to_h: { resource: nil, query: nil }) } }
+ let(:response) { double(parsed_response: { 'data' => { 'foo' => 'bar' } }, headers: { 'x-next-page' => '2' }) }
+
+ subject { described_class.new(options) }
+
+ describe '#extract' do
+ before do
+ allow(subject).to receive(:http_client).and_return(http_client)
+ allow(http_client).to receive(:get).and_return(response)
+ end
+
+ it 'returns instance of ExtractedData' do
+ entity = create(:bulk_import_entity)
+ tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
+
+ extracted_data = subject.extract(context)
+
+ expect(extracted_data).to be_instance_of(BulkImports::Pipeline::ExtractedData)
+ expect(extracted_data.data).to contain_exactly(response.parsed_response)
+ expect(extracted_data.next_page).to eq(response.headers['x-next-page'])
+ expect(extracted_data.has_next_page?).to eq(true)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb
new file mode 100644
index 00000000000..9fa35c4707d
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Pipelines::BadgesPipeline do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ let_it_be(:entity) do
+ create(
+ :bulk_import_entity,
+ source_full_path: 'source/full/path',
+ destination_name: 'My Destination Group',
+ destination_namespace: group.full_path,
+ group: group
+ )
+ end
+
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
+
+ subject { described_class.new(context) }
+
+ describe '#run' do
+ it 'imports a group badge' do
+ first_page = extracted_data(has_next_page: true)
+ last_page = extracted_data(name: 'badge2')
+
+ allow_next_instance_of(BulkImports::Common::Extractors::RestExtractor) do |extractor|
+ allow(extractor)
+ .to receive(:extract)
+ .and_return(first_page, last_page)
+ end
+
+ expect { subject.run }.to change(Badge, :count).by(2)
+
+ badge = group.badges.last
+
+ expect(badge.name).to eq('badge2')
+ expect(badge.link_url).to eq(badge_data['link_url'])
+ expect(badge.image_url).to eq(badge_data['image_url'])
+ end
+
+ describe '#load' do
+ it 'creates a badge' do
+ expect { subject.load(context, badge_data) }.to change(Badge, :count).by(1)
+
+ badge = group.badges.first
+
+ badge_data.each do |key, value|
+ expect(badge[key]).to eq(value)
+ end
+ end
+
+ it 'does nothing when the data is blank' do
+ expect { subject.load(context, nil) }.not_to change(Badge, :count)
+ end
+ end
+
+ describe '#transform' do
+ it 'return transformed badge hash' do
+ badge = subject.transform(context, badge_data)
+
+ expect(badge[:name]).to eq('badge')
+ expect(badge[:link_url]).to eq(badge_data['link_url'])
+ expect(badge[:image_url]).to eq(badge_data['image_url'])
+ expect(badge.keys).to contain_exactly(:name, :link_url, :image_url)
+ end
+
+ context 'when data is blank' do
+ it 'does nothing when the data is blank' do
+ expect(subject.transform(context, nil)).to be_nil
+ end
+ end
+ end
+
+ describe 'pipeline parts' do
+ it { expect(described_class).to include_module(BulkImports::Pipeline) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
+
+ it 'has extractors' do
+ expect(described_class.get_extractor)
+ .to eq(
+ klass: BulkImports::Common::Extractors::RestExtractor,
+ options: {
+ query: BulkImports::Groups::Rest::GetBadgesQuery
+ }
+ )
+ end
+
+ it 'has transformers' do
+ expect(described_class.transformers)
+ .to contain_exactly(
+ { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil }
+ )
+ end
+ end
+
+ def badge_data(name = 'badge')
+ {
+ 'name' => name,
+ 'link_url' => 'https://gitlab.example.com',
+ 'image_url' => 'https://gitlab.example.com/image.png'
+ }
+ end
+
+ def extracted_data(name: 'badge', has_next_page: false)
+ page_info = {
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? '2' : nil
+ }
+
+ BulkImports::Pipeline::ExtractedData.new(data: [badge_data(name)], page_info: page_info)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb b/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb
new file mode 100644
index 00000000000..eef6848e118
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Rest::GetBadgesQuery do
+ describe '.to_h' do
+ it 'returns query resource and page info' do
+ entity = create(:bulk_import_entity)
+ tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
+ encoded_full_path = ERB::Util.url_encode(entity.source_full_path)
+ expected = {
+ resource: ['groups', encoded_full_path, 'badges'].join('/'),
+ query: {
+ page: context.tracker.next_page
+ }
+ }
+
+ expect(described_class.to_h(context)).to eq(expected)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/importers/group_importer_spec.rb b/spec/lib/bulk_imports/importers/group_importer_spec.rb
index 9579ee53251..a7e017ad71c 100644
--- a/spec/lib/bulk_imports/importers/group_importer_spec.rb
+++ b/spec/lib/bulk_imports/importers/group_importer_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe BulkImports::Importers::GroupImporter do
expect_to_run_pipeline BulkImports::Groups::Pipelines::MembersPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::LabelsPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::MilestonesPipeline, context: context
+ expect_to_run_pipeline BulkImports::Groups::Pipelines::BadgesPipeline, context: context
if Gitlab.ee?
expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicsPipeline'.constantize, context: context)
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index 5e24102ed1f..dea54f7315d 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Emails::MergeRequests do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
- is_expected.to have_body_text('You have been mentioned in Merge Request')
+ is_expected.to have_body_text('You have been mentioned in merge request')
is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
is_expected.to have_text_part_content(assignee.name)
is_expected.to have_text_part_content(reviewer.name)
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index c1afc35ee89..80065eb97dd 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -126,26 +126,6 @@ RSpec.describe 'project routing' do
it 'to #archive with "/" in route' do
expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome')
end
-
- it 'to #archive_alternative' do
- expect(get('/gitlab/gitlabhq/-/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', append_sha: true)
- end
-
- it 'to #archive_deprecated' do
- expect(get('/gitlab/gitlabhq/-/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', append_sha: true)
- end
-
- it 'to #archive_deprecated format:zip' do
- expect(get('/gitlab/gitlabhq/-/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master', append_sha: true)
- end
-
- it 'to #archive_deprecated format:tar.bz2' do
- expect(get('/gitlab/gitlabhq/-/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master', append_sha: true)
- end
-
- it 'to #archive_deprecated with "/" in route' do
- expect(get('/gitlab/gitlabhq/-/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'improve/awesome', append_sha: true)
- end
end
describe Projects::BranchesController, 'routing' do
diff --git a/spec/rubocop/cop/graphql/descriptions_spec.rb b/spec/rubocop/cop/graphql/descriptions_spec.rb
index af660aee165..9709a253bdc 100644
--- a/spec/rubocop/cop/graphql/descriptions_spec.rb
+++ b/spec/rubocop/cop/graphql/descriptions_spec.rb
@@ -6,7 +6,7 @@ require_relative '../../../../rubocop/cop/graphql/descriptions'
RSpec.describe RuboCop::Cop::Graphql::Descriptions do
subject(:cop) { described_class.new }
- context 'fields' do
+ context 'with fields' do
it 'adds an offense when there is no description' do
expect_offense(<<~TYPE)
module Types
@@ -46,9 +46,19 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions do
end
TYPE
end
+
+ it 'does not add an offense when there is a resolver' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing, resolver: ThingResolver
+ end
+ end
+ TYPE
+ end
end
- context 'arguments' do
+ context 'with arguments' do
it 'adds an offense when there is no description' do
expect_offense(<<~TYPE)
module Types
@@ -90,7 +100,7 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions do
end
end
- context 'enum values' do
+ context 'with enum values' do
it 'adds an offense when there is no description' do
expect_offense(<<~TYPE)
module Types
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index d32c1397c9b..02b48e8ba06 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -615,13 +615,25 @@ module Ci
create(:ci_build, pipeline: pipeline, tag_list: %w[non-matching])
end
- it "observes queue size of only matching jobs" do
+ it 'observes queue size of only matching jobs' do
# pending_job + 2 x matching ones
expect(Gitlab::Ci::Queue::Metrics.queue_size_total).to receive(:observe)
.with({ runner_type: specific_runner.runner_type }, 3)
expect(execute(specific_runner)).to eq(pending_job)
end
+
+ it 'observes queue processing time by the runner type' do
+ expect(Gitlab::Ci::Queue::Metrics.queue_iteration_duration_seconds)
+ .to receive(:observe)
+ .with({ runner_type: specific_runner.runner_type }, anything)
+
+ expect(Gitlab::Ci::Queue::Metrics.queue_retrieval_duration_seconds)
+ .to receive(:observe)
+ .with({ runner_type: specific_runner.runner_type }, anything)
+
+ expect(execute(specific_runner)).to eq(pending_job)
+ end
end
context 'when ci_register_job_temporary_lock is enabled' do
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
index d5d8884b55a..3514ce286d6 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples 'User views empty wiki' do
end
end
- shared_examples 'empty wiki message' do |writable: false, issuable: false, confluence: false|
+ shared_examples 'empty wiki message' do |writable: false, issuable: false, confluence: false, expect_button: true|
# This mirrors the logic in:
# - app/views/shared/empty_states/_wikis.html.haml
# - WikiHelper#wiki_empty_state_messages
@@ -37,7 +37,7 @@ RSpec.shared_examples 'User views empty wiki' do
if issuable && !writable
expect(element).to have_content("improve the wiki for this #{container_name}")
expect(element).to have_link("issue tracker", href: project_issues_path(project))
- expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
+ expect(element.has_link?("Suggest wiki improvement", href: new_project_issue_path(project))).to be(expect_button)
else
expect(element).not_to have_content("improve the wiki for this #{container_name}")
expect(element).not_to have_link("issue tracker")