summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/rspec/scattered_let.yml20
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue18
-rw-r--r--app/assets/javascripts/releases/components/release_block_header.vue12
-rw-r--r--app/assets/javascripts/releases/graphql/fragments/release.fragment.graphql1
-rw-r--r--app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql85
-rw-r--r--app/assets/javascripts/releases/util.js1
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue5
-rw-r--r--app/assets/javascripts/work_items/components/work_item_weight.vue39
-rw-r--r--app/assets/javascripts/work_items/graphql/provider.js16
-rw-r--r--app/assets/javascripts/work_items/graphql/typedefs.graphql7
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.fragment.graphql4
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.query.graphql4
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--config/feature_flags/development/approval_rules_pagination.yml2
-rw-r--r--config/feature_flags/development/highlight_diffs_optimize_memory_usage.yml8
-rw-r--r--doc/api/merge_request_approvals.md6
-rw-r--r--doc/api/releases/index.md13
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/infrastructure/clusters/connect/new_eks_cluster.md2
-rw-r--r--doc/user/project/releases/index.md15
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/gitlab/diff/highlight_cache.rb29
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js3
-rw-r--r--spec/frontend/releases/__snapshots__/util_spec.js.snap3
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js39
-rw-r--r--spec/frontend/work_items/components/work_item_weight_spec.js78
-rw-r--r--spec/frontend/work_items/mock_data.js9
-rw-r--r--spec/frontend/work_items/pages/work_item_detail_spec.js36
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb38
-rw-r--r--spec/models/merge_request_spec.rb31
-rw-r--r--spec/requests/api/merge_requests_spec.rb19
35 files changed, 382 insertions, 191 deletions
diff --git a/.rubocop_todo/rspec/scattered_let.yml b/.rubocop_todo/rspec/scattered_let.yml
index 069f9a15fff..22ee370523e 100644
--- a/.rubocop_todo/rspec/scattered_let.yml
+++ b/.rubocop_todo/rspec/scattered_let.yml
@@ -1,9 +1,6 @@
---
# Cop supports --auto-correct.
RSpec/ScatteredLet:
- # Offense count: 720
- # Temporarily disabled due to too many offenses
- Enabled: false
Exclude:
- 'ee/spec/features/groups/group_roadmap_spec.rb'
- 'ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb'
@@ -19,10 +16,10 @@ RSpec/ScatteredLet:
- 'ee/spec/graphql/types/instance_security_dashboard_type_spec.rb'
- 'ee/spec/helpers/ee/subscribable_banner_helper_spec.rb'
- 'ee/spec/helpers/trial_status_widget_helper_spec.rb'
- - 'ee/spec/lib/banzai/reference_parser/iteration_parser_spec.rb'
- 'ee/spec/lib/ee/audit/compliance_framework_changes_auditor_spec.rb'
- 'ee/spec/lib/ee/gitlab/ci/config_spec.rb'
- 'ee/spec/lib/ee/gitlab/email/handler/service_desk_handler_spec.rb'
+ - 'ee/spec/lib/gitlab/background_migration/migrate_requirements_to_work_items_spec.rb'
- 'ee/spec/lib/gitlab/ci/parsers/security/dast_spec.rb'
- 'ee/spec/lib/gitlab/ci/parsers/security/formatters/dependency_list_spec.rb'
- 'ee/spec/lib/gitlab/ci/templates/dast_api_gitlab_ci_yaml_spec.rb'
@@ -62,9 +59,12 @@ RSpec/ScatteredLet:
- 'ee/spec/requests/api/graphql/project/pipelines/dast_profile_spec.rb'
- 'ee/spec/requests/api/internal/base_spec.rb'
- 'ee/spec/requests/api/projects_spec.rb'
+ - 'ee/spec/requests/api/settings_spec.rb'
- 'ee/spec/requests/api/vulnerability_findings_spec.rb'
- 'ee/spec/requests/git_http_geo_spec.rb'
+ - 'ee/spec/serializers/license_compliance/collapsed_comparer_entity_spec.rb'
- 'ee/spec/serializers/status_page/incident_serializer_spec.rb'
+ - 'ee/spec/services/app_sec/dast/scan_configs/fetch_service_spec.rb'
- 'ee/spec/services/app_sec/dast/scanner_profiles/update_service_spec.rb'
- 'ee/spec/services/arkose/blocked_users_report_service_spec.rb'
- 'ee/spec/services/arkose/user_verification_service_spec.rb'
@@ -88,13 +88,13 @@ RSpec/ScatteredLet:
- 'ee/spec/services/requirements_management/update_requirement_service_spec.rb'
- 'ee/spec/services/search/group_service_spec.rb'
- 'ee/spec/services/search/project_service_spec.rb'
+ - 'ee/spec/services/security/ingestion/tasks/update_vulnerability_uuids_spec.rb'
- 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/views/shared/_mirror_update_button.html.haml_spec.rb'
- 'ee/spec/views/subscriptions/groups/edit.html.haml_spec.rb'
- 'ee/spec/workers/compliance_management/merge_requests/compliance_violations_worker_spec.rb'
- 'ee/spec/workers/concerns/update_orchestration_policy_configuration_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_audit_logs_1_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/project/project_audit_logs_spec.rb'
- 'spec/controllers/projects/artifacts_controller_spec.rb'
- 'spec/controllers/projects/deploy_keys_controller_spec.rb'
- 'spec/controllers/projects/environments_controller_spec.rb'
@@ -126,14 +126,19 @@ RSpec/ScatteredLet:
- 'spec/lib/banzai/reference_parser/project_parser_spec.rb'
- 'spec/lib/banzai/reference_parser/snippet_parser_spec.rb'
- 'spec/lib/banzai/reference_parser/user_parser_spec.rb'
+ - 'spec/lib/bulk_imports/pipeline/runner_spec.rb'
- 'spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb'
- 'spec/lib/gitlab/asciidoc/include_processor_spec.rb'
- 'spec/lib/gitlab/auth/ldap/person_spec.rb'
- 'spec/lib/gitlab/auth/saml/auth_hash_spec.rb'
+ - 'spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb'
- 'spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb'
+ - 'spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb'
- 'spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb'
- 'spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb'
+ - 'spec/lib/gitlab/background_migration/reset_too_many_tags_skipped_registry_imports_spec.rb'
+ - 'spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb'
- 'spec/lib/gitlab/ci/config/external/file/artifact_spec.rb'
- 'spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb'
@@ -162,6 +167,7 @@ RSpec/ScatteredLet:
- 'spec/lib/gitlab/git/blame_spec.rb'
- 'spec/lib/gitlab/git/diff_collection_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb'
+ - 'spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb'
- 'spec/lib/gitlab/github_import/parallel_scheduling_spec.rb'
- 'spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/project/export_task_spec.rb'
@@ -182,7 +188,6 @@ RSpec/ScatteredLet:
- 'spec/lib/gitlab/spamcheck/client_spec.rb'
- 'spec/lib/gitlab/template/finders/global_template_finder_spec.rb'
- 'spec/lib/gitlab/tree_summary_spec.rb'
- - 'spec/lib/gitlab/usage/service_ping_report_spec.rb'
- 'spec/lib/gitlab/usage_data_metrics_spec.rb'
- 'spec/lib/gitlab/utils/measuring_spec.rb'
- 'spec/lib/gitlab/zentao/client_spec.rb'
@@ -236,6 +241,7 @@ RSpec/ScatteredLet:
- 'spec/requests/api/project_clusters_spec.rb'
- 'spec/requests/api/project_export_spec.rb'
- 'spec/requests/api/rubygem_packages_spec.rb'
+ - 'spec/requests/jira_routing_spec.rb'
- 'spec/requests/projects/releases_controller_spec.rb'
- 'spec/rubocop/cop/migration/update_column_in_batches_spec.rb'
- 'spec/scripts/pipeline_test_report_builder_spec.rb'
@@ -277,6 +283,8 @@ RSpec/ScatteredLet:
- 'spec/services/system_notes/design_management_service_spec.rb'
- 'spec/services/system_notes/merge_requests_service_spec.rb'
- 'spec/services/todo_service_spec.rb'
+ - 'spec/services/web_hook_service_spec.rb'
+ - 'spec/services/work_items/update_service_spec.rb'
- 'spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb'
- 'spec/tasks/gitlab/artifacts/migrate_rake_spec.rb'
- 'spec/workers/concerns/gitlab/github_import/object_importer_spec.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 6194ce8b21f..0c2ea8e4ad0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1c907781819bf8810e15578f3d4d2b25e3ca1053
+f8e688fbf64938cf8563f765c040af39f33e0790
diff --git a/Gemfile b/Gemfile
index 4b70737cc3a..100dc361905 100644
--- a/Gemfile
+++ b/Gemfile
@@ -56,7 +56,7 @@ gem 'omniauth-authentiq', '~> 0.3.3'
gem 'gitlab-omniauth-openid-connect', '~> 0.9.0', require: 'omniauth_openid_connect'
gem 'omniauth-salesforce', '~> 1.0.5'
gem 'omniauth-atlassian-oauth2', '~> 0.2.0'
-gem 'rack-oauth2', '~> 1.19.0'
+gem 'rack-oauth2', '~> 1.21.2'
gem 'jwt', '~> 2.1.0'
# Kerberos authentication. EE-only
@@ -187,7 +187,7 @@ gem 'rack', '~> 2.2.4'
gem 'rack-timeout', '~> 0.6.0', require: 'rack/timeout/base'
group :puma do
- gem 'puma', '~> 5.6.2', require: false
+ gem 'puma', '~> 5.6.4', require: false
gem 'puma_worker_killer', '~> 0.3.1', require: false
gem 'sd_notify', '~> 0.1.0', require: false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 97a73baf0ab..07104377340 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1005,7 +1005,7 @@ GEM
tty-markdown
tty-prompt
public_suffix (4.0.7)
- puma (5.6.2)
+ puma (5.6.4)
nio4r (~> 2.0)
puma_worker_killer (0.3.1)
get_process_mem (~> 0.2)
@@ -1020,7 +1020,7 @@ GEM
rack (>= 1.0, < 3)
rack-cors (1.1.1)
rack (>= 2.0.0)
- rack-oauth2 (1.19.0)
+ rack-oauth2 (1.21.2)
activesupport
attr_required
httpclient
@@ -1670,12 +1670,12 @@ DEPENDENCIES
pry-byebug
pry-rails (~> 0.3.9)
pry-shell (~> 0.5.0)
- puma (~> 5.6.2)
+ puma (~> 5.6.4)
puma_worker_killer (~> 0.3.1)
rack (~> 2.2.4)
rack-attack (~> 6.6.0)
rack-cors (~> 1.1.0)
- rack-oauth2 (~> 1.19.0)
+ rack-oauth2 (~> 1.21.2)
rack-proxy (~> 0.7.2)
rack-timeout (~> 0.6.0)
rails (~> 6.1.4.7)
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
index a049b0eff8d..9430f1f0a4c 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLink, GlTableLite, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
+import { GlLink, GlTableLite, GlDropdownItem, GlDropdown, GlButton } from '@gitlab/ui';
import { last } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
@@ -18,7 +18,6 @@ export default {
components: {
GlLink,
GlTableLite,
- GlIcon,
GlDropdown,
GlDropdownItem,
GlButton,
@@ -77,7 +76,7 @@ export default {
label: '',
hide: !this.canDelete,
class: 'gl-text-right',
- tdClass: 'gl-w-4',
+ tdClass: 'gl-w-4 gl-pt-3!',
},
].filter((c) => !c.hide);
},
@@ -102,6 +101,7 @@ export default {
},
i18n: {
deleteFile: __('Delete file'),
+ moreActionsText: __('More actions'),
},
};
</script>
@@ -156,10 +156,14 @@ export default {
</template>
<template #cell(actions)="{ item }">
- <gl-dropdown category="tertiary" right>
- <template #button-content>
- <gl-icon name="ellipsis_v" />
- </template>
+ <gl-dropdown
+ category="tertiary"
+ icon="ellipsis_v"
+ :text-sr-only="true"
+ :text="$options.i18n.moreActionsText"
+ no-caret
+ right
+ >
<gl-dropdown-item data-testid="delete-file" @click="$emit('delete-file', item)">
{{ $options.i18n.deleteFile }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue
index def38780545..070865cf84b 100644
--- a/app/assets/javascripts/releases/components/release_block_header.vue
+++ b/app/assets/javascripts/releases/components/release_block_header.vue
@@ -7,6 +7,10 @@ import { BACK_URL_PARAM } from '~/releases/constants';
export default {
i18n: {
editButton: __('Edit this release'),
+ historical: __('Historical release'),
+ historicalTooltip: __(
+ 'This release was created with a date in the past. Evidence collection at the moment of the release is unavailable.',
+ ),
},
name: 'ReleaseBlockHeader',
components: {
@@ -65,6 +69,14 @@ export default {
<gl-badge v-if="release.upcomingRelease" variant="warning" class="align-middle">{{
__('Upcoming Release')
}}</gl-badge>
+ <gl-badge
+ v-else-if="release.historicalRelease"
+ v-gl-tooltip
+ :title="$options.i18n.historicalTooltip"
+ class="gl-vertical-align-middle"
+ >
+ {{ $options.i18n.historical }}
+ </gl-badge>
</h2>
<gl-button
v-if="editLink"
diff --git a/app/assets/javascripts/releases/graphql/fragments/release.fragment.graphql b/app/assets/javascripts/releases/graphql/fragments/release.fragment.graphql
index e0de6d12b13..78c1680e641 100644
--- a/app/assets/javascripts/releases/graphql/fragments/release.fragment.graphql
+++ b/app/assets/javascripts/releases/graphql/fragments/release.fragment.graphql
@@ -8,6 +8,7 @@ fragment Release on Release {
releasedAt
createdAt
upcomingRelease
+ historicalRelease
assets {
__typename
count
diff --git a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
index 61a06f268bd..f8cfcf4b7af 100644
--- a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
+++ b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
@@ -1,3 +1,5 @@
+#import "../fragments/release.fragment.graphql"
+
query allReleases(
$fullPath: ID!
$first: Int
@@ -12,88 +14,7 @@ query allReleases(
releases(first: $first, last: $last, before: $before, after: $after, sort: $sort) {
__typename
nodes {
- __typename
- id
- name
- tagName
- tagPath
- descriptionHtml
- releasedAt
- createdAt
- upcomingRelease
- assets {
- __typename
- count
- sources {
- __typename
- nodes {
- __typename
- format
- url
- }
- }
- links {
- __typename
- nodes {
- __typename
- id
- name
- url
- directAssetUrl
- linkType
- external
- }
- }
- }
- evidences {
- __typename
- nodes {
- __typename
- id
- filepath
- collectedAt
- sha
- }
- }
- links {
- __typename
- editUrl
- selfUrl
- openedIssuesUrl
- closedIssuesUrl
- openedMergeRequestsUrl
- mergedMergeRequestsUrl
- closedMergeRequestsUrl
- }
- commit {
- __typename
- id
- sha
- webUrl
- title
- }
- author {
- __typename
- id
- webUrl
- avatarUrl
- username
- }
- milestones {
- __typename
- nodes {
- __typename
- id
- title
- description
- webPath
- stats {
- __typename
- totalIssuesCount
- closedIssuesCount
- }
- }
- }
+ ...Release
}
pageInfo {
__typename
diff --git a/app/assets/javascripts/releases/util.js b/app/assets/javascripts/releases/util.js
index f1f5f4bca4c..a1027ef08d7 100644
--- a/app/assets/javascripts/releases/util.js
+++ b/app/assets/javascripts/releases/util.js
@@ -12,6 +12,7 @@ const convertScalarProperties = (graphQLRelease) =>
'description',
'descriptionHtml',
'upcomingRelease',
+ 'historicalRelease',
]);
const convertDateProperties = ({ releasedAt }) => ({
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index ad90fe88947..f49d8eab9f0 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -114,7 +114,7 @@ export default {
return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_LABELS);
},
workItemWeight() {
- return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT);
+ return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT);
},
workItemHierarchy() {
return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY);
@@ -142,7 +142,7 @@ export default {
<template>
<section class="gl-pt-5">
- <gl-alert v-if="error" variant="danger" @dismiss="error = undefined">
+ <gl-alert v-if="error" class="gl-mb-3" variant="danger" @dismiss="error = undefined">
{{ error }}
</gl-alert>
@@ -236,6 +236,7 @@ export default {
:weight="workItemWeight.weight"
:work-item-id="workItem.id"
:work-item-type="workItemType"
+ @error="error = $event"
/>
</template>
<work-item-description
diff --git a/app/assets/javascripts/work_items/components/work_item_weight.vue b/app/assets/javascripts/work_items/components/work_item_weight.vue
index 30e2c1e56b8..db97f16a010 100644
--- a/app/assets/javascripts/work_items/components/work_item_weight.vue
+++ b/app/assets/javascripts/work_items/components/work_item_weight.vue
@@ -1,9 +1,10 @@
<script>
import { GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-import { TRACKING_CATEGORY_SHOW } from '../constants';
-import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+import { i18n, TRACKING_CATEGORY_SHOW } from '../constants';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
/* eslint-disable @gitlab/require-i18n-strings */
const allowedKeys = [
@@ -98,16 +99,34 @@ export default {
},
updateWeight(event) {
this.isEditing = false;
+
+ const weight = Number(event.target.value);
+ if (this.weight === weight) {
+ return;
+ }
+
this.track('updated_weight');
- this.$apollo.mutate({
- mutation: localUpdateWorkItemMutation,
- variables: {
- input: {
- id: this.workItemId,
- weight: event.target.value === '' ? null : Number(event.target.value),
+ this.$apollo
+ .mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ weightWidget: {
+ weight: event.target.value === '' ? null : weight,
+ },
+ },
},
- },
- });
+ })
+ .then(({ data }) => {
+ if (data.workItemUpdate.errors.length) {
+ throw new Error(data.workItemUpdate.errors.join('\n'));
+ }
+ })
+ .catch((error) => {
+ this.$emit('error', i18n.updateError);
+ Sentry.captureException(error);
+ });
},
},
};
diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js
index 8788ad21e7b..1feef8bcaf8 100644
--- a/app/assets/javascripts/work_items/graphql/provider.js
+++ b/app/assets/javascripts/work_items/graphql/provider.js
@@ -2,7 +2,7 @@ import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import { WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS, WIDGET_TYPE_WEIGHT } from '../constants';
+import { WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS } from '../constants';
import typeDefs from './typedefs.graphql';
import workItemQuery from './work_item.query.graphql';
@@ -10,7 +10,7 @@ export const temporaryConfig = {
typeDefs,
cacheConfig: {
possibleTypes: {
- LocalWorkItemWidget: ['LocalWorkItemLabels', 'LocalWorkItemWeight'],
+ LocalWorkItemWidget: ['LocalWorkItemLabels'],
},
typePolicies: {
WorkItem: {
@@ -25,11 +25,6 @@ export const temporaryConfig = {
allowScopedLabels: true,
nodes: [],
},
- {
- __typename: 'LocalWorkItemWeight',
- type: 'WEIGHT',
- weight: null,
- },
]
);
},
@@ -56,13 +51,6 @@ export const resolvers = {
assigneesWidget.assignees.nodes = [...input.assignees];
}
- if (input.weight != null) {
- const weightWidget = draftData.workItem.mockWidgets.find(
- (widget) => widget.type === WIDGET_TYPE_WEIGHT,
- );
- weightWidget.weight = input.weight;
- }
-
if (input.labels) {
const labelsWidget = draftData.workItem.mockWidgets.find(
(widget) => widget.type === WIDGET_TYPE_LABELS,
diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql
index 48228b15a53..44a2999a72e 100644
--- a/app/assets/javascripts/work_items/graphql/typedefs.graphql
+++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql
@@ -1,7 +1,6 @@
enum LocalWidgetType {
ASSIGNEES
LABELS
- WEIGHT
}
interface LocalWorkItemWidget {
@@ -19,11 +18,6 @@ type LocalWorkItemLabels implements LocalWorkItemWidget {
nodes: [Label!]
}
-type LocalWorkItemWeight implements LocalWorkItemWidget {
- type: LocalWidgetType!
- weight: Int
-}
-
extend type WorkItem {
mockWidgets: [LocalWorkItemWidget]
}
@@ -32,7 +26,6 @@ input LocalUpdateWorkItemInput {
id: WorkItemID!
assignees: [UserCore!]
labels: [Label]
- weight: Int
}
type LocalWorkItemPayload {
diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
index 5f64eda96aa..abed1cb134e 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
@@ -28,6 +28,10 @@ fragment WorkItem on WorkItem {
}
}
}
+ ... on WorkItemWidgetWeight {
+ type
+ weight
+ }
... on WorkItemWidgetHierarchy {
type
parent {
diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
index 61cb8802187..276061af193 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
@@ -12,10 +12,6 @@ query workItem($id: WorkItemID!) {
...Label
}
}
- ... on LocalWorkItemWeight {
- type
- weight
- }
}
}
}
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ec97ab0ea42..cd957f445fd 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1507,7 +1507,7 @@ class MergeRequest < ApplicationRecord
lock_mr
yield
ensure
- unlock_mr
+ unlock_mr if locked?
end
def update_and_mark_in_progress_merge_commit_sha(commit_id)
diff --git a/config/feature_flags/development/approval_rules_pagination.yml b/config/feature_flags/development/approval_rules_pagination.yml
index 494109e3f5a..78d4ad37ced 100644
--- a/config/feature_flags/development/approval_rules_pagination.yml
+++ b/config/feature_flags/development/approval_rules_pagination.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366823
milestone: '15.2'
type: development
group: group::source code
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/highlight_diffs_optimize_memory_usage.yml b/config/feature_flags/development/highlight_diffs_optimize_memory_usage.yml
new file mode 100644
index 00000000000..fb480d2fc93
--- /dev/null
+++ b/config/feature_flags/development/highlight_diffs_optimize_memory_usage.yml
@@ -0,0 +1,8 @@
+---
+name: highlight_diffs_optimize_memory_usage
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92456
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367890
+milestone: '15.3'
+type: development
+group: group::source code
+default_enabled: false
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index 37a926366df..890d7084ec9 100644
--- a/doc/api/merge_request_approvals.md
+++ b/doc/api/merge_request_approvals.md
@@ -79,6 +79,7 @@ POST /projects/:id/approvals
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11877) in GitLab 12.3.
> - Moved to GitLab Premium in 13.9.
> - `protected_branches` property was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/460) in GitLab 12.7.
+> - Pagination support [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31011) in GitLab 15.3 [with a flag](../administration/feature_flags.md) named `approval_rules_pagination`. Enabled by default.
You can request information about a project's approval rules using the following endpoint:
@@ -86,6 +87,8 @@ You can request information about a project's approval rules using the following
GET /projects/:id/approval_rules
```
+Use the `page` and `per_page` [pagination](index.md#offset-based-pagination) parameters to restrict the list of approval rules.
+
**Parameters:**
| Attribute | Type | Required | Description |
@@ -684,6 +687,7 @@ This includes additional information about the users who have already approved
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13712) in GitLab 12.3.
> - Moved to GitLab Premium in 13.9.
+> - Pagination support [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31011) in GitLab 15.3 [with a flag](../administration/feature_flags.md) named `approval_rules_pagination`. Enabled by default.
You can request information about a merge request's approval rules using the following endpoint:
@@ -691,6 +695,8 @@ You can request information about a merge request's approval rules using the fol
GET /projects/:id/merge_requests/:merge_request_iid/approval_rules
```
+Use the `page` and `per_page` [pagination](index.md#offset-based-pagination) parameters to restrict the list of approval rules.
+
**Parameters:**
| Attribute | Type | Required | Description |
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index 7489d0bdc9e..7a3307e9548 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -740,4 +740,15 @@ Example response:
A release with a `released_at` attribute set to a future date is labeled
as an **Upcoming Release** [in the UI](../../user/project/releases/index.md#upcoming-releases).
-Additionally, if a [release is requested from the API](#list-releases), for each release with a `release_at` attribute set to a future date, an additional attribute `upcoming_release` (set to true) will be returned as part of the response.
+Additionally, if a [release is requested from the API](#list-releases), for each release with a `release_at` attribute set to a future date, an additional attribute `upcoming_release` (set to true) is returned as part of the response.
+
+## Historical releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199429) in GitLab 15.2.
+
+A release with a `released_at` attribute set to a past date is labeled
+as an **Historical release** [in the UI](../../user/project/releases/index.md#historical-releases).
+
+Additionally, if a [release is requested from the API](#list-releases), for each
+release with a `release_at` attribute set to a past date, an additional
+attribute `historical_release` (set to true) is returned as part of the response.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index d0a91ab664e..d3856c0c282 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -311,7 +311,7 @@ table.supported-languages ul {
<p>
Although Gradle with Java 8 is supported, there are other issues such that Android project builds are not supported at this time.
Please see the backlog issue <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/336866">Android support for Dependency
- Scanning (gemnasium-maven)</a> for more details. Also, Gradle is not supported when [FIPS mode](../../../development/fips_compliance.md#enable-fips-mode) is enabled.
+ Scanning (gemnasium-maven)</a> for more details. Also, Gradle is not supported when <a href="https://docs.gitlab.com/ee/development/fips_compliance.html#enable-fips-mode">FIPS mode</a> is enabled.
</p>
</li>
<li>
diff --git a/doc/user/infrastructure/clusters/connect/new_eks_cluster.md b/doc/user/infrastructure/clusters/connect/new_eks_cluster.md
index 798e02ab9b4..969ee7de6fb 100644
--- a/doc/user/infrastructure/clusters/connect/new_eks_cluster.md
+++ b/doc/user/infrastructure/clusters/connect/new_eks_cluster.md
@@ -127,7 +127,7 @@ To remove all resources:
- cleanup
destroy:
- extends: .destroy
+ extends: .terraform:destroy
needs: []
```
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index 1d448ca5c94..ad524bd150c 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -298,6 +298,16 @@ release tag. When the `released_at` date and time has passed, the badge is autom
![An upcoming release](img/upcoming_release_v12_7.png)
+## Historical releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199429) in GitLab 15.2.
+
+You can create a release in the past using either the
+[Releases API](../../../api/releases/index.md#historical-releases) or the UI. When you set
+a past `released_at` date, an **Historical release** badge is displayed next to
+the release tag. Due to being released in the past, [release evidence](#release-evidence)
+is not available.
+
## Edit a release
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6.
@@ -828,10 +838,11 @@ keyword. Learn more in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issue
In the API:
-- If you specify a future `released_at` date, the release becomes an **Upcoming Release**
+- If you specify a future `released_at` date, the release becomes an **Upcoming release**
and the evidence is collected on the date of the release. You cannot collect
release evidence before then.
-- If you use a past `released_at` date, no evidence is collected.
+- If you specify a past `released_at` date, the release becomes an **Historical
+ release** and no evidence is collected.
- If you do not specify a `released_at` date, release evidence is collected on the
date the release is created.
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 156a92802b0..0bcf76497a0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -481,7 +481,11 @@ module API
.execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
- present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
+ if immediately_mergeable && !merge_request.merged?
+ render_api_error!("Failed to merge branch", 422)
+ else
+ present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
+ end
end
desc 'Returns the up to date merge-ref HEAD commit'
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
index 8e9dc3a305f..88f396fa7b9 100644
--- a/lib/gitlab/diff/highlight_cache.rb
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -6,7 +6,8 @@ module Gitlab
include Gitlab::Utils::Gzip
include Gitlab::Utils::StrongMemoize
- EXPIRATION = 1.week
+ EXPIRATION = 1.day
+ PREVIOUS_EXPIRATION_PERIOD = 7.days
VERSION = 2
delegate :diffable, to: :@diff_collection
@@ -69,19 +70,33 @@ module Gitlab
def key
strong_memoize(:redis_key) do
- [
- 'highlighted-diff-files',
- diffable.cache_key,
- VERSION,
+ options = [
diff_options,
Feature.enabled?(:use_marker_ranges, diffable.project),
Feature.enabled?(:diff_line_syntax_highlighting, diffable.project)
- ].join(":")
+ ]
+
+ options_for_key =
+ if Feature.enabled?(:highlight_diffs_optimize_memory_usage, diffable.project)
+ [OpenSSL::Digest::SHA256.hexdigest(options.join)]
+ else
+ options
+ end
+
+ ['highlighted-diff-files', diffable.cache_key, VERSION, *options_for_key].join(":")
end
end
private
+ def expiration_period
+ if Feature.enabled?(:highlight_diffs_optimize_memory_usage, diffable.project)
+ EXPIRATION
+ else
+ PREVIOUS_EXPIRATION_PERIOD
+ end
+ end
+
def set_highlighted_diff_lines(diff_file, content)
diff_file.highlighted_diff_lines = content.map do |line|
Gitlab::Diff::Line.safe_init_from_hash(line)
@@ -138,7 +153,7 @@ module Gitlab
# HSETs have to have their expiration date manually updated
#
- redis.expire(key, EXPIRATION)
+ redis.expire(key, expiration_period)
end
record_memory_usage(fetch_memory_usage(redis, key))
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 560ac9599b3..a89e8b0f3a3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19290,6 +19290,9 @@ msgstr ""
msgid "HighlightBar|Time to SLA:"
msgstr ""
+msgid "Historical release"
+msgstr ""
+
msgid "History"
msgstr ""
@@ -40061,6 +40064,9 @@ msgstr ""
msgid "This project will live in your group %{strong_open}%{namespace}%{strong_close}. A project is where you store your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
msgstr ""
+msgid "This release was created with a date in the past. Evidence collection at the moment of the release is unavailable."
+msgstr ""
+
msgid "This repository"
msgstr ""
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
index 0447ead0830..2cf3a2f9ca6 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
@@ -164,6 +164,9 @@ describe('Package Files', () => {
createComponent();
expect(findFirstActionMenu().exists()).toBe(true);
+ expect(findFirstActionMenu().props('icon')).toBe('ellipsis_v');
+ expect(findFirstActionMenu().props('textSrOnly')).toBe(true);
+ expect(findFirstActionMenu().props('text')).toMatchInterpolatedText('More actions');
});
describe('menu items', () => {
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap
index 90a33152877..0a796b0dbe6 100644
--- a/spec/frontend/releases/__snapshots__/util_spec.js.snap
+++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap
@@ -55,6 +55,7 @@ Object {
"commitPath": "http://localhost/releases-namespace/releases-project/-/commit/b83d6e391c22777fca1ed3012fce84f633d7fed0",
"descriptionHtml": "<p data-sourcepos=\\"1:1-1:23\\" dir=\\"auto\\">An okay release <gl-emoji title=\\"shrug\\" data-name=\\"shrug\\" data-unicode-version=\\"9.0\\">🤷</gl-emoji></p>",
"evidences": Array [],
+ "historicalRelease": false,
"milestones": Array [],
"name": "The second release",
"releasedAt": 2019-01-10T00:00:00.000Z,
@@ -159,6 +160,7 @@ Object {
"sha": "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d",
},
],
+ "historicalRelease": false,
"milestones": Array [
Object {
"__typename": "Milestone",
@@ -373,6 +375,7 @@ Object {
"sha": "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d",
},
],
+ "historicalRelease": false,
"milestones": Array [
Object {
"__typename": "Milestone",
diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js
index 167ae4f32a2..c9921185bad 100644
--- a/spec/frontend/releases/components/release_block_header_spec.js
+++ b/spec/frontend/releases/components/release_block_header_spec.js
@@ -1,8 +1,9 @@
-import { GlLink } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlLink, GlBadge } from '@gitlab/ui';
import { merge } from 'lodash';
import originalRelease from 'test_fixtures/api/releases/release.json';
import setWindowLocation from 'helpers/set_window_location_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { __ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleaseBlockHeader from '~/releases/components/release_block_header.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
@@ -12,10 +13,11 @@ describe('Release block header', () => {
let release;
const factory = (releaseUpdates = {}) => {
- wrapper = shallowMount(ReleaseBlockHeader, {
+ wrapper = shallowMountExtended(ReleaseBlockHeader, {
propsData: {
release: merge({}, release, releaseUpdates),
},
+ stubs: { GlBadge },
});
};
@@ -30,6 +32,7 @@ describe('Release block header', () => {
const findHeader = () => wrapper.find('h2');
const findHeaderLink = () => findHeader().find(GlLink);
const findEditButton = () => wrapper.find('.js-edit-button');
+ const findBadge = () => wrapper.findComponent(GlBadge);
describe('when _links.self is provided', () => {
beforeEach(() => {
@@ -84,4 +87,34 @@ describe('Release block header', () => {
expect(findEditButton().exists()).toBe(false);
});
});
+
+ describe('upcoming release', () => {
+ beforeEach(() => {
+ factory({ upcomingRelease: true, historicalRelease: false });
+ });
+
+ it('shows a badge that the release is upcoming', () => {
+ const badge = findBadge();
+
+ expect(badge.text()).toBe(__('Upcoming Release'));
+ expect(badge.props('variant')).toBe('warning');
+ });
+ });
+
+ describe('historical release', () => {
+ beforeEach(() => {
+ factory({ upcomingRelease: false, historicalRelease: true });
+ });
+
+ it('shows a badge that the release is historical', () => {
+ const badge = findBadge();
+
+ expect(badge.text()).toBe(__('Historical release'));
+ expect(badge.attributes('title')).toBe(
+ __(
+ 'This release was created with a date in the past. Evidence collection at the moment of the release is unavailable.',
+ ),
+ );
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_weight_spec.js b/spec/frontend/work_items/components/work_item_weight_spec.js
index c3bbea26cda..8fd2280ff19 100644
--- a/spec/frontend/work_items/components/work_item_weight_spec.js
+++ b/spec/frontend/work_items/components/work_item_weight_spec.js
@@ -1,16 +1,21 @@
import { GlForm, GlFormInput } from '@gitlab/ui';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { __ } from '~/locale';
import WorkItemWeight from '~/work_items/components/work_item_weight.vue';
-import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
-import localUpdateWorkItemMutation from '~/work_items/graphql/local_update_work_item.mutation.graphql';
+import { i18n, TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import { updateWorkItemMutationResponse } from 'jest/work_items/mock_data';
describe('WorkItemWeight component', () => {
+ Vue.use(VueApollo);
+
let wrapper;
- const mutateSpy = jest.fn();
const workItemId = 'gid://gitlab/WorkItem/1';
const workItemType = 'Task';
@@ -22,8 +27,10 @@ describe('WorkItemWeight component', () => {
hasIssueWeightsFeature = true,
isEditing = false,
weight,
+ mutationHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse),
} = {}) => {
wrapper = mountExtended(WorkItemWeight, {
+ apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
propsData: {
canUpdate,
weight,
@@ -33,11 +40,6 @@ describe('WorkItemWeight component', () => {
provide: {
hasIssueWeightsFeature,
},
- mocks: {
- $apollo: {
- mutate: mutateSpy,
- },
- },
});
if (isEditing) {
@@ -131,21 +133,61 @@ describe('WorkItemWeight component', () => {
});
describe('when blurred', () => {
- it('calls a mutation to update the weight', () => {
- const weight = 0;
- createComponent({ isEditing: true, weight });
+ it('calls a mutation to update the weight when the input value is different', () => {
+ const mutationSpy = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+ createComponent({ isEditing: true, weight: 0, mutationHandler: mutationSpy });
+
+ findInput().vm.$emit('blur', { target: { value: 1 } });
+
+ expect(mutationSpy).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ weightWidget: {
+ weight: 1,
+ },
+ },
+ });
+ });
+
+ it('does not call a mutation to update the weight when the input value is the same', () => {
+ const mutationSpy = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+ createComponent({ isEditing: true, mutationHandler: mutationSpy });
findInput().trigger('blur');
- expect(mutateSpy).toHaveBeenCalledWith({
- mutation: localUpdateWorkItemMutation,
- variables: {
- input: {
- id: workItemId,
- weight,
+ expect(mutationSpy).not.toHaveBeenCalledWith();
+ });
+
+ it('emits an error when there is a GraphQL error', async () => {
+ const response = {
+ data: {
+ workItemUpdate: {
+ errors: ['Error!'],
+ workItem: {},
},
},
+ };
+ createComponent({
+ isEditing: true,
+ mutationHandler: jest.fn().mockResolvedValue(response),
+ });
+
+ findInput().trigger('blur');
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
+ });
+
+ it('emits an error when there is a network error', async () => {
+ createComponent({
+ isEditing: true,
+ mutationHandler: jest.fn().mockRejectedValue(new Error()),
});
+
+ findInput().trigger('blur');
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
});
it('tracks updating the weight', () => {
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 0359caf7116..df666b95ad1 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -77,6 +77,7 @@ export const updateWorkItemMutationResponse = {
data: {
workItemUpdate: {
__typename: 'WorkItemUpdatePayload',
+ errors: [],
workItem: {
__typename: 'WorkItem',
id: 'gid://gitlab/WorkItem/1',
@@ -112,6 +113,7 @@ export const workItemResponseFactory = ({
canUpdate = false,
allowsMultipleAssignees = true,
assigneesWidgetPresent = true,
+ weightWidgetPresent = true,
parent = null,
} = {}) => ({
data: {
@@ -148,6 +150,13 @@ export const workItemResponseFactory = ({
},
}
: { type: 'MOCK TYPE' },
+ weightWidgetPresent
+ ? {
+ __typename: 'WorkItemWidgetWeight',
+ type: 'WEIGHT',
+ weight: 0,
+ }
+ : { type: 'MOCK TYPE' },
{
__typename: 'WorkItemWidgetHierarchy',
type: 'HIERARCHY',
diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/pages/work_item_detail_spec.js
index 43869468ad0..b85fc469c23 100644
--- a/spec/frontend/work_items/pages/work_item_detail_spec.js
+++ b/spec/frontend/work_items/pages/work_item_detail_spec.js
@@ -278,12 +278,14 @@ describe('WorkItemDetail component', () => {
describe('weight widget', () => {
describe('when work_items_mvc_2 feature flag is enabled', () => {
describe.each`
- description | includeWidgets | exists
- ${'when widget is returned from API'} | ${true} | ${true}
- ${'when widget is not returned from API'} | ${false} | ${false}
- `('$description', ({ includeWidgets, exists }) => {
- it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => {
- createComponent({ includeWidgets, workItemsMvc2Enabled: true });
+ description | weightWidgetPresent | exists
+ ${'when widget is returned from API'} | ${true} | ${true}
+ ${'when widget is not returned from API'} | ${false} | ${false}
+ `('$description', ({ weightWidgetPresent, exists }) => {
+ it(`${weightWidgetPresent ? 'renders' : 'does not render'} weight component`, async () => {
+ const response = workItemResponseFactory({ weightWidgetPresent });
+ const handler = jest.fn().mockResolvedValue(response);
+ createComponent({ workItemsMvc2Enabled: true, handler });
await waitForPromises();
expect(findWorkItemWeight().exists()).toBe(exists);
@@ -293,18 +295,28 @@ describe('WorkItemDetail component', () => {
describe('when work_items_mvc_2 feature flag is disabled', () => {
describe.each`
- description | includeWidgets | exists
- ${'when widget is returned from API'} | ${true} | ${false}
- ${'when widget is not returned from API'} | ${false} | ${false}
- `('$description', ({ includeWidgets, exists }) => {
- it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => {
- createComponent({ includeWidgets, workItemsMvc2Enabled: false });
+ description | weightWidgetPresent | exists
+ ${'when widget is returned from API'} | ${true} | ${false}
+ ${'when widget is not returned from API'} | ${false} | ${false}
+ `('$description', ({ weightWidgetPresent, exists }) => {
+ it(`${weightWidgetPresent ? 'renders' : 'does not render'} weight component`, async () => {
+ createComponent({ weightWidgetPresent, workItemsMvc2Enabled: false });
await waitForPromises();
expect(findWorkItemWeight().exists()).toBe(exists);
});
});
});
+
+ it('shows an error message when it emits an `error` event', async () => {
+ createComponent({ workItemsMvc2Enabled: true });
+ await waitForPromises();
+
+ findWorkItemWeight().vm.$emit('error', i18n.updateError);
+ await waitForPromises();
+
+ expect(findAlert().text()).toBe(i18n.updateError);
+ });
});
describe('work item information', () => {
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 5350dda5fb2..4a8b338506d 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -115,6 +115,10 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
.once
.and_call_original
+ Gitlab::Redis::Cache.with do |redis|
+ expect(redis).to receive(:expire).with(cache.key, described_class::EXPIRATION)
+ end
+
2.times { cache.write_if_empty }
end
@@ -123,6 +127,20 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
cache.write_if_empty
end
+
+ context 'when highlight_diffs_optimize_memory_usage is disabled' do
+ before do
+ stub_feature_flags(highlight_diffs_optimize_memory_usage: false)
+ end
+
+ it 'sets the previous expiration period' do
+ Gitlab::Redis::Cache.with do |redis|
+ expect(redis).to receive(:expire).with(cache.key, described_class::PREVIOUS_EXPIRATION_PERIOD)
+ end
+
+ cache.write_if_empty
+ end
+ end
end
describe '#write_if_empty' do
@@ -259,8 +277,12 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
describe '#key' do
subject { cache.key }
+ def options_hash(options_array)
+ OpenSSL::Digest::SHA256.hexdigest(options_array.join)
+ end
+
it 'returns cache key' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, true])}")
end
context 'when the `use_marker_ranges` feature flag is disabled' do
@@ -269,7 +291,7 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:false:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, false, true])}")
end
end
@@ -279,7 +301,17 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:false")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, false])}")
+ end
+ end
+
+ context 'when highlight_diffs_optimize_memory_usage is disabled' do
+ before do
+ stub_feature_flags(highlight_diffs_optimize_memory_usage: false)
+ end
+
+ it 'uses the options hash as a part of the cache key' do
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true")
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c3e325c4e6c..d13a725a44b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -4660,6 +4660,37 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '#in_locked_state' do
+ let(:merge_request) { create(:merge_request, :opened) }
+
+ context 'when the merge request does not change state' do
+ it 'returns to previous state and has no errors on the object' do
+ expect(merge_request.opened?).to eq(true)
+
+ merge_request.in_locked_state do
+ expect(merge_request.locked?).to eq(true)
+ end
+
+ expect(merge_request.opened?).to eq(true)
+ expect(merge_request.errors).to be_empty
+ end
+ end
+
+ context 'when the merge request is merged while locked' do
+ it 'becomes merged and has no errors on the object' do
+ expect(merge_request.opened?).to eq(true)
+
+ merge_request.in_locked_state do
+ expect(merge_request.locked?).to eq(true)
+ merge_request.mark_as_merged!
+ end
+
+ expect(merge_request.merged?).to eq(true)
+ expect(merge_request.errors).to be_empty
+ end
+ end
+ end
+
describe '#cleanup_refs' do
subject { merge_request.cleanup_refs(only: only) }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 695c0ed1749..698fe955ae3 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2482,11 +2482,28 @@ RSpec.describe API::MergeRequests do
let(:pipeline) { create(:ci_pipeline, project: project) }
it "returns merge_request in case of success" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
+ expect { put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) }
+ .to change { merge_request.reload.merged? }
+ .from(false)
+ .to(true)
expect(response).to have_gitlab_http_status(:ok)
end
+ context 'when the merge request fails to merge' do
+ it 'returns 422' do
+ expect_next_instance_of(::MergeRequests::MergeService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ expect { put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) }
+ .not_to change { merge_request.reload.merged? }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq("Failed to merge branch")
+ end
+ end
+
context 'when change_response_code_merge_status is enabled' do
it "returns 422 if branch can't be merged" do
allow_next_found_instance_of(MergeRequest) do |merge_request|