diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 6fc76699590029426e1873106af8c4427a221aad..47690171bf2c6f88e681177e338bf7a3ce3c0c91 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -571,6 +571,16 @@ class ProjectType < BaseObject
resolver: Resolvers::DataTransferResolver.project,
description: 'Data transfer data point for a specific period. This is mocked data under a development feature flag.'
+ field :visible_forks, Types::ProjectType.connection_type,
+ null: true,
+ alpha: { milestone: '15.10' },
+ description: "Visible forks of the project." do
+ argument :minimum_access_level,
+ type: ::Types::AccessLevelEnum,
+ required: false,
+ description: 'Minimum access level.'
+ end
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
@@ -659,6 +669,14 @@ def languages
::Projects::RepositoryLanguagesService.new(project, current_user).execute
end
+ def visible_forks(minimum_access_level: nil)
+ if minimum_access_level.nil?
+ object.forks.public_or_visible_to_user(current_user)
+ else
+ object.forks.visible_to_user_and_access_level(current_user, minimum_access_level)
+ end
+ end
+
private
def project
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 74e8035232bd80030b0175163a0c022cdc96e744..055fb72dd7504047901e0b3a1466c143ec1ce58b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -18933,6 +18933,26 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
| `username` | [`String`](#string) | List timelogs for a user. |
+##### `Project.visibleForks`
+
+Visible forks of the project.
+
+WARNING:
+**Introduced** in 15.10.
+This feature is in Alpha. It can be changed or removed at any time.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `minimumAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Minimum access level. |
+
##### `Project.vulnerabilities`
Vulnerabilities reported on the project.
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index ea8018d441331351d731c4ccf773a5dca61991b1..7f26190830e3cf4fe1a2d7a607a22ae0754d1f51 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -4,6 +4,7 @@
RSpec.describe GitlabSchema.types['Project'] do
include GraphqlHelpers
+ include ProjectForksHelper
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
@@ -37,7 +38,7 @@
ci_template timelogs merge_commit_template squash_commit_template work_item_types
recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables
timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages
- incident_management_timeline_event_tags
+ incident_management_timeline_event_tags visible_forks
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -859,4 +860,54 @@
end
end
end
+
+ describe 'visible_forks' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:fork_reporter) { fork_project(project, nil, { repository: true }) }
+ let_it_be(:fork_developer) { fork_project(project, nil, { repository: true }) }
+ let_it_be(:fork_group_developer) { fork_project(project, nil, { repository: true }) }
+ let_it_be(:fork_public) { fork_project(project, nil, { repository: true }) }
+ let_it_be(:fork_private) { fork_project(project, nil, { repository: true }) }
+
+ let(:minimum_access_level) { '' }
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ visibleForks#{minimum_access_level} {
+ nodes {
+ fullPath
+ }
+ }
+ }
+ }
+ )
+ end
+
+ let(:forks) do
+ subject.dig('data', 'project', 'visibleForks', 'nodes')
+ end
+
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+ before do
+ fork_reporter.add_reporter(user)
+ fork_developer.add_developer(user)
+ fork_group_developer.group.add_developer(user)
+ end
+
+ it 'contains all forks' do
+ expect(forks.count).to eq(5)
+ end
+
+ context 'with minimum_access_level DEVELOPER' do
+ let(:minimum_access_level) { '(minimumAccessLevel: DEVELOPER)' }
+
+ it 'contains forks with developer access' do
+ expect(forks).to contain_exactly(a_hash_including('fullPath' => fork_developer.full_path),
+a_hash_including('fullPath' => fork_group_developer.full_path))
+ end
+ end
+ end
end