summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/rake/require.yml5
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/design_management/git_repository.rb66
-rw-r--r--app/models/design_management/repository.rb55
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/project.rb1
-rw-r--r--app/views/shared/form_elements/_description.html.haml3
-rw-r--r--app/views/shared/issuable/form/_default_templates.html.haml2
-rw-r--r--app/views/shared/issuable/form/_title.html.haml4
-rw-r--r--db/docs/design_management_repositories.yml10
-rw-r--r--db/migrate/20230307000000_create_design_management_repository.rb11
-rw-r--r--db/post_migrate/20230321124837_remove_ci_builds_partition_id_default.rb4
-rw-r--r--db/schema_migrations/202303070000001
-rw-r--r--db/structure.sql28
-rw-r--r--doc/user/project/import/index.md14
-rw-r--r--lib/gitlab/gl_repository.rb2
-rw-r--r--qa/qa/flow/pipeline.rb4
-rw-r--r--qa/qa/page/merge_request/show.rb3
-rw-r--r--qa/qa/page/project/menu.rb17
-rw-r--r--qa/qa/page/project/sub_menus/ci_cd.rb2
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/ci_cd.rb45
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/common.rb24
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/compliance.rb37
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/operations.rb57
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/plan.rb57
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/repository.rb61
-rw-r--r--qa/qa/page/project/sub_menus/super_sidebar/settings.rb61
-rw-r--r--qa/qa/resource/pipeline.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb4
-rw-r--r--qa/tasks/webdrivers.rake1
-rw-r--r--spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb6
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/project.json2
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/tree/project/protected_environments.ndjson2
-rw-r--r--spec/frontend/notes/components/note_awards_list_spec.js236
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/design_management/git_repository_spec.rb58
-rw-r--r--spec/models/design_management/repository_spec.rb57
-rw-r--r--spec/models/merge_request_spec.rb4
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/spec_helper.rb5
44 files changed, 727 insertions, 238 deletions
diff --git a/.rubocop_todo/rake/require.yml b/.rubocop_todo/rake/require.yml
deleted file mode 100644
index 0659dd11037..00000000000
--- a/.rubocop_todo/rake/require.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-Rake/Require:
- Details: grace period
- Exclude:
- - 'qa/tasks/webdrivers.rake'
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 4f6ca5a9617..716be080851 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -8,12 +8,10 @@ class CommitStatus < Ci::ApplicationRecord
include Presentable
include BulkInsertableAssociations
include TaggableQueries
- include SafelyChangeColumnDefault
self.table_name = 'ci_builds'
self.primary_key = :id
partitionable scope: :pipeline
- columns_changing_default :partition_id
belongs_to :user
belongs_to :project
diff --git a/app/models/design_management/git_repository.rb b/app/models/design_management/git_repository.rb
new file mode 100644
index 00000000000..92db82f7bd1
--- /dev/null
+++ b/app/models/design_management/git_repository.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module DesignManagement
+ class GitRepository < ::Repository
+ extend ::Gitlab::Utils::Override
+
+ # We define static git attributes for the design repository as this
+ # repository is entirely GitLab-managed rather than user-facing.
+ #
+ # Enable all uploaded files to be stored in LFS.
+ MANAGED_GIT_ATTRIBUTES = <<~GA.freeze
+ /#{DesignManagement.designs_directory}/* filter=lfs diff=lfs merge=lfs -text
+ GA
+
+ # Passing the `project` explicitly saves on one query on the `project` table
+ # in Mutations::DesignManagement::Delete
+
+ def initialize(project)
+ @project = project
+
+ full_path = @project.full_path + Gitlab::GlRepository::DESIGN.path_suffix
+ disk_path = @project.disk_path + Gitlab::GlRepository::DESIGN.path_suffix
+
+ # Ideally a DesignManagement::Repository, not a project would be
+ # the container to this Git repository.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/394816.
+
+ super(
+ full_path,
+ @project,
+ shard: @project.repository_storage,
+ disk_path: disk_path,
+ repo_type: Gitlab::GlRepository::DESIGN
+ )
+ end
+
+ # Override of a method called on Repository instances but sent via
+ # method_missing to Gitlab::Git::Repository where it is defined
+ def info_attributes
+ @info_attributes ||= Gitlab::Git::AttributesParser.new(MANAGED_GIT_ATTRIBUTES)
+ end
+
+ # Override of a method called on Repository instances but sent via
+ # method_missing to Gitlab::Git::Repository where it is defined
+ def attributes(path)
+ info_attributes.attributes(path)
+ end
+
+ # Override of a method called on Repository instances but sent via
+ # method_missing to Gitlab::Git::Repository where it is defined
+ def gitattribute(path, name)
+ attributes(path)[name]
+ end
+
+ # Override of a method called on Repository instances but sent via
+ # method_missing to Gitlab::Git::Repository where it is defined
+ def attributes_at(_ref = nil)
+ info_attributes
+ end
+
+ override :copy_gitattributes
+ def copy_gitattributes(_ref = nil)
+ true
+ end
+ end
+end
diff --git a/app/models/design_management/repository.rb b/app/models/design_management/repository.rb
index 2b1e6070e6b..e6790bc3253 100644
--- a/app/models/design_management/repository.rb
+++ b/app/models/design_management/repository.rb
@@ -1,51 +1,24 @@
# frozen_string_literal: true
module DesignManagement
- class Repository < ::Repository
- extend ::Gitlab::Utils::Override
+ class Repository < ApplicationRecord
+ include ::Gitlab::Utils::StrongMemoize
- # We define static git attributes for the design repository as this
- # repository is entirely GitLab-managed rather than user-facing.
- #
- # Enable all uploaded files to be stored in LFS.
- MANAGED_GIT_ATTRIBUTES = <<~GA
- /#{DesignManagement.designs_directory}/* filter=lfs diff=lfs merge=lfs -text
- GA
+ belongs_to :project, inverse_of: :design_management_repository
+ validates :project, presence: true, uniqueness: true
- def initialize(project)
- full_path = project.full_path + Gitlab::GlRepository::DESIGN.path_suffix
- disk_path = project.disk_path + Gitlab::GlRepository::DESIGN.path_suffix
+ # This is so that git_repo is initialized once `project` has been
+ # set. If it is not set after intialization and saving the record
+ # fails for some reason, the first call to `git_repo`` (initiated by
+ # `delegate_missing_to`) will throw an error because project would
+ # be missing.
+ after_initialize :git_repo
- super(full_path, project, shard: project.repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::DESIGN)
- end
-
- # Override of a method called on Repository instances but sent via
- # method_missing to Gitlab::Git::Repository where it is defined
- def info_attributes
- @info_attributes ||= Gitlab::Git::AttributesParser.new(MANAGED_GIT_ATTRIBUTES)
- end
-
- # Override of a method called on Repository instances but sent via
- # method_missing to Gitlab::Git::Repository where it is defined
- def attributes(path)
- info_attributes.attributes(path)
- end
-
- # Override of a method called on Repository instances but sent via
- # method_missing to Gitlab::Git::Repository where it is defined
- def gitattribute(path, name)
- attributes(path)[name]
- end
-
- # Override of a method called on Repository instances but sent via
- # method_missing to Gitlab::Git::Repository where it is defined
- def attributes_at(_ref = nil)
- info_attributes
- end
+ delegate_missing_to :git_repo
- override :copy_gitattributes
- def copy_gitattributes(_ref = nil)
- true
+ def git_repo
+ GitRepository.new(project)
end
+ strong_memoize_attr :git_repo
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c1511ee1233..ab6c58498ad 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -253,7 +253,7 @@ class MergeRequest < ApplicationRecord
Gitlab::Timeless.timeless(merge_request, &block)
end
- after_transition any => [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking, :can_be_merged, :cannot_be_merged] do |merge_request, transition|
+ after_transition any => [:unchecked, :cannot_be_merged_recheck, :can_be_merged, :cannot_be_merged] do |merge_request, transition|
next if merge_request.skip_merge_status_trigger
merge_request.run_after_commit do
diff --git a/app/models/project.rb b/app/models/project.rb
index 03aa131e71b..b2839ad78a3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -223,6 +223,7 @@ class Project < ApplicationRecord
has_one :zentao_integration, class_name: 'Integrations::Zentao'
has_one :wiki_repository, class_name: 'Projects::WikiRepository', inverse_of: :project
+ has_one :design_management_repository, class_name: 'DesignManagement::Repository', inverse_of: :project
has_one :root_of_fork_network,
foreign_key: 'root_project_id',
inverse_of: :root_project,
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index 2c46b2191c6..12d0af294da 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -2,6 +2,7 @@
- model = local_assigns.fetch(:model)
- form = local_assigns.fetch(:form)
- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a description or drag your files here…')
+- no_issuable_templates = issuable_templates(ref_project, model.to_ability_name).empty?
- supports_quick_actions = true
- preview_url = preview_markdown_path(project, target_type: model.class.name)
@@ -25,3 +26,5 @@
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
.clearfix
.error-alert
+ - if no_issuable_templates && can?(current_user, :push_code, model.project)
+ = render 'shared/issuable/form/default_templates'
diff --git a/app/views/shared/issuable/form/_default_templates.html.haml b/app/views/shared/issuable/form/_default_templates.html.haml
index 50f30e58b35..2dda0049c09 100644
--- a/app/views/shared/issuable/form/_default_templates.html.haml
+++ b/app/views/shared/issuable/form/_default_templates.html.haml
@@ -1,4 +1,4 @@
-%p.form-text.text-muted
+.gl-mt-3.gl-text-secondary
- template_link_url = help_page_path('user/project/description_templates')
- template_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: template_link_url }
= s_('Promotions|Add %{link_start} description templates %{link_end} to help your contributors to communicate effectively!').html_safe % { link_start: template_link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index 4d31baee25b..be836f4b8a9 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -1,6 +1,5 @@
- issuable = local_assigns.fetch(:issuable)
- form = local_assigns.fetch(:form)
-- no_issuable_templates = issuable_templates(ref_project, issuable.to_ability_name).empty?
%div{ data: { testid: 'issue-title-input-field' } }
= form.text_field :title, required: true, aria: { required: true }, maxlength: 255, autofocus: true,
@@ -13,6 +12,3 @@
= s_('MergeRequests|Mark as draft')
= c.help_text do
= s_('MergeRequests|Drafts cannot be merged until marked ready.')
-
- - if no_issuable_templates && can?(current_user, :push_code, issuable.project)
- = render 'shared/issuable/form/default_templates'
diff --git a/db/docs/design_management_repositories.yml b/db/docs/design_management_repositories.yml
new file mode 100644
index 00000000000..61d905c2703
--- /dev/null
+++ b/db/docs/design_management_repositories.yml
@@ -0,0 +1,10 @@
+---
+table_name: design_management_repositories
+classes:
+- DesignManagement::Repository
+feature_categories:
+- design_management
+description: Holds information about Design Management Repositories
+introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111555'
+milestone: '15.11'
+gitlab_schema: gitlab_main \ No newline at end of file
diff --git a/db/migrate/20230307000000_create_design_management_repository.rb b/db/migrate/20230307000000_create_design_management_repository.rb
new file mode 100644
index 00000000000..da4dee9f320
--- /dev/null
+++ b/db/migrate/20230307000000_create_design_management_repository.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class CreateDesignManagementRepository < Gitlab::Database::Migration[2.1]
+ def change
+ create_table :design_management_repositories do |t|
+ t.references :project, index: { unique: true }, null: false, foreign_key: { on_delete: :cascade }
+
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/post_migrate/20230321124837_remove_ci_builds_partition_id_default.rb b/db/post_migrate/20230321124837_remove_ci_builds_partition_id_default.rb
index 17fd8258e9c..a3a80fc6110 100644
--- a/db/post_migrate/20230321124837_remove_ci_builds_partition_id_default.rb
+++ b/db/post_migrate/20230321124837_remove_ci_builds_partition_id_default.rb
@@ -4,10 +4,10 @@ class RemoveCiBuildsPartitionIdDefault < Gitlab::Database::Migration[2.1]
enable_lock_retries!
def up
- change_column_default :ci_builds, :partition_id, from: 100, to: nil
+ # no-op. See https://gitlab.com/gitlab-com/gl-infra/production/-/issues/8588 for details.
end
def down
- change_column_default :ci_builds, :partition_id, from: nil, to: 100
+ # no-op. See https://gitlab.com/gitlab-com/gl-infra/production/-/issues/8588 for details.
end
end
diff --git a/db/schema_migrations/20230307000000 b/db/schema_migrations/20230307000000
new file mode 100644
index 00000000000..273a5553777
--- /dev/null
+++ b/db/schema_migrations/20230307000000
@@ -0,0 +1 @@
+192e21a20619e8940d1b5db69eeac1a7c1bfe32d821b671096be4eef4b4214f1 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 614f4f8c05d..d30a0d01381 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -13009,7 +13009,7 @@ CREATE TABLE ci_builds (
scheduling_type smallint,
id bigint NOT NULL,
stage_id bigint,
- partition_id bigint NOT NULL,
+ partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
);
@@ -15338,6 +15338,22 @@ CREATE SEQUENCE design_management_designs_versions_id_seq
ALTER SEQUENCE design_management_designs_versions_id_seq OWNED BY design_management_designs_versions.id;
+CREATE TABLE design_management_repositories (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE design_management_repositories_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE design_management_repositories_id_seq OWNED BY design_management_repositories.id;
+
CREATE TABLE design_management_versions (
id bigint NOT NULL,
sha bytea NOT NULL,
@@ -24703,6 +24719,8 @@ ALTER TABLE ONLY design_management_designs ALTER COLUMN id SET DEFAULT nextval('
ALTER TABLE ONLY design_management_designs_versions ALTER COLUMN id SET DEFAULT nextval('design_management_designs_versions_id_seq'::regclass);
+ALTER TABLE ONLY design_management_repositories ALTER COLUMN id SET DEFAULT nextval('design_management_repositories_id_seq'::regclass);
+
ALTER TABLE ONLY design_management_versions ALTER COLUMN id SET DEFAULT nextval('design_management_versions_id_seq'::regclass);
ALTER TABLE ONLY design_user_mentions ALTER COLUMN id SET DEFAULT nextval('design_user_mentions_id_seq'::regclass);
@@ -26659,6 +26677,9 @@ ALTER TABLE ONLY design_management_designs
ALTER TABLE ONLY design_management_designs_versions
ADD CONSTRAINT design_management_designs_versions_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY design_management_repositories
+ ADD CONSTRAINT design_management_repositories_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY design_management_versions
ADD CONSTRAINT design_management_versions_pkey PRIMARY KEY (id);
@@ -30140,6 +30161,8 @@ CREATE INDEX index_design_management_designs_versions_on_event ON design_managem
CREATE INDEX index_design_management_designs_versions_on_version_id ON design_management_designs_versions USING btree (version_id);
+CREATE UNIQUE INDEX index_design_management_repositories_on_project_id ON design_management_repositories USING btree (project_id);
+
CREATE INDEX index_design_management_versions_on_author_id ON design_management_versions USING btree (author_id) WHERE (author_id IS NOT NULL);
CREATE INDEX index_design_management_versions_on_issue_id ON design_management_versions USING btree (issue_id);
@@ -35331,6 +35354,9 @@ ALTER TABLE ONLY dast_site_validations
ALTER TABLE ONLY vulnerability_findings_remediations
ADD CONSTRAINT fk_rails_28a8d0cf93 FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
+ALTER TABLE ONLY design_management_repositories
+ ADD CONSTRAINT fk_rails_2938d8dd8d FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY incident_management_issuable_escalation_statuses
ADD CONSTRAINT fk_rails_29abffe3b9 FOREIGN KEY (policy_id) REFERENCES incident_management_escalation_policies(id) ON DELETE SET NULL;
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 8c95e68e1f2..200c2609914 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -159,3 +159,17 @@ For more information, see:
including settings that need checking afterwards and other limitations.
For support, customers must enter into a paid engagement with GitLab Professional Services.
+
+## Security
+
+Only import projects from sources you trust. If you import a project from an untrusted source, it
+may be possible for an attacker to steal your sensitive data. For example, an imported project
+with a malicious `.gitlab-ci.yml` file could allow an attacker to exfiltrate group CI/CD variables.
+
+GitLab self-managed administrators can reduce their attack surface by disabling import sources they don't need:
+
+1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Visibility and access controls**.
+1. Scroll to **Import sources**.
+1. Clear checkboxes for importers that are not required.
diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb
index d123989ef8e..efdb205b8eb 100644
--- a/lib/gitlab/gl_repository.rb
+++ b/lib/gitlab/gl_repository.rb
@@ -34,7 +34,7 @@ module Gitlab
DESIGN = ::Gitlab::GlRepository::RepoType.new(
name: :design,
access_checker_class: ::Gitlab::GitAccessDesign,
- repository_resolver: -> (project) { ::DesignManagement::Repository.new(project) },
+ repository_resolver: -> (project) { ::DesignManagement::Repository.new(project: project) },
suffix: :design
).freeze
diff --git a/qa/qa/flow/pipeline.rb b/qa/qa/flow/pipeline.rb
index 0765a8758ec..d1cfe8dae09 100644
--- a/qa/qa/flow/pipeline.rb
+++ b/qa/qa/flow/pipeline.rb
@@ -9,7 +9,7 @@ module QA
# canceled, created, failed, manual, passed
# pending, running, skipped
def visit_latest_pipeline(status: nil, wait: nil, skip_wait: true)
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform do |index|
index.has_any_pipeline?(wait: wait)
index.wait_for_latest_pipeline(status: status, wait: wait) if status || !skip_wait
@@ -18,7 +18,7 @@ module QA
end
def wait_for_latest_pipeline(status: nil, wait: nil)
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform do |index|
index.has_any_pipeline?(wait: wait)
index.wait_for_latest_pipeline(status: status, wait: wait)
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 451a9c3ee6e..b9bd6b14d7f 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -199,7 +199,8 @@ module QA
end
def click_diffs_tab
- click_element(:diffs_tab)
+ # Do not wait for spinner due to https://gitlab.com/gitlab-org/gitlab/-/issues/398584
+ click_element(:diffs_tab, skip_finished_loading_check: true)
end
def click_pipeline_link
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index db70d3e1d02..24ed2d766f1 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -15,6 +15,15 @@ module QA
include SubMenus::Settings
include SubMenus::Packages
+ if Runtime::Env.super_sidebar_enabled?
+ include SubMenus::SuperSidebar::Plan
+ include SubMenus::SuperSidebar::Settings
+ include SubMenus::SuperSidebar::Repository
+ include SubMenus::SuperSidebar::CiCd
+ include SubMenus::SuperSidebar::Compliance
+ include SubMenus::SuperSidebar::Operations
+ end
+
def click_merge_requests
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Merge requests')
@@ -22,12 +31,16 @@ module QA
end
def click_wiki
+ return go_to_wiki if Runtime::Env.super_sidebar_enabled?
+
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Wiki')
end
end
def click_activity
+ return go_to_activity if Runtime::Env.super_sidebar_enabled?
+
hover_project_information do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Activity')
@@ -36,12 +49,16 @@ module QA
end
def click_snippets
+ return go_to_snippets if Runtime::Env.super_sidebar_enabled?
+
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Snippets')
end
end
def click_members
+ return go_to_members if Runtime::Env.super_sidebar_enabled?
+
hover_project_information do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Members')
diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb
index 4ae51798e54..3547ea76182 100644
--- a/qa/qa/page/project/sub_menus/ci_cd.rb
+++ b/qa/qa/page/project/sub_menus/ci_cd.rb
@@ -15,7 +15,7 @@ module QA
end
end
- def click_ci_cd_pipelines
+ def go_to_pipelines
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'CI/CD')
end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/ci_cd.rb b/qa/qa/page/project/sub_menus/super_sidebar/ci_cd.rb
new file mode 100644
index 00000000000..032842edc43
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/ci_cd.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module CiCd
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_pipelines
+ open_ci_cd_submenu('Pipelines')
+ end
+
+ def go_to_editor
+ open_ci_cd_submenu('Editor')
+ end
+
+ def go_to_jobs
+ open_ci_cd_submenu('Jobs')
+ end
+
+ def go_to_schedules
+ open_ci_cd_submenu('Schedules')
+ end
+
+ private
+
+ def open_ci_cd_submenu(sub_menu)
+ open_submenu("CI/CD", "#ci-cd", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/common.rb b/qa/qa/page/project/sub_menus/super_sidebar/common.rb
new file mode 100644
index 00000000000..7cb14f4189e
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/common.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Common
+ private
+
+ def open_submenu(parent_menu_name, parent_section_id, sub_menu)
+ click_element(:sidebar_menu_link, menu_item: parent_menu_name)
+
+ # TODO: it's not possible to add qa-selectors to sub-menu container
+ within(parent_section_id) do
+ click_element(:sidebar_menu_link, menu_item: sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/compliance.rb b/qa/qa/page/project/sub_menus/super_sidebar/compliance.rb
new file mode 100644
index 00000000000..98f6a04c5c6
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/compliance.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Compliance
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_audit_events
+ open_compliance_submenu('Audit events')
+ end
+
+ def go_to_security_configuration
+ open_compliance_submenu('Security configuration')
+ end
+
+ private
+
+ def open_compliance_submenu(sub_menu)
+ open_submenu("Security and Compliance", "#security-and-compliance", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/operations.rb b/qa/qa/page/project/sub_menus/super_sidebar/operations.rb
new file mode 100644
index 00000000000..f47c113083a
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/operations.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Operations
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_environments
+ open_operations_submenu('Environments')
+ end
+
+ def go_to_feature_flags
+ open_operations_submenu('Feature Flags')
+ end
+
+ def go_to_releases
+ open_operations_submenu('Releases')
+ end
+
+ def go_to_package_registry
+ open_operations_submenu('Package Registry')
+ end
+
+ def go_to_infrastructure_registry
+ open_operations_submenu('Infrastructure Registry')
+ end
+
+ def go_to_kubernetes_clusters
+ open_operations_submenu('Kubernetes clusters')
+ end
+
+ def go_to_terraform
+ open_operations_submenu('Terraform')
+ end
+
+ private
+
+ def open_operations_submenu(sub_menu)
+ open_submenu("Operations", "#operations", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/plan.rb b/qa/qa/page/project/sub_menus/super_sidebar/plan.rb
new file mode 100644
index 00000000000..cf90ad9e955
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/plan.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Plan
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_members
+ open_plan_submenu("Members")
+ end
+
+ def go_to_labels
+ open_plan_submenu("Labels")
+ end
+
+ def go_to_activity
+ open_plan_submenu("Activity")
+ end
+
+ def go_to_boards
+ open_plan_submenu("Boards")
+ end
+
+ def go_to_milestones
+ open_plan_submenu("Milestones")
+ end
+
+ def go_to_service_desk
+ open_plan_submenu("Service Desk")
+ end
+
+ def go_to_wiki
+ open_plan_submenu("Wiki")
+ end
+
+ private
+
+ def open_plan_submenu(sub_menu)
+ open_submenu("Plan", "#plan", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/repository.rb b/qa/qa/page/project/sub_menus/super_sidebar/repository.rb
new file mode 100644
index 00000000000..52a9325c0b1
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/repository.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Repository
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_files
+ open_repository_submenu("Files")
+ end
+
+ def go_to_repository_commits
+ open_repository_submenu("Commits")
+ end
+
+ def go_to_repository_branches
+ open_repository_submenu("Branches")
+ end
+
+ def go_to_repository_tags
+ open_repository_submenu("Tags")
+ end
+
+ def go_to_snippets
+ open_repository_submenu("Snippets")
+ end
+
+ def go_to_contributor_statistics
+ open_repository_submenu("Contributor statistics")
+ end
+
+ def go_to_graph
+ open_repository_submenu("Graph")
+ end
+
+ def go_to_compare_revisions
+ open_repository_submenu("Compare revisions")
+ end
+
+ private
+
+ def open_repository_submenu(sub_menu)
+ open_submenu("Repository", "#repository", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/super_sidebar/settings.rb b/qa/qa/page/project/sub_menus/super_sidebar/settings.rb
new file mode 100644
index 00000000000..f8371385fd6
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/super_sidebar/settings.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module SuperSidebar
+ module Settings
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ include QA::Page::Project::SubMenus::SuperSidebar::Common
+ end
+ end
+
+ def go_to_general_settings
+ open_settings_submenu('General')
+ end
+
+ def go_to_integrations_settings
+ open_settings_submenu('Integrations')
+ end
+
+ def go_to_access_token_settings
+ open_settings_submenu('Access Tokens')
+ end
+
+ def go_to_repository_settings
+ open_settings_submenu('Repository')
+ end
+
+ def go_to_merge_request_settings
+ open_settings_submenu('Merge requests')
+ end
+
+ def go_to_ci_cd_settings
+ open_settings_submenu('CI/CD')
+ end
+
+ def go_to_pages_settings
+ open_settings_submenu('Pages')
+ end
+
+ def go_to_monitor_settings
+ open_settings_submenu('Monitor')
+ end
+
+ private
+
+ def open_settings_submenu(sub_menu)
+ open_submenu("Settings", "#settings", sub_menu)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb
index 7d5036c5cf4..e57784ca3b5 100644
--- a/qa/qa/resource/pipeline.rb
+++ b/qa/qa/resource/pipeline.rb
@@ -28,7 +28,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
Page::Project::Pipeline::New.perform(&:click_run_pipeline_button)
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb
index 4c1319da0cb..4515353dfc5 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb
@@ -49,7 +49,7 @@ module QA
Flow::Login.sign_in
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
index b79f8b5f1f4..b62ae85436f 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
@@ -52,7 +52,7 @@ module QA
project.visit!
# Navigate to Run Pipeline page
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
index e8ec01577b1..9f43471035f 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
@@ -37,7 +37,7 @@ module QA
before do
Flow::Login.sign_in
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
end
it 'can trigger pipeline', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348011' do
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 1e7d0eab365..0a9f30f0529 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Configure',
- only: { subdomain: %i[staging staging-canary] }, product_group: :configure do
+ only: { pipeline: %i[staging staging-canary canary production] }, product_group: :configure do
describe 'Auto DevOps with a Kubernetes Agent' do
let!(:app_project) do
Resource::Project.fabricate_via_api! do |project|
@@ -45,7 +45,7 @@ module QA
app_project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Menu.perform(&:go_to_pipelines)
Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
Page::Project::Pipeline::New.perform(&:click_run_pipeline_button)
diff --git a/qa/tasks/webdrivers.rake b/qa/tasks/webdrivers.rake
index f4fa3fab555..cd2a36ddf6b 100644
--- a/qa/tasks/webdrivers.rake
+++ b/qa/tasks/webdrivers.rake
@@ -1,4 +1,3 @@
# frozen_string_literal: true
-require 'webdrivers'
load 'webdrivers/Rakefile'
diff --git a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
index 2d39e0e5317..a7f3212a6f9 100644
--- a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
+++ b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
@@ -80,8 +80,12 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do
let(:oldest_version) { design.versions.ordered.last }
shared_examples 'a successful request for sha' do
+ before do
+ allow(DesignManagement::GitRepository).to receive(:new).and_call_original
+ end
+
it do
- expect_next_instance_of(DesignManagement::Repository) do |repository|
+ expect_next_instance_of(DesignManagement::GitRepository) do |repository|
expect(repository).to receive(:blob_at).with(expected_ref, design.full_path).and_call_original
end
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json
index a0ac70d7d9c..f57f0f3c08c 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json
@@ -8039,7 +8039,7 @@
"protected_environment_id": 1,
"created_at": "2017-10-19T15:36:23.466Z",
"updated_at": "2017-10-19T15:36:23.466Z",
- "access_level": 40,
+ "access_level": null,
"user_id": 1,
"group_id": null
}
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/protected_environments.ndjson b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/protected_environments.ndjson
index 55afaa8bcf6..f87fdd860c7 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/protected_environments.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/protected_environments.ndjson
@@ -1 +1 @@
-{ "id": 1, "project_id": 9, "created_at": "2017-10-19T15:36:23.466Z", "updated_at": "2017-10-19T15:36:23.466Z", "name": "production", "deploy_access_levels": [ { "id": 1, "protected_environment_id": 1, "created_at": "2017-10-19T15:36:23.466Z", "updated_at": "2017-10-19T15:36:23.466Z", "access_level": 40, "user_id": 1, "group_id": null } ] }
+{ "id": 1, "project_id": 9, "created_at": "2017-10-19T15:36:23.466Z", "updated_at": "2017-10-19T15:36:23.466Z", "name": "production", "deploy_access_levels": [ { "id": 1, "protected_environment_id": 1, "created_at": "2017-10-19T15:36:23.466Z", "updated_at": "2017-10-19T15:36:23.466Z", "access_level": null, "user_id": 1, "group_id": null } ] }
diff --git a/spec/frontend/notes/components/note_awards_list_spec.js b/spec/frontend/notes/components/note_awards_list_spec.js
index 89ac0216f41..0107b27f980 100644
--- a/spec/frontend/notes/components/note_awards_list_spec.js
+++ b/spec/frontend/notes/components/note_awards_list_spec.js
@@ -1,76 +1,110 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
+import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { userDataMock } from 'jest/notes/mock_data';
+import EmojiPicker from '~/emoji/components/picker.vue';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import awardsNote from '~/notes/components/note_awards_list.vue';
import createStore from '~/notes/stores';
-import { noteableDataMock, notesDataMock } from '../mock_data';
-describe('note_awards_list component', () => {
- let store;
- let vm;
- let awardsMock;
- let mock;
-
- const toggleAwardPath = `${TEST_HOST}/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji`;
-
- beforeEach(() => {
- mock = new AxiosMockAdapter(axios);
-
- mock.onPost(toggleAwardPath).reply(HTTP_STATUS_OK, '');
+Vue.use(Vuex);
- const Component = Vue.extend(awardsNote);
-
- store = createStore();
- store.dispatch('setNoteableData', noteableDataMock);
- store.dispatch('setNotesData', notesDataMock);
- awardsMock = [
- {
- name: 'flag_tz',
- user: { id: 1, name: 'Administrator', username: 'root' },
- },
- {
- name: 'cartwheel_tone3',
- user: { id: 12, name: 'Bobbie Stehr', username: 'erin' },
- },
- ];
+describe('Note Awards List', () => {
+ let wrapper;
+ let mock;
- vm = new Component({
+ const awardsMock = [
+ {
+ name: 'flag_tz',
+ user: { id: 1, name: 'Administrator', username: 'root' },
+ },
+ {
+ name: 'cartwheel_tone3',
+ user: { id: 12, name: 'Bobbie Stehr', username: 'erin' },
+ },
+ ];
+ const toggleAwardPathMock = `${TEST_HOST}/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji`;
+
+ const defaultProps = {
+ awards: awardsMock,
+ noteAuthorId: 2,
+ noteId: '545',
+ canAwardEmoji: false,
+ toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
+ };
+
+ const findAddAward = () => wrapper.find('.js-add-award');
+ const findAwardButton = () => wrapper.findByTestId('award-button');
+ const findAllEmojiAwards = () => wrapper.findAll('gl-emoji');
+ const findEmojiPicker = () => wrapper.findComponent(EmojiPicker);
+
+ const createComponent = (props = defaultProps, store = createStore()) => {
+ wrapper = mountExtended(awardsNote, {
store,
propsData: {
- awards: awardsMock,
- noteAuthorId: 2,
- noteId: '545',
- canAwardEmoji: true,
- toggleAwardPath,
+ ...props,
},
- }).$mount();
- });
+ });
+ };
+
+ describe('Note Awards functionality', () => {
+ const toggleAwardRequestSpy = jest.fn();
+ const fakeStore = () => {
+ return new Vuex.Store({
+ getters: {
+ getUserData: () => userDataMock,
+ },
+ actions: {
+ toggleAwardRequest: toggleAwardRequestSpy,
+ },
+ });
+ };
- afterEach(() => {
- mock.restore();
- vm.$destroy();
- });
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ mock.onPost(toggleAwardPathMock).reply(HTTP_STATUS_OK, '');
- it('should render awarded emojis', () => {
- expect(vm.$el.querySelector('.js-awards-block button [data-name="flag_tz"]')).toBeDefined();
- expect(
- vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]'),
- ).toBeDefined();
- });
+ createComponent(
+ {
+ awards: awardsMock,
+ noteAuthorId: 2,
+ noteId: '545',
+ canAwardEmoji: true,
+ toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
+ },
+ fakeStore(),
+ );
+ });
- it('should be possible to remove awarded emoji', () => {
- jest.spyOn(vm, 'handleAward');
- jest.spyOn(vm, 'toggleAwardRequest');
- vm.$el.querySelector('.js-awards-block button').click();
+ afterEach(() => {
+ mock.restore();
+ });
- expect(vm.handleAward).toHaveBeenCalledWith('flag_tz');
- expect(vm.toggleAwardRequest).toHaveBeenCalled();
- });
+ it('should render awarded emojis', () => {
+ const emojiAwards = findAllEmojiAwards();
+
+ expect(emojiAwards).toHaveLength(awardsMock.length);
+ expect(emojiAwards.at(0).attributes('data-name')).toBe('flag_tz');
+ expect(emojiAwards.at(1).attributes('data-name')).toBe('cartwheel_tone3');
+ });
+
+ it('should be possible to add new emoji', () => {
+ expect(findEmojiPicker().exists()).toBe(true);
+ });
+
+ it('should be possible to remove awarded emoji', async () => {
+ await findAwardButton().vm.$emit('click');
- it('should be possible to add new emoji', () => {
- expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
+ const { toggleAwardPath, noteId } = defaultProps;
+ expect(toggleAwardRequestSpy).toHaveBeenCalledWith(expect.anything(), {
+ awardName: awardsMock[0].name,
+ endpoint: toggleAwardPath,
+ noteId,
+ });
+ });
});
describe('when the user name contains special HTML characters', () => {
@@ -79,85 +113,69 @@ describe('note_awards_list component', () => {
user: { id: index, name: `&<>"\`'-${index}`, username: `user-${index}` },
});
- const mountComponent = () => {
- const Component = Vue.extend(awardsNote);
- vm = new Component({
- store,
- propsData: {
- awards: awardsMock,
- noteAuthorId: 0,
- noteId: '545',
- canAwardEmoji: true,
- toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
- },
- }).$mount();
+ const customProps = {
+ awards: awardsMock,
+ noteAuthorId: 0,
+ noteId: '545',
+ canAwardEmoji: true,
+ toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
};
- const findTooltip = () => vm.$el.querySelector('[title]').getAttribute('title');
-
- it('should only escape & and " characters', () => {
- awardsMock = [...new Array(1)].map(createAwardEmoji);
- mountComponent();
- const escapedName = awardsMock[0].user.name.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
-
- expect(vm.$el.querySelector('[title]').outerHTML).toContain(escapedName);
- });
-
it('should not escape special HTML characters twice when only 1 person awarded', () => {
- awardsMock = [...new Array(1)].map(createAwardEmoji);
- mountComponent();
+ const awardsCopy = [...new Array(1)].map(createAwardEmoji);
+ createComponent({
+ ...customProps,
+ awards: awardsCopy,
+ });
- awardsMock.forEach((award) => {
- expect(findTooltip()).toContain(award.user.name);
+ awardsCopy.forEach((award) => {
+ expect(findAwardButton().attributes('title')).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when 2 people awarded', () => {
- awardsMock = [...new Array(2)].map(createAwardEmoji);
- mountComponent();
+ const awardsCopy = [...new Array(2)].map(createAwardEmoji);
+ createComponent({
+ ...customProps,
+ awards: awardsCopy,
+ });
- awardsMock.forEach((award) => {
- expect(findTooltip()).toContain(award.user.name);
+ awardsCopy.forEach((award) => {
+ expect(findAwardButton().attributes('title')).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when more than 10 people awarded', () => {
- awardsMock = [...new Array(11)].map(createAwardEmoji);
- mountComponent();
+ const awardsCopy = [...new Array(11)].map(createAwardEmoji);
+ createComponent({
+ ...customProps,
+ awards: awardsCopy,
+ });
// Testing only the first 10 awards since 11 onward will not be displayed.
- awardsMock.slice(0, 10).forEach((award) => {
- expect(findTooltip()).toContain(award.user.name);
+ awardsCopy.slice(0, 10).forEach((award) => {
+ expect(findAwardButton().attributes('title')).toContain(award.user.name);
});
});
});
- describe('when the user cannot award emoji', () => {
+ describe('when the user cannot award an emoji', () => {
beforeEach(() => {
- const Component = Vue.extend(awardsNote);
-
- vm = new Component({
- store,
- propsData: {
- awards: awardsMock,
- noteAuthorId: 2,
- noteId: '545',
- canAwardEmoji: false,
- toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
- },
- }).$mount();
+ createComponent({
+ awards: awardsMock,
+ noteAuthorId: 2,
+ noteId: '545',
+ canAwardEmoji: false,
+ toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
+ });
});
- it('should not be possible to remove awarded emoji', () => {
- jest.spyOn(vm, 'toggleAwardRequest');
-
- vm.$el.querySelector('.js-awards-block button').click();
-
- expect(vm.toggleAwardRequest).not.toHaveBeenCalled();
+ it('should display an award emoji button with a disabled class', () => {
+ expect(findAwardButton().classes()).toContain('disabled');
});
it('should not be possible to add new emoji', () => {
- expect(vm.$el.querySelector('.js-add-award')).toBeNull();
+ expect(findAddAward().exists()).toBe(false);
});
});
});
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
index 0ec94563cbb..40dcbe16688 100644
--- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -136,7 +136,7 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_identifier) { "design-#{project.id}" }
let(:expected_id) { project.id }
let(:expected_suffix) { '.design' }
- let(:expected_repository) { ::DesignManagement::Repository.new(project) }
+ let(:expected_repository) { project.design_management_repository }
let(:expected_container) { project }
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index bcdbf3d58d0..d481a75667d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -780,6 +780,7 @@ project:
- sbom_occurrences
- analytics_dashboards_configuration_project
- analytics_dashboards_pointer
+- design_management_repository
award_emoji:
- awardable
- user
diff --git a/spec/models/design_management/git_repository_spec.rb b/spec/models/design_management/git_repository_spec.rb
new file mode 100644
index 00000000000..1b07e337cde
--- /dev/null
+++ b/spec/models/design_management/git_repository_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DesignManagement::GitRepository, feature_category: :design_management do
+ let_it_be(:project) { create(:project) }
+ let(:git_repository) { described_class.new(project) }
+
+ shared_examples 'returns parsed git attributes that enable LFS for all file types' do
+ it do
+ expect(subject.patterns).to be_a_kind_of(Hash)
+ expect(subject.patterns).to have_key('/designs/*')
+ expect(subject.patterns['/designs/*']).to eql(
+ { "filter" => "lfs", "diff" => "lfs", "merge" => "lfs", "text" => false }
+ )
+ end
+ end
+
+ describe "#info_attributes" do
+ subject { git_repository.info_attributes }
+
+ include_examples 'returns parsed git attributes that enable LFS for all file types'
+ end
+
+ describe '#attributes_at' do
+ subject { git_repository.attributes_at }
+
+ include_examples 'returns parsed git attributes that enable LFS for all file types'
+ end
+
+ describe '#gitattribute' do
+ it 'returns a gitattribute when path has gitattributes' do
+ expect(git_repository.gitattribute('/designs/file.txt', 'filter')).to eq('lfs')
+ end
+
+ it 'returns nil when path has no gitattributes' do
+ expect(git_repository.gitattribute('/invalid/file.txt', 'filter')).to be_nil
+ end
+ end
+
+ describe '#copy_gitattributes' do
+ it 'always returns regardless of whether given a valid or invalid ref' do
+ expect(git_repository.copy_gitattributes('master')).to be true
+ expect(git_repository.copy_gitattributes('invalid')).to be true
+ end
+ end
+
+ describe '#attributes' do
+ it 'confirms that all files are LFS enabled' do
+ %w[png zip anything].each do |filetype|
+ path = "/#{DesignManagement.designs_directory}/file.#{filetype}"
+ attributes = git_repository.attributes(path)
+
+ expect(attributes['filter']).to eq('lfs')
+ end
+ end
+ end
+end
diff --git a/spec/models/design_management/repository_spec.rb b/spec/models/design_management/repository_spec.rb
index 0115e0c139c..67cdba40f82 100644
--- a/spec/models/design_management/repository_spec.rb
+++ b/spec/models/design_management/repository_spec.rb
@@ -2,57 +2,16 @@
require 'spec_helper'
-RSpec.describe DesignManagement::Repository do
- let(:project) { create(:project) }
- let(:repository) { described_class.new(project) }
+RSpec.describe DesignManagement::Repository, feature_category: :design_management do
+ let_it_be(:project) { create(:project) }
+ let(:subject) { ::DesignManagement::Repository.new({ project: project }) }
- shared_examples 'returns parsed git attributes that enable LFS for all file types' do
- it do
- expect(subject.patterns).to be_a_kind_of(Hash)
- expect(subject.patterns).to have_key('/designs/*')
- expect(subject.patterns['/designs/*']).to eql(
- { "filter" => "lfs", "diff" => "lfs", "merge" => "lfs", "text" => false }
- )
- end
+ describe 'associations' do
+ it { is_expected.to belong_to(:project).inverse_of(:design_management_repository) }
end
- describe "#info_attributes" do
- subject { repository.info_attributes }
-
- include_examples 'returns parsed git attributes that enable LFS for all file types'
- end
-
- describe '#attributes_at' do
- subject { repository.attributes_at }
-
- include_examples 'returns parsed git attributes that enable LFS for all file types'
- end
-
- describe '#gitattribute' do
- it 'returns a gitattribute when path has gitattributes' do
- expect(repository.gitattribute('/designs/file.txt', 'filter')).to eq('lfs')
- end
-
- it 'returns nil when path has no gitattributes' do
- expect(repository.gitattribute('/invalid/file.txt', 'filter')).to be_nil
- end
- end
-
- describe '#copy_gitattributes' do
- it 'always returns regardless of whether given a valid or invalid ref' do
- expect(repository.copy_gitattributes('master')).to be true
- expect(repository.copy_gitattributes('invalid')).to be true
- end
- end
-
- describe '#attributes' do
- it 'confirms that all files are LFS enabled' do
- %w(png zip anything).each do |filetype|
- path = "/#{DesignManagement.designs_directory}/file.#{filetype}"
- attributes = repository.attributes(path)
-
- expect(attributes['filter']).to eq('lfs')
- end
- end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_uniqueness_of(:project) }
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f9822ff36c6..f0b8eb985c4 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -4486,7 +4486,7 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
let(:expected_merge_status) { 'checking' }
include_examples 'for a valid state transition'
- it_behaves_like 'transition triggering mergeRequestMergeStatusUpdated GraphQL subscription'
+ it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription'
end
context 'when the status is checking' do
@@ -4506,7 +4506,7 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
let(:expected_merge_status) { 'cannot_be_merged_rechecking' }
include_examples 'for a valid state transition'
- it_behaves_like 'transition triggering mergeRequestMergeStatusUpdated GraphQL subscription'
+ it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription'
end
context 'when the status is cannot_be_merged' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 0e3c2112691..11adbf161ee 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -42,6 +42,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
it { is_expected.to have_many(:protected_branches) }
it { is_expected.to have_many(:exported_protected_branches) }
it { is_expected.to have_one(:wiki_repository).class_name('Projects::WikiRepository').inverse_of(:project) }
+ it { is_expected.to have_one(:design_management_repository).class_name('DesignManagement::Repository').inverse_of(:project) }
it { is_expected.to have_one(:slack_integration) }
it { is_expected.to have_one(:catalog_resource) }
it { is_expected.to have_one(:microsoft_teams_integration) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b48ad59f7d3..8a02c9c0dd5 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -392,11 +392,6 @@ RSpec.configure do |config|
./ee/spec/requests/api/merge_request_approvals_spec.rb
./ee/spec/requests/api/namespaces_spec.rb
./ee/spec/requests/api/notes_spec.rb
- ./ee/spec/requests/api/project_aliases_spec.rb
- ./ee/spec/requests/api/project_approval_rules_spec.rb
- ./ee/spec/requests/api/project_approval_settings_spec.rb
- ./ee/spec/requests/api/project_approvals_spec.rb
- ./ee/spec/requests/api/projects_spec.rb
./ee/spec/requests/api/settings_spec.rb
./ee/spec/requests/api/users_spec.rb
./ee/spec/support/shared_examples/requests/api/project_approval_rules_api_shared_examples.rb