From 30f9120ba63ea54283c34a046de691a3dbf59450 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 11 Aug 2022 12:09:19 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../graphql/list/all_runners.query.graphql | 12 +--- .../all_runners_connection.fragment.graphql | 13 +++++ app/controllers/autocomplete_controller.rb | 9 +-- app/events/groups/group_transfered_event.rb | 17 ++++++ .../deploy_keys_with_write_access_finder.rb | 21 +++++++ app/helpers/storage_helper.rb | 11 ++-- app/models/issue.rb | 9 ++- app/models/member.rb | 9 +++ app/models/project.rb | 1 + app/services/groups/transfer_service.rb | 17 +++++- .../ops/authenticate_markdown_api.yml | 8 +++ config/initializers/1_settings.rb | 5 ++ ...acy_open_source_license_available_index.rb | 18 ++++++ db/schema_migrations/20220729033851 | 1 + db/structure.sql | 2 + .../application_settings_cache.md | 2 +- doc/administration/monitoring/ip_allowlist.md | 2 +- .../monitoring/prometheus/web_exporter.md | 2 +- .../operations/sidekiq_memory_killer.md | 2 +- doc/api/markdown.md | 24 ++++++-- doc/development/cached_queries.md | 2 +- doc/development/image_scaling.md | 2 +- doc/user/project/wiki/index.md | 1 + lib/api/markdown.rb | 4 +- locale/gitlab.pot | 7 ++- spec/factories/member_roles.rb | 8 +++ ...ploy_keys_with_write_access_finder_spec.rb | 53 +++++++++++++++++ .../design_notes/design_discussion_spec.js | 2 +- .../diffs/components/diff_row_spec.js | 10 ++-- spec/helpers/storage_helper_spec.rb | 11 ++-- spec/models/issue_spec.rb | 57 ++++++++++++++++++- spec/models/member_spec.rb | 30 ++++++++++ spec/requests/api/markdown_spec.rb | 57 ++++++++++++++----- spec/services/groups/transfer_service_spec.rb | 37 ++++++++++++ .../markdown_snapshot_shared_examples.rb | 5 +- spec/workers/every_sidekiq_worker_spec.rb | 1 + tooling/danger/project_helper.rb | 2 + 37 files changed, 408 insertions(+), 66 deletions(-) create mode 100644 app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql create mode 100644 app/events/groups/group_transfered_event.rb create mode 100644 app/finders/autocomplete/deploy_keys_with_write_access_finder.rb create mode 100644 config/feature_flags/ops/authenticate_markdown_api.yml create mode 100644 db/post_migrate/20220729033851_add_partial_legacy_open_source_license_available_index.rb create mode 100644 db/schema_migrations/20220729033851 create mode 100644 spec/factories/member_roles.rb create mode 100644 spec/finders/autocomplete/deploy_keys_with_write_access_finder_spec.rb diff --git a/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql index 7b2617f6c3c..1160596aff3 100644 --- a/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql @@ -1,5 +1,4 @@ -#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql" -#import "~/graphql_shared/fragments/page_info.fragment.graphql" +#import "~/runner/graphql/list/all_runners_connection.fragment.graphql" query getAllRunners( $before: String @@ -25,13 +24,6 @@ query getAllRunners( search: $search sort: $sort ) { - nodes { - ...ListItem - adminUrl - editAdminUrl - } - pageInfo { - ...PageInfo - } + ...AllRunnersConnection } } diff --git a/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql b/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql new file mode 100644 index 00000000000..4440b8e98da --- /dev/null +++ b/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql @@ -0,0 +1,13 @@ +#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql" +#import "~/graphql_shared/fragments/page_info.fragment.graphql" + +fragment AllRunnersConnection on CiRunnerConnection { + nodes { + ...ListItem + adminUrl + editAdminUrl + } + pageInfo { + ...PageInfo + } +} diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 6d1ffc1f2e8..88592efcec7 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,7 +5,6 @@ class AutocompleteController < ApplicationController skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches] before_action :check_search_rate_limit!, only: [:users, :projects] - before_action :authorize_admin_project, only: :deploy_keys_with_owners feature_category :users, [:users, :user] feature_category :projects, [:projects] @@ -61,7 +60,9 @@ class AutocompleteController < ApplicationController end def deploy_keys_with_owners - deploy_keys = DeployKey.with_write_access_for_project(project) + deploy_keys = Autocomplete::DeployKeysWithWriteAccessFinder + .new(current_user, project) + .execute render json: DeployKeys::BasicDeployKeySerializer.new.represent( deploy_keys, { with_owner: true, user: current_user } @@ -70,10 +71,6 @@ class AutocompleteController < ApplicationController private - def authorize_admin_project - render_403 unless Ability.allowed?(current_user, :admin_project, project) - end - def project @project ||= Autocomplete::ProjectFinder .new(current_user, params) diff --git a/app/events/groups/group_transfered_event.rb b/app/events/groups/group_transfered_event.rb new file mode 100644 index 00000000000..da573892108 --- /dev/null +++ b/app/events/groups/group_transfered_event.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Groups + class GroupTransferedEvent < ::Gitlab::EventStore::Event + def schema + { + 'type' => 'object', + 'properties' => { + 'group_id' => { 'type' => 'integer' }, + 'old_root_namespace_id' => { 'type' => 'integer' }, + 'new_root_namespace_id' => { 'type' => 'integer' } + }, + 'required' => %w[group_id old_root_namespace_id new_root_namespace_id] + } + end + end +end diff --git a/app/finders/autocomplete/deploy_keys_with_write_access_finder.rb b/app/finders/autocomplete/deploy_keys_with_write_access_finder.rb new file mode 100644 index 00000000000..a123a0dc4f0 --- /dev/null +++ b/app/finders/autocomplete/deploy_keys_with_write_access_finder.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Autocomplete + # Finder for retrieving deploy keys to use for autocomplete data sources. + class DeployKeysWithWriteAccessFinder + attr_reader :current_user, :project + + def initialize(current_user, project) + @current_user = current_user + @project = project + end + + def execute + return DeployKey.none if project.nil? + + raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + + project.deploy_keys.merge(DeployKeysProject.with_write_access) + end + end +end diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index 6612bb0fc99..9e516d726c1 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -37,12 +37,12 @@ module StorageHelper "about how to reduce your storage.")).html_safe % text_args[:p2] else html_escape_once(s_("UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. " \ - "View and manage your usage from %{strong_start}Group settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more%{link_end} " \ - "about how to reduce your storage.")).html_safe % text_args[:p2] + "Group owners can view namespace storage usage and purchase more from %{strong_start}Group settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more.%{link_end}" \ + )).html_safe % text_args[:p2] end { - text_paragraph_1: html_escape_once(s_("UsageQuota|Effective %{storage_enforcement_date}, %{announcement_link_start}namespace storage limits will apply%{link_end} " \ + text_paragraph_1: html_escape_once(s_("UsageQuota|Effective %{storage_enforcement_date}, namespace storage limits will apply " \ "to the %{strong_start}%{namespace_name}%{strong_end} namespace. %{extra_message}" \ "View the %{rollout_link_start}rollout schedule for this change%{link_end}.")).html_safe % text_args[:p1], text_paragraph_2: text_paragraph_2, @@ -92,8 +92,7 @@ module StorageHelper storage_enforcement_date: root_ancestor.storage_enforcement_date, namespace_name: root_ancestor.name, extra_message: extra_message, - announcement_link_start: ''.html_safe % { url: "#{Gitlab::Saas.community_forum_url}/t/gitlab-introduces-storage-and-transfer-limits-for-users-on-saas/69883" }, - rollout_link_start: ''.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'tbd') }, + rollout_link_start: ''.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'namespace-storage-limit-enforcement-schedule') }, link_end: "".html_safe }.merge(strong_tags), p2: { @@ -102,7 +101,7 @@ module StorageHelper link_end: "".html_safe }.merge(strong_tags), p3: { - faq_link_start: ''.html_safe % { url: "#{Gitlab::Saas.about_pricing_url}faq-efficient-free-tier/#storage-and-transfer-limits-on-gitlab-saas-free-tier" }, + faq_link_start: ''.html_safe % { url: "#{Gitlab::Saas.about_pricing_url}faq-efficient-free-tier/#storage-limits-on-gitlab-saas-free-tier" }, link_end: "".html_safe } } diff --git a/app/models/issue.rb b/app/models/issue.rb index 2ac9c05a4f5..4114467eb25 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -99,6 +99,7 @@ class Issue < ApplicationRecord validates :project, presence: true validates :issue_type, presence: true validates :namespace, presence: true, if: -> { project.present? } + validates :work_item_type, presence: true validate :due_date_after_start_date validate :parent_link_confidentiality @@ -204,7 +205,7 @@ class Issue < ApplicationRecord scope :with_null_relative_position, -> { where(relative_position: nil) } scope :with_non_null_relative_position, -> { where.not(relative_position: nil) } - before_validation :ensure_namespace_id + before_validation :ensure_namespace_id, :ensure_work_item_type after_commit :expire_etag_cache, unless: :importing? after_save :ensure_metrics, unless: :importing? @@ -732,6 +733,12 @@ class Issue < ApplicationRecord def ensure_namespace_id self.namespace = project.project_namespace if project end + + def ensure_work_item_type + return if work_item_type_id.present? || work_item_type_id_change&.last.present? + + self.work_item_type = WorkItems::Type.default_by_type(issue_type) + end end Issue.prepend_mod_with('Issue') diff --git a/app/models/member.rb b/app/models/member.rb index 345bc1b7b3b..0cd1e022617 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -59,6 +59,7 @@ class Member < ApplicationRecord }, if: :project_bot? validate :access_level_inclusion + validate :validate_member_role_access_level scope :with_invited_user_state, -> do joins('LEFT JOIN users as invited_user ON invited_user.email = members.invite_email') @@ -429,6 +430,14 @@ class Member < ApplicationRecord errors.add(:access_level, "is not included in the list") end + def validate_member_role_access_level + return unless member_role_id + + if access_level != member_role.base_access_level + errors.add(:member_role_id, _("role's base access level does not match the access level of the membership")) + end + end + def send_invite # override in subclass end diff --git a/app/models/project.rb b/app/models/project.rb index 28dd51fc67d..e7161053705 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -669,6 +669,7 @@ class Project < ApplicationRecord scope :imported_from, -> (type) { where(import_type: type) } scope :imported, -> { where.not(import_type: nil) } scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) } + scope :last_activity_before, -> (time) { where('projects.last_activity_at < ?', time) } scope :with_service_desk_key, -> (key) do # project_key is not indexed for now diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 29e3a9473ab..6fbf7daeb81 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -36,7 +36,7 @@ module Groups update_crm_objects(was_root_group) end - post_update_hooks(@updated_project_ids) + post_update_hooks(@updated_project_ids, old_root_ancestor_id) propagate_integrations update_pending_builds @@ -44,9 +44,10 @@ module Groups end # Overridden in EE - def post_update_hooks(updated_project_ids) + def post_update_hooks(updated_project_ids, old_root_ancestor_id) refresh_project_authorizations refresh_descendant_groups if @new_parent_group + publish_event(old_root_ancestor_id) end def ensure_allowed_transfer @@ -266,6 +267,18 @@ module Groups CustomerRelations::IssueContact.delete_for_group(@group) end + + def publish_event(old_root_ancestor_id) + event = ::Groups::GroupTransferedEvent.new( + data: { + group_id: group.id, + old_root_namespace_id: old_root_ancestor_id, + new_root_namespace_id: group.root_ancestor.id + } + ) + + Gitlab::EventStore.publish(event) + end end end diff --git a/config/feature_flags/ops/authenticate_markdown_api.yml b/config/feature_flags/ops/authenticate_markdown_api.yml new file mode 100644 index 00000000000..8e7a7833d27 --- /dev/null +++ b/config/feature_flags/ops/authenticate_markdown_api.yml @@ -0,0 +1,8 @@ +--- +name: authenticate_markdown_api +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93727 +rollout_issue_url: +milestone: '15.3' +type: ops +group: group::project management +default_enabled: true diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0c15087ed4e..b271cefadd9 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -779,6 +779,11 @@ Gitlab.ee do Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner'] ||= Settingslogic.new({}) Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['cron'] ||= "0 0 * * *" Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['job_class'] = 'Licenses::ResetSubmitLicenseUsageDataBannerWorker' + Gitlab.com do + Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects'] ||= Settingslogic.new({}) + Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects']['cron'] ||= "30 5 * * 0" + Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects']['job_class'] = 'Projects::DisableLegacyOpenSourceLicenseForInactiveProjectsWorker' + end end # diff --git a/db/post_migrate/20220729033851_add_partial_legacy_open_source_license_available_index.rb b/db/post_migrate/20220729033851_add_partial_legacy_open_source_license_available_index.rb new file mode 100644 index 00000000000..c99c452c149 --- /dev/null +++ b/db/post_migrate/20220729033851_add_partial_legacy_open_source_license_available_index.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddPartialLegacyOpenSourceLicenseAvailableIndex < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + INDEX_NAME = 'index_project_settings_on_legacy_open_source_license_available' + + def up + add_concurrent_index :project_settings, + %i[legacy_open_source_license_available], + where: "legacy_open_source_license_available = TRUE", + name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name(:project_settings, INDEX_NAME) + end +end diff --git a/db/schema_migrations/20220729033851 b/db/schema_migrations/20220729033851 new file mode 100644 index 00000000000..b24c964cdb2 --- /dev/null +++ b/db/schema_migrations/20220729033851 @@ -0,0 +1 @@ +0e8b193943aa02c8b700c06110725fd643378cf79715d1398238abc407639c67 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index fedf2d23036..b401213af76 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -29484,6 +29484,8 @@ CREATE UNIQUE INDEX index_project_repository_states_on_project_id ON project_rep CREATE INDEX index_project_repository_storage_moves_on_project_id ON project_repository_storage_moves USING btree (project_id); +CREATE INDEX index_project_settings_on_legacy_open_source_license_available ON project_settings USING btree (legacy_open_source_license_available) WHERE (legacy_open_source_license_available = true); + CREATE INDEX index_project_settings_on_project_id_partially ON project_settings USING btree (project_id) WHERE (has_vulnerabilities IS TRUE); CREATE UNIQUE INDEX index_project_settings_on_push_rule_id ON project_settings USING btree (push_rule_id); diff --git a/doc/administration/application_settings_cache.md b/doc/administration/application_settings_cache.md index 6c58e6886c4..30fd9ab85a8 100644 --- a/doc/administration/application_settings_cache.md +++ b/doc/administration/application_settings_cache.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/ip_allowlist.md b/doc/administration/monitoring/ip_allowlist.md index adf9516733a..3151b696182 100644 --- a/doc/administration/monitoring/ip_allowlist.md +++ b/doc/administration/monitoring/ip_allowlist.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/web_exporter.md b/doc/administration/monitoring/prometheus/web_exporter.md index fe140b15ed2..99b40ba6591 100644 --- a/doc/administration/monitoring/prometheus/web_exporter.md +++ b/doc/administration/monitoring/prometheus/web_exporter.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md index 12381808523..78719a2bfca 100644 --- a/doc/administration/operations/sidekiq_memory_killer.md +++ b/doc/administration/operations/sidekiq_memory_killer.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/api/markdown.md b/doc/api/markdown.md index c128e8512df..b66a07dc1d5 100644 --- a/doc/api/markdown.md +++ b/doc/api/markdown.md @@ -1,13 +1,27 @@ --- -stage: Create -group: Source Code +stage: Plan +group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- # Markdown API **(FREE)** +Convert Markdown content to HTML. + Available only in APIv4. +## Required authentication + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93727) in GitLab 15.3 [with a flag](../administration/feature_flags.md) named `authenticate_markdown_api`. Enabled by default. + +FLAG: +On self-managed GitLab, by default this feature is enabled and authentication is required. +To remove the requirement to authenticate, ask an administrator to +[disable the feature flag](../administration/feature_flags.md) named `authenticate_markdown_api`. +On GitLab.com, this feature is available. + +All API calls to the Markdown API must be [authenticated](index.md#authentication). + ## Render an arbitrary Markdown document ```plaintext @@ -18,10 +32,12 @@ POST /markdown | --------- | ------- | ------------- | ------------------------------------------ | | `text` | string | yes | The Markdown text to render | | `gfm` | boolean | no | Render text using GitLab Flavored Markdown. Default is `false` | -| `project` | string | no | Use `project` as a context when creating references using GitLab Flavored Markdown. [Authentication](index.md#authentication) is required if a project is not public. | +| `project` | string | no | Use `project` as a context when creating references using GitLab Flavored Markdown | ```shell -curl --header Content-Type:application/json --data '{"text":"Hello world! :tada:", "gfm":true, "project":"group_example/project_example"}' "https://gitlab.example.com/api/v4/markdown" +curl --request POST --header "PRIVATE-TOKEN: " \ + --header "Content-Type:application/json" \ + --data '{"text":"Hello world! :tada:", "gfm":true, "project":"group_example/project_example"}' "https://gitlab.example.com/api/v4/markdown" ``` Response example: diff --git a/doc/development/cached_queries.md b/doc/development/cached_queries.md index b0bf7c7b6f5..7af4c302e93 100644 --- a/doc/development/cached_queries.md +++ b/doc/development/cached_queries.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/development/image_scaling.md b/doc/development/image_scaling.md index 93575429369..2078db8294c 100644 --- a/doc/development/image_scaling.md +++ b/doc/development/image_scaling.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Memory +group: Application Performance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index e8870e2b028..e76c53208d3 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -341,6 +341,7 @@ Support includes: - Creating and editing the structure of tables. - Inserting and formatting code blocks with syntax highlighting. - Live preview of Mermaid, PlantUML, and Kroki diagrams ([Introduced] in GitLab 15.2). +- Real-time visualization of table of contents. ### Use the Content Editor diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index c465087c4a2..1f8255fd6a4 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -2,7 +2,9 @@ module API class Markdown < ::API::Base - feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned + before { authenticate! if Feature.enabled?(:authenticate_markdown_api, type: :ops) } + + feature_category :team_planning params do requires :text, type: String, desc: "The markdown text to render" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2c2ce654412..55ddf413be5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -41934,7 +41934,7 @@ msgstr "" msgid "UsageQuota|Dependency proxy" msgstr "" -msgid "UsageQuota|Effective %{storage_enforcement_date}, %{announcement_link_start}namespace storage limits will apply%{link_end} to the %{strong_start}%{namespace_name}%{strong_end} namespace. %{extra_message}View the %{rollout_link_start}rollout schedule for this change%{link_end}." +msgid "UsageQuota|Effective %{storage_enforcement_date}, namespace storage limits will apply to the %{strong_start}%{namespace_name}%{strong_end} namespace. %{extra_message}View the %{rollout_link_start}rollout schedule for this change%{link_end}." msgstr "" msgid "UsageQuota|File attachments and smaller design graphics." @@ -42045,7 +42045,7 @@ msgstr "" msgid "UsageQuota|The %{strong_start}%{context_name}%{strong_end} project will be affected by this. " msgstr "" -msgid "UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. View and manage your usage from %{strong_start}Group settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more%{link_end} about how to reduce your storage." +msgid "UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. Group owners can view namespace storage usage and purchase more from %{strong_start}Group settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more.%{link_end}" msgstr "" msgid "UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. View and manage your usage from %{strong_start}User settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more%{link_end} about how to reduce your storage." @@ -47170,6 +47170,9 @@ msgstr "" msgid "repository:" msgstr "" +msgid "role's base access level does not match the access level of the membership" +msgstr "" + msgid "satisfied" msgstr "" diff --git a/spec/factories/member_roles.rb b/spec/factories/member_roles.rb new file mode 100644 index 00000000000..bd211844f5a --- /dev/null +++ b/spec/factories/member_roles.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :member_role do + namespace { association(:group) } + base_access_level { Gitlab::Access::DEVELOPER } + end +end diff --git a/spec/finders/autocomplete/deploy_keys_with_write_access_finder_spec.rb b/spec/finders/autocomplete/deploy_keys_with_write_access_finder_spec.rb new file mode 100644 index 00000000000..ed3b1d2d0bf --- /dev/null +++ b/spec/finders/autocomplete/deploy_keys_with_write_access_finder_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Autocomplete::DeployKeysWithWriteAccessFinder do + let_it_be(:user) { create(:user) } + + let(:finder) { described_class.new(user, project) } + + describe '#execute' do + subject(:execute) { finder.execute } + + context 'when project is missing' do + let(:project) { nil } + + it 'returns an empty ActiveRecord::Relation' do + expect(execute).to eq(DeployKey.none) + end + end + + context 'when project is present' do + let_it_be(:project) { create(:project, :public) } + + context 'and current user cannot admin project' do + it 'raises Gitlab::Access::AccessDeniedError' do + expect { execute }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'and current user can admin project' do + before do + project.add_maintainer(user) + end + + context 'when deploy key does not have write access to project' do + let(:deploy_key_project) { create(:deploy_keys_project, project: project) } + + it 'returns an empty ActiveRecord::Relation' do + expect(execute).to eq(DeployKey.none) + end + end + + context 'when deploy key has write access to project' do + let(:deploy_key_project) { create(:deploy_keys_project, :write_access, project: project) } + + it 'returns the deploy keys' do + expect(execute).to match_array([deploy_key_project.deploy_key]) + end + end + end + end + end +end diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js index 77935fbde11..ae2dc23a94d 100644 --- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js @@ -351,7 +351,7 @@ describe('Design discussions component', () => { createComponent(); findReplyPlaceholder().vm.$emit('focus'); - expect(wrapper.emitted('open-form')).toBeTruthy(); + expect(wrapper.emitted('open-form')).toHaveLength(1); }); describe('when user is not logged in', () => { diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js index be81508213b..a74013dc2d4 100644 --- a/spec/frontend/diffs/components/diff_row_spec.js +++ b/spec/frontend/diffs/components/diff_row_spec.js @@ -239,7 +239,7 @@ describe('DiffRow', () => { const coverage = wrapper.find('.line-coverage.right-side'); expect(coverage.attributes('title')).toContain('Test coverage: 5 hits'); - expect(coverage.classes('coverage')).toBeTruthy(); + expect(coverage.classes('coverage')).toBe(true); }); it('for lines without coverage', () => { @@ -248,7 +248,7 @@ describe('DiffRow', () => { const coverage = wrapper.find('.line-coverage.right-side'); expect(coverage.attributes('title')).toContain('No test coverage'); - expect(coverage.classes('no-coverage')).toBeTruthy(); + expect(coverage.classes('no-coverage')).toBe(true); }); it('for unknown lines', () => { @@ -256,9 +256,9 @@ describe('DiffRow', () => { wrapper = createWrapper({ props, state: { coverageFiles } }); const coverage = wrapper.find('.line-coverage.right-side'); - expect(coverage.attributes('title')).toBeFalsy(); - expect(coverage.classes('coverage')).toBeFalsy(); - expect(coverage.classes('no-coverage')).toBeFalsy(); + expect(coverage.attributes('title')).toBeUndefined(); + expect(coverage.classes('coverage')).toBe(false); + expect(coverage.classes('no-coverage')).toBe(false); }); }); diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index 95d6fe232a7..6c3556c874b 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -100,9 +100,9 @@ RSpec.describe StorageHelper do used_storage = helper.storage_counter(free_group.root_storage_statistics&.storage_size || 0) expect(helper.storage_enforcement_banner_info(free_group)).to eql({ - text_paragraph_1: "Effective #{storage_enforcement_date}, namespace storage limits will apply to the #{free_group.name} namespace. View the rollout schedule for this change.", - text_paragraph_2: "The namespace is currently using #{used_storage} of namespace storage. View and manage your usage from Group settings > Usage quotas. Learn more about how to reduce your storage.", - text_paragraph_3: "See our FAQ for more information.", + text_paragraph_1: "Effective #{storage_enforcement_date}, namespace storage limits will apply to the #{free_group.name} namespace. View the rollout schedule for this change.", + text_paragraph_2: "The namespace is currently using #{used_storage} of namespace storage. Group owners can view namespace storage usage and purchase more from Group settings > Usage quotas. Learn more.", + text_paragraph_3: "See our FAQ for more information.", variant: 'warning', namespace_id: free_group.id, callouts_feature_name: 'storage_enforcement_banner_second_enforcement_threshold', @@ -116,7 +116,7 @@ RSpec.describe StorageHelper do end it 'returns a hash with the correct storage size text' do - expect(helper.storage_enforcement_banner_info(free_group)[:text_paragraph_2]).to eql("The namespace is currently using 100 KB of namespace storage. View and manage your usage from Group settings > Usage quotas. Learn more about how to reduce your storage.") + expect(helper.storage_enforcement_banner_info(free_group)[:text_paragraph_2]).to eql("The namespace is currently using 100 KB of namespace storage. Group owners can view namespace storage usage and purchase more from Group settings > Usage quotas. Learn more.") end end @@ -141,7 +141,8 @@ RSpec.describe StorageHelper do end it 'returns the enforcement info' do - expect(helper.storage_enforcement_banner_info(free_group)[:text_paragraph_1]).to include("Effective #{Date.current}, namespace storage limits will apply") + puts helper.storage_enforcement_banner_info(free_group)[:text_paragraph_1] + expect(helper.storage_enforcement_banner_info(free_group)[:text_paragraph_1]).to include("Effective #{Date.current}, namespace storage limits will apply") end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index a28b91d463f..15fe6d7625a 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -222,6 +222,61 @@ RSpec.describe Issue do end end + describe '#ensure_work_item_type' do + let_it_be(:issue_type) { create(:work_item_type, :issue, :default) } + let_it_be(:task_type) { create(:work_item_type, :issue, :default) } + let_it_be(:project) { create(:project) } + + context 'when a type was already set' do + let_it_be(:issue, refind: true) { create(:issue, project: project) } + + it 'does not fetch a work item type from the DB' do + expect(issue.work_item_type_id).to eq(issue_type.id) + expect(WorkItems::Type).not_to receive(:default_by_type) + + expect(issue).to be_valid + end + + it 'does not fetch a work item type from the DB when updating the type' do + expect(issue.work_item_type_id).to eq(issue_type.id) + expect(WorkItems::Type).not_to receive(:default_by_type) + + issue.update!(work_item_type: task_type, issue_type: 'task') + + expect(issue.work_item_type_id).to eq(task_type.id) + end + + it 'ensures a work item type if updated to nil' do + expect(issue.work_item_type_id).to eq(issue_type.id) + + expect do + issue.update!(work_item_type: nil) + end.to not_change(issue, :work_item_type).from(issue_type) + end + end + + context 'when no type was set' do + let_it_be(:issue, refind: true) { build(:issue, project: project, work_item_type: nil).tap { |issue| issue.save!(validate: false) } } + + it 'sets a work item type before validation' do + expect(issue.work_item_type_id).to be_nil + + issue.save! + + expect(issue.work_item_type_id).to eq(issue_type.id) + end + + it 'does not fetch type from DB if provided during update' do + expect(issue.work_item_type_id).to be_nil + expect(WorkItems::Type).not_to receive(:default_by_type) + + issue.update!(work_item_type: task_type, issue_type: 'task') + + expect(issue.work_item_type_id).to eq(task_type.id) + end + end + end + describe '#record_create_action' do it 'records the creation action after saving' do expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_created_action) @@ -387,7 +442,7 @@ RSpec.describe Issue do # TODO: Remove when NOT NULL constraint is added to the relationship describe '#work_item_type' do - let(:issue) { create(:issue, :incident, project: reusable_project, work_item_type: nil) } + let(:issue) { build(:issue, :incident, project: reusable_project, work_item_type: nil).tap { |issue| issue.save!(validate: false) } } it 'returns a default type if the legacy issue does not have a work item type associated yet' do expect(issue.work_item_type_id).to be_nil diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index bc3de547d34..2716244b7f3 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -167,6 +167,36 @@ RSpec.describe Member do end end end + + context 'member role access level' do + let_it_be(:member) { create(:group_member, access_level: Gitlab::Access::DEVELOPER) } + + context 'no member role is associated' do + it 'is valid' do + expect(member).to be_valid + end + end + + context 'member role is associated' do + let_it_be(:member_role) do + create(:member_role, members: [member]) + end + + context 'member role matches access level' do + it 'is valid' do + expect(member).to be_valid + end + end + + context 'member role does not match access level' do + it 'is invalid' do + member_role.base_access_level = Gitlab::Access::MAINTAINER + + expect(member).not_to be_valid + end + end + end + end end describe 'Scopes & finders' do diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb index 15283b5cc63..3e702b05bc9 100644 --- a/spec/requests/api/markdown_spec.rb +++ b/spec/requests/api/markdown_spec.rb @@ -5,9 +5,11 @@ require "spec_helper" RSpec.describe API::Markdown do describe "POST /markdown" do let(:user) {} # No-op. It gets overwritten in the contexts below. + let(:disable_authenticate_markdown_api) { false } before do stub_commonmark_sourcepos_disabled + stub_feature_flags(authenticate_markdown_api: false) if disable_authenticate_markdown_api post api("/markdown", user), params: params end @@ -21,25 +23,51 @@ RSpec.describe API::Markdown do end end - shared_examples "404 Project Not Found" do - it "responses with 404 Not Found" do + shared_examples '404 Project Not Found' do + it 'responds with 404 Not Found' do expect(response).to have_gitlab_http_status(:not_found) expect(response.headers["Content-Type"]).to eq("application/json") expect(json_response).to be_a(Hash) - expect(json_response["message"]).to eq("404 Project Not Found") + expect(json_response['message']).to eq('404 Project Not Found') end end - context "when arguments are invalid" do - context "when text is missing" do + shared_examples '400 Bad Request' do + it 'responds with 400 Bad Request' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.headers['Content-Type']).to eq('application/json') + expect(json_response).to be_a(Hash) + expect(json_response['error']).to eq('text is missing') + end + end + + context 'when not logged in' do + let(:user) {} + let(:params) { {} } + + context 'and authenticate_markdown_api turned on' do + it 'responds with 401 Unathorized' do + expect(response).to have_gitlab_http_status(:unauthorized) + expect(response.headers['Content-Type']).to eq('application/json') + expect(json_response).to be_a(Hash) + expect(json_response['message']).to eq('401 Unauthorized') + end + end + + context 'and authenticate_markdown_api turned off' do + let(:disable_authenticate_markdown_api) { true } + + it_behaves_like '400 Bad Request' + end + end + + context 'when arguments are invalid' do + let(:user) { create(:user) } + + context 'when text is missing' do let(:params) { {} } - it "responses with 400 Bad Request" do - expect(response).to have_gitlab_http_status(:bad_request) - expect(response.headers["Content-Type"]).to eq("application/json") - expect(json_response).to be_a(Hash) - expect(json_response["error"]).to eq("text is missing") - end + it_behaves_like '400 Bad Request' end context "when project is not found" do @@ -53,6 +81,7 @@ RSpec.describe API::Markdown do let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } let(:issue_url) { "http://#{Gitlab.config.gitlab.host}/#{issue.project.namespace.path}/#{issue.project.path}/-/issues/#{issue.iid}" } let(:text) { ":tada: Hello world! :100: #{issue.to_reference}" } @@ -132,13 +161,12 @@ RSpec.describe API::Markdown do context 'when not logged in' do let(:user) {} + let(:disable_authenticate_markdown_api) { true } it_behaves_like 'user without proper access' end context 'when logged in as user without access' do - let(:user) { create(:user) } - it_behaves_like 'user without proper access' end @@ -175,8 +203,9 @@ RSpec.describe API::Markdown do end end - context 'when not logged in' do + context 'when not logged in and authenticate_markdown_api turned off' do let(:user) {} + let(:disable_authenticate_markdown_api) { true } it_behaves_like 'user without proper access' end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index fbcca215282..b543661e9a0 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -22,6 +22,18 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do let!(:group_member) { create(:group_member, :owner, group: group, user: user) } let(:transfer_service) { described_class.new(group, user) } + shared_examples 'publishes a GroupTransferedEvent' do + it do + expect { transfer_service.execute(target) } + .to publish_event(Groups::GroupTransferedEvent) + .with( + group_id: group.id, + old_root_namespace_id: group.root_ancestor.id, + new_root_namespace_id: target.root_ancestor.id + ) + end + end + context 'handling packages' do let_it_be(:group) { create(:group, :public) } let_it_be(:new_group) { create(:group, :public) } @@ -895,6 +907,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect { transfer_service.execute(root_group) } .not_to change { CustomerRelations::IssueContact.count } end + + it_behaves_like 'publishes a GroupTransferedEvent' do + let(:target) { root_group } + end end context 'moving down' do @@ -904,6 +920,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect { transfer_service.execute(another_subgroup) } .not_to change { CustomerRelations::IssueContact.count } end + + it_behaves_like 'publishes a GroupTransferedEvent' do + let(:target) { another_subgroup } + end end context 'moving sideways' do @@ -913,6 +933,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect { transfer_service.execute(another_subgroup) } .not_to change { CustomerRelations::IssueContact.count } end + + it_behaves_like 'publishes a GroupTransferedEvent' do + let(:target) { another_subgroup } + end end context 'moving to new root group' do @@ -932,6 +956,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect { transfer_service.execute(new_parent_group) } .not_to change { CustomerRelations::IssueContact.count } end + + it_behaves_like 'publishes a GroupTransferedEvent' do + let(:target) { new_parent_group } + end end context 'moving to a subgroup within a new root group' do @@ -953,6 +981,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect { transfer_service.execute(subgroup_in_new_parent_group) } .not_to change { CustomerRelations::IssueContact.count } end + + it_behaves_like 'publishes a GroupTransferedEvent' do + let(:target) { subgroup_in_new_parent_group } + end end context 'with permission on the subgroup' do @@ -965,6 +997,11 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do expect(transfer_service.error).to eq("Transfer failed: Group contains contacts/organizations and you don't have enough permissions to move them to the new root group.") end + + it 'does not publish a GroupTransferedEvent' do + expect { transfer_service.execute(subgroup_in_new_parent_group) } + .not_to publish_event(Groups::GroupTransferedEvent) + end end end end diff --git a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb index a90fe9e1723..040b2da9f37 100644 --- a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb +++ b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb @@ -9,6 +9,9 @@ RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_speci # rubocop:enable Layout/LineLength include ApiHelpers + let_it_be(:user) { create(:user) } + let_it_be(:api_url) { api('/markdown', user) } + markdown_examples, html_examples = %w[markdown.yml html.yml].map do |file_name| yaml = File.read("#{glfm_specification_dir}/example_snapshots/#{file_name}") YAML.safe_load(yaml, symbolize_names: true, aliases: true) @@ -29,8 +32,6 @@ RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_speci let(:normalizations) { normalizations_by_example_name.dig(name, :html, :static, :snapshot) } it "verifies conversion of GLFM to HTML", :unlimited_max_formatted_output_length do - api_url = api "/markdown" - # noinspection RubyResolve normalized_html = normalize_html(html, normalizations) diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index e8ec7c28537..4a1bf7dbbf9 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -382,6 +382,7 @@ RSpec.describe 'Every Sidekiq worker' do 'ProjectScheduleBulkRepositoryShardMovesWorker' => 3, 'ProjectTemplateExportWorker' => false, 'ProjectUpdateRepositoryStorageWorker' => 3, + 'Projects::DisableLegacyOpenSourceLicenseForInactiveProjectsWorker' => 3, 'Projects::GitGarbageCollectWorker' => false, 'Projects::InactiveProjectsDeletionNotificationWorker' => 3, 'Projects::PostCreationWorker' => 3, diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb index b37b86ceecc..d8c7d617927 100644 --- a/tooling/danger/project_helper.rb +++ b/tooling/danger/project_helper.rb @@ -63,6 +63,7 @@ module Tooling %r{\A((ee|jh)/)?app/views/} => [:frontend, :backend], %r{\A((ee|jh)/)?public/} => :frontend, %r{\A((ee|jh)/)?spec/(javascripts|frontend|frontend_integration)/} => :frontend, + %r{\A((ee|jh)/)?spec/contracts/consumer} => :frontend, %r{\A((ee|jh)/)?vendor/assets/} => :frontend, %r{\A((ee|jh)/)?scripts/frontend/} => :frontend, %r{(\A|/)( @@ -117,6 +118,7 @@ module Tooling %r{\Alib/gitlab/ci/templates} => :ci_template, %r{\A((ee|jh)/)?spec/features/} => :test, + %r{\A((ee|jh)/)?spec/contracts/} => :test, %r{\A((ee|jh)/)?spec/support/shared_examples/features/} => :test, %r{\A((ee|jh)/)?spec/support/shared_contexts/features/} => :test, %r{\A((ee|jh)/)?spec/support/helpers/features/} => :test,