diff options
47 files changed, 424 insertions, 440 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml index b072b3dc772..9158e02da94 100644 --- a/.gitlab/ci/reports.gitlab-ci.yml +++ b/.gitlab/ci/reports.gitlab-ci.yml @@ -87,7 +87,7 @@ yarn-audit-dependency_scanning: extends: .default-retry stage: test image: - name: ${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/package-hunter-cli:v1.3.3@sha256:1d3af9a61aa01549a62be17fa655fcf06271ac9e1b1e822c2a7930fa1d4a8a6b + name: ${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/package-hunter-cli:v2.1.0@sha256:1f1d31fdc81f6cf0ee305ff0291bfb56f22c5764fe042948ff1676f2f8c60352 entrypoint: [""] variables: HTR_user: '$PACKAGE_HUNTER_USER' diff --git a/.rubocop_todo/layout/empty_line_after_magic_comment.yml b/.rubocop_todo/layout/empty_line_after_magic_comment.yml index 93302cebaa1..536f13e3919 100644 --- a/.rubocop_todo/layout/empty_line_after_magic_comment.yml +++ b/.rubocop_todo/layout/empty_line_after_magic_comment.yml @@ -206,7 +206,6 @@ Layout/EmptyLineAfterMagicComment: - 'ee/app/models/dependencies/dependency_list_export.rb' - 'ee/app/models/ee/issue_assignee.rb' - 'ee/app/models/geo/ci_secure_file_state.rb' - - 'ee/app/models/namespaces/storage/limit_exclusion.rb' - 'ee/app/models/project_security_setting.rb' - 'ee/app/models/protected_environment.rb' - 'ee/app/models/sbom/vulnerable_component_version.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 339edfce783..13ed078563f 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2729,7 +2729,6 @@ Layout/LineLength: - 'lib/gitlab/ci/status/build/waiting_for_approval.rb' - 'lib/gitlab/ci/variables/builder.rb' - 'lib/gitlab/cleanup/project_uploads.rb' - - 'lib/gitlab/cluster/puma_worker_killer_observer.rb' - 'lib/gitlab/composer/version_index.rb' - 'lib/gitlab/config/entry/configurable.rb' - 'lib/gitlab/config/entry/validators.rb' diff --git a/.rubocop_todo/lint/unused_method_argument.yml b/.rubocop_todo/lint/unused_method_argument.yml index 45f18e605b7..dd5f6640abb 100644 --- a/.rubocop_todo/lint/unused_method_argument.yml +++ b/.rubocop_todo/lint/unused_method_argument.yml @@ -430,7 +430,6 @@ Lint/UnusedMethodArgument: - 'lib/gitlab/cleanup/orphan_job_artifact_files.rb' - 'lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb' - 'lib/gitlab/cleanup/project_upload_file_finder.rb' - - 'lib/gitlab/cluster/puma_worker_killer_observer.rb' - 'lib/gitlab/config/entry/composable_hash.rb' - 'lib/gitlab/config/entry/node.rb' - 'lib/gitlab/config/entry/validators.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 0382b0ff43e..0a77e2d8b15 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -3356,8 +3356,6 @@ RSpec/MissingFeatureCategory: - 'spec/lib/gitlab/closing_issue_extractor_spec.rb' - 'spec/lib/gitlab/cluster/lifecycle_events_spec.rb' - 'spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb' - - 'spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb' - - 'spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb' - 'spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb' - 'spec/lib/gitlab/code_navigation_path_spec.rb' - 'spec/lib/gitlab/color_schemes_spec.rb' diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml index 88b6f302e56..704a426595c 100644 --- a/.rubocop_todo/rspec/verified_doubles.yml +++ b/.rubocop_todo/rspec/verified_doubles.yml @@ -456,7 +456,6 @@ RSpec/VerifiedDoubles: - 'spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb' - 'spec/lib/gitlab/cleanup/project_uploads_spec.rb' - 'spec/lib/gitlab/cleanup/remote_uploads_spec.rb' - - 'spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb' - 'spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb' - 'spec/lib/gitlab/color_schemes_spec.rb' - 'spec/lib/gitlab/conan_token_spec.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 61d75e125be..097a2a36348 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0b0e46fe69e9b94e3def2a9318188fcc0f3f00c6 +c47a4d7d1ef1ecf3b0d78b9423e0ef28d8513979 @@ -211,7 +211,6 @@ gem 'rack-timeout', '~> 0.6.3', require: 'rack/timeout/base' group :puma do gem 'puma', '~> 5.6.5', 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 199c806a854..cd882fce0f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1162,9 +1162,6 @@ GEM public_suffix (5.0.0) puma (5.6.5) nio4r (~> 2.0) - puma_worker_killer (0.3.1) - get_process_mem (~> 0.2) - puma (>= 2.7) pyu-ruby-sasl (0.0.3.3) raabro (1.4.0) racc (1.6.2) @@ -1867,7 +1864,6 @@ DEPENDENCIES pry-rails (~> 0.3.9) pry-shell (~> 0.6.1) puma (~> 5.6.5) - puma_worker_killer (~> 0.3.1) rack (~> 2.2.7) rack-attack (~> 6.6.1) rack-cors (~> 1.1.1) diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 317fe6442d4..7eba491430b 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -1,58 +1,10 @@ -import { ASC, MR_FILTER_OPTIONS } from '../../constants'; import * as actions from '../actions'; import * as getters from '../getters'; import mutations from '../mutations'; +import createState from '../state'; export default () => ({ - state: { - discussions: [], - discussionSortOrder: ASC, - persistSortOrder: true, - convertedDisscussionIds: [], - targetNoteHash: null, - lastFetchedAt: null, - currentDiscussionId: null, - batchSuggestionsInfo: [], - currentlyFetchingDiscussions: false, - doneFetchingBatchDiscussions: false, - /** - * selectedCommentPosition & selectedCommentPositionHover structures are the same as `position.line_range`: - * { - * start: { line_code: string, new_line: number, old_line:number, type: string }, - * end: { line_code: string, new_line: number, old_line:number, type: string }, - * } - */ - selectedCommentPosition: null, - selectedCommentPositionHover: null, - - // View layer - isToggleStateButtonLoading: false, - isNotesFetched: false, - isLoading: true, - isLoadingDescriptionVersion: false, - isPromoteCommentToTimelineEventInProgress: false, - - // holds endpoints and permissions provided through haml - notesData: { - markdownDocsPath: '', - }, - userData: {}, - noteableData: { - discussion_locked: false, - confidential: false, // TODO: Move data like this to Issue Store, should not be apart of notes. - current_user: {}, - preview_note_path: 'path/to/preview', - }, - isResolvingDiscussion: false, - commentsDisabled: false, - resolvableDiscussionsCount: 0, - unresolvedDiscussionsCount: 0, - descriptionVersions: {}, - isTimelineEnabled: false, - isFetching: false, - isPollingInitialized: false, - mergeRequestFilters: MR_FILTER_OPTIONS.map((f) => f.value), - }, + state: createState(), actions, getters, mutations, diff --git a/app/assets/javascripts/notes/stores/state.js b/app/assets/javascripts/notes/stores/state.js new file mode 100644 index 00000000000..8e49cd861a1 --- /dev/null +++ b/app/assets/javascripts/notes/stores/state.js @@ -0,0 +1,53 @@ +import { ASC, MR_FILTER_OPTIONS } from '../constants'; + +const createState = () => ({ + discussions: [], + discussionSortOrder: ASC, + persistSortOrder: true, + convertedDisscussionIds: [], + targetNoteHash: null, + lastFetchedAt: null, + currentDiscussionId: null, + batchSuggestionsInfo: [], + currentlyFetchingDiscussions: false, + doneFetchingBatchDiscussions: false, + /** + * selectedCommentPosition & selectedCommentPositionHover structures are the same as `position.line_range`: + * { + * start: { line_code: string, new_line: number, old_line:number, type: string }, + * end: { line_code: string, new_line: number, old_line:number, type: string }, + * } + */ + selectedCommentPosition: null, + selectedCommentPositionHover: null, + + // View layer + isToggleStateButtonLoading: false, + isNotesFetched: false, + isLoading: true, + isLoadingDescriptionVersion: false, + isPromoteCommentToTimelineEventInProgress: false, + + // holds endpoints and permissions provided through haml + notesData: { + markdownDocsPath: '', + }, + userData: {}, + noteableData: { + discussion_locked: false, + confidential: false, // TODO: Move data like this to Issue Store, should not be apart of notes. + current_user: {}, + preview_note_path: 'path/to/preview', + }, + isResolvingDiscussion: false, + commentsDisabled: false, + resolvableDiscussionsCount: 0, + unresolvedDiscussionsCount: 0, + descriptionVersions: {}, + isTimelineEnabled: false, + isFetching: false, + isPollingInitialized: false, + mergeRequestFilters: MR_FILTER_OPTIONS.map((f) => f.value), +}); + +export default createState; diff --git a/app/components/pajamas/alert_component.html.haml b/app/components/pajamas/alert_component.html.haml index 13c458f05e9..a7be57311bb 100644 --- a/app/components/pajamas/alert_component.html.haml +++ b/app/components/pajamas/alert_component.html.haml @@ -2,10 +2,10 @@ - if @show_icon = sprite_icon(icon, css_class: icon_classes) - if @dismissible - %button.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon.js-close{ @close_button_options, - type: 'button', - aria: { label: _('Dismiss') } } - = sprite_icon('close') + = render Pajamas::ButtonComponent.new(category: :tertiary, + icon: 'close', + size: :small, + button_options: dismissible_button_options) .gl-alert-content{ role: 'alert' } - if @title %h4.gl-alert-title diff --git a/app/components/pajamas/alert_component.rb b/app/components/pajamas/alert_component.rb index 4475f4cde6e..008d624b7e2 100644 --- a/app/components/pajamas/alert_component.rb +++ b/app/components/pajamas/alert_component.rb @@ -50,5 +50,13 @@ module Pajamas def icon_classes "gl-alert-icon#{' gl-alert-icon-no-title' if @title.nil?}" end + + def dismissible_button_options + new_options = @close_button_options.deep_symbolize_keys # in case strings were used + new_options[:class] = "js-close gl-dismiss-btn #{new_options[:class]}" + new_options[:aria] ||= {} + new_options[:aria][:label] = _('Dismiss') # this will wipe out label if already present + new_options + end end end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index c58ad92d7a6..82157e48b0b 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -300,11 +300,7 @@ class Packages::Package < ApplicationRecord # TODO: rename the method https://gitlab.com/gitlab-org/gitlab/-/issues/410352 def original_build_info strong_memoize(:original_build_info) do - if Feature.enabled?(:packages_display_last_pipeline, project) - build_infos.last - else - build_infos.first - end + build_infos.last end end diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index 896cccfa0e5..1ec1fb2fdfd 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -65,7 +65,11 @@ module Users artifacts_management_page_feedback_banner: 62, # 63 and 64 were removed with https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120233 branch_rules_info_callout: 65, - create_runner_workflow_banner: 66 + create_runner_workflow_banner: 66, + repository_storage_limit_banner_info_threshold: 67, # EE-only + repository_storage_limit_banner_warning_threshold: 68, # EE-only + repository_storage_limit_banner_alert_threshold: 69, # EE-only + repository_storage_limit_banner_error_threshold: 70 # EE-only } validates :feature_name, diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb index 1cc9f1f50ad..03384b594d6 100644 --- a/app/models/users/group_callout.rb +++ b/app/models/users/group_callout.rb @@ -25,7 +25,11 @@ module Users preview_usage_quota_free_plan_alert: 15, # EE-only enforcement_at_limit_alert: 16, # EE-only web_hook_disabled: 17, # EE-only - unlimited_members_during_trial_alert: 18 # EE-only + unlimited_members_during_trial_alert: 18, # EE-only + repository_storage_limit_banner_info_threshold: 19, # EE-only + repository_storage_limit_banner_warning_threshold: 20, # EE-only + repository_storage_limit_banner_alert_threshold: 21, # EE-only + repository_storage_limit_banner_error_threshold: 22 # EE-only } validates :group, presence: true diff --git a/config/feature_flags/development/members_with_shared_group_access.yml b/config/feature_flags/development/members_with_shared_group_access.yml index 83d3ca62681..f9d9a4fede4 100644 --- a/config/feature_flags/development/members_with_shared_group_access.yml +++ b/config/feature_flags/development/members_with_shared_group_access.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/398621 milestone: '15.11' type: development group: group::tenant scale -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/packages_display_last_pipeline.yml b/config/feature_flags/development/packages_display_last_pipeline.yml deleted file mode 100644 index a90edb4bb86..00000000000 --- a/config/feature_flags/development/packages_display_last_pipeline.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: packages_display_last_pipeline -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120125 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/410362 -milestone: '16.0' -type: development -group: group::package registry -default_enabled: false diff --git a/config/puma.example.development.rb b/config/puma.example.development.rb index 1b75238112f..1183e3d75cf 100644 --- a/config/puma.example.development.rb +++ b/config/puma.example.development.rb @@ -53,10 +53,6 @@ on_restart do end before_fork do - # Signal to the puma killer - enable_puma_worker_killer = !Gitlab::Utils.to_boolean(ENV['DISABLE_PUMA_WORKER_KILLER']) - Gitlab::Cluster::PumaWorkerKillerInitializer.start(@config.options) if enable_puma_worker_killer - # Signal application hooks that we're about to fork Gitlab::Cluster::LifecycleEvents.do_before_fork end diff --git a/config/puma.rb.example b/config/puma.rb.example index d474fc70500..ac3adc1eeec 100644 --- a/config/puma.rb.example +++ b/config/puma.rb.example @@ -36,7 +36,6 @@ bind 'unix:///home/git/gitlab/tmp/sockets/gitlab.socket' workers 3 require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events" -require_relative "/home/git/gitlab/lib/gitlab/cluster/puma_worker_killer_initializer" on_restart do # Signal application hooks that we're about to restart @@ -44,9 +43,6 @@ on_restart do end before_fork do - # Signal to the puma killer - Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options unless ENV['DISABLE_PUMA_WORKER_KILLER'] - # Signal application hooks that we're about to fork Gitlab::Cluster::LifecycleEvents.do_before_fork end diff --git a/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml index 630aeccd6e1..9746663277a 100644 --- a/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml +++ b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml @@ -1,5 +1,5 @@ --- -migration_job_name: CleanupPersonalAccessTokensWithInvalidExpiresAt +migration_job_name: CleanupPersonalAccessTokensWithNilExpiresAt description: Updates value of expires_at column to 365 days from now when it's nil for PersonalAccessTokens feature_category: system_access introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120239 diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 9a7724932eb..c5668d4ae57 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -19023,6 +19023,7 @@ Represents a product analytics dashboard. | <a id="productanalyticsdashboardpanels"></a>`panels` | [`ProductAnalyticsDashboardPanelConnection!`](#productanalyticsdashboardpanelconnection) | Panels shown on the dashboard. (see [Connections](#connections)) | | <a id="productanalyticsdashboardslug"></a>`slug` | [`String!`](#string) | Slug of the dashboard. | | <a id="productanalyticsdashboardtitle"></a>`title` | [`String!`](#string) | Title of the dashboard. | +| <a id="productanalyticsdashboarduserdefined"></a>`userDefined` | [`Boolean!`](#boolean) | Indicates whether the dashboard is user-defined or provided by GitLab. | ### `ProductAnalyticsDashboardPanel` @@ -25618,6 +25619,10 @@ Name of the feature that the callout is for. | <a id="usercalloutfeaturenameenumprofile_personal_access_token_expiry"></a>`PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for profile_personal_access_token_expiry. | | <a id="usercalloutfeaturenameenumproject_quality_summary_feedback"></a>`PROJECT_QUALITY_SUMMARY_FEEDBACK` | Callout feature name for project_quality_summary_feedback. | | <a id="usercalloutfeaturenameenumregistration_enabled_callout"></a>`REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. | +| <a id="usercalloutfeaturenameenumrepository_storage_limit_banner_alert_threshold"></a>`REPOSITORY_STORAGE_LIMIT_BANNER_ALERT_THRESHOLD` | Callout feature name for repository_storage_limit_banner_alert_threshold. | +| <a id="usercalloutfeaturenameenumrepository_storage_limit_banner_error_threshold"></a>`REPOSITORY_STORAGE_LIMIT_BANNER_ERROR_THRESHOLD` | Callout feature name for repository_storage_limit_banner_error_threshold. | +| <a id="usercalloutfeaturenameenumrepository_storage_limit_banner_info_threshold"></a>`REPOSITORY_STORAGE_LIMIT_BANNER_INFO_THRESHOLD` | Callout feature name for repository_storage_limit_banner_info_threshold. | +| <a id="usercalloutfeaturenameenumrepository_storage_limit_banner_warning_threshold"></a>`REPOSITORY_STORAGE_LIMIT_BANNER_WARNING_THRESHOLD` | Callout feature name for repository_storage_limit_banner_warning_threshold. | | <a id="usercalloutfeaturenameenumsecurity_configuration_devops_alert"></a>`SECURITY_CONFIGURATION_DEVOPS_ALERT` | Callout feature name for security_configuration_devops_alert. | | <a id="usercalloutfeaturenameenumsecurity_configuration_upgrade_banner"></a>`SECURITY_CONFIGURATION_UPGRADE_BANNER` | Callout feature name for security_configuration_upgrade_banner. | | <a id="usercalloutfeaturenameenumsecurity_newsletter_callout"></a>`SECURITY_NEWSLETTER_CALLOUT` | Callout feature name for security_newsletter_callout. | diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md index d920c34d90a..fcacc3b5d97 100644 --- a/doc/ci/pipeline_editor/index.md +++ b/doc/ci/pipeline_editor/index.md @@ -113,7 +113,7 @@ where: with the linked configuration. Using `!reference` tags can cause nested configuration that display with -multiple hyphens (`-`) in the expanded view. This behavior is expected, and the extra +multiple hyphens (`-`) at the start of the line in the expanded view. This behavior is expected, and the extra hyphens do not affect the job's execution. For example, this configuration and fully expanded version are both valid: @@ -124,11 +124,27 @@ fully expanded version are both valid: script: - pip install pyflakes + .rule-01: + rules: + - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ + when: manual + allow_failure: true + - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME + + .rule-02: + rules: + - if: $CI_COMMIT_BRANCH == "main" + when: manual + allow_failure: true + lint-python: image: python:latest script: - !reference [.python-req, script] - pyflakes python/ + rules: + - !reference [.rule-01, rules] + - !reference [.rule-02, rules] ``` - Expanded configuration in **Full configuration** tab: @@ -137,11 +153,30 @@ fully expanded version are both valid: ".python-req": script: - pip install pyflakes + ".rule-01": + rules: + - if: "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/" + when: manual + allow_failure: true + - if: "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" + ".rule-02": + rules: + - if: $CI_COMMIT_BRANCH == "main" + when: manual + allow_failure: true lint-python: + image: python:latest script: - - - pip install pyflakes # <- The extra hyphens do not affect the job's execution. + - - pip install pyflakes # <- The extra hyphens do not affect the job's execution. - pyflakes python/ - image: python:latest + rules: + - - if: "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/" # <- The extra hyphens do not affect the job's execution. + when: manual + allow_failure: true + - if: "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" # <- No extra hyphen but aligned with previous rule + - - if: $CI_COMMIT_BRANCH == "main" # <- The extra hyphens do not affect the job's execution. + when: manual + allow_failure: true ``` ## Commit changes to CI configuration diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 2c52a5d743a..8cd924a8c08 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -162,7 +162,7 @@ The following quick actions can be applied through the description field when ed | `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Clear [health status](issues/managing_issues.md#health-status). | `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, and `2`. | `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. -| `/type` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Converts work item to specified type. Available options for `<type>` include `Issue`, `Task`, `Objective` and `Key Result`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385227) in GitLab 16.0 [with a flag](../../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default. +| `/type` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Converts work item to specified type. Available options for `<type>` include `Issue`, `Task`, `Objective` and `Key Result`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385227) in GitLab 16.0. ## Commit messages diff --git a/lib/gitlab/cluster/puma_worker_killer_initializer.rb b/lib/gitlab/cluster/puma_worker_killer_initializer.rb deleted file mode 100644 index 957faf797b5..00000000000 --- a/lib/gitlab/cluster/puma_worker_killer_initializer.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Cluster - class PumaWorkerKillerInitializer - def self.start( - puma_options, - puma_per_worker_max_memory_mb: 1200, - puma_master_max_memory_mb: 950, - additional_puma_dev_max_memory_mb: 200) - - # We are replacing PWK with Watchdog by using backward compatible RssMemoryLimit monitor by default. - # https://gitlab.com/groups/gitlab-org/-/epics/9119 - return if Gitlab::Utils.to_boolean(ENV.fetch('GITLAB_MEMORY_WATCHDOG_ENABLED', true)) - - require 'puma_worker_killer' - - PumaWorkerKiller.config do |config| - worker_count = puma_options[:workers] || 1 - # The Puma Worker Killer checks the total memory used by the cluster, - # i.e. both primary and worker processes. - # https://github.com/schneems/puma_worker_killer/blob/v0.1.0/lib/puma_worker_killer/puma_memory.rb#L57 - # - # Additional memory is added when running in `development` - config.ram = puma_master_max_memory_mb + - (worker_count * puma_per_worker_max_memory_mb) + - (Rails.env.development? ? (1 + worker_count) * additional_puma_dev_max_memory_mb : 0) - - config.frequency = 20 # seconds - - # We just want to limit to a fixed maximum, unrelated to the total amount - # of available RAM. - config.percent_usage = 0.98 - - # Ideally we'll never hit the maximum amount of memory. Restart the workers - # regularly rather than rely on OOM behavior for periodic restarting. - config.rolling_restart_frequency = 43200 # 12 hours in seconds. - - # Spread the rolling restarts out over 1 hour to avoid too many simultaneous - # process startups. - config.rolling_restart_splay_seconds = 0.0..3600.0 # 0 to 1 hour in seconds. - - observer = Gitlab::Cluster::PumaWorkerKillerObserver.new - config.pre_term = observer.callback - end - - PumaWorkerKiller.start - end - end - end -end diff --git a/lib/gitlab/cluster/puma_worker_killer_observer.rb b/lib/gitlab/cluster/puma_worker_killer_observer.rb deleted file mode 100644 index f53051c32ff..00000000000 --- a/lib/gitlab/cluster/puma_worker_killer_observer.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Cluster - class PumaWorkerKillerObserver - def initialize - @counter = Gitlab::Metrics.counter(:puma_killer_terminations_total, 'Number of workers terminated by PumaWorkerKiller') - end - - # returns the Proc to be used as the observer callback block - def callback - method(:log_termination) - end - - private - - def log_termination(worker) - @counter.increment - end - end - end -end diff --git a/lib/gitlab/quick_actions/work_item_actions.rb b/lib/gitlab/quick_actions/work_item_actions.rb index fa43308c9e2..69a49d214ff 100644 --- a/lib/gitlab/quick_actions/work_item_actions.rb +++ b/lib/gitlab/quick_actions/work_item_actions.rb @@ -12,9 +12,6 @@ module Gitlab format(_("Converts work item to %{type}. Widgets not supported in new type are removed."), type: target_type) end types WorkItem - condition do - quick_action_target&.project&.work_items_mvc_2_feature_flag_enabled? - end params 'Task | Objective | Key Result | Issue' command :type do |type_name| work_item_type = ::WorkItems::Type.find_by_name(type_name) diff --git a/lib/gitlab/usage_data_counters/known_events/product_analytics.yml b/lib/gitlab/usage_data_counters/known_events/product_analytics.yml index 5a791c4b3c2..56a077763c8 100644 --- a/lib/gitlab/usage_data_counters/known_events/product_analytics.yml +++ b/lib/gitlab/usage_data_counters/known_events/product_analytics.yml @@ -2,3 +2,5 @@ aggregation: weekly - name: project_initialized_product_analytics aggregation: weekly +- name: user_created_analytics_dashboard + aggregation: weekly diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e39517033a1..a0eec5283e0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5076,6 +5076,9 @@ msgstr "" msgid "Analytics|Add to Dashboard" msgstr "" +msgid "Analytics|Add visualizations" +msgstr "" + msgid "Analytics|An error occurred while loading data" msgstr "" @@ -34153,6 +34156,9 @@ msgstr "" msgid "ProductAnalytics|Compares page views of all pages against each other" msgstr "" +msgid "ProductAnalytics|Create a visualization" +msgstr "" + msgid "ProductAnalytics|Creating your product analytics instance..." msgstr "" @@ -39610,12 +39616,6 @@ msgstr "" msgid "ScanExecutionPolicy|%{rules} actions for the %{scopes} %{branches} %{agents} %{namespaces}" msgstr "" -msgid "ScanExecutionPolicy|%{thenLabelStart}Then%{thenLabelEnd} Run a %{scan} scan on runner that %{tags}" -msgstr "" - -msgid "ScanExecutionPolicy|%{thenLabelStart}Then%{thenLabelEnd} Run a %{scan} scan with %{dastProfiles} with tags %{tags}" -msgstr "" - msgid "ScanExecutionPolicy|A pipeline is run" msgstr "" @@ -39628,6 +39628,12 @@ msgstr "" msgid "ScanExecutionPolicy|If the field is empty, the runner will be automatically selected" msgstr "" +msgid "ScanExecutionPolicy|Run a %{scan} scan on runner that %{tags}" +msgstr "" + +msgid "ScanExecutionPolicy|Run a %{scan} scan with %{dastProfiles} with tags %{tags}" +msgstr "" + msgid "ScanExecutionPolicy|Scanner profile" msgstr "" @@ -47423,9 +47429,6 @@ msgstr "" msgid "Trial|Continue" msgstr "" -msgid "Trial|Dismiss" -msgstr "" - msgid "Trial|Your GitLab Ultimate trial lasts for 30 days, but you can keep your free GitLab account forever. We just need some additional information to activate your trial." msgstr "" diff --git a/package.json b/package.json index 30dafb88268..fed66bb53c5 100644 --- a/package.json +++ b/package.json @@ -279,6 +279,7 @@ "timezone-mock": "^1.0.8", "vue-loader-vue3": "npm:vue-loader@17", "vue-test-utils-compat": "0.0.13", + "vuex-mock-store": "^0.1.0", "webpack-dev-server": "4.15.0", "xhr-mock": "^2.5.1", "yarn-check-webpack-plugin": "^1.2.0", diff --git a/rubocop/cop/rspec/avoid_conditional_statements.rb b/rubocop/cop/rspec/avoid_conditional_statements.rb index d03c3fb5d04..c24133f6469 100644 --- a/rubocop/cop/rspec/avoid_conditional_statements.rb +++ b/rubocop/cop/rspec/avoid_conditional_statements.rb @@ -7,6 +7,8 @@ module RuboCop module RSpec # This cop checks for the usage of conditional statements in specs. # + # https://gitlab.com/gitlab-org/gitlab/-/issues/410138 + # # @example # # # bad diff --git a/spec/components/pajamas/alert_component_spec.rb b/spec/components/pajamas/alert_component_spec.rb index 8f02979357e..4b554564d6e 100644 --- a/spec/components/pajamas/alert_component_spec.rb +++ b/spec/components/pajamas/alert_component_spec.rb @@ -126,25 +126,45 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do end context 'with dismissible content' do - before do - render_inline described_class.new( - close_button_options: { - class: '_close_button_class_', - data: { - testid: '_close_button_testid_' - } - } - ) - end + context 'with no custom options' do + before do + render_inline described_class.new + end - it 'does not have "not dismissible" class' do - expect(page).not_to have_selector('.gl-alert-not-dismissible') + it 'does not have "not dismissible" class' do + expect(page).not_to have_selector('.gl-alert-not-dismissible') + end + + it 'renders a dismiss button and data' do + expect(page).to have_selector('.gl-button.btn-sm.btn-icon.gl-button.gl-dismiss-btn.js-close') + expect(page).to have_selector("[data-testid='close-icon']") + expect(page).to have_selector('[aria-label="Dismiss"]') + end end - it 'renders a dismiss button and data' do - expect(page).to have_selector('.gl-dismiss-btn.js-close._close_button_class_') - expect(page).to have_selector("[data-testid='close-icon']") - expect(page).to have_selector('[data-testid="_close_button_testid_"]') + context 'with custom options' do + before do + render_inline described_class.new( + close_button_options: { + aria: { + label: '_custom_aria_label_' + }, + class: '_close_button_class_', + data: { + testid: '_close_button_testid_', + "custom-attribute": '_custom_data_' + } + } + ) + end + + it 'renders a dismiss button and data' do + expect(page).to have_selector('.gl-button.btn-sm.btn-icon.gl-dismiss-btn.js-close._close_button_class_') + expect(page).to have_selector("[data-testid='close-icon']") + expect(page).to have_selector('[data-testid="_close_button_testid_"]') + expect(page).to have_selector('[aria-label="Dismiss"]') + expect(page).to have_selector('[data-custom-attribute="_custom_data_"]') + end end end diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index e212d464029..335866a7d75 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Environment > Metrics', feature_category: :projects do include PrometheusHelpers let(:user) { create(:user) } - let(:project) { create(:project, :with_prometheus_integration, :repository) } + let_it_be(:project) { create(:project, :with_prometheus_integration, :repository) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:environment) { create(:environment, project: project) } diff --git a/spec/frontend/__helpers__/mocks/mr_notes/stores/index.js b/spec/frontend/__helpers__/mocks/mr_notes/stores/index.js new file mode 100644 index 00000000000..a983edbbb72 --- /dev/null +++ b/spec/frontend/__helpers__/mocks/mr_notes/stores/index.js @@ -0,0 +1,15 @@ +import { Store } from 'vuex-mock-store'; +import createDiffState from 'ee_else_ce/diffs/store/modules/diff_state'; +import createNotesState from '~/notes/stores/state'; + +const store = new Store({ + state: { + diffs: createDiffState(), + notes: createNotesState(), + }, + spy: { + create: (handler) => jest.fn(handler).mockImplementation(() => Promise.resolve()), + }, +}); + +export default store; diff --git a/spec/frontend/batch_comments/components/preview_item_spec.js b/spec/frontend/batch_comments/components/preview_item_spec.js index a19a72af813..191586e44cc 100644 --- a/spec/frontend/batch_comments/components/preview_item_spec.js +++ b/spec/frontend/batch_comments/components/preview_item_spec.js @@ -1,29 +1,33 @@ import { mount } from '@vue/test-utils'; import PreviewItem from '~/batch_comments/components/preview_item.vue'; -import { createStore } from '~/batch_comments/stores'; -import diffsModule from '~/diffs/store/modules'; -import notesModule from '~/notes/stores/modules'; +import store from '~/mr_notes/stores'; import { createDraft } from '../mock_data'; jest.mock('~/behaviors/markdown/render_gfm'); +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); describe('Batch comments draft preview item component', () => { let wrapper; let draft; - function createComponent(isLast = false, extra = {}, extendStore = () => {}) { - const store = createStore(); - store.registerModule('diffs', diffsModule()); - store.registerModule('notes', notesModule()); + beforeEach(() => { + store.reset(); - extendStore(store); + store.getters.getDiscussion = jest.fn(() => null); + }); + function createComponent(isLast = false, extra = {}) { draft = { ...createDraft(), ...extra, }; - wrapper = mount(PreviewItem, { store, propsData: { draft, isLast } }); + wrapper = mount(PreviewItem, { + mocks: { + $store: store, + }, + propsData: { draft, isLast }, + }); } it('renders text content', () => { @@ -87,18 +91,19 @@ describe('Batch comments draft preview item component', () => { describe('for thread', () => { beforeEach(() => { - createComponent(false, { discussion_id: '1', resolve_discussion: true }, (store) => { - store.state.notes.discussions.push({ - id: '1', - notes: [ - { - author: { - name: "Author 'Nick' Name", - }, + store.getters.getDiscussion.mockReturnValue({ + id: '1', + notes: [ + { + author: { + name: "Author 'Nick' Name", }, - ], - }); + }, + ], }); + store.getters.isDiscussionResolved = jest.fn().mockReturnValue(false); + + createComponent(false, { discussion_id: '1', resolve_discussion: true }); }); it('renders title', () => { @@ -114,9 +119,7 @@ describe('Batch comments draft preview item component', () => { describe('for new comment', () => { it('renders title', () => { - createComponent(false, {}, (store) => { - store.state.notes.discussions.push({}); - }); + createComponent(); expect(wrapper.find('.review-preview-item-header-text').text()).toContain('Your new comment'); }); diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index 47a266c2e36..cbbfd88260b 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -1,15 +1,14 @@ import { mount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -import Vuex from 'vuex'; +import { nextTick } from 'vue'; import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json'; import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import { trimText } from 'helpers/text_helper'; import CompareVersionsComponent from '~/diffs/components/compare_versions.vue'; -import { createStore } from '~/mr_notes/stores'; +import store from '~/mr_notes/stores'; import diffsMockData from '../mock_data/merge_request_diffs'; -Vue.use(Vuex); +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); const NEXT_COMMIT_URL = `${TEST_HOST}/?commit_id=next`; const PREV_COMMIT_URL = `${TEST_HOST}/?commit_id=prev`; @@ -20,8 +19,6 @@ beforeEach(() => { describe('CompareVersions', () => { let wrapper; - let store; - let dispatchMock; const targetBranchName = 'tmp-wine-dev'; const { commit } = getDiffWithCommit; @@ -30,10 +27,10 @@ describe('CompareVersions', () => { store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs }; } - dispatchMock = jest.spyOn(store, 'dispatch'); - wrapper = mount(CompareVersionsComponent, { - store, + mocks: { + $store: store, + }, propsData: { mergeRequestDiffs: diffsMockData, diffFilesCountText: '1', @@ -50,8 +47,25 @@ describe('CompareVersions', () => { getCommitNavButtonsElement().find('.btn-group > *:first-child'); beforeEach(() => { - store = createStore(); + store.reset(); + const mergeRequestDiff = diffsMockData[0]; + const version = { + ...mergeRequestDiff, + href: `${TEST_HOST}/latest/version`, + versionName: 'latest version', + }; + store.getters['diffs/diffCompareDropdownSourceVersions'] = [version]; + store.getters['diffs/diffCompareDropdownTargetVersions'] = [ + { + ...version, + selected: true, + versionName: targetBranchName, + }, + ]; + store.getters['diffs/whichCollapsedTypes'] = { any: false }; + store.getters['diffs/isInlineView'] = false; + store.getters['diffs/isParallelView'] = false; store.state.diffs.addedLines = 10; store.state.diffs.removedLines = 20; @@ -104,7 +118,6 @@ describe('CompareVersions', () => { it('should not render Tree List toggle button when there are no changes', () => { createWrapper(); - const treeListBtn = wrapper.find('.js-toggle-tree-list'); expect(treeListBtn.exists()).toBe(false); @@ -118,7 +131,10 @@ describe('CompareVersions', () => { const viewTypeBtn = wrapper.find('#inline-diff-btn'); viewTypeBtn.trigger('click'); - expect(window.location.toString()).toContain('?view=inline'); + expect(store.dispatch).toHaveBeenCalledWith( + 'diffs/setInlineDiffViewType', + expect.any(MouseEvent), + ); }); }); @@ -128,13 +144,16 @@ describe('CompareVersions', () => { const viewTypeBtn = wrapper.find('#parallel-diff-btn'); viewTypeBtn.trigger('click'); - expect(window.location.toString()).toContain('?view=parallel'); + expect(store.dispatch).toHaveBeenCalledWith( + 'diffs/setParallelDiffViewType', + expect.any(MouseEvent), + ); }); }); describe('commit', () => { beforeEach(() => { - store.state.diffs.commit = getDiffWithCommit.commit; + store.state.diffs.commit = commit; createWrapper(); }); @@ -218,7 +237,7 @@ describe('CompareVersions', () => { link.trigger('click'); await nextTick(); - expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { + expect(store.dispatch).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { direction: 'previous', }); }); @@ -248,7 +267,7 @@ describe('CompareVersions', () => { link.trigger('click'); await nextTick(); - expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { + expect(store.dispatch).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { direction: 'next', }); }); diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js index eb895bd9057..e42b98e4d68 100644 --- a/spec/frontend/diffs/components/diff_line_note_form_spec.js +++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js @@ -1,8 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import Vuex from 'vuex'; import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue'; -import { createModules } from '~/mr_notes/stores'; +import store from '~/mr_notes/stores'; import NoteForm from '~/notes/components/note_form.vue'; import MultilineCommentForm from '~/notes/components/multiline_comment_form.vue'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; @@ -10,51 +9,25 @@ import { noteableDataMock } from 'jest/notes/mock_data'; import { getDiffFileMock } from '../mock_data/diff_file'; jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); describe('DiffLineNoteForm', () => { let wrapper; let diffFile; let diffLines; - let actions; - let store; - const getSelectedLine = () => { - const lineCode = diffLines[1].line_code; - return diffFile.highlighted_diff_lines.find((l) => l.line_code === lineCode); - }; - - const createStore = (state) => { - const modules = createModules(); - modules.diffs.actions = { - ...modules.diffs.actions, - saveDiffDiscussion: jest.fn(() => Promise.resolve()), - }; - modules.diffs.getters = { - ...modules.diffs.getters, - diffCompareDropdownTargetVersions: jest.fn(), - diffCompareDropdownSourceVersions: jest.fn(), - selectedSourceIndex: jest.fn(), - }; - modules.notes.getters = { - ...modules.notes.getters, - noteableType: jest.fn(), - }; - actions = modules.diffs.actions; + beforeEach(() => { + diffFile = getDiffFileMock(); + diffLines = diffFile.highlighted_diff_lines; - store = new Vuex.Store({ modules }); - store.state.notes.userData.id = 1; store.state.notes.noteableData = noteableDataMock; - store.replaceState({ ...store.state, ...state }); - }; + store.getters.isLoggedIn = jest.fn().mockReturnValue(true); + store.getters['diffs/getDiffFileByHash'] = jest.fn().mockReturnValue(diffFile); + }); - const createComponent = ({ props, state } = {}) => { + const createComponent = ({ props } = {}) => { wrapper?.destroy(); - diffFile = getDiffFileMock(); - diffLines = diffFile.highlighted_diff_lines; - - createStore(state); - store.state.diffs.diffFiles = [diffFile]; const propsData = { diffFileHash: diffFile.file_hash, @@ -66,7 +39,9 @@ describe('DiffLineNoteForm', () => { }; wrapper = shallowMount(DiffLineNoteForm, { - store, + mocks: { + $store: store, + }, propsData, }); }; @@ -129,7 +104,10 @@ describe('DiffLineNoteForm', () => { expect(confirmAction).toHaveBeenCalled(); await nextTick(); - expect(getSelectedLine().hasForm).toBe(false); + expect(store.dispatch).toHaveBeenCalledWith('diffs/cancelCommentForm', { + lineCode: diffLines[1].line_code, + fileHash: diffFile.file_hash, + }); }); }); @@ -157,6 +135,10 @@ describe('DiffLineNoteForm', () => { }); describe('saving note', () => { + beforeEach(() => { + store.getters.noteableType = 'merge-request'; + }); + it('should save original line', async () => { const lineRange = { start: { @@ -172,20 +154,65 @@ describe('DiffLineNoteForm', () => { old_line: null, }, }; - await findNoteForm().vm.$emit('handleFormUpdate', 'note body'); - expect(actions.saveDiffDiscussion.mock.calls[0][1].formData).toMatchObject({ - lineRange, + + const noteBody = 'note body'; + await findNoteForm().vm.$emit('handleFormUpdate', noteBody); + + expect(store.dispatch).toHaveBeenCalledWith('diffs/saveDiffDiscussion', { + note: noteBody, + formData: { + noteableData: noteableDataMock, + noteableType: store.getters.noteableType, + noteTargetLine: diffLines[1], + diffViewType: store.state.diffs.diffViewType, + diffFile, + linePosition: '', + lineRange, + }, + }); + expect(store.dispatch).toHaveBeenCalledWith('diffs/cancelCommentForm', { + lineCode: diffLines[1].line_code, + fileHash: diffFile.file_hash, }); }); it('should save selected line from the store', async () => { const lineCode = 'test'; store.state.notes.selectedCommentPosition = { start: { line_code: lineCode } }; - createComponent({ state: store.state }); - await findNoteForm().vm.$emit('handleFormUpdate', 'note body'); - expect(actions.saveDiffDiscussion.mock.calls[0][1].formData.lineRange.start.line_code).toBe( - lineCode, - ); + createComponent(); + const noteBody = 'note body'; + + await findNoteForm().vm.$emit('handleFormUpdate', noteBody); + + expect(store.dispatch).toHaveBeenCalledWith('diffs/saveDiffDiscussion', { + note: noteBody, + formData: { + noteableData: noteableDataMock, + noteableType: store.getters.noteableType, + noteTargetLine: diffLines[1], + diffViewType: store.state.diffs.diffViewType, + diffFile, + linePosition: '', + lineRange: { + start: { + line_code: lineCode, + new_line: undefined, + old_line: undefined, + type: undefined, + }, + end: { + line_code: diffLines[1].line_code, + new_line: diffLines[1].new_line, + old_line: diffLines[1].old_line, + type: diffLines[1].type, + }, + }, + }, + }); + expect(store.dispatch).toHaveBeenCalledWith('diffs/cancelCommentForm', { + lineCode: diffLines[1].line_code, + fileHash: diffFile.file_hash, + }); }); }); }); diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js index e637b1dd43d..fd89d52a59e 100644 --- a/spec/frontend/diffs/components/no_changes_spec.js +++ b/spec/frontend/diffs/components/no_changes_spec.js @@ -1,55 +1,53 @@ import { GlButton } from '@gitlab/ui'; import { shallowMount, mount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; import NoChanges from '~/diffs/components/no_changes.vue'; -import { createStore } from '~/mr_notes/stores'; +import store from '~/mr_notes/stores'; import diffsMockData from '../mock_data/merge_request_diffs'; -Vue.use(Vuex); +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); const TEST_TARGET_BRANCH = 'foo'; const TEST_SOURCE_BRANCH = 'dev/update'; +const latestVersionNumber = Math.max(...diffsMockData.map((version) => version.version_index)); describe('Diff no changes empty state', () => { - let wrapper; - let store; - - function createComponent(mountFn = shallowMount) { - wrapper = mountFn(NoChanges, { - store, + const createComponent = (mountFn = shallowMount) => + mountFn(NoChanges, { + mocks: { + $store: store, + }, propsData: { changesEmptyStateIllustration: '', }, }); - } beforeEach(() => { - store = createStore(); - store.state.diffs.mergeRequestDiff = {}; - store.state.notes.noteableData = { + store.reset(); + + store.getters.getNoteableData = { target_branch: TEST_TARGET_BRANCH, source_branch: TEST_SOURCE_BRANCH, }; - store.state.diffs.mergeRequestDiffs = diffsMockData; + store.getters['diffs/diffCompareDropdownSourceVersions'] = []; + store.getters['diffs/diffCompareDropdownTargetVersions'] = []; }); - const findMessage = () => wrapper.find('[data-testid="no-changes-message"]'); + const findMessage = (wrapper) => wrapper.find('[data-testid="no-changes-message"]'); it('prevents XSS', () => { - store.state.notes.noteableData = { + store.getters.getNoteableData = { source_branch: '<script>alert("test");</script>', target_branch: '<script>alert("test");</script>', }; - createComponent(); + const wrapper = createComponent(); expect(wrapper.find('script').exists()).toBe(false); }); describe('Renders', () => { it('Show create commit button', () => { - createComponent(); + const wrapper = createComponent(); expect(wrapper.findComponent(GlButton).exists()).toBe(true); }); @@ -64,15 +62,28 @@ describe('Diff no changes empty state', () => { 'renders text "$expectedText" (sourceIndex=$sourceIndex and targetIndex=$targetIndex)', ({ expectedText, targetIndex, sourceIndex }) => { if (targetIndex !== null) { - store.state.diffs.startVersion = { version_index: targetIndex }; + store.getters['diffs/diffCompareDropdownTargetVersions'] = [ + { + selected: true, + version_index: targetIndex, + versionName: `version ${targetIndex}`, + }, + ]; } if (sourceIndex !== null) { - store.state.diffs.mergeRequestDiff.version_index = sourceIndex; + store.getters['diffs/diffCompareDropdownSourceVersions'] = [ + { + isLatestVersion: sourceIndex === latestVersionNumber, + selected: true, + version_index: targetIndex, + versionName: `version ${sourceIndex}`, + }, + ]; } - createComponent(mount); + const wrapper = createComponent(mount); - expect(findMessage().text()).toBe(expectedText); + expect(findMessage(wrapper).text()).toBe(expectedText); }, ); }); diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js index 3d2bbe43746..cbd2ae3e525 100644 --- a/spec/frontend/diffs/components/settings_dropdown_spec.js +++ b/spec/frontend/diffs/components/settings_dropdown_spec.js @@ -5,44 +5,34 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import SettingsDropdown from '~/diffs/components/settings_dropdown.vue'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; import eventHub from '~/diffs/event_hub'; +import store from '~/mr_notes/stores'; -import createDiffsStore from '../create_diffs_store'; +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); describe('Diff settings dropdown component', () => { - let wrapper; - let store; - - function createComponent(extendStore = () => {}) { - store = createDiffsStore(); - - extendStore(store); - - wrapper = extendedWrapper( + const createComponent = () => + extendedWrapper( mount(SettingsDropdown, { - store, + mocks: { + $store: store, + }, }), ); - } function getFileByFileCheckbox(vueWrapper) { return vueWrapper.findByTestId('file-by-file'); } - function setup({ storeUpdater } = {}) { - createComponent(storeUpdater); - jest.spyOn(store, 'dispatch').mockImplementation(() => {}); - } - beforeEach(() => { - setup(); - }); + store.reset(); - afterEach(() => { - store.dispatch.mockRestore(); + store.getters['diffs/isInlineView'] = false; + store.getters['diffs/isParallelView'] = false; }); describe('tree view buttons', () => { it('list view button dispatches setRenderTreeList with false', () => { + const wrapper = createComponent(); wrapper.find('.js-list-view').trigger('click'); expect(store.dispatch).toHaveBeenCalledWith('diffs/setRenderTreeList', { @@ -51,6 +41,7 @@ describe('Diff settings dropdown component', () => { }); it('tree view button dispatches setRenderTreeList with true', () => { + const wrapper = createComponent(); wrapper.find('.js-tree-view').trigger('click'); expect(store.dispatch).toHaveBeenCalledWith('diffs/setRenderTreeList', { @@ -59,19 +50,18 @@ describe('Diff settings dropdown component', () => { }); it('sets list button as selected when renderTreeList is false', () => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { renderTreeList: false }), - }); + store.state.diffs = { renderTreeList: false }; + + const wrapper = createComponent(); expect(wrapper.find('.js-list-view').classes('selected')).toBe(true); expect(wrapper.find('.js-tree-view').classes('selected')).toBe(false); }); it('sets tree button as selected when renderTreeList is true', () => { - setup({ - storeUpdater: (origStore) => Object.assign(origStore.state.diffs, { renderTreeList: true }), - }); + store.state.diffs = { renderTreeList: true }; + + const wrapper = createComponent(); expect(wrapper.find('.js-list-view').classes('selected')).toBe(false); expect(wrapper.find('.js-tree-view').classes('selected')).toBe(true); @@ -80,32 +70,36 @@ describe('Diff settings dropdown component', () => { describe('compare changes', () => { it('sets inline button as selected', () => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { diffViewType: INLINE_DIFF_VIEW_TYPE }), - }); + store.state.diffs = { diffViewType: INLINE_DIFF_VIEW_TYPE }; + store.getters['diffs/isInlineView'] = true; + + const wrapper = createComponent(); expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(true); expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(false); }); it('sets parallel button as selected', () => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { diffViewType: PARALLEL_DIFF_VIEW_TYPE }), - }); + store.state.diffs = { diffViewType: PARALLEL_DIFF_VIEW_TYPE }; + store.getters['diffs/isParallelView'] = true; + + const wrapper = createComponent(); expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(false); expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(true); }); it('calls setInlineDiffViewType when clicking inline button', () => { + const wrapper = createComponent(); + wrapper.find('.js-inline-diff-button').trigger('click'); expect(store.dispatch).toHaveBeenCalledWith('diffs/setInlineDiffViewType', expect.anything()); }); it('calls setParallelDiffViewType when clicking parallel button', () => { + const wrapper = createComponent(); + wrapper.find('.js-parallel-diff-button').trigger('click'); expect(store.dispatch).toHaveBeenCalledWith( @@ -117,23 +111,23 @@ describe('Diff settings dropdown component', () => { describe('whitespace toggle', () => { it('does not set as checked when showWhitespace is false', () => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { showWhitespace: false }), - }); + store.state.diffs = { showWhitespace: false }; + + const wrapper = createComponent(); expect(wrapper.findByTestId('show-whitespace').element.checked).toBe(false); }); it('sets as checked when showWhitespace is true', () => { - setup({ - storeUpdater: (origStore) => Object.assign(origStore.state.diffs, { showWhitespace: true }), - }); + store.state.diffs = { showWhitespace: true }; + + const wrapper = createComponent(); expect(wrapper.findByTestId('show-whitespace').element.checked).toBe(true); }); it('calls setShowWhitespace on change', async () => { + const wrapper = createComponent(); const checkbox = wrapper.findByTestId('show-whitespace'); const { checked } = checkbox.element; @@ -157,10 +151,9 @@ describe('Diff settings dropdown component', () => { `( 'sets the checkbox to { checked: $checked } if the fileByFile setting is $fileByFile', ({ fileByFile, checked }) => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { viewDiffsFileByFile: fileByFile }), - }); + store.state.diffs = { viewDiffsFileByFile: fileByFile }; + + const wrapper = createComponent(); expect(getFileByFileCheckbox(wrapper).element.checked).toBe(checked); }, @@ -173,11 +166,9 @@ describe('Diff settings dropdown component', () => { `( 'when the file by file setting starts as $start, toggling the checkbox should call setFileByFile with $setting', async ({ start, setting }) => { - setup({ - storeUpdater: (origStore) => - Object.assign(origStore.state.diffs, { viewDiffsFileByFile: start }), - }); + store.state.diffs = { viewDiffsFileByFile: start }; + const wrapper = createComponent(); await getFileByFileCheckbox(wrapper).setChecked(setting); expect(store.dispatch).toHaveBeenCalledWith('diffs/setFileByFile', { diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index f883aea764f..f084458b5c7 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -707,6 +707,7 @@ describe('DiffsStoreActions', () => { [{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }], [], ); + expect(window.location.toString()).toContain('?view=inline'); expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE); }); }); @@ -720,6 +721,7 @@ describe('DiffsStoreActions', () => { [{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }], [], ); + expect(window.location.toString()).toContain('?view=parallel'); expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE); }); }); diff --git a/spec/frontend/issuable/components/issuable_header_warnings_spec.js b/spec/frontend/issuable/components/issuable_header_warnings_spec.js index ff772040d22..cbe2810a443 100644 --- a/spec/frontend/issuable/components/issuable_header_warnings_spec.js +++ b/spec/frontend/issuable/components/issuable_header_warnings_spec.js @@ -1,15 +1,13 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { createStore as createMrStore } from '~/mr_notes/stores'; +import mrStore from '~/mr_notes/stores'; import createIssueStore from '~/notes/stores'; import IssuableHeaderWarnings from '~/issuable/components/issuable_header_warnings.vue'; const ISSUABLE_TYPE_ISSUE = 'issue'; const ISSUABLE_TYPE_MR = 'merge_request'; -Vue.use(Vuex); +jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); describe('IssuableHeaderWarnings', () => { let wrapper; @@ -22,7 +20,9 @@ describe('IssuableHeaderWarnings', () => { const createComponent = ({ store, provide }) => { wrapper = shallowMountExtended(IssuableHeaderWarnings, { - store, + mocks: { + $store: store, + }, provide, directives: { GlTooltip: createMockDirective('gl-tooltip'), @@ -47,9 +47,14 @@ describe('IssuableHeaderWarnings', () => { `( `when locked=$lockStatus, confidential=$confidentialStatus, and hidden=$hiddenStatus`, ({ lockStatus, confidentialStatus, hiddenStatus }) => { - const store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore(); + const store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : mrStore; beforeEach(() => { + // TODO: simplify to single assignment after issue store is mock + if (store === mrStore) { + store.getters.getNoteableData = {}; + } + store.getters.getNoteableData.confidential = confidentialStatus; store.getters.getNoteableData.discussion_locked = lockStatus; store.getters.getNoteableData.targetType = issuableType; diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb deleted file mode 100644 index cb13a711857..00000000000 --- a/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' -require 'puma_worker_killer' - -RSpec.describe Gitlab::Cluster::PumaWorkerKillerInitializer do - describe '.start' do - context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is false' do - before do - stub_env('GITLAB_MEMORY_WATCHDOG_ENABLED', 'false') - end - - it 'configures and start PumaWorkerKiller' do - expect(PumaWorkerKiller).to receive(:config) - expect(PumaWorkerKiller).to receive(:start) - - described_class.start({}) - end - end - - context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is not set' do - it 'configures and start PumaWorkerKiller' do - expect(PumaWorkerKiller).not_to receive(:config) - expect(PumaWorkerKiller).not_to receive(:start) - - described_class.start({}) - end - end - end -end diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb deleted file mode 100644 index cf532cf7be6..00000000000 --- a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' - -RSpec.describe Gitlab::Cluster::PumaWorkerKillerObserver do - let(:counter) { Gitlab::Metrics::NullMetric.instance } - - before do - allow(Gitlab::Metrics).to receive(:counter) - .with(any_args) - .and_return(counter) - end - - describe '#callback' do - subject { described_class.new } - - it 'increments timeout counter' do - worker = double(index: 0) - - expect(counter).to receive(:increment) - - subject.callback.call(worker) - end - end -end diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index e79459e0c7c..6e312dbeb4d 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -1251,16 +1251,6 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis it 'returns the last build info' do expect(package.original_build_info).to eq(second_build_info) end - - context 'with packages_display_last_pipeline disabled' do - before do - stub_feature_flags(packages_display_last_pipeline: false) - end - - it 'returns the first build info' do - expect(package.original_build_info).to eq(first_build_info) - end - end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 966782aca98..2f65441dd01 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -2875,18 +2875,6 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning expect(explanations) .to contain_exactly("Converts work item to Issue. Widgets not supported in new type are removed.") end - - context 'when feature flag work_items_mvc_2 is disabled' do - before do - stub_feature_flags(work_items_mvc_2: false) - end - - it 'does not have the command available' do - _, explanations = service.explain(command, work_item) - - expect(explanations).to be_empty - end - end end describe 'relate command' do diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 82dc6659dbf..ee0854c2483 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -6033,7 +6033,6 @@ - './spec/lib/gitlab/closing_issue_extractor_spec.rb' - './spec/lib/gitlab/cluster/lifecycle_events_spec.rb' - './spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb' -- './spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb' - './spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb' - './spec/lib/gitlab/code_navigation_path_spec.rb' - './spec/lib/gitlab/color_schemes_spec.rb' diff --git a/yarn.lock b/yarn.lock index 9c9a98d4184..645a7f2b97f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12826,6 +12826,13 @@ vuedraggable@^2.23.0: dependencies: sortablejs "^1.9.0" +vuex-mock-store@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/vuex-mock-store/-/vuex-mock-store-0.1.0.tgz#eddd85d32184e93926b3693866ab43efdba95c9d" + integrity sha512-NWbZcw91sxDiCsFt4v6jSF6lr7GUBaVmdLvkYOIXIKtfbEUIN3OO+LuByaAzjmrskr5Xg7QQLueteQZoFLMBoA== + dependencies: + lodash.clonedeep "^4.5.0" + "vuex-vue3@npm:vuex@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.0.0.tgz#ac877aa76a9c45368c979471e461b520d38e6cf5" |