diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b0519119bd8..e455682dcff 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -136,6 +136,10 @@
.commit-author-link {
color: $gl-text-color;
}
+
+ .commit-author-link.gl-text-truncate {
+ max-width: 20ch;
+ }
}
}
diff --git a/app/controllers/groups/autocomplete_sources_controller.rb b/app/controllers/groups/autocomplete_sources_controller.rb
index 191720f69a0..8a3ec13f720 100644
--- a/app/controllers/groups/autocomplete_sources_controller.rb
+++ b/app/controllers/groups/autocomplete_sources_controller.rb
@@ -51,7 +51,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541
# type_id is a misnomer. QuickActions::TargetService actually requires an iid.
QuickActions::TargetService
- .new(nil, current_user, group: @group)
+ .new(container: @group, current_user: current_user)
.execute(params[:type], params[:type_id])
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index dc10004c62b..c496a326051 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -59,7 +59,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541
# type_id is a misnomer. QuickActions::TargetService actually requires an iid.
QuickActions::TargetService
- .new(project, current_user)
+ .new(container: project, current_user: current_user)
.execute(target_type, params[:type_id])
end
diff --git a/app/graphql/resolvers/projects/is_forked_resolver.rb b/app/graphql/resolvers/projects/is_forked_resolver.rb
new file mode 100644
index 00000000000..f1413543b7c
--- /dev/null
+++ b/app/graphql/resolvers/projects/is_forked_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class IsForkedResolver < BaseResolver
+ type GraphQL::Types::Boolean, null: false
+
+ def resolve
+ lazy_fork_network_members = BatchLoader::GraphQL.for(object.id).batch do |ids, loader|
+ ForkNetworkMember.by_projects(ids)
+ .with_fork_network
+ .find_each do |fork_network_member|
+ loader.call(fork_network_member.project_id, fork_network_member)
+ end
+ end
+
+ Gitlab::Graphql::Lazy.with_value(lazy_fork_network_members) do |fork_network_member|
+ next false if fork_network_member.nil?
+
+ fork_network_member.fork_network.root_project_id != object.id
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index aacd67e269e..bedcd08fcb4 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -685,6 +685,12 @@ module Types
description: 'Project allows assigning multiple reviewers to a merge request.',
null: false
+ field :is_forked,
+ GraphQL::Types::Boolean,
+ resolver: Resolvers::Projects::IsForkedResolver,
+ description: 'Project is forked.',
+ null: false
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb
index e0e6906f211..05c9535cef1 100644
--- a/app/models/ci/pipeline_artifact.rb
+++ b/app/models/ci/pipeline_artifact.rb
@@ -10,6 +10,9 @@ module Ci
include FileStoreMounter
include Lockable
include Presentable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
FILE_SIZE_LIMIT = 10.megabytes.freeze
EXPIRATION_DATE = 1.week.freeze
diff --git a/app/models/ci/pipeline_config.rb b/app/models/ci/pipeline_config.rb
index 11decd3fc66..8e992aae2c5 100644
--- a/app/models/ci/pipeline_config.rb
+++ b/app/models/ci/pipeline_config.rb
@@ -3,6 +3,9 @@
module Ci
class PipelineConfig < Ci::ApplicationRecord
include Ci::Partitionable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
self.table_name = 'ci_pipelines_config'
self.primary_key = :pipeline_id
diff --git a/app/models/ci/pipeline_metadata.rb b/app/models/ci/pipeline_metadata.rb
index 21d102374f0..39e2ef5cebb 100644
--- a/app/models/ci/pipeline_metadata.rb
+++ b/app/models/ci/pipeline_metadata.rb
@@ -4,6 +4,9 @@ module Ci
class PipelineMetadata < Ci::ApplicationRecord
include Ci::Partitionable
include Importable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
self.primary_key = :pipeline_id
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
index f18c306cf91..023f948d5f9 100644
--- a/app/models/fork_network_member.rb
+++ b/app/models/fork_network_member.rb
@@ -9,6 +9,9 @@ class ForkNetworkMember < ApplicationRecord
after_destroy :cleanup_fork_network
+ scope :by_projects, ->(ids) { where(project_id: ids) }
+ scope :with_fork_network, -> { joins(:fork_network).includes(:fork_network) }
+
private
def cleanup_fork_network
diff --git a/app/models/user.rb b/app/models/user.rb
index ab5572e5b19..05e35b217f4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -631,6 +631,8 @@ class User < MainClusterwide::ApplicationRecord
.trusted_with_spam)
end
+ scope :preload_user_detail, -> { preload(:user_detail) }
+
def self.supported_keyset_orderings
{
id: [:asc, :desc],
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 10aef87332a..31f79bc7164 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -55,7 +55,7 @@ class PreviewMarkdownService < BaseService
def find_commands_target
QuickActions::TargetService
- .new(project, current_user, group: params[:group])
+ .new(container: project, current_user: current_user, params: { group: params[:group] })
.execute(target_type, target_id)
end
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
index 04ae5287302..63e2c58fc55 100644
--- a/app/services/quick_actions/target_service.rb
+++ b/app/services/quick_actions/target_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QuickActions
- class TargetService < BaseService
+ class TargetService < BaseContainerService
def execute(type, type_iid)
case type&.downcase
when 'workitem'
@@ -19,15 +19,15 @@ module QuickActions
# rubocop: disable CodeReuse/ActiveRecord
def work_item(type_iid)
- WorkItems::WorkItemsFinder.new(current_user, project_id: project.id).find_by(iid: type_iid)
+ WorkItems::WorkItemsFinder.new(current_user, **parent_params).find_by(iid: type_iid)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def issue(type_iid)
- return project.issues.build if type_iid.nil?
+ return container.issues.build if type_iid.nil?
- IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_iid) || project.issues.build
+ IssuesFinder.new(current_user, **parent_params).find_by(iid: type_iid) || container.issues.build
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -42,7 +42,11 @@ module QuickActions
def commit(type_iid)
project.commit(type_iid)
end
+
+ def parent_params
+ group_container? ? { group_id: group.id } : { project_id: project.id }
+ end
end
end
-QuickActions::TargetService.prepend_mod_with('QuickActions::TargetService')
+QuickActions::TargetService.prepend_mod
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 8197abcc787..5fbb20f7535 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -12,6 +12,6 @@
data: { testid: test_id_for_provider(provider) },
id: "oauth-login-#{provider}"
- if render_remember_me
- = render Pajamas::CheckboxTagComponent.new(name: 'remember_me_omniauth', value: nil) do |c|
+ = render Pajamas::CheckboxTagComponent.new(name: 'js-remember-me-omniauth', value: nil) do |c|
- c.with_label do
= _('Remember me')
diff --git a/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb b/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb
new file mode 100644
index 00000000000..a2a0cc7be87
--- /dev/null
+++ b/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineMetadata < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipeline_metadata
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb b/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb
new file mode 100644
index 00000000000..205c7b19db6
--- /dev/null
+++ b/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineArtifact < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipeline_artifacts
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb b/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb
new file mode 100644
index 00000000000..b0a92807f09
--- /dev/null
+++ b/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineConfig < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipelines_config
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/schema_migrations/20240123131916 b/db/schema_migrations/20240123131916
new file mode 100644
index 00000000000..5377f7f4fb9
--- /dev/null
+++ b/db/schema_migrations/20240123131916
@@ -0,0 +1 @@
+43ff332582062a104cef5449444034363c1a71d288bcae7dfdeefbd69500186e
\ No newline at end of file
diff --git a/db/schema_migrations/20240123132014 b/db/schema_migrations/20240123132014
new file mode 100644
index 00000000000..719730631bd
--- /dev/null
+++ b/db/schema_migrations/20240123132014
@@ -0,0 +1 @@
+29392953f2fce7fb1a24dbc49f1ea30c49b1006551599bff98edc4de8061106b
\ No newline at end of file
diff --git a/db/schema_migrations/20240123132048 b/db/schema_migrations/20240123132048
new file mode 100644
index 00000000000..a8a046d0a04
--- /dev/null
+++ b/db/schema_migrations/20240123132048
@@ -0,0 +1 @@
+d14905475e591b7fa855097434d0e810fbb5a0890d7feb7b4fe8a22d5d75335f
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index cd9e8be6d35..7631fecfe7a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14603,7 +14603,7 @@ CREATE TABLE ci_pipeline_artifacts (
verification_checksum bytea,
verification_failure text,
locked smallint DEFAULT 2,
- partition_id bigint DEFAULT 100 NOT NULL,
+ partition_id bigint NOT NULL,
CONSTRAINT check_191b5850ec CHECK ((char_length(file) <= 255)),
CONSTRAINT check_abeeb71caf CHECK ((file IS NOT NULL)),
CONSTRAINT ci_pipeline_artifacts_verification_failure_text_limit CHECK ((char_length(verification_failure) <= 255))
@@ -14658,7 +14658,7 @@ CREATE TABLE ci_pipeline_metadata (
name text,
auto_cancel_on_new_commit smallint DEFAULT 0 NOT NULL,
auto_cancel_on_job_failure smallint DEFAULT 0 NOT NULL,
- partition_id bigint DEFAULT 100 NOT NULL,
+ partition_id bigint NOT NULL,
CONSTRAINT check_9d3665463c CHECK ((char_length(name) <= 255))
);
@@ -14782,7 +14782,7 @@ CREATE TABLE ci_pipelines (
CREATE TABLE ci_pipelines_config (
pipeline_id bigint NOT NULL,
content text NOT NULL,
- partition_id bigint DEFAULT 100 NOT NULL
+ partition_id bigint NOT NULL
);
CREATE SEQUENCE ci_pipelines_id_seq
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index 0d49ac583dd..26caf353314 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -28,8 +28,10 @@ swap:
raketask: Rake task
raketasks: Rake tasks
rspec: RSpec
- self hosted: self-managed
- self-hosted: self-managed
+ GitLab self hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ GitLab self-hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ self hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ self-hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
styleguide: style guide
to login: to log in
can login: can log in
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 0f2f16ffc45..b21e82c9b54 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -24351,6 +24351,7 @@ Represents vulnerability finding of a security report on the pipeline.
| `importStatus` | [`String`](#string) | Status of import background job of the project. |
| `incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. |
| `isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Indicates if a project is a catalog resource. |
+| `isForked` | [`Boolean!`](#boolean) | Project is forked. |
| `issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. |
| `issuesEnabled` | [`Boolean`](#boolean) | Indicates if Issues are enabled for the current user. |
| `jiraImportStatus` | [`String`](#string) | Status of Jira import background job of the project. |
diff --git a/doc/api/members.md b/doc/api/members.md
index 9d7aa85ba93..ead5b3d6be7 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -29,8 +29,7 @@ In GitLab 14.8 and earlier, projects in personal namespaces have an `access_leve
The `group_saml_identity` attribute is only visible to group owners for [SSO-enabled groups](../user/group/saml_sso/index.md).
-The `email` attribute is only visible to group owners for users provisioned by the group with [SCIM](../user/group/saml_sso/scim_setup.md).
-[Issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453) proposes to change the criteria for access to the `email` attribute from provisioned users to [enterprise users](../user/enterprise_user/index.md).
+The `email` attribute is only visible to group owners for [enterprise users](../user/enterprise_user/index.md) of the group when an API request is sent to the group itself, or that group's subgroups or projects.
## List all members of a group or project
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 5854704521b..eafb31241b0 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -252,7 +252,7 @@ Ideally, you should use [CI/CD variables](../variables/predefined_variables.md)
to replace those values at runtime when each review app is created:
- `data-project-id` is the project ID, which can be found by the `CI_PROJECT_ID`
- variable.
+ variable or on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- `data-merge-request-id` is the merge request ID, which can be found by the
`CI_MERGE_REQUEST_IID` variable. `CI_MERGE_REQUEST_IID` is available only if
[`rules:if: $CI_PIPELINE_SOURCE == "merge_request_event`](../pipelines/merge_request_pipelines.md#use-rules-to-add-jobs)
diff --git a/doc/ci/triggers/index.md b/doc/ci/triggers/index.md
index 49ff0ee2356..b628159ad21 100644
--- a/doc/ci/triggers/index.md
+++ b/doc/ci/triggers/index.md
@@ -78,7 +78,7 @@ In each example, replace:
- `` with your trigger token.
- `` with a branch or tag name, like `main`.
- `` with your project ID, like `123456`. The project ID is displayed
- at the top of every project's landing page.
+ on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
### Use a CI/CD job
@@ -100,8 +100,8 @@ trigger_pipeline:
In this example:
-- `1234` is the project ID for `project-B`. The project ID is displayed at the top
- of every project's landing page.
+- `1234` is the project ID for `project-B`. The project ID is displayed on the
+ [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- The [`rules`](../yaml/index.md#rules) cause the job to run every time a tag is added to `project-A`.
- `MY_TRIGGER_TOKEN` is a [masked CI/CD variables](../variables/index.md#mask-a-cicd-variable)
that contains the trigger token.
@@ -119,7 +119,7 @@ Replace:
- The URL with `https://gitlab.com` or the URL of your instance.
- `` with your project ID, like `123456`. The project ID is displayed
- at the top of the project's landing page.
+ on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- `` with a branch or tag name, like `main`. This value takes precedence over the `ref_name` in the webhook payload.
The payload's `ref` is the branch that fired the trigger in the source repository.
You must URL-encode the `ref_name` if it contains slashes.
diff --git a/doc/development/internal_analytics/internal_event_instrumentation/migration.md b/doc/development/internal_analytics/internal_event_instrumentation/migration.md
index 79ca45ed84c..7ed1adfb187 100644
--- a/doc/development/internal_analytics/internal_event_instrumentation/migration.md
+++ b/doc/development/internal_analytics/internal_event_instrumentation/migration.md
@@ -21,7 +21,7 @@ If you are already tracking events in Snowplow, you can also start collecting me
The event triggered by Internal Events has some special properties compared to previously tracking with Snowplow directly:
1. The `label`, `property` and `value` attributes are not used within Internal Events and are always empty.
-1. The `category` is automatically set to `InternalEventTracking`
+1. The `category` is automatically set to the location where the event happened. For Frontend events it is the page name and for Backend events it is a class name. If the page name or class name is not used, the default value of `"InternalEventTracking"` will be used.
Make sure that you are okay with this change before you migrate and dashboards are changed accordingly.
@@ -73,9 +73,11 @@ import { InternalEvents } from '~/tracking';
mixins: [InternalEvents.mixin()]
...
...
-this.trackEvent('action')
+this.trackEvent('action', 'category')
```
+If you are currently passing `category` and need to keep it, it can be passed as the second argument in the `trackEvent` method, as illustrated in the previous example. Nonetheless, it is strongly advised against using the `category` parameter for new events. This is because, by default, the category field is populated with information about where the event was triggered.
+
You can use [this MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123901/diffs) as an example. It migrates the `devops_adoption_app` component to use Internal Events Tracking.
If you are using `data-track-action` in the component, you have to change it to `data-event-tracking` to migrate to Internal Events Tracking.
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index e0a97568b5b..2a8a0766323 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -81,11 +81,10 @@ For more information about our plans for language support in SAST, see the [cate
| TypeScript | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep/#sast-rules) | 13.10 |
-Footnotes:
+ Footnotes:
-
The SpotBugs-based analyzer supports [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/), and [SBT](https://www.scala-sbt.org/). It can also be used with variants like the [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html), [Grails](https://grails.org/), and the [Maven wrapper](https://github.com/takari/maven-wrapper). However, SpotBugs has [limitations](https://gitlab.com/gitlab-org/gitlab/-/issues/350801) when used against [Ant](https://ant.apache.org/)-based projects. We recommend using the Semgrep-based analyzer for Ant-based Java or Scala projects.
+
The SpotBugs-based analyzer supports Gradle, Maven, and SBT. It can also be used with variants like the Gradle wrapper, Grails, and the Maven wrapper. However, SpotBugs has limitations when used against Ant-based projects. You should use the Semgrep-based analyzer for Ant-based Java or Scala projects.
-
## End of supported analyzers
diff --git a/doc/user/enterprise_user/index.md b/doc/user/enterprise_user/index.md
index cc7e0e3b499..5a6ee56b775 100644
--- a/doc/user/enterprise_user/index.md
+++ b/doc/user/enterprise_user/index.md
@@ -203,10 +203,7 @@ A top-level group Owner can [set up verified domains to bypass confirmation emai
### Get users' email addresses through the API
A top-level group Owner can use the [group and project members API](../../api/members.md) to access
-users' information. For users provisioned by the group with [SCIM](../group/saml_sso/scim_setup.md),
-this information includes users' email addresses.
-
-[Issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453) proposes to change the criteria for access to email addresses from provisioned users to enterprise users.
+users' information. For enterprise users of the group this information includes users' email addresses.
### Remove enterprise management features from an account
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a59734d643d..9b2c6c37fd6 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -738,6 +738,12 @@ module API
namespace: namespace,
project: project
)
+ rescue Gitlab::InternalEvents::UnknownEventError => e
+ Gitlab::ErrorTracking.track_exception(e, event_name: event_name)
+
+ # We want to keep the error silent on production to keep the behavior
+ # consistent with StandardError rescue
+ unprocessable_entity!(e.message) if Gitlab.dev_or_test_env?
rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, event_name: event_name)
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index d625b2c0fe6..09bb336e19c 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -73,7 +73,7 @@ module API
end
desc 'Updates a group or project invitation.' do
- success Entities::Member
+ success Entities::Invitation
tags %w[invitations]
end
params do
@@ -103,7 +103,7 @@ module API
updated_member = result[:members].first
if result[:status] == :success
- present_members updated_member
+ present_member_invitations updated_member
else
render_validation_error!(updated_member)
end
diff --git a/lib/bulk_imports/common/pipelines/members_pipeline.rb b/lib/bulk_imports/common/pipelines/members_pipeline.rb
index 548b191dc25..90df8453d77 100644
--- a/lib/bulk_imports/common/pipelines/members_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/members_pipeline.rb
@@ -27,7 +27,9 @@ module BulkImports
return if user_membership && user_membership[:access_level] >= data[:access_level]
# Create new membership for any other access level
- portable.members.create!(data)
+ member = portable.members.new(data)
+ member.importing = true # avoid sending new member notification to the invited user
+ member.save!
end
private
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c1b1daf08e9..dad32def094 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2918,6 +2918,9 @@ msgstr ""
msgid "Add an impersonation token"
msgstr ""
+msgid "Add another branch"
+msgstr ""
+
msgid "Add another link"
msgstr ""
@@ -44054,6 +44057,9 @@ msgstr ""
msgid "SecurityOrchestration|Add new approver"
msgstr ""
+msgid "SecurityOrchestration|Add project full path after @ to following branches: %{branches}"
+msgstr ""
+
msgid "SecurityOrchestration|Add protected branches"
msgstr ""
@@ -44123,6 +44129,9 @@ msgstr ""
msgid "SecurityOrchestration|Choose approver type"
msgstr ""
+msgid "SecurityOrchestration|Choose exception branches"
+msgstr ""
+
msgid "SecurityOrchestration|Choose specific role"
msgstr ""
@@ -44192,6 +44201,9 @@ msgstr ""
msgid "SecurityOrchestration|Every time a pipeline runs for %{branches}%{branchExceptionsString}"
msgstr ""
+msgid "SecurityOrchestration|Exception branches"
+msgstr ""
+
msgid "SecurityOrchestration|Exceptions"
msgstr ""
@@ -44207,6 +44219,9 @@ msgstr ""
msgid "SecurityOrchestration|Failed to load images."
msgstr ""
+msgid "SecurityOrchestration|Fill in branch name with project name in the format of %{boldStart}branch-name@project-path,%{boldEnd} separate with `,`"
+msgstr ""
+
msgid "SecurityOrchestration|Following projects:"
msgstr ""
@@ -44276,6 +44291,9 @@ msgstr ""
msgid "SecurityOrchestration|No actions defined - policy will not run."
msgstr ""
+msgid "SecurityOrchestration|No branches yet"
+msgstr ""
+
msgid "SecurityOrchestration|No compliance frameworks"
msgstr ""
@@ -44320,6 +44338,9 @@ msgstr ""
msgid "SecurityOrchestration|Overwrite the current CI/CD code with the new file's content?"
msgstr ""
+msgid "SecurityOrchestration|Please remove duplicated values"
+msgstr ""
+
msgid "SecurityOrchestration|Policies"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 72ce6cfe43c..ec053724906 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 13', '>= 13.1.0', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 14', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.11.0', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 126061e83cb..26dae330ef6 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -118,8 +118,8 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (13.1.0)
- activesupport (>= 6.1, < 7.1)
+ gitlab-qa (14.0.0)
+ activesupport (>= 6.1, < 7.2)
gitlab (~> 4.19)
http (~> 5.0)
nokogiri (~> 1.10)
@@ -354,7 +354,7 @@ DEPENDENCIES
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 13, >= 13.1.0)
+ gitlab-qa (~> 14)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.11.0)
influxdb-client (~> 3.0)
@@ -380,4 +380,4 @@ DEPENDENCIES
zeitwerk (~> 2.6, >= 2.6.12)
BUNDLED WITH
- 2.5.4
+ 2.5.5
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index c5ad7bca824..1d8a44de7d9 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -402,7 +402,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
it 'displays the remember me checkbox' do
visit new_user_session_path
- expect(page).to have_field('remember_me_omniauth')
+ expect(page).to have_field('js-remember-me-omniauth')
end
context 'when remember me is not enabled' do
@@ -413,7 +413,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
it 'does not display the remember me checkbox' do
visit new_user_session_path
- expect(page).not_to have_field('remember_me_omniauth')
+ expect(page).not_to have_field('js-remember-me-omniauth')
end
end
diff --git a/spec/frontend/fixtures/static/oauth_remember_me.html b/spec/frontend/fixtures/static/oauth_remember_me.html
deleted file mode 100644
index d7519dd695f..00000000000
--- a/spec/frontend/fixtures/static/oauth_remember_me.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/spec/frontend/oauth_remember_me_spec.js b/spec/frontend/oauth_remember_me_spec.js
deleted file mode 100644
index 4fea216302f..00000000000
--- a/spec/frontend/oauth_remember_me_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import $ from 'jquery';
-import htmlOauthRememberMe from 'test_fixtures_static/oauth_remember_me.html';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
-
-describe('OAuthRememberMe', () => {
- const findFormAction = (selector) => {
- return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
- };
-
- beforeEach(() => {
- setHTMLFixture(htmlOauthRememberMe);
-
- new OAuthRememberMe({ container: $('.js-oauth-login') }).bindEvents();
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- it('adds and removes the "remember_me" query parameter from all OAuth login buttons', () => {
- $('.js-oauth-login #remember_me_omniauth').click();
-
- expect(findFormAction('.twitter')).toBe('http://example.com/?remember_me=1');
- expect(findFormAction('.github')).toBe('http://example.com/?remember_me=1');
- expect(findFormAction('.facebook')).toBe(
- 'http://example.com/?redirect_fragment=L1&remember_me=1',
- );
-
- $('.js-oauth-login #remember_me_omniauth').click();
-
- expect(findFormAction('.twitter')).toBe('http://example.com/');
- expect(findFormAction('.github')).toBe('http://example.com/');
- expect(findFormAction('.facebook')).toBe('http://example.com/?redirect_fragment=L1');
- });
-});
diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
index 7607381a981..60cf5dc65a2 100644
--- a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
+++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
@@ -1,13 +1,12 @@
-import $ from 'jquery';
import htmlSessionsNew from 'test_fixtures/sessions/new.html';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
+import {
+ appendUrlFragment,
+ appendRedirectQuery,
+ toggleRememberMeQuery,
+} from '~/pages/sessions/new/preserve_url_fragment';
describe('preserve_url_fragment', () => {
- const findFormAction = (selector) => {
- return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
- };
-
beforeEach(() => {
setHTMLFixture(htmlSessionsNew);
});
@@ -16,41 +15,74 @@ describe('preserve_url_fragment', () => {
resetHTMLFixture();
});
- it('adds the url fragment to the login form actions', () => {
- preserveUrlFragment('#L65');
+ describe('non-OAuth login forms', () => {
+ describe('appendUrlFragment', () => {
+ const findFormAction = () => document.querySelector('.js-non-oauth-login form').action;
- expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in#L65');
+ it('adds the url fragment to the login form actions', () => {
+ appendUrlFragment('#L65');
+
+ expect(findFormAction()).toBe('http://test.host/users/sign_in#L65');
+ });
+
+ it('does not add an empty url fragment to the login form actions', () => {
+ appendUrlFragment();
+
+ expect(findFormAction()).toBe('http://test.host/users/sign_in');
+ });
+ });
});
- it('does not add an empty url fragment to the login form actions', () => {
- preserveUrlFragment();
+ describe('OAuth login forms', () => {
+ const findFormAction = (selector) =>
+ document.querySelector(`.js-oauth-login #oauth-login-${selector}`).parentElement.action;
- expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in');
- });
+ describe('appendRedirectQuery', () => {
+ it('does not add an empty query parameter to the login form actions', () => {
+ appendRedirectQuery();
- it('does not add an empty query parameter to OmniAuth login buttons', () => {
- preserveUrlFragment();
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+ });
- expect(findFormAction('#oauth-login-auth0')).toBe('http://test.host/users/auth/auth0');
- });
+ describe('adds "redirect_fragment" query parameter to the login form actions', () => {
+ it('when "remember_me" is not present', () => {
+ appendRedirectQuery('#L65');
- describe('adds "redirect_fragment" query parameter to OmniAuth login buttons', () => {
- it('when "remember_me" is not present', () => {
- preserveUrlFragment('#L65');
+ expect(findFormAction('auth0')).toBe(
+ 'http://test.host/users/auth/auth0?redirect_fragment=L65',
+ );
+ });
- expect(findFormAction('#oauth-login-auth0')).toBe(
- 'http://test.host/users/auth/auth0?redirect_fragment=L65',
- );
+ it('when "remember_me" is present', () => {
+ document
+ .querySelectorAll('form')
+ .forEach((form) => form.setAttribute('action', `${form.action}?remember_me=1`));
+
+ appendRedirectQuery('#L65');
+
+ expect(findFormAction('auth0')).toBe(
+ 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
+ );
+ });
+ });
});
- it('when "remember-me" is present', () => {
- $('.js-oauth-login form').attr('action', (i, href) => `${href}?remember_me=1`);
+ describe('toggleRememberMeQuery', () => {
+ const rememberMe = () => document.querySelector('#js-remember-me-omniauth');
- preserveUrlFragment('#L65');
+ it('toggles "remember_me" query parameter', () => {
+ toggleRememberMeQuery();
- expect(findFormAction('#oauth-login-auth0')).toBe(
- 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
- );
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+
+ rememberMe().click();
+
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0?remember_me=1');
+
+ rememberMe().click();
+
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+ });
});
});
});
diff --git a/spec/frontend/repository/components/commit_info_spec.js b/spec/frontend/repository/components/commit_info_spec.js
index 4e570346d97..f868bc0623e 100644
--- a/spec/frontend/repository/components/commit_info_spec.js
+++ b/spec/frontend/repository/components/commit_info_spec.js
@@ -16,14 +16,15 @@ const commit = {
const findTextExpander = () => wrapper.findComponent(GlButton);
const findUserLink = () => wrapper.findByText(commit.author.name);
+const findCommitterWrapper = () => wrapper.findByTestId('committer');
const findUserAvatarLink = () => wrapper.findComponent(UserAvatarLink);
const findAuthorName = () => wrapper.findByText(`${commit.authorName} authored`);
const findCommitRowDescription = () => wrapper.find('pre');
const findTitleHtml = () => wrapper.findByText(commit.titleHtml);
-const createComponent = async ({ commitMock = {}, prevBlameLink } = {}) => {
+const createComponent = async ({ commitMock = {}, prevBlameLink, span = 3 } = {}) => {
wrapper = shallowMountExtended(CommitInfo, {
- propsData: { commit: { ...commit, ...commitMock }, prevBlameLink },
+ propsData: { commit: { ...commit, ...commitMock }, prevBlameLink, span },
});
await nextTick();
@@ -46,6 +47,22 @@ describe('Repository last commit component', () => {
expect(findAuthorName().exists()).toBe(true);
});
+ it('truncates author name when commit spans less than 3 lines', () => {
+ createComponent({ span: 2 });
+
+ expect(findCommitterWrapper().classes()).toEqual([
+ 'committer',
+ 'gl-flex-basis-full',
+ 'gl-display-inline-flex',
+ ]);
+ expect(findUserLink().classes()).toEqual([
+ 'commit-author-link',
+ 'js-user-link',
+ 'gl-display-inline-block',
+ 'gl-text-truncate',
+ ]);
+ });
+
it('does not render description expander when description is null', () => {
createComponent();
diff --git a/spec/frontend/tracking/internal_events_spec.js b/spec/frontend/tracking/internal_events_spec.js
index 295b08f4b1c..194d33ae6b9 100644
--- a/spec/frontend/tracking/internal_events_spec.js
+++ b/spec/frontend/tracking/internal_events_spec.js
@@ -4,6 +4,7 @@ import InternalEvents from '~/tracking/internal_events';
import { LOAD_INTERNAL_EVENTS_SELECTOR } from '~/tracking/constants';
import * as utils from '~/tracking/utils';
import { Tracker } from '~/tracking/tracker';
+import Tracking from '~/tracking';
jest.mock('~/api', () => ({
trackInternalEvent: jest.fn(),
@@ -20,13 +21,23 @@ const event = 'TestEvent';
describe('InternalEvents', () => {
describe('trackEvent', () => {
+ const category = 'TestCategory';
+
it('trackEvent calls API.trackInternalEvent with correct arguments', () => {
- InternalEvents.trackEvent(event);
+ InternalEvents.trackEvent(event, category);
expect(API.trackInternalEvent).toHaveBeenCalledTimes(1);
expect(API.trackInternalEvent).toHaveBeenCalledWith(event);
});
+ it('trackEvent calls Tracking.event with correct arguments including category', () => {
+ jest.spyOn(Tracking, 'event').mockImplementation(() => {});
+
+ InternalEvents.trackEvent(event, category);
+
+ expect(Tracking.event).toHaveBeenCalledWith(category, event, expect.any(Object));
+ });
+
it('trackEvent calls trackBrowserSDK with correct arguments', () => {
jest.spyOn(InternalEvents, 'trackBrowserSDK').mockImplementation(() => {});
@@ -63,7 +74,7 @@ describe('InternalEvents', () => {
await wrapper.findByTestId('button').trigger('click');
expect(trackEventSpy).toHaveBeenCalledTimes(1);
- expect(trackEventSpy).toHaveBeenCalledWith(event);
+ expect(trackEventSpy).toHaveBeenCalledWith(event, undefined);
});
});
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index f6e178f5b28..96201f0827f 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages
incident_management_timeline_event_tags visible_forks inherited_ci_variables autocomplete_users
ci_cd_settings detailed_import_status value_streams ml_models
- allows_multiple_merge_request_assignees allows_multiple_merge_request_reviewers
+ allows_multiple_merge_request_assignees allows_multiple_merge_request_reviewers is_forked
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -771,6 +771,57 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
end
end
+ describe 'is_forked' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unforked_project) { create(:project, :public) }
+ let!(:forked_project) { fork_project(unforked_project) }
+ let(:project) { nil }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ isForked
+ }
+ }
+ )
+ end
+
+ let(:response) { GitlabSchema.execute(query).as_json }
+
+ subject(:is_forked) { response.dig('data', 'project', 'isForked') }
+
+ context 'when project has a fork network' do
+ context 'when fork is itself' do
+ let(:project) { unforked_project }
+
+ it { is_expected.to be false }
+ end
+
+ context 'when fork is not itself' do
+ let(:project) { forked_project }
+
+ it { is_expected.to be true }
+
+ it 'avoids N+1 queries' do
+ query_count = ActiveRecord::QueryRecorder.new { response }
+
+ expect(query_count).not_to exceed_query_limit(8)
+ end
+ end
+ end
+
+ context 'when project does not have a fork network' do
+ let(:project) { unforked_project }
+
+ before do
+ allow(project).to receive(:fork_network).and_return(nil)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+
describe 'branch_rules' do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public) }
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index d1dee70e34d..6a2449cbcdb 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -860,13 +860,30 @@ RSpec.describe API::Helpers, feature_category: :shared do
)
end
- it 'logs an exception for unknown event' do
+ it 'tracks an exception and renders 422 for unknown event', :aggregate_failures do
expect(Gitlab::InternalEvents).to receive(:track_event).and_raise(Gitlab::InternalEvents::UnknownEventError, "Unknown event: #{unknown_event}")
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(
instance_of(Gitlab::InternalEvents::UnknownEventError),
event_name: unknown_event
)
+ expect(helper).to receive(:unprocessable_entity!).with("Unknown event: #{unknown_event}")
+
+ helper.track_event(unknown_event,
+ user: user,
+ namespace_id: namespace.id,
+ project_id: project.id
+ )
+ end
+
+ it 'logs an exception for tracking errors' do
+ expect(Gitlab::InternalEvents).to receive(:track_event).and_raise(ArgumentError, "Error message")
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+ .with(
+ instance_of(ArgumentError),
+ event_name: unknown_event
+ )
helper.track_event(unknown_event,
user: user,
diff --git a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
index 65d4e8b4978..5fc0c8fa239 100644
--- a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
@@ -87,6 +87,12 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category
expect(member.expires_at).to eq(nil)
end
+ it 'does not send new member notification' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.load(context, member_data)
+ end
+
context 'when user_id is current user id' do
it 'does not create new membership' do
data = { user_id: user.id }
diff --git a/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
index fad10aba882..dc62a520d07 100644
--- a/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLockedStatus do
+RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLockedStatus, feature_category: :build_artifacts do
describe '#perform' do
let(:batch_table) { :ci_pipeline_artifacts }
let(:batch_column) { :id }
@@ -30,11 +30,11 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLock
let(:locked_pipeline) { pipelines.create!(locked: locked, partition_id: 100) }
# rubocop:disable Layout/LineLength
- let!(:locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 1024, file_type: 0, file_format: 'gzip', file: 'a.gz', locked: unknown) }
- let!(:unlocked_artifact_1) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 2048, file_type: 1, file_format: 'raw', file: 'b', locked: unknown) }
- let!(:unlocked_artifact_2) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 4096, file_type: 2, file_format: 'gzip', file: 'c.gz', locked: unknown) }
- let!(:already_unlocked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: unlocked) }
- let!(:already_locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: locked) }
+ let!(:locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 1024, file_type: 0, file_format: 'gzip', file: 'a.gz', locked: unknown, partition_id: 100) }
+ let!(:unlocked_artifact_1) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 2048, file_type: 1, file_format: 'raw', file: 'b', locked: unknown, partition_id: 100) }
+ let!(:unlocked_artifact_2) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 4096, file_type: 2, file_format: 'gzip', file: 'c.gz', locked: unknown, partition_id: 100) }
+ let!(:already_unlocked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: unlocked, partition_id: 100) }
+ let!(:already_locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: locked, partition_id: 100) }
# rubocop:enable Layout/LineLength
subject do
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index b34eb7964ca..edefddfc9d7 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -25,4 +25,30 @@ RSpec.describe ForkNetworkMember do
expect(ForkNetwork.count).to eq(1)
end
end
+
+ describe '#by_projects' do
+ let_it_be(:fork_network_member_1) { create(:fork_network_member) }
+ let_it_be(:fork_network_member_2) { create(:fork_network_member) }
+
+ it 'returns fork network members by project ids' do
+ expect(
+ described_class.by_projects(
+ [fork_network_member_1.project_id, fork_network_member_2.project_id]
+ )
+ ).to match_array([fork_network_member_1, fork_network_member_2])
+ end
+ end
+
+ describe '#with_fork_network' do
+ let_it_be(:fork_network_member_1) { create(:fork_network_member) }
+ let_it_be(:fork_network_member_2) { create(:fork_network_member) }
+
+ it 'avoids N+1 queries' do
+ query_count = ActiveRecord::QueryRecorder.new do
+ described_class.all.with_fork_network.find_each(&:fork_network)
+ end
+
+ expect(query_count).not_to exceed_query_limit(1)
+ end
+ end
end
diff --git a/spec/requests/groups/autocomplete_sources_spec.rb b/spec/requests/groups/autocomplete_sources_spec.rb
index 02fb04a4af8..5d190074534 100644
--- a/spec/requests/groups/autocomplete_sources_spec.rb
+++ b/spec/requests/groups/autocomplete_sources_spec.rb
@@ -14,6 +14,42 @@ RSpec.describe 'groups autocomplete', feature_category: :groups_and_projects do
sign_in(user)
end
+ describe '#members' do
+ context 'when type is WorkItem' do
+ let(:type) { 'Workitem' }
+
+ it 'returns the correct response', :aggregate_failures do
+ work_item = create(:work_item, :group_level, namespace: group, author: user)
+
+ get members_group_autocomplete_sources_path(group, type_id: work_item.iid, type: type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response).to contain_exactly(
+ hash_including('type' => 'User', 'username' => user.username),
+ hash_including('type' => 'Group', 'username' => group.full_path)
+ )
+ end
+ end
+
+ context 'when type is Issue' do
+ let(:type) { 'Issue' }
+
+ it 'returns the correct response', :aggregate_failures do
+ issue = create(:issue, :group_level, namespace: group, author: user)
+
+ get members_group_autocomplete_sources_path(group, type_id: issue.iid, type: type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response).to contain_exactly(
+ hash_including('type' => 'User', 'username' => user.username),
+ hash_including('type' => 'Group', 'username' => group.full_path)
+ )
+ end
+ end
+ end
+
describe '#issues' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb
index 5f4e92cf955..311f2680379 100644
--- a/spec/services/quick_actions/target_service_spec.rb
+++ b/spec/services/quick_actions/target_service_spec.rb
@@ -3,13 +3,11 @@
require 'spec_helper'
RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:service) { described_class.new(project, user) }
-
- before do
- project.add_maintainer(user)
- end
+ let_it_be(:group) { create(:group) }
+ let_it_be_with_reload(:project) { create(:project, group: group) }
+ let_it_be(:user) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:container) { project }
+ let(:service) { described_class.new(container: container, current_user: user) }
describe '#execute' do
shared_examples 'no target' do |type_iid:|
@@ -32,7 +30,7 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
it 'builds a new target' do
target = service.execute(type, type_iid)
- expect(target.project).to eq(project)
+ expect(target.resource_parent).to eq(container)
expect(target).to be_new_record
end
end
@@ -45,6 +43,15 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
it_behaves_like 'find target'
it_behaves_like 'build target', type_iid: nil
it_behaves_like 'build target', type_iid: -1
+
+ context 'when issue belongs to a group' do
+ let(:container) { group }
+ let(:target) { create(:issue, :group_level, namespace: group) }
+
+ it_behaves_like 'find target'
+ it_behaves_like 'build target', type_iid: nil
+ it_behaves_like 'build target', type_iid: -1
+ end
end
context 'for work item' do
@@ -53,6 +60,13 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
let(:type) { 'WorkItem' }
it_behaves_like 'find target'
+
+ context 'when work item belongs to a group' do
+ let(:container) { group }
+ let(:target) { create(:work_item, :group_level, namespace: group) }
+
+ it_behaves_like 'find target'
+ end
end
context 'for merge request' do
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index cc45cb1292d..7752488ab44 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -112,7 +112,7 @@ module LoginHelpers
visit new_user_session_path
expect(page).to have_css('.js-oauth-login')
- check 'remember_me_omniauth' if remember_me
+ check 'js-remember-me-omniauth' if remember_me
click_button "oauth-login-#{provider}"
end