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>2022-06-30 21:09:30 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-30 21:09:30 +0300
commit370736438075748c36abd7fd7dd32a8ef98048f9 (patch)
treed74dd4529092edeb7dcb914bf0311f962d89e7bb
parente7b262a4c5cf70fed6eb25ba7a0eb1336e6eb639 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/batch_comments/components/submit_dropdown.vue20
-rw-r--r--app/assets/javascripts/integrations/constants.js2
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue26
-rw-r--r--app/assets/javascripts/integrations/edit/components/sections/configuration.vue1
-rw-r--r--app/assets/javascripts/releases/components/app_edit_new.vue37
-rw-r--r--app/assets/javascripts/releases/components/release_block_footer.vue4
-rw-r--r--app/assets/javascripts/releases/graphql/fragments/release_for_editing.fragment.graphql1
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/actions.js4
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/getters.js1
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js1
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/mutations.js4
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/state.js2
-rw-r--r--app/assets/javascripts/releases/util.js6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue14
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue3
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/controllers/projects/settings/integrations_controller.rb4
-rw-r--r--app/helpers/releases_helper.rb3
-rw-r--r--app/helpers/todos_helper.rb10
-rw-r--r--app/models/todo.rb4
-rw-r--r--db/post_migrate/20220630050050_index_vulnerability_reads_on_casted_cluster_agent_id_full.rb18
-rw-r--r--db/schema_migrations/202206300500501
-rw-r--r--db/structure.sql2
-rw-r--r--doc/api/usage_data.md2
-rw-r--r--doc/development/service_ping/metrics_dictionary.md2
-rw-r--r--lib/gitlab/database/background_migration/batched_migration_runner.rb5
-rw-r--r--locale/gitlab.pot10
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/project/settings/integrations.rb5
-rw-r--r--qa/qa/page/project/settings/services/pipeline_status_emails.rb35
-rw-r--r--qa/qa/resource/user.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb58
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_status_emails_spec.rb134
-rw-r--r--qa/qa/vendor/mail_hog/api.rb75
-rw-r--r--spec/controllers/projects/settings/integrations_controller_spec.rb6
-rw-r--r--spec/factories/keys.rb11
-rw-r--r--spec/features/admin/users/user_spec.rb4
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb19
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js57
-rw-r--r--spec/frontend/releases/__snapshots__/util_spec.js.snap7
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js27
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js26
-rw-r--r--spec/frontend/releases/components/release_block_spec.js5
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js9
-rw-r--r--spec/frontend/releases/stores/modules/detail/getters_spec.js2
-rw-r--r--spec/frontend/releases/stores/modules/detail/mutations_spec.js11
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js14
-rw-r--r--spec/helpers/releases_helper_spec.rb2
-rw-r--r--spec/helpers/todos_helper_spec.rb55
-rw-r--r--spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/jwt_spec.rb4
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb7
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/models/todo_spec.rb20
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/requests/jwks_controller_spec.rb6
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb3
-rw-r--r--yarn.lock8
59 files changed, 673 insertions, 149 deletions
diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
index 5f4a1e44ea3..b070848cae9 100644
--- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
@@ -22,6 +22,18 @@ export default {
computed: {
...mapGetters(['getNotesData', 'getNoteableData', 'noteableType', 'getCurrentUserLastNote']),
},
+ mounted() {
+ // We override the Bootstrap Vue click outside behaviour
+ // to allow for clicking in the autocomplete dropdowns
+ // without this override the submit dropdown will close
+ // whenever a item in the autocomplete dropdown is clicked
+ const originalClickOutHandler = this.$refs.dropdown.$refs.dropdown.clickOutHandler;
+ this.$refs.dropdown.$refs.dropdown.clickOutHandler = (e) => {
+ if (!e.target.closest('.atwho-container')) {
+ originalClickOutHandler(e);
+ }
+ };
+ },
methods: {
...mapActions('batchComments', ['publishReview']),
async submitReview() {
@@ -52,7 +64,13 @@ export default {
</script>
<template>
- <gl-dropdown right class="submit-review-dropdown" variant="info" category="secondary">
+ <gl-dropdown
+ ref="dropdown"
+ right
+ class="submit-review-dropdown"
+ variant="info"
+ category="secondary"
+ >
<template #button-content>
{{ __('Finish review') }}
<gl-icon class="dropdown-chevron" name="chevron-up" />
diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js
index e4f6e931ec0..1973a3c91ef 100644
--- a/app/assets/javascripts/integrations/constants.js
+++ b/app/assets/javascripts/integrations/constants.js
@@ -18,7 +18,7 @@ export const overrideDropdownDescriptions = {
};
export const I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE = s__(
- 'Integrations|Connection failed. Please check your settings.',
+ 'Integrations|Connection failed. Check your integration settings.',
);
export const I18N_DEFAULT_ERROR_MESSAGE = __('Something went wrong on our end.');
export const I18N_SUCCESSFUL_CONNECTION_MESSAGE = s__('Integrations|Connection successful.');
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 9307d7c2d3d..f1f574c6424 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -140,15 +140,24 @@ export default {
this.isTesting = true;
testIntegrationSettings(this.propsSource.testPath, this.getFormData())
- .then(({ data: { error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } }) => {
- if (error) {
- this.setIsValidated();
- this.$toast.show(message);
- return;
- }
+ .then(
+ ({
+ data: {
+ error,
+ message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
+ service_response: serviceResponse,
+ },
+ }) => {
+ if (error) {
+ const errorMessage = serviceResponse ? [message, serviceResponse].join(' ') : message;
+ this.setIsValidated();
+ this.$toast.show(errorMessage);
+ return;
+ }
- this.$toast.show(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
- })
+ this.$toast.show(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
+ },
+ )
.catch((error) => {
this.$toast.show(I18N_DEFAULT_ERROR_MESSAGE);
Sentry.captureException(error);
@@ -284,6 +293,7 @@ export default {
:key="`${currentKey}-${field.name}`"
v-bind="field"
:is-validated="isValidated"
+ :data-qa-selector="`${field.name}_div`"
/>
</div>
</div>
diff --git a/app/assets/javascripts/integrations/edit/components/sections/configuration.vue b/app/assets/javascripts/integrations/edit/components/sections/configuration.vue
index 9e1ad24ae9f..b8fd8995744 100644
--- a/app/assets/javascripts/integrations/edit/components/sections/configuration.vue
+++ b/app/assets/javascripts/integrations/edit/components/sections/configuration.vue
@@ -33,6 +33,7 @@ export default {
:key="`${currentKey}-${field.name}`"
v-bind="field"
:is-validated="isValidated"
+ :data-qa-selector="`${field.name}_div`"
/>
</div>
</template>
diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue
index 327da1fb2a1..716b78b94fc 100644
--- a/app/assets/javascripts/releases/components/app_edit_new.vue
+++ b/app/assets/javascripts/releases/components/app_edit_new.vue
@@ -1,5 +1,13 @@
<script>
-import { GlButton, GlFormCheckbox, GlFormInput, GlFormGroup, GlLink, GlSprintf } from '@gitlab/ui';
+import {
+ GlButton,
+ GlDatepicker,
+ GlFormCheckbox,
+ GlFormInput,
+ GlFormGroup,
+ GlLink,
+ GlSprintf,
+} from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { isSameOriginUrl, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
@@ -16,6 +24,7 @@ export default {
GlFormInput,
GlFormGroup,
GlButton,
+ GlDatepicker,
GlLink,
GlSprintf,
MarkdownField,
@@ -31,6 +40,7 @@ export default {
'markdownDocsPath',
'markdownPreviewPath',
'editReleaseDocsPath',
+ 'upcomingReleaseDocsPath',
'releasesPagePath',
'release',
'newMilestonePath',
@@ -76,6 +86,14 @@ export default {
this.updateIncludeTagNotes(includeTagNotes);
},
},
+ releasedAt: {
+ get() {
+ return this.release.releasedAt;
+ },
+ set(date) {
+ this.updateReleasedAt(date);
+ },
+ },
cancelPath() {
const backUrl = getParameterByName(BACK_URL_PARAM);
@@ -118,6 +136,7 @@ export default {
'updateReleaseNotes',
'updateReleaseMilestones',
'updateIncludeTagNotes',
+ 'updateReleasedAt',
]),
submitForm() {
if (!this.isFormSubmissionDisabled) {
@@ -166,6 +185,22 @@ export default {
/>
</div>
</gl-form-group>
+ <gl-form-group :label="__('Release date')" label-for="release-released-at">
+ <template #label-description>
+ <gl-sprintf
+ :message="
+ __(
+ 'The date when the release is ready. A release with a date in the future is labeled as an %{linkStart}Upcoming Release%{linkEnd}.',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="upcomingReleaseDocsPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
+ <gl-datepicker id="release-released-at" v-model="releasedAt" :default-date="releasedAt" />
+ </gl-form-group>
<gl-form-group data-testid="release-notes">
<label for="release-notes">{{ __('Release notes') }}</label>
<div class="bordered-box pr-3 pl-3">
diff --git a/app/assets/javascripts/releases/components/release_block_footer.vue b/app/assets/javascripts/releases/components/release_block_footer.vue
index 91d6d0911a4..e7eaf5a49bf 100644
--- a/app/assets/javascripts/releases/components/release_block_footer.vue
+++ b/app/assets/javascripts/releases/components/release_block_footer.vue
@@ -42,9 +42,9 @@ export default {
default: null,
},
releasedAt: {
- type: String,
+ type: Date,
required: false,
- default: '',
+ default: null,
},
},
computed: {
diff --git a/app/assets/javascripts/releases/graphql/fragments/release_for_editing.fragment.graphql b/app/assets/javascripts/releases/graphql/fragments/release_for_editing.fragment.graphql
index 236d266a40a..f6ed9a8317c 100644
--- a/app/assets/javascripts/releases/graphql/fragments/release_for_editing.fragment.graphql
+++ b/app/assets/javascripts/releases/graphql/fragments/release_for_editing.fragment.graphql
@@ -3,6 +3,7 @@ fragment ReleaseForEditing on Release {
name
tagName
description
+ releasedAt
assets {
links {
nodes {
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
index 08197377f61..bf4400ea59a 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
@@ -246,3 +246,7 @@ export const fetchTagNotes = ({ commit, state }, tagName) => {
export const updateIncludeTagNotes = ({ commit }, includeTagNotes) => {
commit(types.UPDATE_INCLUDE_TAG_NOTES, includeTagNotes);
};
+
+export const updateReleasedAt = ({ commit }, releasedAt) => {
+ commit(types.UPDATE_RELEASED_AT, releasedAt);
+};
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
index 0ca5eb9931a..240e85b75f3 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
@@ -138,6 +138,7 @@ export const releaseUpdateMutatationVariables = (state, getters) => {
projectPath: state.projectPath,
tagName: state.release.tagName,
name,
+ releasedAt: state.release.releasedAt,
description: state.includeTagNotes
? getters.formattedReleaseNotes
: state.release.description,
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js b/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
index daa077309a1..fca13138ee2 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
@@ -26,3 +26,4 @@ export const RECEIVE_TAG_NOTES_SUCCESS = 'RECEIVE_TAG_NOTES_SUCCESS';
export const RECEIVE_TAG_NOTES_ERROR = 'RECEIVE_TAG_NOTES_ERROR';
export const UPDATE_INCLUDE_TAG_NOTES = 'UPDATE_INCLUDE_TAG_NOTES';
+export const UPDATE_RELEASED_AT = 'UPDATE_RELEASED_AT';
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
index 6b22468bbfe..e28654a8980 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
@@ -14,6 +14,7 @@ export default {
description: '',
milestones: [],
groupMilestones: [],
+ releasedAt: new Date(),
assets: {
links: [],
},
@@ -113,4 +114,7 @@ export default {
[types.UPDATE_INCLUDE_TAG_NOTES](state, includeTagNotes) {
state.includeTagNotes = includeTagNotes;
},
+ [types.UPDATE_RELEASED_AT](state, releasedAt) {
+ state.release.releasedAt = releasedAt;
+ },
};
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/state.js b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
index 33cb3ee06d0..f866daa2b1e 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/state.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
@@ -10,6 +10,7 @@ export default ({
newMilestonePath,
releasesPagePath,
editReleaseDocsPath,
+ upcomingReleaseDocsPath,
tagName = null,
defaultBranch = null,
@@ -25,6 +26,7 @@ export default ({
newMilestonePath,
releasesPagePath,
editReleaseDocsPath,
+ upcomingReleaseDocsPath,
/**
* The name of the tag associated with the release, provided by the backend.
diff --git a/app/assets/javascripts/releases/util.js b/app/assets/javascripts/releases/util.js
index 22d5fb4f620..21aad4d716e 100644
--- a/app/assets/javascripts/releases/util.js
+++ b/app/assets/javascripts/releases/util.js
@@ -11,10 +11,13 @@ const convertScalarProperties = (graphQLRelease) =>
'tagPath',
'description',
'descriptionHtml',
- 'releasedAt',
'upcomingRelease',
]);
+const convertDateProperties = ({ releasedAt }) => ({
+ releasedAt: new Date(releasedAt),
+});
+
const convertAssets = (graphQLRelease) => {
let sources = [];
if (graphQLRelease.assets.sources?.nodes) {
@@ -88,6 +91,7 @@ const convertMilestones = (graphQLRelease) => ({
*/
export const convertGraphQLRelease = (graphQLRelease) => ({
...convertScalarProperties(graphQLRelease),
+ ...convertDateProperties(graphQLRelease),
...convertAssets(graphQLRelease),
...convertEvidences(graphQLRelease),
...convertLinks(graphQLRelease),
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
index c20dd3b677d..d17c8a123d5 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -72,9 +72,12 @@ export default {
},
},
computed: {
+ isMergeRequest() {
+ return this.issuableType === IssuableType.MergeRequest;
+ },
cannotMerge() {
const canMerge = this.user.mergeRequestInteraction?.canMerge || this.user.can_merge;
- return this.issuableType === IssuableType.MergeRequest && !canMerge;
+ return this.isMergeRequest && !canMerge;
},
tooltipTitle() {
const { name = '', availability = '' } = this.user;
@@ -86,6 +89,10 @@ export default {
});
},
tooltipOption() {
+ if (this.isMergeRequest) {
+ return null;
+ }
+
return {
container: 'body',
placement: this.tooltipPlacement,
@@ -96,6 +103,10 @@ export default {
return this.user.web_url || this.user.webUrl;
},
assigneeId() {
+ if (this.isMergeRequest) {
+ return null;
+ }
+
return isGid(this.user.id) ? getIdFromGraphQLId(this.user.id) : this.user.id;
},
},
@@ -105,6 +116,7 @@ export default {
<template>
<!-- must be `d-inline-block` or parent flex-basis causes width issues -->
<gl-link
+ v-gl-tooltip="tooltipOption"
:href="assigneeUrl"
:title="tooltipTitle"
:data-user-id="assigneeId"
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
index 36a08482e69..c9b0a4ae2b3 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
@@ -68,10 +68,9 @@ export default {
<template>
<!-- must be `d-inline-block` or parent flex-basis causes width issues -->
<gl-link
+ v-gl-tooltip="tooltipOption"
:href="reviewerUrl"
:title="tooltipTitle"
- :data-user-id="user.id"
- data-placement="left"
class="gl-display-inline-block js-user-link"
>
<!-- use d-flex so that slot can be appropriately styled -->
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index eeffc4fc21b..1e921b4234e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -432,7 +432,6 @@ $gl-input-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
-$gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
$browser-scrollbar-size: 10px;
diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb
index 443274608e3..cee9e9feb7b 100644
--- a/app/controllers/projects/settings/integrations_controller.rb
+++ b/app/controllers/projects/settings/integrations_controller.rb
@@ -89,7 +89,7 @@ module Projects
unless result[:success]
return {
error: true,
- message: s_('Integrations|Connection failed. Please check your settings.'),
+ message: s_('Integrations|Connection failed. Check your integration settings.'),
service_response: result[:message].to_s,
test_failed: true
}
@@ -99,7 +99,7 @@ module Projects
rescue *Gitlab::HTTP::HTTP_ERRORS => e
{
error: true,
- message: s_('Integrations|Connection failed. Please check your settings.'),
+ message: s_('Integrations|Connection failed. Check your integration settings.'),
service_response: e.message,
test_failed: true
}
diff --git a/app/helpers/releases_helper.rb b/app/helpers/releases_helper.rb
index a516ac85131..0fe60c37b85 100644
--- a/app/helpers/releases_helper.rb
+++ b/app/helpers/releases_helper.rb
@@ -81,7 +81,8 @@ module ReleasesHelper
release_assets_docs_path: releases_help_page_path(anchor: 'release-assets'),
manage_milestones_path: project_milestones_path(@project),
new_milestone_path: new_project_milestone_path(@project),
- edit_release_docs_path: releases_help_page_path(anchor: 'edit-a-release')
+ edit_release_docs_path: releases_help_page_path(anchor: 'edit-a-release'),
+ upcoming_release_docs_path: releases_help_page_path(anchor: 'upcoming-releases')
}
end
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 8529959f73c..c4cfd3b2287 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -66,7 +66,13 @@ module TodosHelper
return _('design') if todo.for_design?
return _('alert') if todo.for_alert?
- todo.target_type.titleize.downcase
+ target_type = if todo.for_issue_or_work_item?
+ todo.target.issue_type
+ else
+ todo.target_type
+ end
+
+ target_type.titleize.downcase
end
def todo_target_path(todo)
@@ -80,6 +86,8 @@ module TodosHelper
todos_design_path(todo, path_options)
elsif todo.for_alert?
details_project_alert_management_path(todo.project, todo.target)
+ elsif todo.for_issue_or_work_item?
+ Gitlab::UrlBuilder.build(todo.target, only_path: true)
else
path = [todo.resource_parent, todo.target]
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 45ab770a0f6..cff7a93f72f 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -230,6 +230,10 @@ class Todo < ApplicationRecord
target_type == AlertManagement::Alert.name
end
+ def for_issue_or_work_item?
+ [Issue.name, WorkItem.name].any? { |klass_name| target_type == klass_name }
+ end
+
# override to return commits, which are not active record
def target
if for_commit?
diff --git a/db/post_migrate/20220630050050_index_vulnerability_reads_on_casted_cluster_agent_id_full.rb b/db/post_migrate/20220630050050_index_vulnerability_reads_on_casted_cluster_agent_id_full.rb
new file mode 100644
index 00000000000..58b6342e30f
--- /dev/null
+++ b/db/post_migrate/20220630050050_index_vulnerability_reads_on_casted_cluster_agent_id_full.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class IndexVulnerabilityReadsOnCastedClusterAgentIdFull < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_vuln_reads_on_casted_cluster_agent_id_where_it_is_null'
+
+ def up
+ add_concurrent_index :vulnerability_reads,
+ :casted_cluster_agent_id,
+ name: INDEX_NAME,
+ where: 'casted_cluster_agent_id IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index_by_name :vulnerability_reads, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20220630050050 b/db/schema_migrations/20220630050050
new file mode 100644
index 00000000000..2ec998847eb
--- /dev/null
+++ b/db/schema_migrations/20220630050050
@@ -0,0 +1 @@
+dfb314ef76efc54a2464e6b84e71753caf58bc8508f9e64b403066ea4847fe56 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 817e0795f2f..8ee7d12ede8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29909,6 +29909,8 @@ COMMENT ON INDEX index_verification_codes_on_phone_and_visitor_id_code IS 'JiHu-
CREATE UNIQUE INDEX index_vuln_historical_statistics_on_project_id_and_date ON vulnerability_historical_statistics USING btree (project_id, date);
+CREATE INDEX index_vuln_reads_on_casted_cluster_agent_id_where_it_is_null ON vulnerability_reads USING btree (casted_cluster_agent_id) WHERE (casted_cluster_agent_id IS NOT NULL);
+
CREATE INDEX index_vuln_reads_on_project_id_state_severity_and_vuln_id ON vulnerability_reads USING btree (project_id, state, severity, vulnerability_id DESC);
CREATE INDEX index_vulnerabilites_common_finder_query ON vulnerabilities USING btree (project_id, state, report_type, severity, id);
diff --git a/doc/api/usage_data.md b/doc/api/usage_data.md
index 6e50794a0ac..9a94c8fd12a 100644
--- a/doc/api/usage_data.md
+++ b/doc/api/usage_data.md
@@ -34,7 +34,7 @@ Example response:
by month
product_section: enablement
product_stage: enablement
- product_group: group::global search
+ product_group: global_search
product_category: global_search
value_type: number
status: active
diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md
index dd201678e51..58cf95d12c3 100644
--- a/doc/development/service_ping/metrics_dictionary.md
+++ b/doc/development/service_ping/metrics_dictionary.md
@@ -207,7 +207,7 @@ description: GitLab instance unique identifier
product_category: collection
product_section: growth
product_stage: growth
-product_group: group::product intelligence
+product_group: product_intelligence
value_type: string
status: active
milestone: 9.1
diff --git a/lib/gitlab/database/background_migration/batched_migration_runner.rb b/lib/gitlab/database/background_migration/batched_migration_runner.rb
index d15886f02b8..c93e0c462b5 100644
--- a/lib/gitlab/database/background_migration/batched_migration_runner.rb
+++ b/lib/gitlab/database/background_migration/batched_migration_runner.rb
@@ -76,7 +76,10 @@ module Gitlab
run_migration_while(migration, :finalizing)
- raise FailedToFinalize unless migration.finished?
+ error_message = "Batched migration #{migration.job_class_name} could not be completed and a manual action is required."\
+ "Check the admin panel at (`/admin/background_migrations`) for more details."
+
+ raise FailedToFinalize, error_message unless migration.finished?
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8541420acc3..108ad5b925e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18137,7 +18137,7 @@ msgstr ""
msgid "GroupActivityMetrics|Issues created"
msgstr ""
-msgid "GroupActivityMetrics|Last 90 days"
+msgid "GroupActivityMetrics|Last 30 days"
msgstr ""
msgid "GroupActivityMetrics|Members added"
@@ -20766,7 +20766,7 @@ msgstr ""
msgid "Integrations|Connection details"
msgstr ""
-msgid "Integrations|Connection failed. Please check your settings."
+msgid "Integrations|Connection failed. Check your integration settings."
msgstr ""
msgid "Integrations|Connection successful."
@@ -31795,6 +31795,9 @@ msgstr ""
msgid "Release assets documentation"
msgstr ""
+msgid "Release date"
+msgstr ""
+
msgid "Release does not have the same project as the milestone"
msgstr ""
@@ -38397,6 +38400,9 @@ msgstr ""
msgid "The data source is connected, but there is no data to display. %{documentationLink}"
msgstr ""
+msgid "The date when the release is ready. A release with a date in the future is labeled as an %{linkStart}Upcoming Release%{linkEnd}."
+msgstr ""
+
msgid "The default CI/CD configuration file and path for new projects."
msgstr ""
diff --git a/package.json b/package.json
index e33699b0402..34027119cbc 100644
--- a/package.json
+++ b/package.json
@@ -170,7 +170,7 @@
"sortablejs": "^1.10.2",
"string-hash": "1.1.3",
"style-loader": "^2.0.0",
- "swagger-ui-dist": "4.8.0",
+ "swagger-ui-dist": "4.12.0",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4",
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
index 420dcb63918..0d5515aacdf 100644
--- a/qa/qa/page/project/settings/integrations.rb
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -8,12 +8,17 @@ module QA
view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
+ element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
end
def click_on_prometheus_integration
click_element :prometheus_link
end
+ def click_pipelines_email_link
+ click_element :pipelines_email_link
+ end
+
def click_jira_link
click_element :jira_link
end
diff --git a/qa/qa/page/project/settings/services/pipeline_status_emails.rb b/qa/qa/page/project/settings/services/pipeline_status_emails.rb
new file mode 100644
index 00000000000..2f78577e3d5
--- /dev/null
+++ b/qa/qa/page/project/settings/services/pipeline_status_emails.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ module Services
+ class PipelineStatusEmails < QA::Page::Base
+ view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do
+ element :recipients_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern
+ element :notify_only_broken_pipelines_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern
+ element :save_changes_button
+ end
+
+ def set_recipients(emails)
+ within_element :recipients_div do
+ fill_in 'Recipients', with: emails.join(',')
+ end
+ end
+
+ def toggle_notify_broken_pipelines
+ within_element :notify_only_broken_pipelines_div do
+ uncheck 'Notify only broken pipelines', allow_label_click: true
+ end
+ end
+
+ def click_save_button
+ click_element(:save_changes_button)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index 1b848feb50d..2fb8b18b71f 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -104,12 +104,6 @@ module QA
false
end
- def api_delete
- super
-
- QA::Runtime::Logger.debug("Deleted user '#{username}'")
- end
-
def api_delete_path
"/users/#{id}?hard_delete=#{hard_delete_on_api_removal}"
rescue NoValueError
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
index 44cae31f5d8..b1d59b90e9c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
@@ -2,49 +2,45 @@
module QA
RSpec.describe 'Manage' do
- describe 'Check for broken images', :requires_admin do
- before(:context) do
- @api_client = Runtime::API::Client.as_admin
- @new_user = Resource::User.fabricate_via_api! do |user|
- user.api_client = @api_client
- end
- @new_admin = Resource::User.fabricate_via_api! do |user|
- user.admin = true
- user.api_client = @api_client
- end
+ shared_examples 'loads all images' do |admin|
+ let(:api_client) { Runtime::API::Client.as_admin }
- Page::Main::Menu.perform(&:sign_out_if_signed_in)
+ let(:user) do
+ Resource::User.fabricate_via_api! do |resource|
+ resource.admin = admin
+ resource.api_client = api_client
+ end
end
- after(:context) do
- @new_user.remove_via_api!
- @new_admin.remove_via_api!
+ after do
+ user.remove_via_api!
end
- shared_examples 'loads all images' do
- it 'loads all images' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: new_user) }
+ it 'loads all images' do
+ Flow::Login.sign_in(as: user)
- Page::Dashboard::Welcome.perform do |welcome|
- expect(welcome).to have_welcome_title("Welcome to GitLab")
+ Page::Dashboard::Welcome.perform do |welcome|
+ expect(welcome).to have_welcome_title("Welcome to GitLab")
- # This would be better if it were a visual validation test
- expect(welcome).to have_loaded_all_images
- end
+ # This would be better if it were a visual validation test
+ expect(welcome).to have_loaded_all_images
end
end
+ end
- context 'when logged in as a new user', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347885' do
- it_behaves_like 'loads all images' do
- let(:new_user) { @new_user }
- end
+ describe 'Check for broken images', :requires_admin, :reliable do
+ context(
+ 'when logged in as a new user',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347885'
+ ) do
+ it_behaves_like 'loads all images', false
end
- context 'when logged in as a new admin', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347884' do
- it_behaves_like 'loads all images' do
- let(:new_user) { @new_admin }
- end
+ context(
+ 'when logged in as a new admin',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347884'
+ ) do
+ it_behaves_like 'loads all images', true
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
index f41e5985622..b815186cd49 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
@@ -46,19 +46,22 @@ module QA
total = mailhog_data.dig('total')
subjects = mailhog_data.dig('items')
.map(&method(:mailhog_item_subject))
- .join("\n")
Runtime::Logger.debug(%Q[Total number of emails: #{total}])
- Runtime::Logger.debug(%Q[Subjects:\n#{subjects}])
+ Runtime::Logger.debug(%Q[Subjects:\n#{subjects.join("\n")}])
# Expect at least two invitation messages: group and project
- mailhog_data if total >= 2
+ mailhog_data if mailhog_project_message_count(subjects) >= 1
end
end
def mailhog_item_subject(item)
item.dig('Content', 'Headers', 'Subject', 0)
end
+
+ def mailhog_project_message_count(subjects)
+ subjects.count { |subject| subject.include?('project was granted') }
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_status_emails_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_status_emails_spec.rb
new file mode 100644
index 00000000000..f4794b3a904
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_status_emails_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.shared_examples 'notifies on a pipeline' do |exit_code|
+ before do
+ push_commit(exit_code: exit_code)
+ end
+
+ it 'sends an email' do
+ meta = exit_code_meta(exit_code)
+
+ project.visit!
+ Flow::Pipeline.wait_for_latest_pipeline(status: meta[:status])
+
+ messages = mail_hog_messages(mail_hog)
+ subjects = messages.map(&:subject)
+ targets = messages.map(&:to)
+
+ aggregate_failures do
+ expect(subjects).to include(meta[:email_subject])
+ expect(subjects).to include(/#{Regexp.escape(project.name)}/)
+ expect(targets).to include(*emails)
+ end
+ end
+ end
+
+ RSpec.describe 'Verify', :orchestrated, :runner, :requires_admin, :smtp do
+ describe 'Pipeline status emails' do
+ let(:executor) { "qa-runner-#{Time.now.to_i}" }
+ let(:emails) { %w[foo@bar.com baz@buzz.com] }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'pipeline-status-project'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = executor
+ runner.tags = [executor]
+ end
+ end
+
+ let(:mail_hog) { Vendor::MailHog::API.new }
+
+ before(:all) do
+ Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ before do
+ setup_pipeline_emails(emails)
+ end
+
+ describe 'when pipeline passes', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366240' do
+ include_examples 'notifies on a pipeline', 0
+ end
+
+ describe 'when pipeline fails', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366241' do
+ include_examples 'notifies on a pipeline', 1
+ end
+
+ def push_commit(exit_code: 0)
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: gitlab_ci_yaml(exit_code: exit_code)
+ }
+ ]
+ )
+ end
+ end
+
+ def setup_pipeline_emails(emails)
+ page.visit Runtime::Scenario.gitlab_address
+ Flow::Login.sign_in_unless_signed_in
+
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ QA::Page::Project::Settings::Integrations.perform(&:click_pipelines_email_link)
+
+ QA::Page::Project::Settings::Services::PipelineStatusEmails.perform do |pipeline_status_emails|
+ pipeline_status_emails.toggle_notify_broken_pipelines # notify on pass and fail
+ pipeline_status_emails.set_recipients(emails)
+ pipeline_status_emails.click_save_button
+ end
+ end
+
+ def gitlab_ci_yaml(exit_code: 0, tag: executor)
+ <<~YAML
+ test-pipeline-email:
+ tags:
+ - #{tag}
+ script: sleep 5; exit #{exit_code};
+ YAML
+ end
+
+ private
+
+ def exit_code_meta(exit_code)
+ {
+ 0 => { status: 'passed', email_subject: /Successful pipeline/ },
+ 1 => { status: 'failed', email_subject: /Failed pipeline/ }
+ }[exit_code]
+ end
+
+ def mail_hog_messages(mail_hog_api)
+ Support::Retrier.retry_until(sleep_interval: 1) do
+ Runtime::Logger.debug('Fetching email...')
+
+ messages = mail_hog_api.fetch_messages
+ logs = messages.map { |m| "#{m.to}: #{m.subject}" }
+
+ Runtime::Logger.debug("MailHog Logs: #{logs.join("\n")}")
+
+ # for failing pipelines we have three messages
+ # one for the owner
+ # and one for each recipient
+ messages if mail_hog_pipeline_count(messages) >= 2
+ end
+ end
+
+ def mail_hog_pipeline_count(messages)
+ messages.count { |message| message.subject.include?('pipeline') }
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/mail_hog/api.rb b/qa/qa/vendor/mail_hog/api.rb
new file mode 100644
index 00000000000..85eb0631624
--- /dev/null
+++ b/qa/qa/vendor/mail_hog/api.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module MailHog
+ # Represents a Set of messages from a MailHog response
+ class Messages
+ include Enumerable
+
+ attr_reader :data
+
+ def initialize(data)
+ @data = data
+ end
+
+ def total
+ data.dig('total')
+ end
+
+ def each
+ data.dig('items')&.each do |item|
+ yield MessageItem.new(item)
+ end
+ end
+ end
+
+ # Represents an email item from a MailHog response
+ class MessageItem
+ attr_reader :data
+
+ def initialize(data)
+ @data = data
+ end
+
+ def to
+ data.dig('Content', 'Headers', 'To', 0)
+ end
+
+ def subject
+ data.dig('Content', 'Headers', 'Subject', 0)
+ end
+ end
+
+ class API
+ include Support::API
+
+ attr_reader :hostname
+
+ def initialize(hostname: QA::Runtime::Env.mailhog_hostname || 'localhost')
+ @hostname = hostname
+ end
+
+ def base_url
+ "http://#{hostname}:8025"
+ end
+
+ def api_messages_url(version: 2)
+ "#{base_url}/api/v#{version}/messages"
+ end
+
+ def delete_messages
+ delete(api_messages_url(version: 1))
+ end
+
+ def fetch_messages
+ Messages.new(JSON.parse(fetch_messages_json))
+ end
+
+ def fetch_messages_json
+ get(api_messages_url).body
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb
index 847d3bf8956..8ee9f22aa7f 100644
--- a/spec/controllers/projects/settings/integrations_controller_spec.rb
+++ b/spec/controllers/projects/settings/integrations_controller_spec.rb
@@ -148,7 +148,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
expect(response).to be_successful
expect(json_response).to eq(
'error' => true,
- 'message' => 'Connection failed. Please check your settings.',
+ 'message' => 'Connection failed. Check your integration settings.',
'service_response' => '',
'test_failed' => true
)
@@ -163,7 +163,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
expect(response).to be_successful
expect(json_response).to eq(
'error' => true,
- 'message' => 'Connection failed. Please check your settings.',
+ 'message' => 'Connection failed. Check your integration settings.',
'service_response' => "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed",
'test_failed' => true
)
@@ -177,7 +177,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
expect(response).to be_successful
expect(json_response).to eq(
'error' => true,
- 'message' => 'Connection failed. Please check your settings.',
+ 'message' => 'Connection failed. Check your integration settings.',
'service_response' => 'Connection refused',
'test_failed' => true
)
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index a7478ce2657..ccf835fe56e 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -3,8 +3,13 @@
FactoryBot.define do
factory :key do
title
- key { SSHData::PrivateKey::RSA.generate(1024, unsafe_allow_small_key: true).public_key.openssh(comment: 'dummy@gitlab.com') }
-
+ key do
+ # Larger keys take longer to generate, and since this factory gets called frequently,
+ # let's only create the smallest one we need.
+ SSHData::PrivateKey::RSA.generate(
+ ::Gitlab::SSHPublicKey.supported_sizes(:rsa).min, unsafe_allow_small_key: true
+ ).public_key.openssh(comment: 'dummy@gitlab.com')
+ end
trait :expired do
to_create { |key| key.save!(validate: false) }
expires_at { 2.days.ago }
@@ -16,7 +21,7 @@ FactoryBot.define do
end
factory :key_without_comment do
- key { SSHData::PrivateKey::RSA.generate(1024, unsafe_allow_small_key: true).public_key.openssh }
+ key { SSHData::PrivateKey::RSA.generate(3072, unsafe_allow_small_key: true).public_key.openssh }
end
factory :deploy_key, class: 'DeployKey'
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index 18bb03f4617..bc88b90a2dd 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -372,8 +372,8 @@ RSpec.describe 'Admin::Users::User' do
describe 'show user keys', :js do
it do
- key1 = create(:key, user: user, title: 'ssh-rsa Key1', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1')
- key2 = create(:key, user: user, title: 'ssh-rsa Key2', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2')
+ key1 = create(:key, user: user, title: 'ssh-rsa Key1')
+ key2 = create(:key, user: user, title: 'ssh-rsa Key2')
visit admin_user_path(user)
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index adb43d60306..e02cd182b2c 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -60,6 +60,21 @@ RSpec.describe 'Dashboard Todos' do
end
end
+ context 'when todo references an issue of type task' do
+ let(:task) { create(:issue, :task, project: project) }
+ let!(:task_todo) { create(:todo, :mentioned, user: user, project: project, target: task, author: author) }
+
+ before do
+ sign_in(user)
+
+ visit dashboard_todos_path
+ end
+
+ it 'displays the correct issue type name' do
+ expect(page).to have_content('mentioned you on task')
+ end
+ end
+
context 'user has an unauthorized todo' do
before do
sign_in(user)
@@ -85,6 +100,10 @@ RSpec.describe 'Dashboard Todos' do
visit dashboard_todos_path
end
+ it 'displays the correct issue type name' do
+ expect(page).to have_content('mentioned you on issue')
+ end
+
it 'has todo present' do
expect(page).to have_selector('.todos-list .todo', count: 1)
end
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index a2bdece821f..21e57a2e33c 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -596,37 +596,42 @@ describe('IntegrationForm', () => {
});
describe.each`
- scenario | replyStatus | errorMessage | expectToast | expectSentry
- ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true}
- ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${'an error'} | ${false}
- ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false}
- `('$scenario', ({ replyStatus, errorMessage, expectToast, expectSentry }) => {
- beforeEach(async () => {
- mockAxios.onPut(mockTestPath).replyOnce(replyStatus, {
- error: Boolean(errorMessage),
- message: errorMessage,
+ scenario | replyStatus | errorMessage | serviceResponse | expectToast | expectSentry
+ ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true}
+ ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${undefined} | ${'an error'} | ${false}
+ ${'when "test settings" returns an error with details'} | ${httpStatus.OK} | ${'an error.'} | ${'extra info'} | ${'an error. extra info'} | ${false}
+ ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false}
+ `(
+ '$scenario',
+ ({ replyStatus, errorMessage, serviceResponse, expectToast, expectSentry }) => {
+ beforeEach(async () => {
+ mockAxios.onPut(mockTestPath).replyOnce(replyStatus, {
+ error: Boolean(errorMessage),
+ message: errorMessage,
+ service_response: serviceResponse,
+ });
+
+ await findTestButton().vm.$emit('click', new Event('click'));
+ await waitForPromises();
});
- await findTestButton().vm.$emit('click', new Event('click'));
- await waitForPromises();
- });
-
- it(`calls toast with '${expectToast}'`, () => {
- expect(mockToastShow).toHaveBeenCalledWith(expectToast);
- });
+ it(`calls toast with '${expectToast}'`, () => {
+ expect(mockToastShow).toHaveBeenCalledWith(expectToast);
+ });
- it('sets `loading` prop of test button to `false`', () => {
- expect(findTestButton().props('loading')).toBe(false);
- });
+ it('sets `loading` prop of test button to `false`', () => {
+ expect(findTestButton().props('loading')).toBe(false);
+ });
- it('sets save button `disabled` prop to `false`', () => {
- expect(findProjectSaveButton().props('disabled')).toBe(false);
- });
+ it('sets save button `disabled` prop to `false`', () => {
+ expect(findProjectSaveButton().props('disabled')).toBe(false);
+ });
- it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => {
- expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0);
- });
- });
+ it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => {
+ expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0);
+ });
+ },
+ );
});
});
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap
index fd2a8eec4d4..10d250c5ebb 100644
--- a/spec/frontend/releases/__snapshots__/util_spec.js.snap
+++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap
@@ -57,7 +57,7 @@ Object {
"evidences": Array [],
"milestones": Array [],
"name": "The second release",
- "releasedAt": "2019-01-10T00:00:00Z",
+ "releasedAt": 2019-01-10T00:00:00.000Z,
"tagName": "v1.2",
"tagPath": "/releases-namespace/releases-project/-/tags/v1.2",
"upcomingRelease": true,
@@ -188,7 +188,7 @@ Object {
},
],
"name": "The first release",
- "releasedAt": "2018-12-10T00:00:00Z",
+ "releasedAt": 2018-12-10T00:00:00.000Z,
"tagName": "v1.1",
"tagPath": "/releases-namespace/releases-project/-/tags/v1.1",
"upcomingRelease": true,
@@ -267,6 +267,7 @@ Object {
},
],
"name": "The first release",
+ "releasedAt": 2018-12-10T00:00:00.000Z,
"tagName": "v1.1",
},
}
@@ -400,7 +401,7 @@ Object {
},
],
"name": "The first release",
- "releasedAt": "2018-12-10T00:00:00Z",
+ "releasedAt": 2018-12-10T00:00:00.000Z,
"tagName": "v1.1",
"tagPath": "/releases-namespace/releases-project/-/tags/v1.1",
"upcomingRelease": true,
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index 80be27c92ff..a32001ed72b 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -1,21 +1,23 @@
-import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge } from 'lodash';
import Vuex from 'vuex';
import { nextTick } from 'vue';
-import { GlFormCheckbox } from '@gitlab/ui';
-import originalRelease from 'test_fixtures/api/releases/release.json';
+import { GlDatepicker, GlFormCheckbox } from '@gitlab/ui';
+import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json';
+import { convertOneReleaseGraphQLResponse } from '~/releases/util';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import * as commonUtils from '~/lib/utils/common_utils';
import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+const originalRelease = originalOneReleaseForEditingQueryResponse.data.project.release;
const originalMilestones = originalRelease.milestones;
const releasesPagePath = 'path/to/releases/page';
+const upcomingReleaseDocsPath = 'path/to/upcoming/release/docs';
describe('Release edit/new component', () => {
let wrapper;
@@ -33,6 +35,7 @@ describe('Release edit/new component', () => {
projectId: '8',
groupId: '42',
groupMilestonesAvailable: true,
+ upcomingReleaseDocsPath,
};
actions = {
@@ -68,7 +71,7 @@ describe('Release edit/new component', () => {
),
);
- wrapper = mount(ReleaseEditNewApp, {
+ wrapper = mountExtended(ReleaseEditNewApp, {
store,
provide: {
glFeatures: featureFlags,
@@ -88,7 +91,7 @@ describe('Release edit/new component', () => {
mock.onGet('/api/v4/projects/8/milestones').reply(200, originalMilestones);
- release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true });
+ release = convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse).data;
});
afterEach(() => {
@@ -128,6 +131,18 @@ describe('Release edit/new component', () => {
expect(wrapper.find('#release-title').element.value).toBe(release.name);
});
+ it('renders the released at date in the "Released at" datepicker', () => {
+ expect(wrapper.findComponent(GlDatepicker).props('value')).toBe(release.releasedAt);
+ });
+
+ it('links to the documentation on upcoming releases in the "Released at" description', () => {
+ const link = wrapper.findByRole('link', { name: 'Upcoming Release' });
+
+ expect(link.exists()).toBe(true);
+
+ expect(link.attributes('href')).toBe(upcomingReleaseDocsPath);
+ });
+
it('renders the release notes in the "Release notes" textarea', () => {
expect(wrapper.find('#release-notes').element.value).toBe(release.description);
});
diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js
index b095e9e1d78..848e802df4b 100644
--- a/spec/frontend/releases/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/components/release_block_footer_spec.js
@@ -2,14 +2,16 @@ import { GlLink, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
import { nextTick } from 'vue';
-import originalRelease from 'test_fixtures/api/releases/release.json';
+import originalOneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json';
+import { convertOneReleaseGraphQLResponse } from '~/releases/util';
import { trimText } from 'helpers/text_helper';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
// TODO: Encapsulate date helpers https://gitlab.com/gitlab-org/gitlab/-/issues/320883
const MONTHS_IN_MS = 1000 * 60 * 60 * 24 * 31;
-const mockFutureDate = new Date(new Date().getTime() + MONTHS_IN_MS).toISOString();
+const mockFutureDate = new Date(new Date().getTime() + MONTHS_IN_MS);
+
+const originalRelease = convertOneReleaseGraphQLResponse(originalOneReleaseQueryResponse).data;
describe('Release block footer', () => {
let wrapper;
@@ -18,7 +20,7 @@ describe('Release block footer', () => {
const factory = async (props = {}) => {
wrapper = mount(ReleaseBlockFooter, {
propsData: {
- ...convertObjectPropsToCamelCase(release, { deep: true }),
+ ...originalRelease,
...props,
},
});
@@ -55,8 +57,8 @@ describe('Release block footer', () => {
const commitLink = commitInfoSectionLink();
expect(commitLink.exists()).toBe(true);
- expect(commitLink.text()).toBe(release.commit.short_id);
- expect(commitLink.attributes('href')).toBe(release.commit_path);
+ expect(commitLink.text()).toBe(release.commit.shortId);
+ expect(commitLink.attributes('href')).toBe(release.commitPath);
});
it('renders the tag icon', () => {
@@ -70,8 +72,8 @@ describe('Release block footer', () => {
const commitLink = tagInfoSection().find(GlLink);
expect(commitLink.exists()).toBe(true);
- expect(commitLink.text()).toBe(release.tag_name);
- expect(commitLink.attributes('href')).toBe(release.tag_path);
+ expect(commitLink.text()).toBe(release.tagName);
+ expect(commitLink.attributes('href')).toBe(release.tagPath);
});
it('renders the author and creation time info', () => {
@@ -114,14 +116,14 @@ describe('Release block footer', () => {
const avatarImg = authorDateInfoSection().find('img');
expect(avatarImg.exists()).toBe(true);
- expect(avatarImg.attributes('src')).toBe(release.author.avatar_url);
+ expect(avatarImg.attributes('src')).toBe(release.author.avatarUrl);
});
it("renders a link to the author's profile", () => {
const authorLink = authorDateInfoSection().find(GlLink);
expect(authorLink.exists()).toBe(true);
- expect(authorLink.attributes('href')).toBe(release.author.web_url);
+ expect(authorLink.attributes('href')).toBe(release.author.webUrl);
});
});
@@ -138,7 +140,7 @@ describe('Release block footer', () => {
it('renders the commit SHA as plain text (instead of a link)', () => {
expect(commitInfoSectionLink().exists()).toBe(false);
- expect(commitInfoSection().text()).toBe(release.commit.short_id);
+ expect(commitInfoSection().text()).toBe(release.commit.shortId);
});
});
@@ -155,7 +157,7 @@ describe('Release block footer', () => {
it('renders the tag name as plain text (instead of a link)', () => {
expect(tagInfoSectionLink().exists()).toBe(false);
- expect(tagInfoSection().text()).toBe(release.tag_name);
+ expect(tagInfoSection().text()).toBe(release.tagName);
});
});
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index c4910ae9b2f..17e2af687a6 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -1,7 +1,8 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
import { nextTick } from 'vue';
-import originalRelease from 'test_fixtures/api/releases/release.json';
+import originalOneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json';
+import { convertOneReleaseGraphQLResponse } from '~/releases/util';
import * as commonUtils from '~/lib/utils/common_utils';
import * as urlUtility from '~/lib/utils/url_utility';
import EvidenceBlock from '~/releases/components/evidence_block.vue';
@@ -34,7 +35,7 @@ describe('Release block', () => {
beforeEach(() => {
jest.spyOn($.fn, 'renderGFM');
- release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true });
+ release = convertOneReleaseGraphQLResponse(originalOneReleaseQueryResponse).data;
});
afterEach(() => {
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index 41653f62ebf..c6aa67a5cd3 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -168,6 +168,15 @@ describe('Release edit/new actions', () => {
});
});
+ describe('updateReleasedAt', () => {
+ it(`commits ${types.UPDATE_RELEASED_AT} with the updated date`, () => {
+ const newDate = new Date();
+ return testAction(actions.updateReleasedAt, newDate, state, [
+ { type: types.UPDATE_RELEASED_AT, payload: newDate },
+ ]);
+ });
+ });
+
describe('updateCreateFrom', () => {
it(`commits ${types.UPDATE_CREATE_FROM} with the updated ref`, () => {
const newRef = 'my-feature-branch';
diff --git a/spec/frontend/releases/stores/modules/detail/getters_spec.js b/spec/frontend/releases/stores/modules/detail/getters_spec.js
index c42c6c00f56..310e6407ada 100644
--- a/spec/frontend/releases/stores/modules/detail/getters_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/getters_spec.js
@@ -302,6 +302,7 @@ describe('Release edit/new getters', () => {
name: 'release.name',
description: 'release.description',
milestones: ['release.milestone[0].title'],
+ releasedAt: new Date(2022, 5, 30),
},
},
{
@@ -310,6 +311,7 @@ describe('Release edit/new getters', () => {
name: 'release.name',
description: 'release.description',
milestones: ['release.milestone[0].title'],
+ releasedAt: new Date(2022, 5, 30),
},
],
[
diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
index 85844831e0b..32f0e07715d 100644
--- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
@@ -30,6 +30,7 @@ describe('Release edit/new mutations', () => {
description: '',
milestones: [],
groupMilestones: [],
+ releasedAt: new Date(),
assets: {
links: [],
},
@@ -82,6 +83,16 @@ describe('Release edit/new mutations', () => {
});
});
+ describe(`${types.UPDATE_RELEASED_AT}`, () => {
+ it("updates the release's released at date", () => {
+ state.release = release;
+ const newDate = new Date();
+ mutations[types.UPDATE_RELEASED_AT](state, newDate);
+
+ expect(state.release.releasedAt).toBe(newDate);
+ });
+ });
+
describe(`${types.UPDATE_CREATE_FROM}`, () => {
it('updates the ref that the ref will be created from', () => {
state.createFrom = 'main';
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
index a286eeef14f..517b4f12559 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -120,6 +120,7 @@ describe('AssigneeAvatarLink component', () => {
it('passes the correct user id for REST API', () => {
createComponent({
tooltipHasName: true,
+ issuableType: 'issue',
user: userDataMock(),
});
@@ -131,9 +132,22 @@ describe('AssigneeAvatarLink component', () => {
createComponent({
tooltipHasName: true,
+ issuableType: 'issue',
user: { ...userDataMock(), id: convertToGraphQLId(TYPE_USER, userId) },
});
expect(findUserLink().attributes('data-user-id')).toBe(String(userId));
});
+
+ it.each`
+ issuableType | userId
+ ${'merge_request'} | ${undefined}
+ ${'issue'} | ${'1'}
+ `('it sets data-user-id as $userId for $issuableType', ({ issuableType, userId }) => {
+ createComponent({
+ issuableType,
+ });
+
+ expect(findUserLink().attributes('data-user-id')).toBe(userId);
+ });
});
diff --git a/spec/helpers/releases_helper_spec.rb b/spec/helpers/releases_helper_spec.rb
index b7493e84c6a..330f1c3147a 100644
--- a/spec/helpers/releases_helper_spec.rb
+++ b/spec/helpers/releases_helper_spec.rb
@@ -64,6 +64,7 @@ RSpec.describe ReleasesHelper do
release_assets_docs_path
manage_milestones_path
new_milestone_path
+ upcoming_release_docs_path
edit_release_docs_path)
expect(helper.data_for_edit_release_page.keys).to match_array(keys)
@@ -83,6 +84,7 @@ RSpec.describe ReleasesHelper do
manage_milestones_path
new_milestone_path
default_branch
+ upcoming_release_docs_path
edit_release_docs_path)
expect(helper.data_for_new_release_page.keys).to match_array(keys)
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index 922fb1d7c92..73a454fd9f7 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe TodosHelper do
let_it_be(:user) { create(:user) }
let_it_be(:author) { create(:user) }
- let_it_be(:issue) { create(:issue, title: 'Issue 1') }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, title: 'Issue 1', project: project) }
let_it_be(:design) { create(:design, issue: issue) }
let_it_be(:note) do
create(:note,
@@ -16,7 +17,7 @@ RSpec.describe TodosHelper do
let_it_be(:design_todo) do
create(:todo, :mentioned,
user: user,
- project: issue.project,
+ project: project,
target: design,
author: author,
note: note)
@@ -27,6 +28,15 @@ RSpec.describe TodosHelper do
create(:todo, target: alert)
end
+ let_it_be(:task_todo) do
+ task = create(:work_item, :task, project: project)
+ create(:todo, target: task, target_type: task.class.name, project: project)
+ end
+
+ let_it_be(:issue_todo) do
+ create(:todo, target: issue)
+ end
+
describe '#todos_count_format' do
it 'shows fuzzy count for 100 or more items' do
expect(helper.todos_count_format(100)).to eq '99+'
@@ -113,27 +123,52 @@ RSpec.describe TodosHelper do
)
end
end
+
+ context 'when given a task' do
+ let(:todo) { task_todo }
+
+ it 'responds with an appropriate path' do
+ path = helper.todo_target_path(todo)
+
+ expect(path).to eq("/#{todo.project.full_path}/-/work_items/#{todo.target.id}")
+ end
+ end
end
describe '#todo_target_type_name' do
+ subject { helper.todo_target_type_name(todo) }
+
context 'when given a design todo' do
let(:todo) { design_todo }
- it 'responds with an appropriate target type name' do
- name = helper.todo_target_type_name(todo)
-
- expect(name).to eq('design')
- end
+ it { is_expected.to eq('design') }
end
context 'when given an alert todo' do
let(:todo) { alert_todo }
- it 'responds with an appropriate target type name' do
- name = helper.todo_target_type_name(todo)
+ it { is_expected.to eq('alert') }
+ end
+
+ context 'when given a task todo' do
+ let(:todo) { task_todo }
+
+ it { is_expected.to eq('task') }
+ end
+
+ context 'when given an issue todo' do
+ let(:todo) { issue_todo }
+
+ it { is_expected.to eq('issue') }
+ end
- expect(name).to eq('alert')
+ context 'when given a merge request todo' do
+ let(:todo) do
+ merge_request = create(:merge_request, source_project: project)
+ create(:todo, target: merge_request)
end
+
+ it { is_expected.to eq('merge request') }
end
end
diff --git a/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb b/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
index 12ed47a1025..b3157dd15fb 100644
--- a/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
+++ b/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe Atlassian::JiraConnect::Jwt::Asymmetric do
describe '#valid?' do
+ let_it_be(:private_key) { OpenSSL::PKey::RSA.generate 3072 }
+
subject(:asymmetric_jwt) { described_class.new(jwt, verification_claims) }
let(:verification_claims) { jwt_claims }
@@ -12,7 +14,6 @@ RSpec.describe Atlassian::JiraConnect::Jwt::Asymmetric do
let(:client_key) { '1234' }
let(:public_key_id) { '123e4567-e89b-12d3-a456-426614174000' }
let(:jwt_headers) { { kid: public_key_id } }
- let(:private_key) { OpenSSL::PKey::RSA.generate 2048 }
let(:jwt) { JWT.encode(jwt_claims, private_key, 'RS256', jwt_headers) }
let(:public_key) { private_key.public_key }
let(:install_keys_url) { "https://connect-install-keys.atlassian.com/#{public_key_id}" }
diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb
index 179e2efc0c7..1fab6807773 100644
--- a/spec/lib/gitlab/ci/jwt_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_spec.rb
@@ -121,8 +121,8 @@ RSpec.describe Gitlab::Ci::Jwt do
describe '.for_build' do
shared_examples 'generating JWT for build' do
context 'when signing key is present' do
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
- let(:rsa_key_data) { rsa_key.to_s }
+ let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) }
+ let_it_be(:rsa_key_data) { rsa_key.to_s }
it 'generates JWT with key id' do
_payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
index 97459d4a7be..d744bf7d4a6 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -380,6 +380,11 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
context 'when migration fails to complete' do
+ let(:error_message) do
+ "Batched migration #{batched_migration.job_class_name} could not be completed and a manual action is required."\
+ "Check the admin panel at (`/admin/background_migrations`) for more details."
+ end
+
it 'raises an error' do
batched_migration.batched_jobs.with_status(:failed).update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS)
@@ -390,7 +395,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
column_name,
job_arguments
)
- end.to raise_error described_class::FailedToFinalize
+ end.to raise_error(described_class::FailedToFinalize, error_message)
end
end
end
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index 8f0d62d8f0c..6d2026752d6 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe JSONWebToken::RSAToken do
end
context 'for invalid key to raise an exception' do
- let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
+ let(:new_key) { OpenSSL::PKey::RSA.generate(3072) }
subject { JWT.decode(rsa_encoded, new_key, true, { algorithm: 'RS256' }) }
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 651e2cf273f..7df22078c6d 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -114,6 +114,26 @@ RSpec.describe Todo do
end
end
+ describe '#for_issue_or_work_item?' do
+ it 'returns true when target is an Issue' do
+ subject.target_type = 'Issue'
+
+ expect(subject.for_issue_or_work_item?).to be_truthy
+ end
+
+ it 'returns true when target is a WorkItem' do
+ subject.target_type = 'WorkItem'
+
+ expect(subject.for_issue_or_work_item?).to be_truthy
+ end
+
+ it 'returns false when target is not an Issue' do
+ subject.target_type = 'DesignManagement::Design'
+
+ expect(subject.for_issue_or_work_item?).to be_falsey
+ end
+ end
+
describe '#target' do
context 'for commits' do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index dcf6b224009..196254d4609 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3001,7 +3001,7 @@ RSpec.describe User do
it 'has all ssh keys' do
user = create :user
- key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id
+ key = create :key_without_comment, user_id: user.id
expect(user.all_ssh_keys).to include(a_string_starting_with(key.key))
end
diff --git a/spec/requests/jwks_controller_spec.rb b/spec/requests/jwks_controller_spec.rb
index 6dbb5988f58..c9dcc238c29 100644
--- a/spec/requests/jwks_controller_spec.rb
+++ b/spec/requests/jwks_controller_spec.rb
@@ -18,9 +18,9 @@ RSpec.describe JwksController do
end
describe 'GET /-/jwks' do
- let(:ci_jwt_signing_key) { OpenSSL::PKey::RSA.generate(1024) }
- let(:ci_jwk) { ci_jwt_signing_key.to_jwk }
- let(:oidc_jwk) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key).to_jwk }
+ let_it_be(:ci_jwt_signing_key) { OpenSSL::PKey::RSA.generate(3072) }
+ let_it_be(:ci_jwk) { ci_jwt_signing_key.to_jwk }
+ let_it_be(:oidc_jwk) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key).to_jwk }
before do
stub_application_setting(ci_jwt_signing_key: ci_jwt_signing_key.to_s)
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index f18869fb380..3be59af6a37 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
RSpec.shared_context 'container registry auth service context' do
+ let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) }
+
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
let(:authentication_abilities) do
diff --git a/yarn.lock b/yarn.lock
index e5b5cf166ca..056c6ac2b0f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11951,10 +11951,10 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-swagger-ui-dist@4.8.0:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-4.8.0.tgz#5f39a038a02ffbd5defb8e1921a9ac1620d779ae"
- integrity sha512-jdcO4XcbwkAtrwvHp90Usjx3d4JZMjaiS02CxBFfuSxr6G8DBXPcK471+N6BcBkwZK7VTgpUBFAyyarsAvKYFQ==
+swagger-ui-dist@4.12.0:
+ version "4.12.0"
+ resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-4.12.0.tgz#986d90f05e81fb9db3ca40372278a5d8ce71db3a"
+ integrity sha512-B0Iy2ueXtbByE6OOyHTi3lFQkpPi/L7kFOKFeKTr44za7dJIELa9kzaca6GkndCgpK1QTjArnoXG+aUy0XQp1w==
symbol-observable@^1.0.4:
version "1.2.0"