diff options
42 files changed, 292 insertions, 274 deletions
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb index ea1aa6d2e9e..d4a910bf226 100644 --- a/app/finders/merge_request_target_project_finder.rb +++ b/app/finders/merge_request_target_project_finder.rb @@ -39,3 +39,5 @@ class MergeRequestTargetProjectFinder end # rubocop: enable CodeReuse/ActiveRecord end + +MergeRequestTargetProjectFinder.prepend_mod_with("MergeRequestTargetProjectFinder") diff --git a/app/graphql/types/achievements/achievement_type.rb b/app/graphql/types/achievements/achievement_type.rb index a39a58ef6f1..d733bf39a51 100644 --- a/app/graphql/types/achievements/achievement_type.rb +++ b/app/graphql/types/achievements/achievement_type.rb @@ -5,7 +5,7 @@ module Types class AchievementType < BaseObject graphql_name 'Achievement' - connection_type_class(Types::CountableConnectionType) + connection_type_class Types::CountableConnectionType authorize :read_achievement diff --git a/app/graphql/types/achievements/user_achievement_type.rb b/app/graphql/types/achievements/user_achievement_type.rb index 1d52ce4ef2b..7cdcb66576c 100644 --- a/app/graphql/types/achievements/user_achievement_type.rb +++ b/app/graphql/types/achievements/user_achievement_type.rb @@ -5,7 +5,7 @@ module Types class UserAchievementType < BaseObject graphql_name 'UserAchievement' - connection_type_class(Types::CountableConnectionType) + connection_type_class Types::CountableConnectionType authorize :read_user_achievement diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index c17406b3e56..eb134b74cac 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -8,8 +8,8 @@ module Types present_using ::AlertManagement::AlertPresenter - implements(Types::Notes::NoteableInterface) - implements(Types::TodoableInterface) + implements Types::Notes::NoteableInterface + implements Types::TodoableInterface authorize :read_alert_management_alert diff --git a/app/graphql/types/alert_management/http_integration_type.rb b/app/graphql/types/alert_management/http_integration_type.rb index bba9cb1bbfc..7c026be86a3 100644 --- a/app/graphql/types/alert_management/http_integration_type.rb +++ b/app/graphql/types/alert_management/http_integration_type.rb @@ -6,7 +6,7 @@ module Types graphql_name 'AlertManagementHttpIntegration' description 'An endpoint and credentials used to accept alerts for a project' - implements(Types::AlertManagement::IntegrationType) + implements Types::AlertManagement::IntegrationType authorize :admin_operations diff --git a/app/graphql/types/alert_management/prometheus_integration_type.rb b/app/graphql/types/alert_management/prometheus_integration_type.rb index 9a2ef78eca7..0f61eeaa177 100644 --- a/app/graphql/types/alert_management/prometheus_integration_type.rb +++ b/app/graphql/types/alert_management/prometheus_integration_type.rb @@ -8,7 +8,7 @@ module Types include ::Gitlab::Routing - implements(Types::AlertManagement::IntegrationType) + implements Types::AlertManagement::IntegrationType authorize :admin_project diff --git a/app/graphql/types/ci/group_variable_type.rb b/app/graphql/types/ci/group_variable_type.rb index acb003b5977..7be9b3df0b8 100644 --- a/app/graphql/types/ci/group_variable_type.rb +++ b/app/graphql/types/ci/group_variable_type.rb @@ -8,7 +8,7 @@ module Types description 'CI/CD variables for a group.' connection_type_class Types::Ci::GroupVariableConnectionType - implements(VariableInterface) + implements VariableInterface field :environment_scope, GraphQL::Types::String, null: true, diff --git a/app/graphql/types/ci/instance_variable_type.rb b/app/graphql/types/ci/instance_variable_type.rb index 7ffc52deb73..e3230556769 100644 --- a/app/graphql/types/ci/instance_variable_type.rb +++ b/app/graphql/types/ci/instance_variable_type.rb @@ -7,7 +7,7 @@ module Types graphql_name 'CiInstanceVariable' description 'CI/CD variables for a GitLab instance.' - implements(VariableInterface) + implements VariableInterface field :id, GraphQL::Types::ID, null: false, diff --git a/app/graphql/types/ci/manual_variable_type.rb b/app/graphql/types/ci/manual_variable_type.rb index ed92a6645b4..dcdaa3a6b19 100644 --- a/app/graphql/types/ci/manual_variable_type.rb +++ b/app/graphql/types/ci/manual_variable_type.rb @@ -7,7 +7,7 @@ module Types graphql_name 'CiManualVariable' description 'CI/CD variables given to a manual job.' - implements(VariableInterface) + implements VariableInterface field :environment_scope, GraphQL::Types::String, null: true, diff --git a/app/graphql/types/ci/pipeline_schedule_variable_type.rb b/app/graphql/types/ci/pipeline_schedule_variable_type.rb index 1cb407bc2e4..f9c18d6f7df 100644 --- a/app/graphql/types/ci/pipeline_schedule_variable_type.rb +++ b/app/graphql/types/ci/pipeline_schedule_variable_type.rb @@ -7,7 +7,7 @@ module Types authorize :read_pipeline_schedule_variables - implements(VariableInterface) + implements VariableInterface end end end diff --git a/app/graphql/types/ci/project_variable_type.rb b/app/graphql/types/ci/project_variable_type.rb index 8c1eb481774..cd069be3320 100644 --- a/app/graphql/types/ci/project_variable_type.rb +++ b/app/graphql/types/ci/project_variable_type.rb @@ -8,7 +8,7 @@ module Types description 'CI/CD variables for a project.' connection_type_class Types::Ci::ProjectVariableConnectionType - implements(VariableInterface) + implements VariableInterface field :environment_scope, GraphQL::Types::String, null: true, diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb index 2a2c8af5468..ad484b09d4d 100644 --- a/app/graphql/types/commit_type.rb +++ b/app/graphql/types/commit_type.rb @@ -8,7 +8,7 @@ module Types present_using CommitPresenter - implements(Types::TodoableInterface) + implements Types::TodoableInterface field :id, type: GraphQL::Types::ID, null: false, description: 'ID (global ID) of the commit.' diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb index be5edd17643..d253ca8bfea 100644 --- a/app/graphql/types/design_management/design_type.rb +++ b/app/graphql/types/design_management/design_type.rb @@ -10,10 +10,10 @@ module Types alias_method :design, :object - implements(Types::Notes::NoteableInterface) - implements(Types::DesignManagement::DesignFields) - implements(Types::CurrentUserTodos) - implements(Types::TodoableInterface) + implements Types::Notes::NoteableInterface + implements Types::DesignManagement::DesignFields + implements Types::CurrentUserTodos + implements Types::TodoableInterface field :description, GraphQL::Types::String, diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 8577d2b8dc5..7b533a2b46e 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -6,9 +6,9 @@ module Types connection_type_class Types::IssueConnectionType - implements(Types::Notes::NoteableInterface) - implements(Types::CurrentUserTodos) - implements(Types::TodoableInterface) + implements Types::Notes::NoteableInterface + implements Types::CurrentUserTodos + implements Types::TodoableInterface authorize :read_issue diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 30878c17c6b..701fc793436 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -6,9 +6,9 @@ module Types connection_type_class Types::MergeRequestConnectionType - implements(Types::Notes::NoteableInterface) - implements(Types::CurrentUserTodos) - implements(Types::TodoableInterface) + implements Types::Notes::NoteableInterface + implements Types::CurrentUserTodos + implements Types::TodoableInterface authorize :read_merge_request diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb index 5e40c8008a9..7afb1f392d3 100644 --- a/app/graphql/types/notes/discussion_type.rb +++ b/app/graphql/types/notes/discussion_type.rb @@ -9,7 +9,7 @@ module Types authorize :read_note - implements(Types::ResolvableInterface) + implements Types::ResolvableInterface field :created_at, Types::TimeType, null: false, description: "Timestamp of the discussion's creation." diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 754c9b3f356..e7e032c67c6 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -9,7 +9,7 @@ module Types expose_permissions Types::PermissionTypes::Note - implements(Types::ResolvableInterface) + implements Types::ResolvableInterface field :max_access_level_of_author, GraphQL::Types::String, null: true, diff --git a/app/graphql/types/projects/services/base_service_type.rb b/app/graphql/types/projects/services/base_service_type.rb index 9a48aafa5a8..c77dc5c8539 100644 --- a/app/graphql/types/projects/services/base_service_type.rb +++ b/app/graphql/types/projects/services/base_service_type.rb @@ -7,7 +7,7 @@ module Types class BaseServiceType < BaseObject graphql_name 'BaseService' - implements(Types::Projects::ServiceType) + implements Types::Projects::ServiceType authorize :admin_project end diff --git a/app/graphql/types/projects/services/jira_service_type.rb b/app/graphql/types/projects/services/jira_service_type.rb index ac274d7f890..a774d381e2b 100644 --- a/app/graphql/types/projects/services/jira_service_type.rb +++ b/app/graphql/types/projects/services/jira_service_type.rb @@ -7,7 +7,7 @@ module Types class JiraServiceType < BaseObject graphql_name 'JiraService' - implements(Types::Projects::ServiceType) + implements Types::Projects::ServiceType authorize :admin_project diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb index 5ee0500b1e0..6e6d0edbe15 100644 --- a/app/graphql/types/snippet_type.rb +++ b/app/graphql/types/snippet_type.rb @@ -5,7 +5,7 @@ module Types graphql_name 'Snippet' description 'Represents a snippet entry' - implements(Types::Notes::NoteableInterface) + implements Types::Notes::NoteableInterface present_using SnippetPresenter diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb index 1e58781dbb9..05798ba3d2f 100644 --- a/app/graphql/types/work_item_type.rb +++ b/app/graphql/types/work_item_type.rb @@ -4,7 +4,7 @@ module Types class WorkItemType < BaseObject graphql_name 'WorkItem' - implements(Types::TodoableInterface) + implements Types::TodoableInterface authorize :read_work_item diff --git a/app/models/project.rb b/app/models/project.rb index d3642e7d7d0..8c5bb34d3f2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -586,6 +586,7 @@ class Project < ApplicationRecord scope :pending_delete, -> { where(pending_delete: true) } scope :without_deleted, -> { where(pending_delete: false) } scope :not_hidden, -> { where(hidden: false) } + scope :not_in_groups, ->(groups) { where.not(group: groups) } scope :not_aimed_for_deletion, -> { where(marked_for_deletion_at: nil).without_deleted } scope :with_storage_feature, ->(feature) do diff --git a/config/feature_flags/development/complete_p_ci_builds_partitioning.yml b/config/feature_flags/development/hide_unaccessible_saml_branches.yml index bd6ed2644af..27761e0e087 100644 --- a/config/feature_flags/development/complete_p_ci_builds_partitioning.yml +++ b/config/feature_flags/development/hide_unaccessible_saml_branches.yml @@ -1,8 +1,8 @@ --- -name: complete_p_ci_builds_partitioning -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126701 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414396 +name: hide_unaccessible_saml_branches +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122972 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419594 milestone: '16.2' type: development -group: group::pipeline execution +group: group::code review default_enabled: false diff --git a/config/feature_flags/development/search_index_integrity.yml b/config/feature_flags/development/search_index_integrity.yml index 84e1e4b65c8..87a9536fede 100644 --- a/config/feature_flags/development/search_index_integrity.yml +++ b/config/feature_flags/development/search_index_integrity.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392981 milestone: '15.10' type: development group: group::global search -default_enabled: false +default_enabled: true diff --git a/danger/model_validations/Dangerfile b/danger/model_validations/Dangerfile new file mode 100644 index 00000000000..5daea3cdaa2 --- /dev/null +++ b/danger/model_validations/Dangerfile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +model_validations.add_comment_for_added_validations diff --git a/danger/plugins/model_validations.rb b/danger/plugins/model_validations.rb new file mode 100644 index 00000000000..ceb8db32da2 --- /dev/null +++ b/danger/plugins/model_validations.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/model_validations' + +module Danger + class ModelValidations < ::Danger::Plugin + # Put the helper code somewhere it can be tested + include Tooling::Danger::ModelValidations + end +end diff --git a/doc/ci/yaml/signing_examples.md b/doc/ci/yaml/signing_examples.md index c18e6de6614..a4f8d6bc2f9 100644 --- a/doc/ci/yaml/signing_examples.md +++ b/doc/ci/yaml/signing_examples.md @@ -22,7 +22,7 @@ For details on the mapping between GitLab OIDC claims and Fulcio certificate ext - You must be using GitLab.com. - Your project's CI/CD configuration must be located in the project. -## Cosign +## Sign or verify container images and build artifacts by using Cosign You can use Cosign to sign and verify container images and build artifacts. diff --git a/doc/development/fe_guide/migrating_from_vuex.md b/doc/development/fe_guide/migrating_from_vuex.md index ff79343b79d..8dca744e192 100644 --- a/doc/development/fe_guide/migrating_from_vuex.md +++ b/doc/development/fe_guide/migrating_from_vuex.md @@ -60,15 +60,12 @@ export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) => The easiest type of values to migrate are static values, either: -- Client-side constants -- Rails-injected dataset - - Client-side constants: If the static value is a client-side constant, it may have been implemented in the store for easy access by other state properties or methods. However, it is generally a better practice to add such values to a `constants.js` file and import it when needed. - Rails-injected dataset: These are values that we may need to provide to our Vue apps. They are static, so adding them to the VueX store is not necessary and it could instead - be done easily through the `provide/inject` Vue API, which would be equivalent but without the VueX overhead. + be done easily through the `provide/inject` Vue API, which would be equivalent but without the VueX overhead. This should **only** be injected inside the top-most JS file that mounts our component. If we take a look at our example above, we can already see that two properties contain `Endpoint` in their name, which probably means that these come from our Rails dataset. To confirm this, we would search the codebase for these properties and see where they are defined, which is the case in our example. Additionally, `blobPath` is also a static property, and a little less obvious here is that `pageInfo` is actually a constant! It is never modified and is only used as a default value that we use inside our getter: diff --git a/doc/user/application_security/dependency_list/img/dependency_list_v16_3.png b/doc/user/application_security/dependency_list/img/dependency_list_v16_3.png Binary files differnew file mode 100644 index 00000000000..93bf0dfc756 --- /dev/null +++ b/doc/user/application_security/dependency_list/img/dependency_list_v16_3.png diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md index d41c0eff860..8a4c15b082e 100644 --- a/doc/user/application_security/dependency_list/index.md +++ b/doc/user/application_security/dependency_list/index.md @@ -19,7 +19,7 @@ details about those dependencies, including their known vulnerabilities. It is a <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> For an overview, see [Project Dependency](https://www.youtube.com/watch?v=ckqkn9Tnbw4). -To see the dependency list, go to your project and select **Secure > Dependency list**. +To see the dependency list, go to your project or group and select **Secure > Dependency list**. This information is sometimes referred to as a Software Bill of Materials, SBOM, or BOM. @@ -74,21 +74,37 @@ Dependency paths are supported for the following package managers: - [Yarn 1.x](https://classic.yarnpkg.com/lang/en/) - [sbt](https://www.scala-sbt.org) -## Licenses +### Licenses > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10536) in GitLab 12.3. If the [License Compliance](../../compliance/license_compliance/index.md) CI job is configured, [discovered licenses](../../compliance/license_compliance/index.md#supported-languages-and-package-managers) are displayed on this page. +## View a group's dependencies + +![Dependency list](img/dependency_list_v16_3.png) + +GitLab displays dependencies with the following information: + +| Field | Description | +|-----------|-------------| +| Component | The dependency's name and version. | +| Packager | The packager used to install the dependency. | +| Location | For operating system dependencies, this lists the image that was scanned. For application dependencies, this shows a link to the packager-specific lock file in your project that declared the dependency. It also shows the [dependency path](#dependency-paths) to a top-level dependency, if any, and if supported. If there are multiple locations, the total number of locations is displayed. | +| Projects | Links to the project related to the dependency. If there are multiple projects, the total number of projects is displayed. | + +Displayed dependencies are initially sorted by packager. They +can also be sorted by name. + ## Downloading the dependency list -You can download your project's full list of dependencies and their details in +You can download the full list of dependencies and their details in `JSON` format. ### In the UI -You can download your project's list of dependencies and their details in JSON format by selecting the **Export** button. The dependency list only shows the results of the last successful pipeline to run on the default branch. +You can download your group's or project's list of dependencies and their details in JSON format by selecting the **Export** button. The dependency list only shows the results of the last successful pipeline to run on the default branch. ### Using the API diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index e7b7790cf9e..4e1fdb1b920 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -314,9 +314,13 @@ are analyzed. There is no limit to the depth of nested or transitive dependencie Dependency Scanning supports the following official analyzers: +- `gemnasium` +- `gemnasium-maven` +- `gemnasium-python` + +Each of these supported Gemnasium-based Dependency Scanning analyzers exist in the following project: + - [`gemnasium`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium) -- [`gemnasium-maven`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven) -- [`gemnasium-python`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python) The analyzers are published as Docker images, which Dependency Scanning uses to launch dedicated containers for each analysis. You can also integrate a custom diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index ea2a66c7cc7..d949a5dabef 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -496,7 +496,7 @@ path = "/gitleaks.toml" ] ``` -## Specify a remote configuration file +### Specify a remote configuration file Projects can be configured with a [CI/CD variable](../../../ci/variables/index.md) in order to specify a ruleset configuration outside of the current repository. diff --git a/doc/user/project/repository/mirror/img/mirror_error_v16_3.png b/doc/user/project/repository/mirror/img/mirror_error_v16_3.png Binary files differnew file mode 100644 index 00000000000..7d3c03534ef --- /dev/null +++ b/doc/user/project/repository/mirror/img/mirror_error_v16_3.png diff --git a/doc/user/project/repository/mirror/troubleshooting.md b/doc/user/project/repository/mirror/troubleshooting.md index 6474d0c08ed..2bbc69ff9f7 100644 --- a/doc/user/project/repository/mirror/troubleshooting.md +++ b/doc/user/project/repository/mirror/troubleshooting.md @@ -6,7 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Troubleshooting repository mirroring **(FREE)** -Should an error occur during a push, GitLab displays an **Error** highlight for that repository. Details on the error can then be seen by hovering over the highlight text. +When mirroring fails, project maintainers can see a link similar to **{warning-solid}** **Pull mirroring failed 1 hour ago.** +on the project details page. Select this link to go directly to the mirroring settings, +where GitLab displays an **Error** badge for the mirrored repository. You can hover your mouse cursor +over the badge to display the text of the error: + +![Error message shown on hover](img/mirror_error_v16_3.png) ## Received RST_STREAM with error code 2 with GitHub diff --git a/lib/banzai/filter/issuable_reference_expansion_filter.rb b/lib/banzai/filter/issuable_reference_expansion_filter.rb index ec7778a3630..1c3e25b3b27 100644 --- a/lib/banzai/filter/issuable_reference_expansion_filter.rb +++ b/lib/banzai/filter/issuable_reference_expansion_filter.rb @@ -45,7 +45,7 @@ module Banzai # Example: Issue Title (#123 - closed) def expand_reference_with_title_and_state(node, issuable) - node.content = "#{issuable.title.truncate(50)} (#{node.content}" + node.content = "#{expand_emoji(issuable.title).truncate(50)} (#{node.content}" node.content += " - #{issuable_state_text(issuable)}" if VISIBLE_STATES.include?(issuable.state) node.content += ')' end @@ -124,6 +124,13 @@ module Banzai def group context[:group] end + + def expand_emoji(string) + string.gsub(/(?<!\w):(\w+):(?!\w)/) do |match| + emoji_codepoint = TanukiEmoji.find_by_alpha_code(::Regexp.last_match(1))&.codepoints + !emoji_codepoint.nil? ? emoji_codepoint : match + end + end end end end diff --git a/lib/gitlab/database/ci_builds_partitioning.rb b/lib/gitlab/database/ci_builds_partitioning.rb deleted file mode 100644 index 9f8b19f2d23..00000000000 --- a/lib/gitlab/database/ci_builds_partitioning.rb +++ /dev/null @@ -1,224 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Database - class CiBuildsPartitioning - include AsyncDdlExclusiveLeaseGuard - - ATTEMPTS = 5 - LOCK_TIMEOUT = 10.seconds - LEASE_TIMEOUT = 30.minutes - - FK_NAME = :fk_e20479742e_p - TEMP_FK_NAME = :temp_fk_e20479742e_p - NEXT_PARTITION_ID = 101 - BUILDS_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_builds_101' - ANNOTATION_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_job_annotations_101' - RUNNER_MACHINE_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_runner_machine_builds_101' - - def initialize(logger: Gitlab::AppLogger) - @connection = ::Ci::ApplicationRecord.connection - @timing_configuration = Array.new(ATTEMPTS) { [LOCK_TIMEOUT, 3.minutes] } - @logger = logger - end - - def execute - return unless can_execute? - - try_obtain_lease do - swap_foreign_keys - create_new_ci_builds_partition - create_new_job_annotations_partition - create_new_runner_machine_partition - end - - rescue StandardError => e - log_info("Failed to execute: #{e.message}") - end - - private - - attr_reader :connection, :timing_configuration, :logger - - delegate :quote_table_name, :quote_column_name, to: :connection - - def swap_foreign_keys - if new_foreign_key_exists? - log_info('Foreign key already renamed, nothing to do') - - return - end - - with_lock_retries do - connection.execute drop_old_foreign_key_sql - - rename_constraint :p_ci_builds_metadata, TEMP_FK_NAME, FK_NAME - - each_partition do |partition| - rename_constraint partition.identifier, TEMP_FK_NAME, FK_NAME - end - end - - log_info('Foreign key successfully renamed') - end - - def create_new_ci_builds_partition - if connection.table_exists?(BUILDS_PARTITION_NAME) - log_info('p_ci_builds partition exists, nothing to do') - return - end - - with_lock_retries do - connection.execute new_ci_builds_partition_sql - end - - log_info('Partition for p_ci_builds successfully created') - end - - def create_new_job_annotations_partition - if connection.table_exists?(ANNOTATION_PARTITION_NAME) - log_info('p_ci_job_annotations partition exists, nothing to do') - return - end - - with_lock_retries do - connection.execute new_job_annotations_partition_sql - end - - log_info('Partition for p_ci_job_annotations successfully created') - end - - def create_new_runner_machine_partition - if connection.table_exists?(RUNNER_MACHINE_PARTITION_NAME) - log_info('p_ci_runner_machine_builds partition exists, nothing to do') - return - end - - with_lock_retries do - connection.execute new_runner_machine_partition_sql - end - - log_info('Partition for p_ci_runner_machine_builds successfully created') - end - - def can_execute? - return false if process_disabled? - return false unless Gitlab.com? - - if vacuum_running? - log_info('Autovacuum detected') - - return false - end - - true - end - - def process_disabled? - ::Feature.disabled?(:complete_p_ci_builds_partitioning) - end - - def new_foreign_key_exists? - Gitlab::Database::SharedModel.using_connection(connection) do - Gitlab::Database::PostgresForeignKey - .by_constrained_table_name_or_identifier(:p_ci_builds_metadata) - .by_referenced_table_name(:p_ci_builds) - .by_name(FK_NAME) - .exists? - end - end - - def vacuum_running? - Gitlab::Database::SharedModel.using_connection(connection) do - Gitlab::Database::PostgresAutovacuumActivity - .wraparound_prevention - .for_tables(%i[ci_builds ci_builds_metadata]) - .any? - end - end - - def drop_old_foreign_key_sql - <<~SQL.squish - SET LOCAL statement_timeout TO '11s'; - - LOCK TABLE ci_builds, p_ci_builds_metadata IN ACCESS EXCLUSIVE MODE; - - ALTER TABLE p_ci_builds_metadata DROP CONSTRAINT #{FK_NAME}; - SQL - end - - def rename_constraint(table_name, old_name, new_name) - connection.execute <<~SQL - ALTER TABLE #{quote_table_name(table_name)} - RENAME CONSTRAINT #{quote_column_name(old_name)} TO #{quote_column_name(new_name)} - SQL - end - - def new_ci_builds_partition_sql - <<~SQL - SET LOCAL statement_timeout TO '11s'; - - LOCK ci_pipelines, ci_stages IN SHARE ROW EXCLUSIVE MODE; - LOCK TABLE ONLY p_ci_builds IN ACCESS EXCLUSIVE MODE; - - CREATE TABLE IF NOT EXISTS #{BUILDS_PARTITION_NAME} - PARTITION OF p_ci_builds - FOR VALUES IN (#{NEXT_PARTITION_ID}); - SQL - end - - def new_job_annotations_partition_sql - <<~SQL - SET LOCAL statement_timeout TO '11s'; - - LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE; - LOCK TABLE ONLY p_ci_job_annotations IN ACCESS EXCLUSIVE MODE; - - CREATE TABLE IF NOT EXISTS #{ANNOTATION_PARTITION_NAME} - PARTITION OF p_ci_job_annotations - FOR VALUES IN (#{NEXT_PARTITION_ID}); - SQL - end - - def new_runner_machine_partition_sql - <<~SQL - SET LOCAL statement_timeout TO '11s'; - - LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE; - LOCK TABLE ONLY p_ci_runner_machine_builds IN ACCESS EXCLUSIVE MODE; - - CREATE TABLE IF NOT EXISTS #{RUNNER_MACHINE_PARTITION_NAME} - PARTITION OF p_ci_runner_machine_builds - FOR VALUES IN (#{NEXT_PARTITION_ID}); - SQL - end - - def with_lock_retries(&block) - Gitlab::Database::WithLockRetries.new( - timing_configuration: timing_configuration, - connection: connection, - logger: logger, - klass: self.class - ).run(raise_on_exhaustion: true, &block) - end - - def each_partition(&block) - Gitlab::Database::SharedModel.using_connection(connection) do - Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_builds_metadata, &block) - end - end - - def log_info(message) - logger.info(message: message, class: self.class.to_s) - end - - def connection_db_config - ::Ci::ApplicationRecord.connection_db_config - end - - def lease_timeout - LEASE_TIMEOUT - end - end - end -end diff --git a/lib/gitlab/database/reindexing.rb b/lib/gitlab/database/reindexing.rb index 9c860ebc6aa..4a1b0be848e 100644 --- a/lib/gitlab/database/reindexing.rb +++ b/lib/gitlab/database/reindexing.rb @@ -59,7 +59,6 @@ module Gitlab # most bloated indexes for reindexing. def self.perform_with_heuristic(candidate_indexes = Gitlab::Database::PostgresIndex.reindexing_support, maximum_records: DEFAULT_INDEXES_PER_INVOCATION) IndexSelection.new(candidate_indexes).take(maximum_records).each do |index| - Gitlab::Database::CiBuildsPartitioning.new.execute Coordinator.new(index).perform end end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb index 9fed6787ade..0817d2f4870 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb @@ -74,7 +74,14 @@ module QA end end - it 'merges when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347842' do + it( + 'merges when pipeline succeeds', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347842', + quarantine: { + type: :flaky, + issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/346425" + } + ) do Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' diff --git a/qa/qa/support/helpers/plan.rb b/qa/qa/support/helpers/plan.rb index 14273bd0821..c3867b4a1b8 100644 --- a/qa/qa/support/helpers/plan.rb +++ b/qa/qa/support/helpers/plan.rb @@ -57,10 +57,11 @@ module QA }.freeze CODE_SUGGESTIONS = { + # Annual plan, rate and price plan_id: '8a8aa0ac8874ddc4018878da1f736782', rate_charge_id: '8a8aa0ac8874ddc4018878da1f9a6784', name: 'code suggestions', - price: 9 + price: 108 }.freeze LICENSE_TYPE = { diff --git a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb index e14b1362687..06bb0edc92c 100644 --- a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb @@ -173,6 +173,48 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor expect(doc.css('a').last.text).to eq("#{issuable.title} (#{issuable.to_reference} - closed)") end + context 'for references with fenced emoji' do + def issuable_link(issuable) + create_link( + issuable.to_reference, + "#{issuable_type}": issuable.id, + reference_type: issuable_type, + reference_format: '+' + ) + end + + it 'expands emoji for references with +' do + issuable = create_item(issuable_type, :opened, title: 'Some issue :eagle:') + doc = filter(issuable_link(issuable), context) + + expect(/\p{Emoji_Presentation}/ =~ doc.css('a').last.text).not_to be_nil + expect(doc.css('a').last.text.scan(/\p{Emoji_Presentation}/)).to eq(["🦅"]) + end + + it 'expands when emoji is embedded at the beginning of a string' do + issuable = create_item(issuable_type, :opened, title: ':eagle: Some issue') + doc = filter(issuable_link(issuable), context) + + expect(/\p{Emoji_Presentation}/ =~ doc.css('a').last.text).not_to be_nil + expect(doc.css('a').last.text.scan(/\p{Emoji_Presentation}/)).to eq(["🦅"]) + end + + it 'expands when emoji appears multiple times' do + issuable = create_item(issuable_type, :opened, title: ':eagle: Some issue :dog:') + doc = filter(issuable_link(issuable), context) + + expect(/\p{Emoji_Presentation}/ =~ doc.css('a').last.text).not_to be_nil + expect(doc.css('a').last.text.scan(/\p{Emoji_Presentation}/)).to eq(["🦅", "🐶"]) + end + + it 'does not expand when emoji is embedded mid-string' do + issuable = create_item(issuable_type, :opened, title: 'Some:eagle:issue') + doc = filter(issuable_link(issuable), context) + + expect(/\p{Emoji_Presentation}/ =~ doc.css('a').last.text).to be_nil + end + end + it 'shows title for references with +s' do issuable = create_item(issuable_type, :opened, title: 'Some issue') link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type, diff --git a/spec/tooling/danger/model_validations_spec.rb b/spec/tooling/danger/model_validations_spec.rb new file mode 100644 index 00000000000..bfd63f71656 --- /dev/null +++ b/spec/tooling/danger/model_validations_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'gitlab-dangerfiles' +require 'danger' +require 'danger/plugins/internal/helper' +require 'gitlab/dangerfiles/spec_helper' + +require_relative '../../../tooling/danger/model_validations' +require_relative '../../../tooling/danger/project_helper' + +RSpec.describe Tooling::Danger::ModelValidations, feature_category: :tooling do + include_context "with dangerfile" + + let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) } + let(:fake_project_helper) { instance_double(Tooling::Danger::ProjectHelper) } + + subject(:model_validations) { fake_danger.new(helper: fake_helper) } + + before do + allow(model_validations).to receive(:project_helper).and_return(fake_project_helper) + end + + describe '#add_comment_for_added_validations' do + let(:file_lines) { file_diff.map { |line| line.delete_prefix('+').delete_prefix('-') } } + let(:filename) { 'app/models/user.rb' } + + before do + allow(model_validations.project_helper).to receive(:file_lines).and_return(file_lines) + allow(model_validations.helper).to receive(:all_changed_files).and_return([filename]) + allow(model_validations.helper).to receive(:changed_lines).with(filename).and_return(file_diff) + end + + context 'when model has a newly added validation' do + let(:file_diff) do + [ + "+ scope :admins, -> { where(admin: true) }", + "+ validates :name, presence: true, length: { maximum: 255 }", + "+ validates_with UserValidator", + "+ validate :check_password_weakness", + "+ validates_each :restricted_visibility_levels do |record, attr, value|", + "+ validates_associated :members", + "+ with_options if: :is_admin? do |admin|", + "+ admin.validates :password, length: { minimum: 10 }", + "+ admin.validates :email, presence: true", + "+ end", + "+ with_options if: :is_admin? { |admin| admin.validates :email, presence: true }", + "- validates :first_name, length: { maximum: 127 }" + ] + end + + it 'adds suggestions at the correct line' do + suggested_line = "\n#{described_class::SUGGEST_MR_COMMENT.chomp}" + + matching_line_numbers = [*2..6, 8, 9, 11] + matching_line_numbers.each do |line_number| + expect(model_validations).to receive(:markdown).with(suggested_line, file: filename, line: line_number) + end + + model_validations.add_comment_for_added_validations + end + end + + context 'when model does not have a newly added validation' do + let(:file_diff) do + [ + "+ scope :admins, -> { where(admin: true) }", + "- validates :first_name, length: { maximum: 127 }" + ] + end + + it 'does not add suggestion' do + expect(model_validations).not_to receive(:markdown) + + model_validations.add_comment_for_added_validations + end + end + end + + describe '#changed_model_files' do + let(:expected_files) do + %w[ + app/models/user.rb + app/models/users/user_follow_user.rb + ee/app/models/ee/user.rb + ee/app/models/sca/license_policy.rb + ] + end + + before do + all_new_files = %w[ + app/models/user.rb + app/models/users/user_follow_user.rb + ee/app/models/ee/user.rb + ee/app/models/sca/license_policy.rb + config/metrics/count_7d/new_metric.yml + app/assets/index.js + ] + + allow(model_validations.helper).to receive(:all_changed_files).and_return(all_new_files) + end + + it 'returns added and modified files' do + expect(model_validations.changed_model_files).to match_array(expected_files) + end + end +end diff --git a/tooling/danger/model_validations.rb b/tooling/danger/model_validations.rb new file mode 100644 index 00000000000..4d7018753ff --- /dev/null +++ b/tooling/danger/model_validations.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative 'suggestor' + +module Tooling + module Danger + module ModelValidations + include ::Tooling::Danger::Suggestor + + MODEL_FILES_REGEX = 'app/models' + EE_PREFIX = 'ee/' + VALIDATION_METHODS = %w[validates validate validates_each validates_with validates_associated].freeze + VALIDATIONS_REGEX = /^\+\s*(.*\.)?(#{VALIDATION_METHODS.join('|')})[( ]/ + + CODE_QUALITY_URL = "https://docs.gitlab.com/ee/development/code_review.html#quality" + SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT.freeze + Did you consider new validations can break existing records? + Please follow the [code quality guidelines about new model validations](#{CODE_QUALITY_URL}) when adding a new + model validation. + + If you're adding the validations to a model with no records you can ignore this warning. + SUGGEST_COMMENT + + def add_comment_for_added_validations + changed_model_files.each do |filename| + add_suggestion( + filename: filename, + regex: VALIDATIONS_REGEX, + comment_text: SUGGEST_MR_COMMENT + ) + end + end + + def changed_model_files + changed_files = helper.all_changed_files + ee_folder_prefix = "(#{EE_PREFIX})?" + + changed_files.grep(%r{\A#{ee_folder_prefix}#{MODEL_FILES_REGEX}}) + end + end + end +end |