diff options
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 @@ -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  +## 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| |