From a7f69f74814920683e544f5dbbe79b7359dae8bc Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 22 Aug 2023 21:09:08 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- app/models/namespace.rb | 1 + app/models/plan.rb | 2 + .../optimize_group_template_query.yml | 8 + ...ackfill_user_preferences_with_defaults.yml | 6 + ...backfill_user_preferences_with_defaults.rb | 28 ++ db/schema_migrations/20230818085219 | 1 + doc/ci/components/index.md | 63 +++- .../end_to_end/beginners_guide.md | 7 +- .../end_to_end/best_practices.md | 8 +- .../testing_guide/end_to_end/resources.md | 60 +++- ...backfill_user_preferences_with_defaults.rb | 21 ++ lib/gitlab/ci/components/instance_path.rb | 49 ++- qa/qa/page/admin/menu.rb | 70 +---- qa/qa/page/main/menu.rb | 193 ++---------- qa/qa/page/sub_menus/common.rb | 19 -- qa/qa/page/sub_menus/create_new_menu.rb | 2 +- .../super_sidebar/context_switcher.rb | 6 + qa/qa/resource/api_fabricator.rb | 2 +- .../integrations/webhook_events_spec.rb | 8 +- .../migration/gitlab_migration_issue_spec.rb | 6 +- .../closes_issue_via_pushing_a_commit_spec.rb | 6 +- .../integrations/slash_commands_spec.rb | 7 +- .../add_design_content_spec.rb | 2 +- .../issue/check_mentions_for_xss_spec.rb | 4 +- .../collapse_comments_in_discussions_spec.rb | 2 +- .../2_plan/issue/export_as_csv_spec.rb | 6 +- .../issue/filter_issue_comments_spec.rb | 2 +- .../2_plan/issue/issue_suggestions_spec.rb | 4 +- .../browser_ui/2_plan/issue/mentions_spec.rb | 4 +- .../2_plan/issue/real_time_assignee_spec.rb | 6 +- .../2_plan/milestone/assign_milestone_spec.rb | 6 +- .../related_issues/related_issues_spec.rb | 20 +- .../transient/comment_on_discussion_spec.rb | 2 +- .../group/group_member_access_request_spec.rb | 5 +- .../project/project_owner_permissions_spec.rb | 12 +- .../user/follow_user_activity_spec.rb | 7 +- ...ill_user_preferences_with_defaults_spec.rb | 66 ++++ .../ci/components/instance_path_spec.rb | 295 +++++++++++++----- ...ill_user_preferences_with_defaults_spec.rb | 27 ++ spec/models/namespace_spec.rb | 9 + spec/models/plan_spec.rb | 12 + 41 files changed, 619 insertions(+), 445 deletions(-) create mode 100644 config/feature_flags/development/optimize_group_template_query.yml create mode 100644 db/docs/batched_background_migrations/backfill_user_preferences_with_defaults.yml create mode 100644 db/post_migrate/20230818085219_queue_backfill_user_preferences_with_defaults.rb create mode 100644 db/schema_migrations/20230818085219 create mode 100644 lib/gitlab/background_migration/backfill_user_preferences_with_defaults.rb create mode 100644 spec/lib/gitlab/background_migration/backfill_user_preferences_with_defaults_spec.rb create mode 100644 spec/migrations/20230818085219_queue_backfill_user_preferences_with_defaults_spec.rb diff --git a/app/models/namespace.rb b/app/models/namespace.rb index be39b894ef9..6ba2bf39bb6 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -166,6 +166,7 @@ class Namespace < ApplicationRecord scope :sort_by_type, -> { order(arel_table[:type].asc.nulls_first) } scope :include_route, -> { includes(:route) } scope :by_parent, -> (parent) { where(parent_id: parent) } + scope :by_root_id, -> (root_id) { where('traversal_ids[1] IN (?)', root_id) } scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) } scope :in_organization, -> (organization) { where(organization: organization) } diff --git a/app/models/plan.rb b/app/models/plan.rb index 22c1201421c..9ab22bc045a 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -5,6 +5,8 @@ class Plan < MainClusterwide::ApplicationRecord has_one :limits, class_name: 'PlanLimits' + scope :by_name, ->(name) { where(name: name) } + ALL_PLANS = [DEFAULT].freeze DEFAULT_PLANS = [DEFAULT].freeze private_constant :ALL_PLANS, :DEFAULT_PLANS diff --git a/config/feature_flags/development/optimize_group_template_query.yml b/config/feature_flags/development/optimize_group_template_query.yml new file mode 100644 index 00000000000..d5dc8b16476 --- /dev/null +++ b/config/feature_flags/development/optimize_group_template_query.yml @@ -0,0 +1,8 @@ +--- +name: optimize_group_template_query +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129399 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/422390 +milestone: '16.4' +type: development +group: group::source code +default_enabled: false diff --git a/db/docs/batched_background_migrations/backfill_user_preferences_with_defaults.yml b/db/docs/batched_background_migrations/backfill_user_preferences_with_defaults.yml new file mode 100644 index 00000000000..bee4b1f38a9 --- /dev/null +++ b/db/docs/batched_background_migrations/backfill_user_preferences_with_defaults.yml @@ -0,0 +1,6 @@ +--- +migration_job_name: BackfillUserPreferencesWithDefaults +description: Backfills the user_preferences table columns with their default values +feature_category: user_profile +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125774 +milestone: 16.4 diff --git a/db/post_migrate/20230818085219_queue_backfill_user_preferences_with_defaults.rb b/db/post_migrate/20230818085219_queue_backfill_user_preferences_with_defaults.rb new file mode 100644 index 00000000000..0651f21f240 --- /dev/null +++ b/db/post_migrate/20230818085219_queue_backfill_user_preferences_with_defaults.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class QueueBackfillUserPreferencesWithDefaults < Gitlab::Database::Migration[2.1] + MIGRATION = "BackfillUserPreferencesWithDefaults" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 3_000 + SUB_BATCH_SIZE = 200 + MAX_BATCH_SIZE = 10_000 + + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + queue_batched_background_migration( + MIGRATION, + :user_preferences, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE, + max_batch_size: MAX_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :user_preferences, :id, []) + end +end diff --git a/db/schema_migrations/20230818085219 b/db/schema_migrations/20230818085219 new file mode 100644 index 00000000000..f4e60ba7e60 --- /dev/null +++ b/db/schema_migrations/20230818085219 @@ -0,0 +1 @@ +c69e2ffe6f6bec22fb182a5fc26c02717a1546f31777cc8167103639c0e48c65 \ No newline at end of file diff --git a/doc/ci/components/index.md b/doc/ci/components/index.md index 4a739bdfcf6..143a7436e78 100644 --- a/doc/ci/components/index.md +++ b/doc/ci/components/index.md @@ -39,14 +39,71 @@ To create a components repository, you must: ### Directory structure -A components repository can host one or more components. +A components repository can host one or more components, and must follow a mandatory file structure. -Components repositories must follow a mandatory file structure, containing: +Component configurations can be saved through the following directory structure, containing: + +- A `templates` directory at the top level of your components repository. All component configuration files + should be saved under this directory. +- Files ending in `.yml` containing the component configurations, one file per component. +- A Markdown `README.md` file explaining the details of all the components in the repository. + +For example, if the project contains a single component and a pipeline to test the component, +the file structure should be similar to: + +```plaintext +├── templates/ +│ └── only_template.yml +├── README.md +└── .gitlab-ci.yml +``` + +This example component could be referenced with a path similar to `gitlab.com/my-username/my-component/only_template@`, +if the project is: + +- On GitLab.com +- Named `my-component` +- In a personal namespace named `my-username` + +The templates directory and the suffix of the configuration file should be excluded from the referenced path. + +If the project contains multiple components, then the file structure should be similar to: + +```plaintext +├── README.md +├── .gitlab-ci.yml +└── templates/ + └── all-scans.yml + └── secret-detection.yml +``` + +These components would be referenced with these paths: + +- `gitlab.com/my-username/my-component/all-scans` +- `gitlab.com/my-username/my-component/secret-detection` + +You can omit the filename in the path if the configuration file is named `template.yml`. +For example, the following component could be referenced with `gitlab.com/my-username/my-component/dast`: + +```plaintext +├── README.md +├── .gitlab-ci.yml +├── templates/ +│ └── dast +│ └── template.yml +``` + +#### Component configurations saved in any directory (deprecated) + +NOTE: +Saving component configurations through this directory structure is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/415855). + +Components configurations can be saved through the following directory structure, containing: - `template.yml`: The component configuration, one file per component. If there is only one component, this file can be in the root of the project. If there are multiple components, each file must be in a separate subdirectory. -- `README.md`: A documentation file explaining the details of the all the components in the repository. +- `README.md`: A documentation file explaining the details of all the components in the repository. For example, if the project is on GitLab.com, named `my-component`, and in a personal namespace named `my-username`: diff --git a/doc/development/testing_guide/end_to_end/beginners_guide.md b/doc/development/testing_guide/end_to_end/beginners_guide.md index 4627d5d29cb..3f372f64b2c 100644 --- a/doc/development/testing_guide/end_to_end/beginners_guide.md +++ b/doc/development/testing_guide/end_to_end/beginners_guide.md @@ -274,12 +274,7 @@ stage and the Project Management Group, so [create a file](#identify-the-devops- module QA RSpec.describe 'Plan' do describe 'Issues', product_group: :project_management do - let(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.title = 'My issue' - issue.description = 'This is an issue specific to this test' - end - end + let(:issue) { create(:issue) } before do Flow::Login.sign_in diff --git a/doc/development/testing_guide/end_to_end/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md index 96bd02d235b..04ce32395aa 100644 --- a/doc/development/testing_guide/end_to_end/best_practices.md +++ b/doc/development/testing_guide/end_to_end/best_practices.md @@ -169,18 +169,14 @@ Page::Main::Menu.perform do |menu| end #=> Good -issue = Resource::Issue.fabricate_via_api! do |issue| - issue.name = 'issue-name' -end +issue = create(:issue, name: 'issue-name') Project::Issues::Index.perform do |index| expect(index).to have_issue(issue) end #=> Bad -issue = Resource::Issue.fabricate_via_api! do |issue| - issue.name = 'issue-name' -end +issue = create(:issue, name: 'issue-name') Project::Issues::Index.perform do |index| expect(index).to have_issue(issue) diff --git a/doc/development/testing_guide/end_to_end/resources.md b/doc/development/testing_guide/end_to_end/resources.md index becdc375c63..09ce882ddab 100644 --- a/doc/development/testing_guide/end_to_end/resources.md +++ b/doc/development/testing_guide/end_to_end/resources.md @@ -392,7 +392,7 @@ In this case, the result is similar to calling `Resource::Shirt.fabricate!`. ### Factories -You may also use FactoryBot invocations to create resources within your tests. +You may also use [FactoryBot](https://github.com/thoughtbot/factory_bot/) invocations to create resources within your tests. ```ruby # create a project via the API to use in the test @@ -405,7 +405,63 @@ let(:issue) { create(:issue, project: project) } let(:project) { create(:project, :private, name: 'my-project-name', add_name_uuid: false) } ``` -All factories are defined in [`qa/qa/factories`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/factories/). +All factories are defined in [`qa/qa/factories`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/factories/) and are representative of +their respective `QA::Resource::Base` class. + +For example, a factory `:issue` can be found in `qa/resource/issue.rb`. A factory `:project` can be found in `qa/resource/project.rb`. + +#### Create a new Factory + +Given a resource: + +```ruby +# qa/resource/shirt.rb +module QA + module Resource + class Shirt < Base + attr_accessor :name + attr_reader :read_only + + attribute :brand + + def api_post_body + { name: name, brand: brand } + end + end + end +end +``` + +Define a factory with defaults and overrides: + +```ruby +# qa/factories/shirts.rb +module QA + FactoryBot.define do + factory :shirt, class: 'QA::Resource::Shirt' do + brand { 'BrandName' } + + trait :with_name do + name { 'Shirt Name' } + end + end + end +end +``` + +In the test, create the resource via the API: + +```ruby +let(:my_shirt) { create(:shirt, brand: 'AnotherBrand') } # +let(:named_shirt) { create(:shirt, :with_name) } # +let(:invalid_shirt) { create(:shirt, read_only: true) } # NoMethodError + +it 'creates a shirt' do + expect(my_shirt.brand).to eq('AnotherBrand') + expect(named_shirt.name).to eq('Shirt Name') + expect(invalid_shirt).to raise_error(NoMethodError) # tries to call Resource::Shirt#read_only= +end +``` ### Resources cleanup diff --git a/lib/gitlab/background_migration/backfill_user_preferences_with_defaults.rb b/lib/gitlab/background_migration/backfill_user_preferences_with_defaults.rb new file mode 100644 index 00000000000..2fba6e66d48 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_user_preferences_with_defaults.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Backfills the user_preferences table columns with their default values + class BackfillUserPreferencesWithDefaults < BatchedMigrationJob + operation_name :backfill_user_preferences_with_defaults + feature_category :user_profile + + def perform + each_sub_batch do |sub_batch| + connection.transaction do + sub_batch.where(tab_width: nil).update_all(tab_width: 8) + sub_batch.where(time_display_relative: nil).update_all(time_display_relative: true) + sub_batch.where(render_whitespace_in_code: nil).update_all(render_whitespace_in_code: false) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/components/instance_path.rb b/lib/gitlab/ci/components/instance_path.rb index e0ef598da1b..7ccfd195108 100644 --- a/lib/gitlab/ci/components/instance_path.rb +++ b/lib/gitlab/ci/components/instance_path.rb @@ -7,6 +7,7 @@ module Gitlab include Gitlab::Utils::StrongMemoize LATEST_VERSION_KEYWORD = '~latest' + TEMPLATES_DIR = 'templates' def self.match?(address) address.include?('@') && address.start_with?(Settings.gitlab_ci['component_fqdn']) @@ -26,7 +27,7 @@ module Gitlab raise Gitlab::Access::AccessDeniedError unless Ability.allowed?(current_user, :download_code, project) - project.repository.blob_data_at(sha, project_file_path) + templates_dir_path_content || content(sha, custom_dir_template_file_path) end def project @@ -34,13 +35,6 @@ module Gitlab end strong_memoize_attr :project - def project_file_path - return unless project - - component_dir = instance_path.delete_prefix(project.full_path) - File.join(component_dir, @content_filename).delete_prefix('/') - end - def sha return unless project return latest_version_sha if version == LATEST_VERSION_KEYWORD @@ -49,6 +43,12 @@ module Gitlab end strong_memoize_attr :sha + def project_file_path + return unless project + + custom_dir_template_file_path + end + private attr_reader :version, :path @@ -57,6 +57,11 @@ module Gitlab @full_path.delete_prefix(host) end + def component_path + instance_path.delete_prefix(project.full_path) + end + strong_memoize_attr :component_path + # Given a path like "my-org/sub-group/the-project/path/to/component" # find the project "my-org/sub-group/the-project" by looking at all possible paths. def find_project_by_component_path(path) @@ -75,6 +80,34 @@ module Gitlab def latest_version_sha project.releases.latest&.sha end + + def custom_dir_template_file_path + File.join(component_path, @content_filename).delete_prefix('/') + end + + def templates_dir_file_path + File.join(TEMPLATES_DIR, "#{component_path}.yml") + end + + # Given a path like "my-org/sub-group/the-project/templates/component" + # returns "templates/component/template.yml" + def templates_dir_template_file_path + File.join(TEMPLATES_DIR, component_path, @content_filename) + end + + def templates_dir_exists? + project.repository.tree.trees.map(&:name).include?(TEMPLATES_DIR) + end + + def templates_dir_path_content + return unless templates_dir_exists? + + content(sha, templates_dir_file_path) || content(sha, templates_dir_template_file_path) + end + + def content(sha, path) + project.repository.blob_data_at(sha, path) + end end end end diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb index 3b044f8a051..8fd813a5ddc 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/admin/menu.rb @@ -5,11 +5,8 @@ module QA module Admin class Menu < Page::Base include SubMenus::Common - - if QA::Runtime::Env.super_sidebar_enabled? - prepend Sidebar::Overview - prepend Sidebar::Settings - end + include Sidebar::Overview + include Sidebar::Settings view 'lib/sidebars/admin/menus/admin_overview_menu.rb' do element :admin_overview_submenu_content @@ -23,73 +20,12 @@ module QA element :admin_monitoring_menu_link end - def go_to_preferences_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_preferences_link - end - end - - def go_to_repository_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_repository_link - end - end - - def go_to_integration_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_integrations_link - end - end - - def go_to_general_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_general_link - end - end - - def go_to_metrics_and_profiling_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_metrics_and_profiling_link - end - end - - def go_to_network_settings - hover_element(:admin_settings_menu_link) do - click_element :admin_settings_network_link - end - end - - def go_to_users_overview - click_element :admin_overview_users_link - end - - def go_to_groups_overview - click_element :admin_overview_groups_link - end - - def go_to_security_and_compliance - hover_element(:admin_settings_menu_link) do - click_element :admin_security_and_compliance_link - end - end - def go_to_applications - return click_element(:nav_item_link, submenu_item: 'Applications') if Runtime::Env.super_sidebar_enabled? - - click_element(:sidebar_menu_link, menu_item: 'Applications') + click_element(:nav_item_link, submenu_item: 'Applications') end private - def hover_element(element) - within_sidebar do - scroll_to_element(element) - find_element(element).hover - - yield - end - end - def within_sidebar(&block) page.within('.sidebar-top-level-items', &block) end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index f5bfeecec10..38f99a3024b 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -7,57 +7,34 @@ module QA # We need to check phone_layout? instead of mobile_layout? here # since tablets have the regular top navigation bar prepend Mobile::Page::Main::Menu if Runtime::Env.phone_layout? + include SubMenus::CreateNewMenu + include SubMenus::SuperSidebar::ContextSwitcher - if Runtime::Env.super_sidebar_enabled? - prepend SubMenus::CreateNewMenu - include SubMenus::SuperSidebar::ContextSwitcher + view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do + element :navbar, required: true # TODO: rename to sidebar once it's default implementation end - if QA::Runtime::Env.super_sidebar_enabled? - # Define alternative navbar (super sidebar) which does not yet implement all the same elements - view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do - element :navbar, required: true # TODO: rename to sidebar once it's default implementation - end + view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do + element 'user-dropdown', required: !Runtime::Env.phone_layout? + element :user_avatar_content, required: !Runtime::Env.phone_layout? + element :sign_out_link + element :edit_profile_link + end - view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do - element 'user-dropdown', required: !Runtime::Env.phone_layout? - element :user_avatar_content, required: !Runtime::Env.phone_layout? - element :sign_out_link - element :edit_profile_link - end + view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do + element :user_profile_link + end - view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do - element :user_profile_link - end + view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do + element 'super-sidebar-search-button' + element 'stop-impersonation-btn' + element 'issues-shortcut-button', required: !Runtime::Env.phone_layout? + element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout? + element 'todos-shortcut-button', required: !Runtime::Env.phone_layout? + end - view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do - element 'super-sidebar-search-button' - element 'stop-impersonation-btn' - element 'issues-shortcut-button', required: !Runtime::Env.phone_layout? - element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout? - element 'todos-shortcut-button', required: !Runtime::Env.phone_layout? - end - - view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do - element 'global-search-input' - end - else - view 'app/views/layouts/header/_default.html.haml' do - element :navbar, required: true - element :canary_badge_link - element :user_avatar_content, required: !Runtime::Env.phone_layout? - element 'user-dropdown', required: !Runtime::Env.phone_layout? - element 'stop-impersonation-btn' - element 'issues-shortcut-button', required: !Runtime::Env.phone_layout? - element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout? - element 'todos-shortcut-button', required: !Runtime::Env.phone_layout? - end - - view 'app/views/layouts/header/_current_user_dropdown.html.haml' do - element :sign_out_link - element :edit_profile_link - element :user_profile_link - end + view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do + element 'global-search-input' end view 'app/assets/javascripts/nav/components/top_nav_app.vue' do @@ -102,88 +79,31 @@ module QA end def go_to_projects - return click_element(:nav_item_link, submenu_item: 'Projects') if Runtime::Env.super_sidebar_enabled? - - click_element(:sidebar_menu_link, menu_item: 'Projects') + click_element(:nav_item_link, submenu_item: 'Projects') end def go_to_groups # This needs to be fixed in the tests themselves. Fullfillment tests try to go to groups view from the # group. Instead of having a global hack, explicit test should navigate to correct view first. # see: https://gitlab.com/gitlab-org/gitlab/-/issues/403589#note_1383040061 - if Runtime::Env.super_sidebar_enabled? - go_to_your_work unless has_element?(:nav_item_link, submenu_item: 'Groups', wait: 0) - click_element(:nav_item_link, submenu_item: 'Groups') - elsif has_element?(:sidebar_menu_link, menu_item: 'Groups') - # Use new functionality to visit Groups where possible - click_element(:sidebar_menu_link, menu_item: 'Groups') - else - # Otherwise fallback to previous functionality - # See https://gitlab.com/gitlab-org/gitlab/-/issues/403589 - # and related issues - within_groups_menu do - click_element(:menu_item_link, title: 'View all groups') - end - end + go_to_your_work unless has_element?(:nav_item_link, submenu_item: 'Groups', wait: 0) + click_element(:nav_item_link, submenu_item: 'Groups') end def go_to_snippets - return click_element(:nav_item_link, submenu_item: 'Snippets') if Runtime::Env.super_sidebar_enabled? - - click_element(:sidebar_menu_link, menu_item: 'Snippets') + click_element(:nav_item_link, submenu_item: 'Snippets') end def go_to_workspaces - return click_element(:nav_item_link, submenu_item: 'Workspaces') if Runtime::Env.super_sidebar_enabled? - - click_element(:sidebar_menu_link, menu_item: 'Workspaces') - end - - def go_to_create_project - click_element('new-menu-toggle') - click_element(:global_new_project_link) - end - - def go_to_create_group - click_element('new-menu-toggle') - click_element(:global_new_group_link) - end - - def go_to_create_snippet - click_element('new-menu-toggle') - click_element(:global_new_snippet_link) + click_element(:nav_item_link, submenu_item: 'Workspaces') end def go_to_menu_dropdown_option(option_name) - return click_element(option_name) if QA::Runtime::Env.super_sidebar_enabled? - - within_top_menu do - click_element(:navbar_dropdown, title: 'Menu') - click_element(option_name) - end + click_element(option_name) end - # To go to one of the popular pages using the provided shortcut buttons within top menu - # @param [Symbol] the name of the element (e.g: `:issues_shortcut button`) - # @example: - # Menu.perform do |menu| - # menu.go_to_page_by_shortcut('issues-shortcut-button') #=> Go to Issues page using shortcut button - # end - def go_to_page_by_shortcut(button) - within_top_menu do - click_element(button) - end - end - - def go_to_admin_area - Runtime::Env.super_sidebar_enabled? ? super : click_admin_area - - return unless has_text?('Enter admin mode', wait: 1.0) - - Admin::NewSession.perform do |new_session| - new_session.set_password(Runtime::User.admin_password) - new_session.click_enter_admin_mode - end + def go_to_todos + click_element('todos-shortcut-button') end def signed_in? @@ -243,7 +163,7 @@ module QA end def search_for(term) - click_element(Runtime::Env.super_sidebar_enabled? ? 'super-sidebar-search-button' : :search_box) + click_element('super-sidebar-search-button') fill_element('global-search-input', "#{term}\n") end @@ -255,15 +175,6 @@ module QA has_no_element?(:user_avatar_content, wait: wait) end - def has_admin_area_link?(wait: Capybara.default_max_wait_time) - return super if Runtime::Env.super_sidebar_enabled? - - within_top_menu do - click_element(:navbar_dropdown, title: 'Menu') - has_element?(:admin_area_link, wait: wait) - end - end - def click_stop_impersonation_link click_element('stop-impersonation-btn') end @@ -278,55 +189,15 @@ module QA has_element?(:canary_badge_link) end - def enable_new_navigation - Runtime::Logger.info("Enabling super sidebar!") - return Runtime::Logger.info("User is not signed in, skipping") unless has_element?(:navbar, wait: 2) - - return Runtime::Logger.info("Super sidebar is already enabled") if has_css?('[data-testid="super-sidebar"]') - - within_user_menu { click_element(:new_navigation_toggle) } - end - - def disable_new_navigation - Runtime::Logger.info("Disabling super sidebar!") - return Runtime::Logger.info("User is not signed in, skipping") unless has_element?(:navbar, wait: 2) - - unless has_css?('[data-testid="super-sidebar"]') - return Runtime::Logger.info("Super sidebar is already disabled") - end - - within_user_menu { click_element(:new_navigation_toggle) } - end - private - def within_top_menu(&block) - within_element(:navbar, &block) - end - def within_user_menu(&block) - within_top_menu do + within_element(:navbar) do click_element :user_avatar_content unless has_element?(:user_profile_link, wait: 1) within_element('user-dropdown', &block) end end - - def within_groups_menu(&block) - go_to_menu_dropdown_option(:groups_dropdown) - - within_element('menu-subview', &block) - end - - def within_projects_menu(&block) - go_to_menu_dropdown_option(:projects_dropdown) - - within_element('menu-subview', &block) - end - - def click_admin_area - go_to_menu_dropdown_option(:admin_area_link) - end end end end diff --git a/qa/qa/page/sub_menus/common.rb b/qa/qa/page/sub_menus/common.rb index 19d07e885c6..10d1fc28d37 100644 --- a/qa/qa/page/sub_menus/common.rb +++ b/qa/qa/page/sub_menus/common.rb @@ -16,27 +16,12 @@ module QA end end - def hover_element(element) - within_sidebar do - find_element(element).hover - yield - end - end - def within_sidebar(&block) wait_for_requests within_element(:navbar, &block) end - def within_submenu(element = nil, &block) - if element - within_element(element, &block) - else - within_submenu_without_element(&block) - end - end - private # Opens the new item menu and yields to the block @@ -64,10 +49,6 @@ module QA click_element(:nav_item_link, submenu_item: sub_menu) end end - - def within_submenu_without_element(&block) - has_css?('.fly-out-list') ? within('.fly-out-list', &block) : yield - end end end end diff --git a/qa/qa/page/sub_menus/create_new_menu.rb b/qa/qa/page/sub_menus/create_new_menu.rb index 1f8641827be..aaf763ac33e 100644 --- a/qa/qa/page/sub_menus/create_new_menu.rb +++ b/qa/qa/page/sub_menus/create_new_menu.rb @@ -6,7 +6,7 @@ module QA module CreateNewMenu extend QA::Page::PageConcern - def self.prepended(base) + def self.included(base) super base.class_eval do diff --git a/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb b/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb index af06438782d..83e84661553 100644 --- a/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb +++ b/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb @@ -31,6 +31,12 @@ module QA def go_to_admin_area go_to_context("Admin Area") + return unless has_text?('Enter admin mode', wait: 1.0) + + Admin::NewSession.perform do |new_session| + new_session.set_password(Runtime::User.admin_password) + new_session.click_enter_admin_mode + end end def has_admin_area_link?(wait: Capybara.default_max_wait_time) diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 5f431103df3..7c6c6cc0288 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -231,7 +231,7 @@ module QA # @example # wait_for_resource_availability('https://gitlab.com/api/v4/projects/1234') # @example - # wait_for_resource_availability(resource_web_url(Resource::Issue.fabricate_via_api!)) + # wait_for_resource_availability(resource_web_url(create(:issue))) def wait_for_resource_availability(resource_web_url) return unless Runtime::Address.valid?(resource_web_url) diff --git a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb index 783c3a65c7d..280d35854b9 100644 --- a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb +++ b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb @@ -53,9 +53,7 @@ module QA it 'sends an issues and note event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349723' do Resource::ProjectWebHook.setup(session: session, issues: true, note: true) do |webhook, smocker| - issue = Resource::Issue.fabricate_via_api! do |issue_init| - issue_init.project = webhook.project - end + issue = create(:issue, project: webhook.project) Resource::ProjectIssueNote.fabricate_via_api! do |note| note.project = issue.project @@ -118,9 +116,7 @@ module QA } do Resource::ProjectWebHook.setup(fail_mock, session: session, issues: true) do |webhook, smocker| hook_trigger_times.times do - Resource::Issue.fabricate_via_api! do |issue_init| - issue_init.project = webhook.project - end + create(:issue, project: webhook.project) # using sleep to give rate limiter a chance to activate. sleep 0.5 diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index bff1837f51b..5e3ff9e055c 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -6,11 +6,7 @@ module QA include_context 'with gitlab project migration' let!(:source_issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.api_client = source_admin_api_client - issue.project = source_project - issue.labels = %w[label_one label_two] - end + create(:issue, project: source_project, labels: %w[label_one label_two], api_client: source_admin_api_client) end let(:source_issue_comments) do diff --git a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb index 6935f9de486..ecaa329d800 100644 --- a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb +++ b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb @@ -7,12 +7,8 @@ module QA include Support::API describe 'Issue', product_group: :project_management do - let(:issue) do - Resource::Issue.fabricate_via_api! - end - + let(:issue) { create(:issue) } let(:issue_id) { issue.api_response[:iid] } - let(:api_client) { Runtime::API::Client.new(:gitlab) } before do diff --git a/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb index 99be4e87251..9e6d79316ac 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb @@ -55,12 +55,7 @@ module QA end context 'with gitlab issue' do - let!(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end - end - + let!(:issue) { create(:issue, project: project) } let(:comment) { "Comment #{SecureRandom.hex(6)}" } it 'displays an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377891' do diff --git a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb index 63d30da9ec3..b2cb1cd309f 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Plan', product_group: :product_planning do describe 'Design Management' do - let(:issue) { Resource::Issue.fabricate_via_api! } + let(:issue) { create(:issue) } let(:design_filename) { 'banana_sample.gif' } let(:design) { Runtime::Path.fixture('designs', design_filename) } let(:annotation) { "This design is great!" } diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb index 5d8f9f7d7b3..3cbe02fa2e7 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -18,9 +18,7 @@ module QA project.add_member(user) - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end.visit! + create(:issue, project: project).visit! end after do diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index d446a773809..a1c4f49c58a 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -5,7 +5,7 @@ module QA describe 'collapse comments in issue discussions' do let(:my_first_reply) { 'My first reply' } let(:one_reply) { '1 reply' } - let(:issue) { Resource::Issue.fabricate_via_api! } + let(:issue) { create(:issue) } before do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb index 45c7f307834..67bb4d0bf72 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb @@ -8,11 +8,7 @@ module QA before do Flow::Login.sign_in - 2.times do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end - end + create_list(:issue, 2, project: project) project.visit! Page::Project::Menu.perform(&:go_to_issues) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index 8af39cb6a82..83f9573fbf8 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -6,7 +6,7 @@ module QA before do Flow::Login.sign_in - Resource::Issue.fabricate_via_api!.visit! + create(:issue).visit! end it 'filters comments and activities in an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347948' do diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb index c85ea5e8a69..f1306949716 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -8,9 +8,7 @@ module QA before do Flow::Login.sign_in - Resource::Issue.fabricate_via_api! do |issue| - issue.title = issue_title - end.project.visit! + create(:issue, title: issue_title).project.visit! end it 'shows issue suggestions when creating a new issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347995' do diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index b2dca4fc312..fe4a8aad109 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -36,9 +36,7 @@ module QA issue.project = project end.visit! else - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end.visit! + create(:issue, project: project).visit! end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb index d282b0dbbd5..09b0ec25099 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb @@ -15,11 +15,7 @@ module QA end it 'update without refresh', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347941' do - issue = Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - issue.assignee_ids = [user1.id] - end - + issue = create(:issue, project: project, assignee_ids: [user1.id]) issue.visit! Page::Project::Issue::Show.perform do |show| diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb index c3c2ad68abf..e2f9465c5a4 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb @@ -12,11 +12,7 @@ module QA let(:project) { create(:project, name: "project-to-test-milestones-#{SecureRandom.hex(4)}", group: group) } - let(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end - end + let(:issue) { create(:issue, project: project) } before do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb index 87a0cf1b310..91e7a7523b0 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb @@ -4,35 +4,25 @@ module QA RSpec.describe 'Plan', :reliable, product_group: :project_management do describe 'Related issues' do let(:project) { create(:project, name: 'project-to-test-related-issues') } - let(:issue_1) do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end - end - - let(:issue_2) do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - end - end + let(:issues) { create_list(:issue, 2, project: project) } before do Flow::Login.sign_in end it 'relates and unrelates one issue to/from another', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347994' do - issue_1.visit! + issues.first.visit! Page::Project::Issue::Show.perform do |show| max_wait = 60 - show.relate_issue(issue_2) + show.relate_issue(issues.last) - expect(show.related_issuable_item).to have_text(issue_2.title, wait: max_wait) + expect(show.related_issuable_item).to have_text(issues.last.title, wait: max_wait) show.click_remove_related_issue_button - expect(show).not_to have_text(issue_2.title, wait: max_wait) + expect(show).not_to have_text(issues.last.title, wait: max_wait) end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb index 878c4dea26e..1e84bf34135 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb @@ -20,7 +20,7 @@ module QA Runtime::Env.transient_trials.times do |i| QA::Runtime::Logger.info("Transient bug test action - Trial #{i}") - Resource::Issue.fabricate_via_api!.visit! + create(:issue).visit! Page::Project::Issue::Show.perform do |issue_page| issue_page.select_all_activities_filter diff --git a/qa/qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb b/qa/qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb index 0c977e5259c..16e4a5ea774 100644 --- a/qa/qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb @@ -23,10 +23,7 @@ module QA Flow::Login.sign_in_as_admin - Page::Main::Menu.perform do |menu| - menu.go_to_page_by_shortcut('todos-shortcut-button') - end - + Page::Main::Menu.perform(&:go_to_todos) Page::Dashboard::Todos.perform do |todos| todos.filter_todos_by_group(group) end diff --git a/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb b/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb index 310b8747584..2da4e30e82f 100644 --- a/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb +++ b/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb @@ -15,11 +15,7 @@ module QA shared_examples 'adds user as owner' do |project_type, testcase| let!(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.api_client = owner_api_client - issue.project = project - issue.title = 'Test Owner Deletes Issue' - end + create(:issue, title: 'Test Owner Deletes Issue', project: project, api_client: owner_api_client) end before do @@ -46,11 +42,7 @@ module QA shared_examples 'adds user as maintainer' do |testcase| let!(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.api_client = owner_api_client - issue.project = project - issue.title = 'Test Maintainer Deletes Issue' - end + create(:issue, title: 'Test Maintainer Deletes Issue', project: project, api_client: owner_api_client) end before do diff --git a/qa/qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb b/qa/qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb index a119d600667..9ce380fb443 100644 --- a/qa/qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb @@ -45,12 +45,7 @@ module QA end end - let(:issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.project = project - issue.api_client = followed_user_api_client - end - end + let(:issue) { create(:issue, project: project, api_client: followed_user_api_client) } let(:comment) do Resource::ProjectIssueNote.fabricate_via_api! do |project_issue_note| diff --git a/spec/lib/gitlab/background_migration/backfill_user_preferences_with_defaults_spec.rb b/spec/lib/gitlab/background_migration/backfill_user_preferences_with_defaults_spec.rb new file mode 100644 index 00000000000..b66b930b7ac --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_user_preferences_with_defaults_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillUserPreferencesWithDefaults, + schema: 20230818085219, + feature_category: :user_profile do + let(:user_preferences) { table(:user_preferences) } + let(:users) { table(:users) } + let(:columns) { [:tab_width, :time_display_relative, :render_whitespace_in_code] } + let(:initial_column_values) do + [ + [nil, nil, nil], + [10, nil, nil], + [nil, false, nil], + [nil, nil, true] + ] + .map { |row| columns.zip(row).to_h } + end + + let(:final_column_values) do + [ + [8, true, false], + [10, true, false], + [8, false, false], + [8, true, true] + ] + .map { |row| columns.zip(row).to_h } + end + + subject(:perform_migration) do + described_class + .new( + start_id: user_preferences.minimum(:id), + end_id: user_preferences.maximum(:id), + batch_table: :user_preferences, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ) + .perform + end + + before do + initial_column_values.each_with_index do |attributes, index| + user = users.create!(projects_limit: 1, email: "user#{index}@gitlab.com") + user_preference = user_preferences.create!(attributes.merge(user_id: user.id)) + final_column_values[index].merge!(id: user_preference.id) + end + end + + it 'backfills the null values with the default values' do + perform_migration + + final_column_values.each { |attributes| match_attributes(attributes) } + end + + def match_attributes(attributes) + migrated_user_preference = user_preferences.find(attributes[:id]) + + expect(migrated_user_preference.tab_width).to eq(attributes[:tab_width]) + expect(migrated_user_preference.time_display_relative).to eq(attributes[:time_display_relative]) + expect(migrated_user_preference.render_whitespace_in_code).to eq(attributes[:render_whitespace_in_code]) + end +end diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb index f4bc706f9b4..007c90d458e 100644 --- a/spec/lib/gitlab/ci/components/instance_path_spec.rb +++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb @@ -14,125 +14,248 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline end describe 'FQDN path' do - let_it_be(:existing_project) { create(:project, :repository) } - - let(:project_path) { existing_project.full_path } - let(:address) { "acme.com/#{project_path}/component@#{version}" } let(:version) { 'master' } - context 'when project exists' do - it 'provides the expected attributes', :aggregate_failures do - expect(path.project).to eq(existing_project) - expect(path.host).to eq(current_host) - expect(path.sha).to eq(existing_project.commit('master').id) - expect(path.project_file_path).to eq('component/template.yml') + context 'when the project repository contains a templates directory' do + let_it_be(:existing_project) do + create( + :project, :custom_repo, + files: { + 'templates/file.yml' => 'image: alpine_1', + 'templates/dir/template.yml' => 'image: alpine_2' + } + ) end - context 'when content exists' do - let(:content) { 'image: alpine' } + let(:project_path) { existing_project.full_path } + let(:address) { "acme.com/#{project_path}/file@#{version}" } - before do - allow_next_instance_of(Repository) do |instance| - allow(instance) - .to receive(:blob_data_at) - .with(existing_project.commit('master').id, 'component/template.yml') - .and_return(content) - end + before do + existing_project.add_developer(user) + end + + context 'when user does not have permissions' do + it 'raises an error when fetching the content' do + expect { path.fetch_content!(current_user: build(:user)) } + .to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'when templates directory is top level' do + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1') end - context 'when user has permissions to read code' do - before do - existing_project.add_developer(user) - end + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('file/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) + end + + context 'when file name is `template.yml`' do + let(:address) { "acme.com/#{project_path}/dir@#{version}" } it 'fetches the content' do - expect(path.fetch_content!(current_user: user)).to eq(content) + expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2') end - end - context 'when user does not have permissions to download code' do - it 'raises an error when fetching the content' do - expect { path.fetch_content!(current_user: user) } - .to raise_error(Gitlab::Access::AccessDeniedError) + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('dir/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) end end end + + context 'when the project is nested under a subgroup' do + let_it_be(:existing_group) { create(:group, :nested) } + let_it_be(:existing_project) do + create( + :project, :custom_repo, + files: { + 'templates/file.yml' => 'image: alpine_1' + }, + group: existing_group + ) + end + + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1') + end + + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('file/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) + end + end + + context 'when fetching the latest version' do + let_it_be(:existing_project) do + create( + :project, :custom_repo, + files: { + 'templates/file.yml' => 'image: alpine_1' + } + ) + end + + let(:version) { '~latest' } + + let(:latest_sha) do + existing_project.repository.find_commits_by_message('Updates image').commits.last.sha + end + + before do + create(:release, project: existing_project, sha: existing_project.repository.root_ref_sha, + released_at: Time.zone.now - 1.day) + + existing_project.repository.update_file( + user, 'templates/file.yml', 'image: alpine_2', + message: 'Updates image', branch_name: existing_project.default_branch + ) + + create(:release, project: existing_project, sha: latest_sha, + released_at: Time.zone.now) + end + + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2') + end + + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('file/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(latest_sha) + end + end + + context 'when version does not exist' do + let(:version) { 'non-existent' } + + it 'returns nil when fetching the content' do + expect(path.fetch_content!(current_user: user)).to be_nil + end + + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('file/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to be_nil + end + end + + context 'when current GitLab instance is installed on a relative URL' do + let(:address) { "acme.com/gitlab/#{project_path}/file@#{version}" } + let(:current_host) { 'acme.com/gitlab/' } + + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1') + end + + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('file/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) + end + end end - context 'when project path is nested under a subgroup' do - let(:existing_group) { create(:group, :nested) } - let(:existing_project) { create(:project, :repository, group: existing_group) } + # All the following tests are for deprecated code and will be removed + # in https://gitlab.com/gitlab-org/gitlab/-/issues/415855 + context 'when the project does not contain a templates directory' do + let(:project_path) { existing_project.full_path } + let(:address) { "acme.com/#{project_path}/component@#{version}" } + + let_it_be(:existing_project) do + create( + :project, :custom_repo, + files: { + 'component/template.yml' => 'image: alpine' + } + ) + end + + before do + existing_project.add_developer(user) + end + + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine') + end it 'provides the expected attributes', :aggregate_failures do - expect(path.project).to eq(existing_project) expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('component/template.yml') + expect(path.project).to eq(existing_project) expect(path.sha).to eq(existing_project.commit('master').id) - expect(path.project_file_path).to eq('component/template.yml') - end - end - - context 'when current GitLab instance is installed on a relative URL' do - let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" } - let(:current_host) { 'acme.com/gitlab/' } - - it 'provides the expected attributes', :aggregate_failures do - expect(path.project).to eq(existing_project) - expect(path.host).to eq(current_host) - expect(path.sha).to eq(existing_project.commit('master').id) - expect(path.project_file_path).to eq('component/template.yml') - end - end - - context 'when version does not exist' do - let(:version) { 'non-existent' } - - it 'provides the expected attributes', :aggregate_failures do - expect(path.project).to eq(existing_project) - expect(path.host).to eq(current_host) - expect(path.sha).to be_nil - expect(path.project_file_path).to eq('component/template.yml') end - it 'returns nil when fetching the content' do - expect(path.fetch_content!(current_user: user)).to be_nil - end - end - - context 'when version is `~latest`' do - let(:version) { '~latest' } - - context 'when project has releases' do - let_it_be(:latest_release) do - create(:release, project: existing_project, sha: 'sha-1', released_at: Time.zone.now) + context 'when project path is nested under a subgroup' do + let_it_be(:existing_group) { create(:group, :nested) } + let_it_be(:existing_project) do + create( + :project, :custom_repo, + files: { + 'component/template.yml' => 'image: alpine' + }, + group: existing_group + ) end - before_all do - # Previous release - create(:release, project: existing_project, sha: 'sha-2', released_at: Time.zone.now - 1.day) + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine') end - it 'returns the sha of the latest release' do - expect(path.sha).to eq(latest_release.sha) + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('component/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) end end - context 'when project does not have releases' do - it { expect(path.sha).to be_nil } - end - end + context 'when current GitLab instance is installed on a relative URL' do + let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" } + let(:current_host) { 'acme.com/gitlab/' } - context 'when project does not exist' do - let(:project_path) { 'non-existent/project' } + it 'fetches the content' do + expect(path.fetch_content!(current_user: user)).to eq('image: alpine') + end - it 'provides the expected attributes', :aggregate_failures do - expect(path.project).to be_nil - expect(path.host).to eq(current_host) - expect(path.sha).to be_nil - expect(path.project_file_path).to be_nil + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('component/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to eq(existing_project.commit('master').id) + end end - it 'returns nil when fetching the content' do - expect(path.fetch_content!(current_user: user)).to be_nil + context 'when version does not exist' do + let(:version) { 'non-existent' } + + it 'returns nil when fetching the content' do + expect(path.fetch_content!(current_user: user)).to be_nil + end + + it 'provides the expected attributes', :aggregate_failures do + expect(path.host).to eq(current_host) + expect(path.project_file_path).to eq('component/template.yml') + expect(path.project).to eq(existing_project) + expect(path.sha).to be_nil + end + end + + context 'when user does not have permissions' do + it 'raises an error when fetching the content' do + expect { path.fetch_content!(current_user: build(:user)) } + .to raise_error(Gitlab::Access::AccessDeniedError) + end end end end diff --git a/spec/migrations/20230818085219_queue_backfill_user_preferences_with_defaults_spec.rb b/spec/migrations/20230818085219_queue_backfill_user_preferences_with_defaults_spec.rb new file mode 100644 index 00000000000..eff14be22f6 --- /dev/null +++ b/spec/migrations/20230818085219_queue_backfill_user_preferences_with_defaults_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillUserPreferencesWithDefaults, feature_category: :user_profile do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :user_preferences, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE, + max_batch_size: described_class::MAX_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 623c9c7e07c..a03dac83113 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -443,6 +443,15 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do end end + describe '.by_root_id' do + it 'returns correct namespaces' do + expect(described_class.by_root_id(namespace1.id)).to match_array([namespace1, namespace1sub]) + expect(described_class.by_root_id(namespace2.id)).to match_array([namespace2, namespace2sub]) + expect(described_class.by_root_id(namespace1sub.id)).to be_empty + expect(described_class.by_root_id(nil)).to be_empty + end + end + describe '.filter_by_path' do it 'includes correct namespaces' do expect(described_class.filter_by_path(namespace1.path)).to eq([namespace1]) diff --git a/spec/models/plan_spec.rb b/spec/models/plan_spec.rb index 73e88a17e24..fe3365ca78f 100644 --- a/spec/models/plan_spec.rb +++ b/spec/models/plan_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' RSpec.describe Plan do + describe 'scopes', :aggregate_failures do + let_it_be(:default_plan) { create(:default_plan) } + + describe '.by_name' do + it 'returns plans by their name' do + expect(described_class.by_name('default')).to match_array([default_plan]) + expect(described_class.by_name(%w[default unknown])).to match_array([default_plan]) + expect(described_class.by_name(nil)).to be_empty + end + end + end + describe '#default?' do subject { plan.default? }