From e690e4ea1fff57191fb1166e5022c2bcfe308c86 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 30 May 2023 21:09:08 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rules.gitlab-ci.yml | 4 +- .rubocop_todo/cop/ignored_columns.yml | 8 ++ .rubocop_todo/layout/argument_alignment.yml | 12 -- ...e_end_string_concatenation_indentation.yml | 1 - .rubocop_todo/naming/inclusive_language.yml | 1 - .../rspec/missing_feature_category.yml | 1 - .../components/edit_environment.vue | 40 ++++++- .../components/environment_form.vue | 2 +- app/assets/javascripts/environments/edit.js | 9 +- .../graphql/queries/environment.query.graphql | 4 + .../mutations/ci/ci_cd_settings_update.rb | 17 --- app/graphql/types/mutation_type.rb | 5 - app/helpers/search_helper.rb | 14 ++- app/models/clusters/agent.rb | 31 ++--- app/models/namespace.rb | 10 +- app/models/project.rb | 7 +- .../projects/environments/edit.html.haml | 2 +- ...value_stream_dashboard_on_off_setting.yml} | 10 +- doc/api/graphql/reference/index.md | 29 ----- lib/gitlab/search/abuse_detection.rb | 4 +- lib/gitlab/search/params.rb | 2 +- locale/gitlab.pot | 6 + rubocop/cop/ignored_columns.rb | 52 +++++--- .../environments/edit_environment_spec.js | 66 ++++++----- .../environments/environment_form_spec.js | 17 +++ spec/helpers/search_helper_spec.rb | 112 ++++++++++++++++-- .../lib/gitlab/search/abuse_detection_spec.rb | 2 +- spec/lib/gitlab/search/params_spec.rb | 2 +- spec/models/namespace_spec.rb | 12 ++ spec/models/project_spec.rb | 12 ++ .../ci/project_ci_cd_settings_update_spec.rb | 29 ----- spec/requests/api/release/links_spec.rb | 14 +-- spec/rubocop/cop/ignored_columns_spec.rb | 27 +++-- 33 files changed, 342 insertions(+), 222 deletions(-) create mode 100644 .rubocop_todo/cop/ignored_columns.yml delete mode 100644 app/graphql/mutations/ci/ci_cd_settings_update.rb rename config/feature_flags/development/{remove_cicd_settings_update.yml => value_stream_dashboard_on_off_setting.yml} (61%) diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 38173b27e3e..74257fbe293 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1355,12 +1355,12 @@ ########## .notify:rules:create-issues-for-failing-tests: rules: - - <<: *if-not-canonical-namespace + - if: '$CREATE_ISSUES_FOR_FAILING_TESTS != "true"' when: never # Don't report child pipeline failures - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"' when: never - - if: '$CREATE_ISSUES_FOR_FAILING_TESTS == "true"' + - <<: *if-dot-com-gitlab-org-default-branch when: on_failure allow_failure: true diff --git a/.rubocop_todo/cop/ignored_columns.yml b/.rubocop_todo/cop/ignored_columns.yml new file mode 100644 index 00000000000..eaba218d385 --- /dev/null +++ b/.rubocop_todo/cop/ignored_columns.yml @@ -0,0 +1,8 @@ +--- +Cop/IgnoredColumns: + Details: grace period + Exclude: + - 'app/models/loose_foreign_keys/deleted_record.rb' + - 'ee/lib/ee/gitlab/background_migration/create_vulnerability_links.rb' + - 'ee/lib/ee/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb' + - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb' diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index c78390f4874..af8306d944f 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -983,18 +983,6 @@ Layout/ArgumentAlignment: - 'ee/app/models/ee/merge_request.rb' - 'ee/app/models/ee/namespace.rb' - 'ee/app/models/ee/namespace/storage/notification.rb' - - 'ee/app/models/ee/project.rb' - - 'ee/app/models/ee/project_group_link.rb' - - 'ee/app/models/ee/projects/wiki_repository.rb' - - 'ee/app/models/ee/upload.rb' - - 'ee/app/models/ee/user.rb' - - 'ee/app/models/ee/vulnerability.rb' - - 'ee/app/models/geo/hashed_storage_migrated_event.rb' - - 'ee/app/models/geo/project_registry.rb' - - 'ee/app/models/geo/project_wiki_repository_state.rb' - - 'ee/app/models/geo/wiki_repository_state.rb' - - 'ee/app/models/geo_node.rb' - - 'ee/app/models/geo_node_status.rb' - 'ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb' - 'ee/app/models/group_merge_request_approval_setting.rb' - 'ee/app/models/incident_management/escalation_rule.rb' diff --git a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml index d1b6eddb465..b7fdd3979bc 100644 --- a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml +++ b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml @@ -215,7 +215,6 @@ Layout/LineEndStringConcatenationIndentation: - 'rubocop/cop/graphql/descriptions.rb' - 'rubocop/cop/graphql/resolver_type.rb' - 'rubocop/cop/group_public_or_visible_to_user.rb' - - 'rubocop/cop/ignored_columns.rb' - 'rubocop/cop/inject_enterprise_edition_module.rb' - 'rubocop/cop/migration/add_concurrent_index.rb' - 'rubocop/cop/migration/add_limit_to_text_columns.rb' diff --git a/.rubocop_todo/naming/inclusive_language.yml b/.rubocop_todo/naming/inclusive_language.yml index 465e338bbe7..8950e2e77f4 100644 --- a/.rubocop_todo/naming/inclusive_language.yml +++ b/.rubocop_todo/naming/inclusive_language.yml @@ -53,7 +53,6 @@ Naming/InclusiveLanguage: - 'rubocop/cop/destroy_all.rb' - 'rubocop/cop/graphql/id_type.rb' - 'rubocop/cop/group_public_or_visible_to_user.rb' - - 'rubocop/cop/ignored_columns.rb' - 'rubocop/cop/inject_enterprise_edition_module.rb' - 'rubocop/cop/migration/add_columns_to_wide_tables.rb' - 'spec/controllers/concerns/issuable_collections_spec.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index b4533feec23..36c4fc33345 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -5310,7 +5310,6 @@ RSpec/MissingFeatureCategory: - 'spec/rubocop/cop/graphql/old_types_spec.rb' - 'spec/rubocop/cop/graphql/resolver_type_spec.rb' - 'spec/rubocop/cop/group_public_or_visible_to_user_spec.rb' - - 'spec/rubocop/cop/ignored_columns_spec.rb' - 'spec/rubocop/cop/include_sidekiq_worker_spec.rb' - 'spec/rubocop/cop/inject_enterprise_edition_module_spec.rb' - 'spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb' diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue index 08920fd660c..7905c5cf572 100644 --- a/app/assets/javascripts/environments/components/edit_environment.vue +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -14,13 +14,19 @@ export default { EnvironmentForm, }, mixins: [glFeatureFlagsMixin()], - inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath', 'environmentName'], + inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath'], + props: { + environment: { + required: true, + type: Object, + }, + }, apollo: { environment: { query: getEnvironment, variables() { return { - environmentName: this.environmentName, + environmentName: this.environment.name, projectFullPath: this.projectPath, }; }, @@ -31,16 +37,38 @@ export default { }, data() { return { + isQueryLoading: false, loading: false, formEnvironment: null, }; }, - computed: { - isQueryLoading() { - return this.$apollo.queries.environment.loading; - }, + mounted() { + if (this.glFeatures?.environmentSettingsToGraphql) { + this.fetchWithGraphql(); + } else { + this.formEnvironment = { + id: this.environment.id, + name: this.environment.name, + externalUrl: this.environment.external_url, + }; + } }, methods: { + async fetchWithGraphql() { + this.$apollo.addSmartQuery('environmentData', { + variables() { + return { environmentName: this.environment.name, projectFullPath: this.projectPath }; + }, + query: getEnvironment, + update(data) { + const result = data?.project?.environment || {}; + this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id }; + }, + watchLoading: (isLoading) => { + this.isQueryLoading = isLoading; + }, + }); + }, onChange(environment) { this.formEnvironment = environment; }, diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index 0087dac0897..266b221b481 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -112,7 +112,7 @@ export default { const selectedAgentById = this.agentsList.find( (agent) => agent.value === this.selectedAgentId, ); - return selectedAgentById?.text; + return selectedAgentById?.text || this.environment.clusterAgent?.name; }, filteredAgentsList() { const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js index 69aecc1f443..b26d96e15bd 100644 --- a/app/assets/javascripts/environments/edit.js +++ b/app/assets/javascripts/environments/edit.js @@ -15,7 +15,7 @@ export default (el) => { updateEnvironmentPath, protectedEnvironmentSettingsPath, projectPath, - environmentName, + environment, } = el.dataset; return new Vue({ @@ -26,10 +26,13 @@ export default (el) => { updateEnvironmentPath, protectedEnvironmentSettingsPath, projectPath, - environmentName, }, render(h) { - return h(EditEnvironment); + return h(EditEnvironment, { + props: { + environment: JSON.parse(environment), + }, + }); }, }); }; diff --git a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql index f8f1f3ace84..20402e8d32e 100644 --- a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql @@ -5,6 +5,10 @@ query getEnvironment($projectFullPath: ID!, $environmentName: String) { id name externalUrl + clusterAgent { + id + name + } } } } diff --git a/app/graphql/mutations/ci/ci_cd_settings_update.rb b/app/graphql/mutations/ci/ci_cd_settings_update.rb deleted file mode 100644 index a7d99d2a496..00000000000 --- a/app/graphql/mutations/ci/ci_cd_settings_update.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module Ci - # TODO: Remove after 16.0, see https://gitlab.com/gitlab-org/gitlab/-/issues/361801#note_1373963840 - class CiCdSettingsUpdate < ProjectCiCdSettingsUpdate - graphql_name 'CiCdSettingsUpdate' - - def ready?(**args) - raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`remove_cicd_settings_update` feature flag is enabled.' \ - if Feature.enabled?(:remove_cicd_settings_update) - - super - end - end - end -end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 4564c0b0c0d..efc7bf89693 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -142,11 +142,6 @@ module Types mount_mutation Mutations::Ci::PipelineSchedule::Play mount_mutation Mutations::Ci::PipelineSchedule::Create mount_mutation Mutations::Ci::PipelineSchedule::Update - mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: { - reason: :renamed, - replacement: 'ProjectCiCdSettingsUpdate', - milestone: '15.0' - } mount_mutation Mutations::Ci::ProjectCiCdSettingsUpdate mount_mutation Mutations::Ci::Job::ArtifactsDestroy mount_mutation Mutations::Ci::Job::Play diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 2187126272d..b4c2d80d8e7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -37,6 +37,8 @@ module SearchHelper end def resource_results(term) + return [] if term.length < Gitlab::Search::Params::MIN_TERM_LENGTH + [ groups_autocomplete(term), projects_autocomplete(term), @@ -307,7 +309,7 @@ module SearchHelper # Autocomplete results for the current user's groups # rubocop: disable CodeReuse/ActiveRecord def groups_autocomplete(term, limit = 5) - current_user.authorized_groups.order_id_desc.search(term).limit(limit).map do |group| + current_user.authorized_groups.order_id_desc.search(term, use_minimum_char_limit: false).limit(limit).map do |group| { category: "Groups", id: group.id, @@ -341,7 +343,7 @@ module SearchHelper # Autocomplete results for the current user's projects # rubocop: disable CodeReuse/ActiveRecord def projects_autocomplete(term, limit = 5) - current_user.authorized_projects.order_id_desc.search(term, include_namespace: true) + current_user.authorized_projects.order_id_desc.search(term, include_namespace: true, use_minimum_char_limit: false) .sorted_by_stars_desc.non_archived.limit(limit).map do |p| { category: "Projects", @@ -357,9 +359,11 @@ module SearchHelper def users_autocomplete(term, limit = 5) return [] unless current_user && Ability.allowed?(current_user, :read_users_list) - SearchService - .new(current_user, { scope: 'users', per_page: limit, search: term }) - .search_objects + is_current_user_admin = current_user.can_admin_all_resources? + + scope = is_current_user_admin ? User.all : User.without_forbidden_states + scope.search(term, with_private_emails: is_current_user_admin, use_minimum_char_limit: false) + .limit(limit) .map do |user| { category: "Users", diff --git a/app/models/clusters/agent.rb b/app/models/clusters/agent.rb index a03649cc41f..372fdfda1ea 100644 --- a/app/models/clusters/agent.rb +++ b/app/models/clusters/agent.rb @@ -3,6 +3,7 @@ module Clusters class Agent < ApplicationRecord include FromUnion + include Gitlab::Utils::StrongMemoize self.table_name = 'cluster_agents' @@ -67,10 +68,8 @@ module Clusters return false unless user return false unless ::Feature.enabled?(:expose_authorized_cluster_agents, project) - ::Project.from_union( - all_ci_access_authorized_projects_for(user).limit(1), - all_ci_access_authorized_namespaces_for(user).limit(1) - ).exists? + all_ci_access_authorized_projects_for(user).exists? || + all_ci_access_authorized_namespaces_for(user).exists? end def user_access_authorized_for?(user) @@ -95,47 +94,35 @@ module Clusters def all_ci_access_authorized_projects_for(user) ::Project.joins(:ci_access_project_authorizations) .joins(:project_authorizations) + .joins(:namespace) .where(agent_project_authorizations: { agent_id: id }) .where(project_authorizations: { user_id: user.id, access_level: Gitlab::Access::DEVELOPER.. }) + .where('namespaces.traversal_ids @> ARRAY[?]', root_namespace.id) end def all_ci_access_authorized_namespaces_for(user) - ::Project.with(root_namespace_cte.to_arel) - .with(all_ci_access_authorized_namespaces_cte.to_arel) + ::Project.with(all_ci_access_authorized_namespaces_cte.to_arel) .joins('INNER JOIN all_authorized_namespaces ON all_authorized_namespaces.id = projects.namespace_id') .joins(:project_authorizations) .where(project_authorizations: { user_id: user.id, access_level: Gitlab::Access::DEVELOPER.. }) end - def root_namespace_cte - Gitlab::SQL::CTE.new(:root_namespace, root_namespace.to_sql) - end - def all_ci_access_authorized_namespaces_cte Gitlab::SQL::CTE.new(:all_authorized_namespaces, all_ci_access_authorized_namespaces.to_sql) end def all_ci_access_authorized_namespaces Namespace.select("traversal_ids[array_length(traversal_ids, 1)] AS id") - .joins("INNER JOIN root_namespace ON " \ - "namespaces.traversal_ids @> ARRAY[root_namespace.root_id]") .joins("INNER JOIN agent_group_authorizations ON " \ "namespaces.traversal_ids @> ARRAY[agent_group_authorizations.group_id::integer]") .where(agent_group_authorizations: { agent_id: id }) + .where('namespaces.traversal_ids @> ARRAY[?]', root_namespace.id) end def root_namespace - Namespace.select("traversal_ids[1] AS root_id") - .where("traversal_ids @> ARRAY(?)", project_namespace) - .limit(1) - end - - def project_namespace - ::Project.select('namespace_id') - .joins(:cluster_agents) - .where(cluster_agents: { id: id }) - .limit(1) + project.root_namespace end + strong_memoize_attr :root_namespace end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index a36ceb77d8f..15966709c94 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -230,11 +230,15 @@ class Namespace < ApplicationRecord # query - The search query as a String. # # Returns an ActiveRecord::Relation. - def search(query, include_parents: false) + def search(query, include_parents: false, use_minimum_char_limit: true) if include_parents - without_project_namespaces.where(id: Route.for_routable_type(Namespace.name).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id)) + without_project_namespaces + .where(id: Route.for_routable_type(Namespace.name) + .fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]], + use_minimum_char_limit: use_minimum_char_limit) + .select(:source_id)) else - without_project_namespaces.fuzzy_search(query, [:path, :name]) + without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit) end end diff --git a/app/models/project.rb b/app/models/project.rb index 44a360c6580..167b9283452 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -900,11 +900,12 @@ class Project < ApplicationRecord # search. # # query - The search query as a String. - def search(query, include_namespace: false) + def search(query, include_namespace: false, use_minimum_char_limit: true) if include_namespace - joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description]) + joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description], + use_minimum_char_limit: use_minimum_char_limit) else - fuzzy_search(query, [:path, :name, :description]) + fuzzy_search(query, [:path, :name, :description], use_minimum_char_limit: use_minimum_char_limit) end end diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index 6b8af173097..c7752a45c63 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -5,4 +5,4 @@ update_environment_path: project_environment_path(@project, @environment), protected_environment_settings_path: (project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') if @project.licensed_feature_available?(:protected_environments)), project_path: @project.full_path, - environment_name: @environment.name } } + environment: environment_data(@environment) } } diff --git a/config/feature_flags/development/remove_cicd_settings_update.yml b/config/feature_flags/development/value_stream_dashboard_on_off_setting.yml similarity index 61% rename from config/feature_flags/development/remove_cicd_settings_update.yml rename to config/feature_flags/development/value_stream_dashboard_on_off_setting.yml index d26355e6084..351cd72779d 100644 --- a/config/feature_flags/development/remove_cicd_settings_update.yml +++ b/config/feature_flags/development/value_stream_dashboard_on_off_setting.yml @@ -1,8 +1,8 @@ --- -name: remove_cicd_settings_update -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119061 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/409379 -milestone: '16.0' +name: value_stream_dashboard_on_off_setting +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120610 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/411223 +milestone: '16.1' type: development -group: group::runner +group: group::optimize default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index b4373c20aa0..f59b7521ce6 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1514,35 +1514,6 @@ Input type: `CiAiGenerateConfigInput` | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | `userMessage` | [`AiMessageType`](#aimessagetype) | User chat message. | -### `Mutation.ciCdSettingsUpdate` - -WARNING: -**Deprecated** in 15.0. -This was renamed. -Use: `ProjectCiCdSettingsUpdate`. - -Input type: `CiCdSettingsUpdateInput` - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| `fullPath` | [`ID!`](#id) | Full Path of the project the settings belong to. | -| `inboundJobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI/CD job tokens generated in other projects have restricted access to this project. | -| `jobTokenScopeEnabled` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** Outbound job token scope is being removed. This field can now only be set to false. Deprecated in 16.0. | -| `keepLatestArtifact` | [`Boolean`](#boolean) | Indicates if the latest artifact should be kept for the project. | -| `mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merge pipelines are enabled for the project. | -| `mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. | - -#### Fields - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `ciCdSettings` | [`ProjectCiCdSetting!`](#projectcicdsetting) | CI/CD settings after mutation. | -| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | - ### `Mutation.ciJobTokenScopeAddProject` Input type: `CiJobTokenScopeAddProjectInput` diff --git a/lib/gitlab/search/abuse_detection.rb b/lib/gitlab/search/abuse_detection.rb index 7b5377bce88..8711d078ea9 100644 --- a/lib/gitlab/search/abuse_detection.rb +++ b/lib/gitlab/search/abuse_detection.rb @@ -8,7 +8,6 @@ module Gitlab ABUSIVE_TERM_SIZE = 100 ALLOWED_CHARS_REGEX = %r{\A[[:alnum:]_\-\/\.!]+\z}.freeze - MINIMUM_SEARCH_CHARS = 2 ALLOWED_SCOPES = %w( blobs @@ -50,7 +49,8 @@ module Gitlab exclusion: { in: STOP_WORDS, message: 'stopword only abusive search detected' }, allow_blank: true validates :query_string, - length: { minimum: MINIMUM_SEARCH_CHARS, message: 'abusive tiny search detected' }, unless: :skip_tiny_search_validation?, allow_blank: true + length: { minimum: Params::MIN_TERM_LENGTH, message: 'abusive tiny search detected' }, + unless: :skip_tiny_search_validation?, allow_blank: true validates :query_string, no_abusive_term_length: { maximum: ABUSIVE_TERM_SIZE, maximum_for_url: ABUSIVE_TERM_SIZE * 2 } diff --git a/lib/gitlab/search/params.rb b/lib/gitlab/search/params.rb index 1ae14e5e618..6eb24a92be6 100644 --- a/lib/gitlab/search/params.rb +++ b/lib/gitlab/search/params.rb @@ -7,7 +7,7 @@ module Gitlab SEARCH_CHAR_LIMIT = 4096 SEARCH_TERM_LIMIT = 64 - MIN_TERM_LENGTH = 3 + MIN_TERM_LENGTH = 2 # Generic validation validates :query_string, length: { maximum: SEARCH_CHAR_LIMIT } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d66a953275e..45b03bdcf08 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21599,6 +21599,9 @@ msgstr "" msgid "GroupSettings|Email notifications are disabled" msgstr "" +msgid "GroupSettings|Enable overview background aggregation for Value Streams Dashboard" +msgstr "" + msgid "GroupSettings|Export group" msgstr "" @@ -21692,6 +21695,9 @@ msgstr "" msgid "GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group" msgstr "" +msgid "GroupSettings|Value Streams Dashboard" +msgstr "" + msgid "GroupSettings|What are badges?" msgstr "" diff --git a/rubocop/cop/ignored_columns.rb b/rubocop/cop/ignored_columns.rb index 542d78c59e9..8b17447c46b 100644 --- a/rubocop/cop/ignored_columns.rb +++ b/rubocop/cop/ignored_columns.rb @@ -2,40 +2,60 @@ module RuboCop module Cop - # Cop that blacklists the usage of `ActiveRecord::Base.ignored_columns=` directly + # Cop that flags the usage of `ActiveRecord::Base.ignored_columns=` directly + # + # @example + # # bad + # class User < ApplicationRecord + # self.ignored_columns = [:name] + # self.ignored_columns += [:full_name] + # end + # + # # good + # class User < ApplicationRecord + # include IgnorableColumns + # + # ignore_column :name, remove_after: '2023-05-22', remove_with: '16.0' + # ignore_column :full_name, remove_after: '2023-05-22', remove_with: '16.0' + # end class IgnoredColumns < RuboCop::Cop::Base - USE_CONCERN_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.' - WRONG_MODEL_MSG = 'If the model exists in CE and EE, the column has to be ignored ' \ - 'in the CE model. If the model only exists in EE, then it has to be added there.' + USE_CONCERN_ADD_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.' + USE_CONCERN_SET_MSG = 'Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.' + WRONG_MODEL_MSG = <<~MSG + If the model exists in CE and EE, the column has to be ignored + in the CE model. If the model only exists in EE, then it has to be added there. + MSG - def_node_matcher :ignored_columns?, <<~PATTERN + RESTRICT_ON_SEND = %i[ignored_columns ignored_columns= ignore_column ignore_columns].freeze + + def_node_matcher :ignored_columns_add?, <<~PATTERN (send (self) :ignored_columns) PATTERN - def_node_matcher :ignore_columns?, <<~PATTERN - (send nil? :ignore_columns ...) + def_node_matcher :ignored_columns_set?, <<~PATTERN + (send (self) :ignored_columns= ...) PATTERN - def_node_matcher :ignore_column?, <<~PATTERN - (send nil? :ignore_column ...) + def_node_matcher :using_ignore_columns?, <<~PATTERN + (send nil? {:ignore_columns :ignore_column}...) PATTERN def on_send(node) - if ignored_columns?(node) - add_offense(node, message: USE_CONCERN_MSG) + if ignored_columns_add?(node) + add_offense(node.loc.selector, message: USE_CONCERN_ADD_MSG) end - if using_ignore?(node) && used_in_wrong_model? + if ignored_columns_set?(node) + add_offense(node.loc.selector, message: USE_CONCERN_SET_MSG) + end + + if using_ignore_columns?(node) && used_in_wrong_model? add_offense(node, message: WRONG_MODEL_MSG) end end private - def using_ignore?(node) - ignore_columns?(node) || ignore_column?(node) - end - def used_in_wrong_model? file_path = processed_source.file_path diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index c0be1558c43..f436c96f4a5 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -18,7 +18,12 @@ jest.mock('~/lib/utils/url_utility'); jest.mock('~/alert'); const newExternalUrl = 'https://google.ca'; -const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com' }; +const environment = { + id: '1', + name: 'foo', + externalUrl: 'https://foo.example.com', + clusterAgent: null, +}; const resolvedEnvironment = { project: { id: '1', environment } }; const environmentUpdate = { environment: { id: '1', path: 'path/to/environment', clusterAgentId: null }, @@ -34,40 +39,43 @@ const provide = { updateEnvironmentPath: '/projects/environments/1', protectedEnvironmentSettingsPath: '/projects/1/settings/ci_cd', projectPath: '/path/to/project', - environmentName: environment.name, }; describe('~/environments/components/edit.vue', () => { let wrapper; let mock; - const createMockApolloProvider = (mutationResult, environmentSettingsToGraphql) => { + const createMockApolloProvider = (mutationResult) => { Vue.use(VueApollo); - const mocks = [[getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })]]; - - if (environmentSettingsToGraphql) { - mocks.push([ + const mocks = [ + [getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })], + [ updateEnvironment, jest.fn().mockResolvedValue({ data: { environmentUpdate: mutationResult } }), - ]); - } + ], + ]; return createMockApollo(mocks); }; - const createWrapper = async ({ - mutationResult = environmentUpdate, - environmentSettingsToGraphql = false, - } = {}) => { + const createWrapper = () => { wrapper = mountExtended(EditEnvironment, { + propsData: { environment: { id: '1', name: 'foo', external_url: 'https://foo.example.com' } }, + provide, + }); + }; + + const createWrapperWithApollo = async ({ mutationResult = environmentUpdate } = {}) => { + wrapper = mountExtended(EditEnvironment, { + propsData: { environment: {} }, provide: { ...provide, glFeatures: { - environmentSettingsToGraphql, + environmentSettingsToGraphql: true, }, }, - apolloProvider: createMockApolloProvider(mutationResult, environmentSettingsToGraphql), + apolloProvider: createMockApolloProvider(mutationResult), }); await waitForPromises(); @@ -109,9 +117,18 @@ describe('~/environments/components/edit.vue', () => { }); describe('when environmentSettingsToGraphql feature is enabled', () => { + describe('when mounted', () => { + beforeEach(() => { + createWrapperWithApollo(); + }); + it('renders loading icon when environment query is loading', () => { + expect(showsLoading()).toBe(true); + }); + }); + describe('when mutation successful', () => { beforeEach(async () => { - await createWrapper({ environmentSettingsToGraphql: true }); + await createWrapperWithApollo(); }); it('shows loader after form is submitted', async () => { @@ -132,9 +149,8 @@ describe('~/environments/components/edit.vue', () => { describe('when mutation failed', () => { beforeEach(async () => { - await createWrapper({ + await createWrapperWithApollo({ mutationResult: environmentUpdateError, - environmentSettingsToGraphql: true, }); }); @@ -149,9 +165,9 @@ describe('~/environments/components/edit.vue', () => { }); describe('when environmentSettingsToGraphql feature is disabled', () => { - beforeEach(async () => { + beforeEach(() => { mock = new MockAdapter(axios); - await createWrapper(); + createWrapper(); }); afterEach(() => { @@ -202,14 +218,4 @@ describe('~/environments/components/edit.vue', () => { expect(showsLoading()).toBe(false); }); }); - - describe('when environment query is loading', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders loading icon', () => { - expect(showsLoading()).toBe(true); - }); - }); }); diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index 9c70d8924b2..db81c490747 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -270,4 +270,21 @@ describe('~/environments/components/form.vue', () => { ]); }); }); + + describe('when environment has an associated agent', () => { + const environmentWithAgent = { + ...DEFAULT_PROPS.environment, + clusterAgent: { id: '1', name: 'agent-1' }, + clusterAgentId: '1', + }; + beforeEach(() => { + wrapper = createWrapperWithApollo({ + propsData: { environment: environmentWithAgent }, + }); + }); + + it('updates agent selector field with the name of the associated agent', () => { + expect(findAgentSelector().props('toggleText')).toBe('agent-1'); + }); + }); }); diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 2cea577a852..b5e5e5a711f 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -64,13 +64,6 @@ RSpec.describe SearchHelper, feature_category: :global_search do let_it_be(:another_user) { create(:user, name: 'Jane Doe') } let(:term) { 'jane' } - it 'makes a call to SearchService' do - params = { search: term, per_page: 5, scope: 'users' } - expect(SearchService).to receive(:new).with(current_user, params).and_call_original - - search_autocomplete_opts(term) - end - it 'returns users matching the term' do result = search_autocomplete_opts(term) expect(result.size).to eq(1) @@ -88,6 +81,68 @@ RSpec.describe SearchHelper, feature_category: :global_search do end end + describe 'permissions' do + let(:term) { 'jane@doe' } + let(:private_email_user) { create(:user, email: term) } + let(:public_email_user) { create(:user, :public_email, email: term) } + let(:banned_user) { create(:user, :banned, email: term) } + let(:user_with_other_email) { create(:user, email: 'something@else') } + let(:secondary_email) { create(:email, :confirmed, user: user_with_other_email, email: term) } + let(:ids) { search_autocomplete_opts(term).pluck(:id) } + + context 'when current_user is an admin' do + before do + allow(current_user).to receive(:can_admin_all_resources?).and_return(true) + end + + it 'includes users with matching public emails' do + public_email_user + expect(ids).to include(public_email_user.id) + end + + it 'includes users in forbidden states' do + banned_user + expect(ids).to include(banned_user.id) + end + + it 'includes users without matching public emails but with matching private emails' do + private_email_user + expect(ids).to include(private_email_user.id) + end + + it 'includes users matching on secondary email' do + secondary_email + expect(ids).to include(secondary_email.user_id) + end + end + + context 'when current_user is not an admin' do + before do + allow(current_user).to receive(:can_admin_all_resources?).and_return(false) + end + + it 'includes users with matching public emails' do + public_email_user + expect(ids).to include(public_email_user.id) + end + + it 'does not include users in forbidden states' do + banned_user + expect(ids).not_to include(banned_user.id) + end + + it 'does not include users without matching public emails but with matching private emails' do + private_email_user + expect(ids).not_to include(private_email_user.id) + end + + it 'does not include users matching on secondary email' do + secondary_email + expect(ids).not_to include(secondary_email.user_id) + end + end + end + context 'with limiting' do let!(:users) { create_list(:user, 6, name: 'Jane Doe') } @@ -268,7 +323,7 @@ RSpec.describe SearchHelper, feature_category: :global_search do expect(results.first).to include({ category: 'In this project', id: issue.id, - label: 'test title (#1)', + label: "test title (##{issue.iid})", url: ::Gitlab::Routing.url_helpers.project_issue_path(issue.project, issue), avatar_url: '' # project has no avatar }) @@ -306,8 +361,47 @@ RSpec.describe SearchHelper, feature_category: :global_search do end end + describe 'resource_results' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user, name: 'User') } + let_it_be(:group) { create(:group, name: 'Group') } + let_it_be(:project) { create(:project, name: 'Project') } + let!(:issue) { create(:issue, project: project) } + let(:issue_iid) { "\##{issue.iid}" } + + before do + allow(self).to receive(:current_user).and_return(user) + group.add_owner(user) + project.add_owner(user) + @project = project + end + + where(:term, :size, :category) do + 'g' | 0 | 'Groups' + 'gr' | 1 | 'Groups' + 'gro' | 1 | 'Groups' + 'p' | 0 | 'Projects' + 'pr' | 1 | 'Projects' + 'pro' | 1 | 'Projects' + 'u' | 0 | 'Users' + 'us' | 1 | 'Users' + 'use' | 1 | 'Users' + ref(:issue_iid) | 1 | 'In this project' + end + + with_them do + it 'returns results only if the term is more than or equal to Gitlab::Search::Params::MIN_TERM_LENGTH' do + results = resource_results(term) + + expect(results.size).to eq(size) + expect(results.first[:category]).to eq(category) if size == 1 + end + end + end + describe 'projects_autocomplete' do - let_it_be(:user) { create(:user, name: "madelein") } + let_it_be(:user) { create(:user) } let_it_be(:project_1) { create(:project, name: 'test 1') } let_it_be(:project_2) { create(:project, name: 'test 2') } let(:search_term) { 'test' } diff --git a/spec/lib/gitlab/search/abuse_detection_spec.rb b/spec/lib/gitlab/search/abuse_detection_spec.rb index 7fb9621141c..f9a1d0211b9 100644 --- a/spec/lib/gitlab/search/abuse_detection_spec.rb +++ b/spec/lib/gitlab/search/abuse_detection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Search::AbuseDetection do +RSpec.describe Gitlab::Search::AbuseDetection, feature_category: :global_search do subject { described_class.new(params) } let(:params) { { query_string: 'foobar' } } diff --git a/spec/lib/gitlab/search/params_spec.rb b/spec/lib/gitlab/search/params_spec.rb index 13770e550ec..3235a0b2126 100644 --- a/spec/lib/gitlab/search/params_spec.rb +++ b/spec/lib/gitlab/search/params_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Search::Params do +RSpec.describe Gitlab::Search::Params, feature_category: :global_search do subject { described_class.new(params, detect_abuse: detect_abuse) } let(:search) { 'search' } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 2bb791c90df..c9995122bb2 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1067,6 +1067,18 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do expect(described_class.search('PARENT-PATH/NEW-PATH', include_parents: true)).to eq([second_group]) end + it 'defaults use_minimum_char_limit to true' do + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true).once + + described_class.search('my namespace') + end + + it 'passes use_minimum_char_limit if it is set' do + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false).once + + described_class.search('my namespace', use_minimum_char_limit: false) + end + context 'with project namespaces' do let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') } let_it_be(:project_namespace) { project.project_namespace } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dbccba5ff84..12a34bcee78 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2997,6 +2997,18 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr expect(described_class.search(project.path.upcase)).to eq([project]) end + it 'defaults use_minimum_char_limit to true' do + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true).once + + described_class.search('kitten') + end + + it 'passes use_minimum_char_limit if it is set' do + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false).once + + described_class.search('kitten', use_minimum_char_limit: false) + end + context 'when include_namespace is true' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb index aa00069b241..fd92ed198e7 100644 --- a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb @@ -64,35 +64,6 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr expect(project.keep_latest_artifact).to eq(false) end - describe 'ci_cd_settings_update deprecated mutation' do - let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) } - - it 'returns error' do - post_graphql_mutation(mutation, current_user: user) - - expect(graphql_errors).to( - include( - hash_including('message' => '`remove_cicd_settings_update` feature flag is enabled.') - ) - ) - end - - context 'when remove_cicd_settings_update FF is disabled' do - before do - stub_feature_flags(remove_cicd_settings_update: false) - end - - it 'updates ci cd settings' do - post_graphql_mutation(mutation, current_user: user) - - project.reload - - expect(response).to have_gitlab_http_status(:success) - expect(project.keep_latest_artifact).to eq(false) - end - end - end - it 'allows setting job_token_scope_enabled to false' do post_graphql_mutation(mutation, current_user: user) diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index b8c10de2302..3420e38f4af 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -5,12 +5,12 @@ require 'spec_helper' RSpec.describe API::Release::Links, feature_category: :release_orchestration do include Ci::JobTokenScopeHelpers - let(:project) { create(:project, :repository, :private) } - let(:maintainer) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:non_project_member) { create(:user) } - let(:commit) { create(:commit, project: project) } + let_it_be_with_reload(:project) { create(:project, :repository, :private) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:non_project_member) { create(:user) } + let_it_be(:commit) { create(:commit, project: project) } let!(:release) do create(:release, @@ -19,7 +19,7 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do author: maintainer) end - before do + before_all do project.add_maintainer(maintainer) project.add_developer(developer) project.add_reporter(reporter) diff --git a/spec/rubocop/cop/ignored_columns_spec.rb b/spec/rubocop/cop/ignored_columns_spec.rb index c6c44399624..8d2c6b92c70 100644 --- a/spec/rubocop/cop/ignored_columns_spec.rb +++ b/spec/rubocop/cop/ignored_columns_spec.rb @@ -3,12 +3,21 @@ require 'rubocop_spec_helper' require_relative '../../../rubocop/cop/ignored_columns' -RSpec.describe RuboCop::Cop::IgnoredColumns do - it 'flags direct use of ignored_columns instead of the IgnoredColumns concern' do +RSpec.describe RuboCop::Cop::IgnoredColumns, feature_category: :database do + it 'flags use of `self.ignored_columns +=` instead of the IgnoredColumns concern' do expect_offense(<<~RUBY) class Foo < ApplicationRecord self.ignored_columns += %i[id] - ^^^^^^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`. + ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`. + end + RUBY + end + + it 'flags use of `self.ignored_columns =` instead of the IgnoredColumns concern' do + expect_offense(<<~RUBY) + class Foo < ApplicationRecord + self.ignored_columns = %i[id] + ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of setting `self.ignored_columns`. end RUBY end @@ -16,7 +25,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do context 'when only CE model exist' do let(:file_path) { full_path('app/models/bar.rb') } - it 'does not flag ignore_columns usage in CE model' do + it 'does not flag `ignore_columns` usage in CE model' do expect_no_offenses(<<~RUBY, file_path) class Bar < ApplicationRecord ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22' @@ -24,7 +33,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do RUBY end - it 'flags ignore_column usage in EE model' do + it 'does not flag `ignore_column` usage in CE model' do expect_no_offenses(<<~RUBY, file_path) class Baz < ApplicationRecord ignore_column :bar, remove_with: '14.3', remove_after: '2021-09-22' @@ -40,7 +49,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(false) end - it 'flags ignore_columns usage in EE model' do + it 'does not flag `ignore_columns` usage in EE model' do expect_no_offenses(<<~RUBY, file_path) class Bar < ApplicationRecord ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22' @@ -48,7 +57,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do RUBY end - it 'flags ignore_column usage in EE model' do + it 'does not flag `ignore_column` usage in EE model' do expect_no_offenses(<<~RUBY, file_path) class Bar < ApplicationRecord ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22' @@ -64,7 +73,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(true) end - it 'flags ignore_columns usage in EE model' do + it 'flags `ignore_columns` usage in EE model' do expect_offense(<<~RUBY, file_path) class Bar < ApplicationRecord ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22' @@ -73,7 +82,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do RUBY end - it 'flags ignore_column usage in EE model' do + it 'flags `ignore_column` usage in EE model' do expect_offense(<<~RUBY, file_path) class Bar < ApplicationRecord ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22'